├── LICENSE ├── README.md ├── getadvanceddns.js └── getadvanceddnsv2.js /LICENSE: -------------------------------------------------------------------------------- 1 | Modified MIT License 2 | 3 | (c) 2025 Rosalyn Newell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, subject to the Additional Terms of Use outlined below, including without limitation the rights to use, copy, modify, merge, publish, and distribute copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the following conditions are met: 6 | 7 | • The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | • Compliance with the Additional Terms of Use is mandatory for all users of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | Additional Terms of Use 13 | 14 | Although this Software is provided under the Modified MIT License, the following specific restrictions apply: 15 | 16 | You are welcome to: 17 | 18 | • Use, modify, and share the code for personal, educational, and internal business purposes (with appropriate credit to the author). 19 | 20 | • Fork the repository, submit pull requests, and contribute improvements (which will be credited). 21 | 22 | • Reference or build upon this work for non-commercial, private, or internal purposes only. 23 | 24 | • Use this script to improve the sustainability, efficiency, or general performance of your own website builds, whether for commercial or non-commercial projects. 25 | 26 | You are not permitted to: 27 | 28 | • Integrate this code (in whole or in part) into products, services, or tools offered for sale or placed behind a paywall. 29 | 30 | • Charge others for access to this code as a service, whether modified or original. 31 | 32 | • Distribute modified or original versions of the Software outside of forks of the repository without attribution and an unmodified link to the original repository. 33 | 34 | • Create a web application, SaaS tool, or other GUI-based service based on this code. 35 | 36 | • Re-license or sub-license this code or derived code under different terms. 37 | 38 | For commercial licensing inquiries (e.g., building a web-based service), please contact the author via https://codewordcreative.com. 39 | 40 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 41 | 42 | Compliance with these Terms of Use is mandatory for all users of the Software. 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced DNS Checker VERSION 2 2 | A CLI node.JS tool for fetching and logging all DNS records for backup and inspection, now with subdomain support. 3 | 4 | ## Dependencies 5 | Same as before. Node.js: You won't get far without it. dig: command-line utility, usually included by default on Linux/macOS. On Windows, install it via WSL or use a version of BIND that includes dig. 6 | 7 | ## What's next? 8 | ### Features 9 | Not sure. I looked at adding a config file. That's probably a nice idea. Or at least a version that's pretty much identical but allows you to set the domains, arguments, and subdomains etc. in the file, so it's a click and run to copy all your DNS data at once. That said, DNS data doesn't change often, so I'm not sure I want to encourage pointless backups. I'd like to have some people test it so I can be sure I'm catching more of the important subdomain cases. In an ideal world, I'd subset the subdomains even further to minimise unnecessary checks, though that's probably a low-return situation. 10 | 11 | ### A web UI version 12 | I'll reconsider this option after improving general function and improving customisation options. It probably is possible to do this in a way that avoids security issues. As per the licence, anyone is welcome to use the code on their own systems for their own projects. Public webapp use is a right I reserve for me, though access would be provided free of charge. Those in the security and sustainability community can talk to me about possible exceptions. 13 | 14 | ---- 15 | 16 | # Updates in this version, and why V1 is still available 17 | This version can now reliably pick up important subdomain-level DNS records for a wide variety of use cases. However, my own ability to simply research common services online and check against my own domains is limited. I'd very much welcome testing - please let me know if there's a record it's missing, or some sort of quirk. V1 is still available simply because I am not totally finished creating and testing V2. 18 | 19 | ## Custom subdomain support 20 | Add subdomains to your request with the percentage sign and separate them by commas. This ensures domains that contain a hyphen are still processed correctly. These subdomains will be tested the same way as main domains, in full. This is a good way to include subdomains you know about and need to remember, and any custom subdomain. 21 | 22 | ## Multiple domains now separated by spaces 23 | That was a logical step. 24 | 25 | ## Probe all standard subdomain records 26 | This was a lot of work. I deliberately omitted anything that someone may not automatically choose to make public - these can be added manually as custom subdomains anyway. But I also wanted to maximise efficiency, not checking for records that were extremely unlikely to exist. 27 | 28 | ## Caveat: Wildcards 29 | This version has a wildcard checker, at least for A and AAAA records. Where a wildcard is found and the subdomain gives the same result as the wildcard result, it will NOT be included unless specifically included at the command line. In testing, I did find a wildcard text record, but I think this is uncommon. Let me know. 30 | 31 | ---- 32 | 33 | # Set up 34 | As before. It's a command-line interface. Just download it and save it wherever you want to run it 35 | 36 | ## Customise the output directory if desired 37 | It defaults to a subdirectory called dns-output. 38 | 39 | ## Change: Subdomains 40 | See above. You can now test custom subdomains like this: node advanceddnscheckerv2.js%sub1,sub2. Or you can test just the standard subdomain records by adding the -x argument. You can do both at the same time. 41 | 42 | ---- 43 | 44 | # To use 45 | Navigate to the file, then use it like this: 46 | 47 | `node advanceddnsv2.js example.com` 48 | 49 | In the default setup, it will gather all known DNS records on the main domain and save the output to a subdirectory called dns-output in an appropriately named text file. 50 | 51 | You can use arguments to reduce the scope (overview, -o), print the output just to the console (print, -p), expand the scope to an extensive standardised list of subdomains (extended, -x), switch to using the authoritative nameservers (nameserver, -ns), or output to both the console and the drive (print and save, -ps). 52 | 53 | ## Multiple domains 54 | `node advanceddnsv2.js example.com example.de` 55 | CHANGED: You can include multiple domains at once. Separate them with a space. 56 | 57 | ## Custom subdomains 58 | `node advanceddnsv2.js example.com%sub1,sub2 example.de%sub3,sub4` 59 | CHANGED: You can include custom subdomains, which will be tested against the same records as the main domain. Add directly after the domain with a % in front, and separate them with a comma without a space. Use this for any important subdomains unlikely to show up in a general probe. 60 | 61 | ## Arguments 62 | ### -o or --overview 63 | This pulls a trimmed-down list of key records of interest when generally inspecting the data, e.g. for sustainability or security purposes. 64 | 65 | ### -x or --extended 66 | This pulls an extensive list of preset, commonly used subdomain and subdomain-like records. This is probably the exciting part. 67 | 68 | ### -ns or --nameserver 69 | Switch to pulling the results from the authoritative nameserver defined in the NS record. 70 | 71 | ### -ps or --printandsave 72 | Outputs the results to the console BUT ALSO saves the output to disk as usual. 73 | 74 | ### A combination 75 | `node advanceddns.js example.com,example.de%sub,sub1 -o -ps` 76 | This would fetch the overview DNS records from example.com, example.de, and the records at sub.example.de and sub1.example.de, output them to the console, and save them to disk. 77 | 78 | ---- 79 | 80 | ## Full records checked on main domains and custom subdomain entries: 81 | 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'SOA', 'DNSKEY', 'DS', 'CDNSKEY', 'CDS', 'CAA', 'LOC', 'NAPTR', 'SMIMEA', 'SSHFP', 'TLSA', 'SRV', 'CERT', 'NSEC', 'NSEC3PARAM', 'RRSIG', 'LOC', 'TXT' 82 | 83 | ## Overview records: 84 | CHANGED, reduced to: `'A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME'` 85 | 86 | ## Subdomains 87 | These are organised in a complicated way to maximise efficiency, performance, and accuracy. Too many checks and it'd take a long time, and generate bad data. Please excuse the formatting - it's pretty much a straight copy from the arrays. 88 | Each array is a list of records probed for, and the contents are the subdomains (or subdomain-like entries) we are looking for in the DNS. If you spot a mistake, please submit it as an issue, email me, or whatever else. I'll fix it. It's not unlikely! Equally, if it's an omission, let me know. I won't want to or be able to accommodate every use case, but there are a lot of services out there and it's hard to know what the up-to-date info is if I don't use them myself. Appreciated 89 | 90 | `'A,AAAA,CNAME,TXT': 'www', 'cdn', 'static', 'assets', 'media', 'img', 'js', 'css', 'fonts', 'calendar', 'drive', 'docs' 91 | 'A,AAAA': 'crm', 'erp', 'shop', 'store', 'uat', 'pages', 'ipv4', 'ipv6', 'imap', 'pop', '_sip' 92 | 'A,CNAME,SRV': 'webmail', 'autodiscover' 93 | 'A,AAAA,MX,TXT,SPF,DKIM,DMARC': 'mail' 94 | 'A,AAAA,TXT,SPF,DKIM,DMARC': 'smtp', 95 | 'A,AAAA,CNAME,SRV,TXT': 'api', 'gateway', 'vault', '_lyncdiscover', '_enterpriseregistration' 96 | 'TXT': '_google._domainkey', '_dmarc', 'selector1_domainkey', 'selector2_domainkey', 'zohoverify', '_domainconnect', '_atproto', 'default_bimi', '_acme-challenge', 'm1._domainkey','mg', '_mta-sts' 97 | 'CNAME': '_google._domainkey', '_zmverify', '_amazonses', '_mailgun', 'fm1._domainkey', 'fm2._domainkey','fm3._domainkey', 's1._domainkey', 's2._domainkey', 'mail._domainkey', 'mailo._domainkey', 'mesmtp._domainkey', '_tiktok', 'funnels', 'enterpriseenrollment', 'enterpriseregistration', 'lyncdiscover', 'autodiscover' 98 | 'SRV': '_imaps._tcp', '_pop3s._tcp', '_sip._tls', '_sipfederationtls._tcp', '_autodiscover._tcp', '_submission._tcp', '_submissions._tcp', '_imap._tcp', '_imaps._tcp', '_pop3._tcp', '_pop3s._tcp', '_jmap._tcp', '_smtps._tcp', '_autodiscover._tcp', '_tls', '_cf.tls', '_carddav._tcp', '_carddavs._tcp', '_caldav._tcp', '_caldavs._tcp'` 99 | 100 | ---- 101 | 102 | ## NOTE: 103 | Version 2 with subdomain support coming soon. I'm testing it. It works. I'm just weighing up the impact of different approaches and the most efficient way to avoid wildcard results. It can be done, it's just a matter of defining the best approach. I was thinking of including a separate option to query authoritative nameservers, but since these, too, sometimes have wildcards, the benefit is not guaranteed. Therefore, I'm more likely to just update the original version and integrate the check developed for that approach into the main release. On the other hand... I'm really excited by how much people like even the first version. :) This second version delivers absolutely everything. I just need to make it work better with nameservers that use wildcards. My proposed solution is to simply include a warning if wildcard entries are detected, as it makes it impossible to detect individual A or AAAA subdomain records. 104 | 105 | ### Update on the above after adding V2 106 | Well done, past me. It looks like I promised what I actually went on to deliver. Keeping the old instructions below for anyone wanting to stick with the old version. 107 | 108 | 109 | # Advanced DNS checker VERSION 1 110 | A CLI node.JS tool for fetching and logging all DNS records for backup and inspection. 111 | ## Dependencies 112 | Node.js: You won't get far without it. 113 | dig: command-line utility, usually included by default on Linux/macOS. On Windows, install it via WSL or use a version of BIND that includes dig. 114 | 115 | ---- 116 | 117 | # Set up 118 | It's a command-line interface. Just download it and save it wherever you want to run it 119 | 120 | ## Customise the output directory if desired 121 | It defaults to a subdirectory called dns-output. 122 | 123 | ## Caveat: Subdomains 124 | Subdomain records currently aren't covered at all. I'll soon add support for common subdomain formats, but less usual ones will take more work. 125 | 126 | ---- 127 | 128 | # To use 129 | Navigate to the file, then use it like this: 130 | 131 | `node advanceddns.js example.com` 132 | 133 | In the default setup, it will gather all known DNS records and save the output to a subdirectory called dns-output in an appropriately named text file. 134 | 135 | You can use arguments to reduce the scope, print the output just to the console, or output to both the console and the drive. 136 | 137 | ## Multiple domains 138 | `node advanceddns.js example.com,example.de` 139 | You can include multiple domains at once. Separate them with a comma and nothing else. 140 | 141 | ## Arguments 142 | ### -o or --overview 143 | This pulls a trimmed-down list of key records of interest when generally inspecting the data, e.g. for sustainability or security purposes. 144 | 145 | ### -p or --print 146 | Outputs the results to the console instead of just saving a file. 147 | 148 | ### -s or --save 149 | Saves the results to a timestamped .txt file (this is default behaviour). 150 | 151 | ### A combination 152 | `node advanceddns.js example.com,example.de -o -p -s` 153 | This would fetch the overview DNS records from example.com, output them to the console, and save them to disk. 154 | 155 | ---- 156 | 157 | ## The full records 158 | `'A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SRV', 'PTR', 'SOA', 'CAA', 'AFSDB', 'APL', 'CDNSKEY', 'CDS', 'CERT', 'CSYNC', 'DHCID', 'DLV', 'DNAME', 'DNSKEY', 'DS', 'HINFO', 'HIP', 'IPSECKEY', 'IXFR', 'KEY', 'KX', 'LOC', 'NAPTR', 'NSEC', 'NSEC3', 'NSEC3PARAM', 'OPENPGPKEY', 'OPT', 'RP', 'RRSIG', 'SIG', 'SMIMEA', 'SSHFP', 'TA', 'TKEY', 'TLSA', 'TSIG', 'URI', 'ZONEMD'` 159 | 160 | ## The overview records 161 | `'A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA', 'CAA', 'DNSKEY', 'DS', 'SSHFP'` 162 | 163 | ## More? 164 | Happy to add more. Any that don't exist aren't output in the file, anyway, so it doesn't hurt a lot to add and check for them. 165 | 166 | ---- 167 | 168 | ## Why I made it 169 | I genuinely couldn't find anything that scraped every single useful or even essential DNS record in one go, with the option to save it to an appropriately named file for backup. Most that did exist relied only on the tools available with NSLookup, too. It feels like an essential backup, really. It's also a lot easier to copy between text files when migrating between sites. Then I noticed it was interesting to see what other websites were doing. 170 | 171 | ## Ideal use case 172 | Backups. Migrations. General peace of mind. 173 | 174 | ## Interesting use case 175 | Nosing at the third-party usage of big companies and government organisations. Spotting sustainability and security gaps. 176 | 177 | --- 178 | 179 | ## Special licence terms 180 | See the notes on the licence tab. Commercial use by companies to improve their own websites - as in, the key intent of this code - is fine. Where there is ambiguity here, the licence notes take precedent. 181 | -------------------------------------------------------------------------------- /getadvanceddns.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const util = require('util'); 5 | const execPromise = util.promisify(exec); 6 | 7 | // Default save path - subdirectory of wherever this script is loaded from 8 | const saveDirectory = 'DNS-output'; 9 | 10 | // DNS record types 11 | const fullRecordTypes = [ 12 | 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SRV', 'PTR', 'SOA', 'CAA', 'AFSDB', 'APL', 'CDNSKEY', 'CDS', 'CERT', 'CSYNC', 'DHCID', 'DLV', 'DNAME', 'DNSKEY', 'DS', 'HINFO', 'HIP', 'IPSECKEY', 'IXFR', 'KEY', 'KX', 'LOC', 'NAPTR', 'NSEC', 'NSEC3', 'NSEC3PARAM', 'OPENPGPKEY', 'OPT', 'RP', 'RRSIG', 'SIG', 'SMIMEA', 'SSHFP', 'TA', 'TKEY', 'TLSA', 'TSIG', 'URI', 'ZONEMD' 13 | ]; 14 | 15 | const overviewRecordTypes = [ 16 | 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA', 'CAA', 'DNSKEY', 'DS', 'SSHFP' 17 | ]; 18 | 19 | // CLI arguments 20 | const args = process.argv.slice(2); 21 | const input = args[0]; 22 | const options = args.slice(1).map(arg => arg.toLowerCase()); 23 | 24 | const useOverview = options.includes('-o') || options.includes('--overview'); 25 | const printToConsole = options.includes('-p') || options.includes('--print'); 26 | const saveToFile = options.includes('-s') || options.includes('--save') || useOverview || options.length === 0; 27 | 28 | if (!input) { 29 | console.error('Usage: node getadvanceddns.js [options]'); 30 | console.error('Options:'); 31 | console.error(' -o, --overview Only fetch overview DNS records (e.g., inspecting security and sustainability)'); 32 | console.error(' -p, --print Print DNS results *only* to the console'); 33 | console.error(' -s, --save (Also) save DNS results to a file (default, but can be combined with the print option)'); 34 | console.error('Example: node getadvanceddns.js example.com,example.org,example.net -o -p -s'); 35 | process.exit(1); 36 | } 37 | 38 | // Process the domain(s) (comma-separated, no spaces) 39 | let domains = input.split(',').map(domain => domain.trim()).filter(Boolean); 40 | 41 | if (domains.length === 0) { 42 | console.error('No valid domains provided.'); 43 | process.exit(1); 44 | } 45 | 46 | const recordTypes = useOverview ? overviewRecordTypes : fullRecordTypes; 47 | 48 | // Extract and clean valid DNS records 49 | function extractRecords(stdout) { 50 | if (!stdout || stdout.includes('not found') || stdout.includes('no records') || stdout.includes('NXDOMAIN')) { 51 | return null; 52 | } 53 | 54 | return stdout.split('\n').map(line => line.trim()).filter(line => line !== '').join('\n'); 55 | } 56 | 57 | // Fetch DNS records by type 58 | async function getDnsRecords(domain, types) { 59 | let output = ''; 60 | let notFound = []; 61 | 62 | for (const type of types) { 63 | try { 64 | const { stdout } = await execPromise(`dig ${domain} ${type} +short`); 65 | const cleanData = extractRecords(stdout); 66 | if (cleanData) { 67 | output += `${type}:\n${cleanData}\n\n`; 68 | } else { 69 | notFound.push(type); 70 | } 71 | } catch (err) { 72 | console.error(`Error fetching ${type} record for ${domain}:`, err.message); 73 | } 74 | } 75 | 76 | return { output, notFound }; 77 | } 78 | 79 | // Save and/or print results 80 | async function getAllDnsRecords(domain, types, print, save) { 81 | const { output, notFound } = await getDnsRecords(domain, types); 82 | 83 | if (print) { 84 | console.log(output); 85 | } 86 | 87 | if (save) { 88 | const date = new Date(); 89 | const filename = `dns-records-${domain.replace(/\W/g, '_')}_${date.getFullYear().toString().slice(2)}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}${date.getMinutes().toString().padStart(2, '0')}.txt`; 90 | const savePath = path.join(saveDirectory, filename); 91 | 92 | try { 93 | if (!fs.existsSync(saveDirectory)) { 94 | fs.mkdirSync(saveDirectory, { recursive: true }); 95 | } 96 | 97 | fs.writeFileSync(savePath, output); 98 | console.log(`DONE: DNS records saved to: ${savePath}`); 99 | } catch (writeErr) { 100 | console.error(`ERROR: Error saving file: ${writeErr.message}`); 101 | } 102 | } 103 | 104 | if (notFound.length > 0) { 105 | console.log(`NOTE: These record types were not found: ${notFound.join(', ')}`); 106 | } 107 | } 108 | 109 | // Process all domains 110 | async function processDomains(domains, recordTypes, print, save) { 111 | for (const domain of domains) { 112 | console.log(`Fetching DNS records for ${domain}...`); 113 | await getAllDnsRecords(domain, recordTypes, print, save); 114 | } 115 | } 116 | 117 | // If `-s` is explicitly mentioned, both save the file and display on console 118 | if (printToConsole && saveToFile) { 119 | console.log('Ready to print and save results...'); 120 | } else if (printToConsole) { 121 | console.log('Ready to print results...'); 122 | } else if (saveToFile) { 123 | console.log('Ready to save results...'); 124 | } 125 | 126 | processDomains(domains, recordTypes, printToConsole, saveToFile); 127 | -------------------------------------------------------------------------------- /getadvanceddnsv2.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const util = require('util'); 5 | const execPromise = util.promisify(exec); 6 | 7 | // Default save path - subdirectory of wherever this script is loaded from 8 | const saveDirectory = 'DNS-output'; 9 | 10 | // DNS record types for main domain 11 | const fullRecordTypes = [ 12 | 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'SOA', 'DNSKEY', 'DS', 'CDNSKEY', 'CDS', 'CAA', 'LOC', 'NAPTR', 'SMIMEA', 'SSHFP', 'TLSA', 'SRV', 'CERT', 'NSEC', 'NSEC3PARAM', 'RRSIG', 'LOC', 'TXT' 13 | ]; 14 | 15 | // DNS record types and the subdomains to check for subdomains when probed in bulk 16 | const recordGroups = { 17 | 'A,AAAA,CNAME,TXT': [ 18 | 'www', 'cdn', 'static', 'assets', 'media', 'img', 'js', 'css', 'fonts', 'calendar', 'drive', 'docs', 19 | ], 20 | 'A,AAAA': [ 21 | 'crm', 'erp', 'shop', 'store', 'uat', 'pages', 'ipv4', 'ipv6', 'imap', 'pop', '_sip', 22 | ], 23 | 'A,CNAME,SRV': [ 24 | 'webmail', 'autodiscover', 25 | ], 26 | 'A,AAAA,TXT': [ 27 | 'mail', 'smtp', 28 | ], 29 | 'A,AAAA,CNAME,SRV,TXT': [ 30 | 'api', 'gateway', 'vault', 'lyncdiscover', '_enterpriseregistration', 31 | ], 32 | 'TXT': [ 33 | 'google._domainkey', 'default._domainkey', '_dmarc', 'selector1_domainkey', 'selector2_domainkey', 'zohoverify', '_domainconnect', '_atproto', 'default_bimi', '_acme-challenge', 'm1._domainkey','mg','_mta-sts', '_spf', 34 | ], 35 | 'CNAME': [ 36 | 'google._domainkey', '_zmverify', '_amazonses', '_mailgun', 'fm1._domainkey', 'fm2._domainkey','fm3._domainkey', 's1._domainkey', 's2._domainkey', 'mail._domainkey', 'mailo._domainkey', 'mesmtp._domainkey', '_tiktok', 'funnels', 'enterpriseenrollment', 'enterpriseregistration', 37 | ], 38 | 'SRV': [ 39 | '_sip._tls', '_sipfederationtls._tcp', '_autodiscover._tcp', '_submission._tcp', '_submissions._tcp', '_imap._tcp', '_imaps._tcp', '_pop3._tcp', '_jmap._tcp', '_smtps._tcp', '_autodiscover._tcp', '_tls', '_cf.tls', '_carddav._tcp', '_carddavs._tcp', '_caldav._tcp', '_caldavs._tcp', 40 | ], 41 | }; 42 | 43 | // CLI arguments 44 | const args = process.argv.slice(2); 45 | const inputDomains = []; 46 | const customSubs = {}; 47 | const options = []; 48 | 49 | for (const arg of args) { 50 | if (arg.startsWith('-')) { 51 | options.push(arg.toLowerCase()); 52 | } else { 53 | const parts = arg.split('%'); // Use '%' as a delimiter for subdomains 54 | const domainPart = parts[0]; 55 | const subdomains = parts.slice(1).join(',').split(',').map(s => s.trim()).filter(Boolean); 56 | 57 | inputDomains.push(domainPart); 58 | 59 | if (subdomains.length > 0) { 60 | customSubs[domainPart] = subdomains; 61 | } 62 | } 63 | } 64 | 65 | // Arguments and options, now defaulting to always save to file unless print argument is added (new print and save option covers combined use case) 66 | const printToConsole = options.includes('-p') || options.includes('--print'); 67 | const saveAndPrintToConsole = options.includes('-ps') || options.includes('--printsave'); 68 | const useExtended = options.includes('-x') || options.includes('--extended'); 69 | const useOverview = options.includes('-o') || options.includes('--overview'); 70 | const useNameservers = options.includes('-ns') || options.includes('--nameserver'); 71 | const saveToFile = !printToConsole; 72 | const recordTypes = useOverview ? ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME'] : fullRecordTypes; 73 | 74 | if (inputDomains.length === 0) { 75 | console.error('Usage: node getadvanceddns.js [domain2[%sub3]] [options]'); 76 | process.exit(1); 77 | } 78 | 79 | // There are peanuts. This the setup for a wildcard check, as I am assuming everyone is too cool to have a peanutspeanutspeanuts subdomain 80 | const peanutsARecords = {}; 81 | const peanutsAAAARecords = {}; 82 | 83 | function extractRecords(stdout) { 84 | return stdout 85 | .split('\n') 86 | .map(l => l.trim()) 87 | .filter(Boolean); 88 | } 89 | 90 | // Option to fetch records directly from the authoritative nameserver listed in the NS records 91 | async function getAuthoritativeNameservers(domain) { 92 | try { 93 | const { stdout } = await execPromise(`dig NS ${domain} +short`); 94 | const nameservers = stdout.split('\n').map(ns => ns.trim()).filter(Boolean); 95 | if (nameservers.length === 0) { 96 | console.log(`⚠️ No authoritative NS records found for ${domain}`); 97 | } 98 | return nameservers; 99 | } catch (err) { 100 | console.error(`⚠️ Error fetching authoritative NS records for ${domain}:`, err); 101 | return []; 102 | } 103 | } 104 | 105 | // Basic reminder that it is working - not sure it's great but hey it was requested 106 | let spinnerInterval; 107 | function startSpinner(message) { 108 | const spinnerChars = ['|', '/', '-', '\\']; 109 | let i = 0; 110 | process.stdout.write(message); 111 | spinnerInterval = setInterval(() => { 112 | process.stdout.write(`\r${message} ${spinnerChars[i++ % spinnerChars.length]}`); 113 | }, 150); 114 | } 115 | 116 | function stopSpinner() { 117 | clearInterval(spinnerInterval); 118 | process.stdout.write('\r'); 119 | } 120 | 121 | // Here are the peanuts: wildcard detection, but also notificication that we are starting... 122 | async function detectPeanuts(domain) { 123 | try { 124 | const peanutSubdomain = `peanutspeanutspeanuts.${domain}`; 125 | startSpinner(`\n👀 Checking ${domain}`); 126 | 127 | const { stdout: aStdout } = await execPromise(`dig ${peanutSubdomain} A +short`); 128 | const aRecords = extractRecords(aStdout); 129 | peanutsARecords[domain] = aRecords.length > 0 ? aRecords[0] : ''; 130 | 131 | const { stdout: aaaaStdout } = await execPromise(`dig ${peanutSubdomain} AAAA +short`); 132 | const aaaaRecords = extractRecords(aaaaStdout); 133 | peanutsAAAARecords[domain] = aaaaRecords.length > 0 ? aaaaRecords[0] : ''; 134 | 135 | stopSpinner(); 136 | console.log(`🥜 Peanut (wildcard) check for ${domain}: A=${peanutsARecords[domain] || 'None'}, AAAA=${peanutsAAAARecords[domain] || 'None'}`); 137 | 138 | if (peanutsARecords[domain] || peanutsAAAARecords[domain]) { 139 | console.log('⚠️ Wildcard A/AAAA records detected - automatic subdomain filtering applied.\n'); 140 | } 141 | 142 | } catch (err) { 143 | stopSpinner(); 144 | console.error(`Error checking for wildcard records on ${domain}:\n`, err); 145 | peanutsARecords[domain] = ''; 146 | peanutsAAAARecords[domain] = ''; 147 | } 148 | } 149 | 150 | // Processing peanuts - checks to ensure wildcard results are cleaned 151 | function isPeanutsRecord(record, domain, subdomain = '') { 152 | const isRootDomain = (subdomain === '' || subdomain === '@'); 153 | 154 | if (isRootDomain) { 155 | return (record.trim() === ''); 156 | } 157 | 158 | const isCustomSub = customSubs[domain]?.includes(subdomain); 159 | if (isCustomSub) { 160 | return false; 161 | } 162 | 163 | return ( 164 | record.trim() === '' || 165 | record.trim() === peanutsARecords[domain].trim() || 166 | record.trim() === peanutsAAAARecords[domain].trim() 167 | ); 168 | } 169 | 170 | // Get DNS records 171 | async function getDnsRecords(domain, types, nameservers) { 172 | let output = ''; 173 | let notFound = []; 174 | 175 | for (const type of types) { 176 | try { 177 | let digCommand = useNameservers 178 | ? `dig @${nameservers[0]} ${domain} ${type} +short +nocomments +noquestion +nocmd` 179 | : `dig ${domain} ${type} +short +nocomments +noquestion +nocmd`; 180 | const { stdout } = await execPromise(digCommand); 181 | const cleanRecords = extractRecords(stdout); 182 | 183 | const validRecords = cleanRecords.filter(record => !isPeanutsRecord(record, domain, '@')); 184 | 185 | if (validRecords.length > 0) { 186 | output += `${type}:\n${validRecords.join('\n')}\n\n`; 187 | } else { 188 | notFound.push(type); 189 | } 190 | } catch (err) { 191 | console.error(`Error querying ${domain} for ${type}:`, err); 192 | notFound.push(type); 193 | } 194 | } 195 | return { output, notFound }; 196 | } 197 | 198 | // Get subdomain records 199 | async function getSubdomainRecords(domain, nameservers, subdomain, recordGroup) { 200 | const fqdn = subdomain === '@' ? domain : `${subdomain}.${domain}`; 201 | let output = ''; 202 | const recordTypes = recordGroup.split(','); 203 | 204 | for (const type of recordTypes) { 205 | try { 206 | let digCommand = useNameservers 207 | ? `dig @${nameservers[0]} ${fqdn} ${type} +short +nocomments +noquestion +nocmd` 208 | : `dig ${fqdn} ${type} +short +nocomments +noquestion +nocmd`; 209 | const { stdout } = await execPromise(digCommand); 210 | const cleanRecords = extractRecords(stdout); 211 | 212 | const validRecords = cleanRecords.filter(record => !isPeanutsRecord(record, domain, subdomain)); 213 | 214 | if (validRecords.length > 0) { 215 | output += `${fqdn} (${type}):\n${validRecords.join('\n')}\n\n`; 216 | } 217 | } catch (err) { 218 | console.error(`Error querying ${fqdn} for type ${type}:`, err); 219 | } 220 | } 221 | return output; 222 | } 223 | 224 | // Fetch the records 225 | async function getAllDnsRecords(domain, types, print = false, save = false, extended = false) { 226 | await detectPeanuts(domain); 227 | 228 | const nameservers = await getAuthoritativeNameservers(domain); 229 | const { output: baseOutput, notFound } = await getDnsRecords(domain, types, nameservers); 230 | 231 | let fullOutput = baseOutput; 232 | const seen = new Set(); 233 | 234 | const customSubsForDomain = customSubs[domain] || []; 235 | for (const sub of customSubsForDomain) { 236 | if (!seen.has(sub)) { 237 | fullOutput += await getSubdomainRecords(domain, nameservers, sub, 'A,AAAA,CNAME,TXT'); 238 | seen.add(sub); 239 | } 240 | } 241 | 242 | if (extended) { 243 | for (const group in recordGroups) { 244 | const subs = recordGroups[group]; 245 | for (const sub of subs) { 246 | if (!seen.has(sub)) { 247 | fullOutput += await getSubdomainRecords(domain, nameservers, sub, group); 248 | seen.add(sub); 249 | } 250 | } 251 | } 252 | } 253 | return fullOutput; 254 | } 255 | 256 | // Get, save and/or print results 257 | async function processDomains() { 258 | let output = ''; 259 | let errors = []; 260 | for (const domain of inputDomains) { 261 | try { 262 | const domainOutput = await getAllDnsRecords(domain, recordTypes, printToConsole, saveToFile, useExtended); 263 | output += domainOutput; 264 | 265 | if (printToConsole || saveAndPrintToConsole) { 266 | console.log(domainOutput); 267 | } 268 | 269 | if (saveToFile) { 270 | try { 271 | const date = new Date(); 272 | const domainForFilename = domain || 'unknown_domain'; 273 | const filename = `dns-records-${domainForFilename.replace(/\W/g, '_')}_${date.getFullYear().toString().slice(2)}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}${date.getMinutes().toString().padStart(2, '0')}.txt`; 274 | const filePath = path.join(saveDirectory, filename); 275 | 276 | fs.mkdirSync(saveDirectory, { recursive: true }); 277 | 278 | await fs.promises.writeFile(filePath, domainOutput); 279 | console.log(`💾 DNS records for ${domain} saved as ${filePath}\n`); 280 | 281 | } catch (err) { 282 | console.error(`Error saving records for o${domain}: ${err.message}`); 283 | } 284 | } 285 | 286 | } catch (err) { 287 | console.error(`Error processing ${domain}:`, err); 288 | errors.push(domain); 289 | } 290 | } 291 | 292 | if (errors.length > 0) { 293 | console.log(`\n❌ Failed to process the following domains:`); 294 | errors.forEach(domain => console.log(` - ${domain}`)); 295 | } 296 | } 297 | 298 | processDomains(); 299 | --------------------------------------------------------------------------------