diff --git a/README.md b/README.md index 5b1c1b2..696fede 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,16 @@ Example: /dig domain: cloudflare.com type: AAAA records short: True ``` +### Disable DNSSEC checking + +You can disable DNSSEC checking in the `dig` command by passing `cdflag` as true. This will get the records even if validation fails. + +Example: + +```txt +/dig domain: cloudflare.com type: AAAA records cdflag: True +``` + ### Refreshing existing results You can refresh the DNS lookup results by clicking the Refresh button. Clicking it will trigger the bot to re-request the DNS query in the message, and update the results in the message. Any user can click this button. @@ -185,6 +195,16 @@ Example: /multi-dig domain: cloudflare.com types: CDS CDNSKEY short: True ``` +### Disable DNSSEC checking + +Exactly like `dig` command, you can disable dns checking by passing `cdflag` as true. This will get the records even if validation fails. + +Example: + +```txt +/multi-dig domain: cloudflare.com type: AAAA records cdflag: True +``` + ### Refreshing existing results The `/multi-dig` command also provides a refresh button below each set of DNS results requested (or after each block of 10 DNS record types, if you requested more than 10). diff --git a/src/commands/dig.js b/src/commands/dig.js index f322b61..fd46be0 100644 --- a/src/commands/dig.js +++ b/src/commands/dig.js @@ -35,6 +35,12 @@ export default { type: ApplicationCommandOptionType.Boolean, required: false, }, + { + name: 'cdflag', + description: 'Disable DNSSEC checking', + type: ApplicationCommandOptionType.Boolean, + required: false, + }, { name: 'provider', description: 'DNS provider to use', @@ -49,6 +55,7 @@ export default { const rawDomain = ((interaction.data.options.find(opt => opt.name === 'domain') || {}).value || '').trim(); const rawType = ((interaction.data.options.find(opt => opt.name === 'type') || {}).value || '').trim(); const rawShort = (interaction.data.options.find(opt => opt.name === 'short') || {}).value || false; + const rawCdFlag = (interaction.data.options.find(opt => opt.name === 'cdflag') || {}).value || false; const rawProvider = ((interaction.data.options.find(opt => opt.name === 'provider') || {}).value || '').trim(); // Parse domain input, return any error response @@ -64,7 +71,7 @@ export default { // Do the processing after acknowledging the Discord command wait((async () => { // Run dig and get the embeds - const [ embed ] = await handleDig({ domain, types: [ type ], short: rawShort, provider }); + const [ embed ] = await handleDig({ domain, types: [ type ], short: rawShort, cdflag: rawCdFlag, provider }); // Edit the original deferred response await editDeferred(interaction, { diff --git a/src/commands/multi-dig.js b/src/commands/multi-dig.js index de896d2..fe9a6d3 100644 --- a/src/commands/multi-dig.js +++ b/src/commands/multi-dig.js @@ -30,6 +30,12 @@ export default { type: ApplicationCommandOptionType.Boolean, required: false, }, + { + name: 'cdflag', + description: 'Disable DNSSEC checking', + type: ApplicationCommandOptionType.Boolean, + required: false, + }, { name: 'provider', description: 'DNS provider to use', @@ -44,6 +50,7 @@ export default { const rawDomain = ((interaction.data.options.find(opt => opt.name === 'domain') || {}).value || '').trim(); const rawTypes = ((interaction.data.options.find(opt => opt.name === 'types') || {}).value || '').trim(); const rawShort = (interaction.data.options.find(opt => opt.name === 'short') || {}).value || false; + const rawCdFlag = (interaction.data.options.find(opt => opt.name === 'cdflag') || {}).value || false; const rawProvider = ((interaction.data.options.find(opt => opt.name === 'provider') || {}).value || '').trim(); // Parse domain input, return any error response @@ -68,7 +75,7 @@ export default { // Do the processing after acknowledging the Discord command wait((async () => { // Run dig and get the embeds - const embeds = await handleDig({ domain, types, short: rawShort, provider }); + const embeds = await handleDig({ domain, types, short: rawShort, cdflag: rawCdFlag, provider }); // Edit the original deferred response with the first 10 embeds const messageBase = { diff --git a/src/components/dig-provider.js b/src/components/dig-provider.js index 849653d..93aa112 100644 --- a/src/components/dig-provider.js +++ b/src/components/dig-provider.js @@ -36,6 +36,7 @@ export default { domain: embeds[0].name, types: embeds.map(data => data.type), short: embeds[0].short, + cdflag: embeds[0].cdflag, provider, }); diff --git a/src/components/dig-refresh.js b/src/components/dig-refresh.js index d4db7a9..e399814 100644 --- a/src/components/dig-refresh.js +++ b/src/components/dig-refresh.js @@ -28,6 +28,7 @@ export default { domain: embeds[0].name, types: embeds.map(data => data.type), short: embeds[0].short, + cdflag: embeds[0].cdflag, provider: embeds[0].provider, }); diff --git a/src/utils/dig.js b/src/utils/dig.js index a4fb1d8..ca4cecd 100644 --- a/src/utils/dig.js +++ b/src/utils/dig.js @@ -5,6 +5,8 @@ import providers from './providers.js'; import { presentTable } from './table.js'; import { createEmbed } from './embed.js'; +const DNSSEC_DISABLED_WARNING_MESSAGE = ':warning: cd bit set for request, DNSSEC validation disabled'; + export const validateDomain = (input, response) => { // Clean the input const cleaned = input @@ -28,15 +30,15 @@ export const validateDomain = (input, response) => { }; }; -export const handleDig = async ({ domain, types, short, provider }) => { +export const handleDig = async ({ domain, types, short, cdflag, provider }) => { // Make the DNS queries const results = await Promise.all(types.map(type => - performLookupWithCache(domain, type, provider.doh).then(data => ({ type, data })))); + performLookupWithCache(domain, type, provider.doh, cdflag).then(data => ({ type, data })))); // Define the presenter const present = (type, data) => { // Generate the dig command equivalent - const digCmd = `\`${data.name} ${type} @${provider.dig} +noall +answer${short ? ' +short' : ''}\`\n`; + const digCmd = `\`${data.name} ${type} @${provider.dig} +noall +answer${short ? ' +short' : ''}${cdflag ? ' +cdflag' : ''}\`\n`; // Error message if (typeof data === 'object' && data.message) @@ -44,7 +46,9 @@ export const handleDig = async ({ domain, types, short, provider }) => { // No results if (typeof data !== 'object' || !Array.isArray(data.answer) || data.answer.length === 0) - return `${digCmd}\nNo records found`; + return `${digCmd}\nNo records found${cdflag + ? `\n\n${DNSSEC_DISABLED_WARNING_MESSAGE}` + : ''}`; // Map the data if short requested const sourceRows = short ? data.answer.map(x => x.data) : data.answer; @@ -61,14 +65,18 @@ export const handleDig = async ({ domain, types, short, provider }) => { return `${digCmd}\`\`\`\n${rowsStr}${truncStr}\n\`\`\``; }; + const maxLength = 4096 - (cdflag ? DNSSEC_DISABLED_WARNING_MESSAGE.length : 0); + // Keep adding rows until we reach Discord 4096 char limit for (const row of sourceRows) { - if (output([...finalRows, row]).length > 4096) break; + if (output([...finalRows, row]).length > maxLength) break; finalRows.push(row); } // Render and return final rows - return output(finalRows); + return `${output(finalRows)}${cdflag + ? `\n${DNSSEC_DISABLED_WARNING_MESSAGE}` + : ''}`; }; // Convert results to an embed @@ -77,7 +85,7 @@ export const handleDig = async ({ domain, types, short, provider }) => { export const parseEmbed = embed => { // Match the domain name, type and if the short format was requested - const descMatch = embed.description.match(/^`(\S+) (\S+) @(\S+) \+noall \+answer( \+short)?`\n/); + const descMatch = embed.description.match(/^`(\S+) (\S+) @(\S+) \+noall \+answer( \+short)?( \+cdflag)?`\n/); if (!descMatch) return null; // Check the type @@ -92,6 +100,7 @@ export const parseEmbed = embed => { name: descMatch[1], type: descMatch[2], short: !!descMatch[4], + cdflag: !!descMatch[5], provider, }; }; diff --git a/src/utils/dns.js b/src/utils/dns.js index a1a515b..da9ad38 100644 --- a/src/utils/dns.js +++ b/src/utils/dns.js @@ -1,5 +1,5 @@ import { Buffer } from 'buffer'; -import { decode, encode, RECURSION_DESIRED } from 'dns-packet'; +import { decode, encode, RECURSION_DESIRED, CHECKING_DISABLED } from 'dns-packet'; import { toRcode } from 'dns-packet/rcodes.js'; import fetch from 'node-fetch'; import cache from './cache.js'; @@ -74,11 +74,12 @@ const processAnswer = (type, answer) => { return answer; }; -const performLookupJson = async (domain, type, endpoint) => { +const performLookupJson = async (domain, type, endpoint, cdflag) => { // Build the query URL const query = new URL(endpoint.endpoint); query.searchParams.set('name', domain); query.searchParams.set('type', type.toLowerCase()); + query.searchParams.set('cd', cdflag.toString().toLowerCase()); // Make our request return fetch(query.href, { @@ -90,12 +91,12 @@ const performLookupJson = async (domain, type, endpoint) => { const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; -const performLookupDns = async (domain, type, endpoint) => { +const performLookupDns = async (domain, type, endpoint, cdflag) => { // Build the query packet const packet = encode({ type: 'query', id: randInt(1, 65534), - flags: RECURSION_DESIRED, + flags: RECURSION_DESIRED | (cdflag ? CHECKING_DISABLED : 0), questions: [ { name: domain, type, @@ -111,30 +112,34 @@ const performLookupDns = async (domain, type, endpoint) => { headers: { Accept: 'application/dns-message', }, - }).then(res => res.arrayBuffer()).then(data => { - const dec = decode(Buffer.from(data)); - return { - Status: toRcode(dec.rcode), - Question: dec.questions, - Answer: dec.answers, - }; - }); + }) + .then(res => res.arrayBuffer()) + .then(data => { + const dec = decode(Buffer.from(data)); + return { + Status: toRcode(dec.rcode), + Question: dec.questions, + Answer: dec.answers, + }; + }); }; -const performLookupRequest = async (domain, type, endpoint) => { +const performLookupRequest = async (domain, type, endpoint, cdflag) => { switch (endpoint.type) { case 'json': - return performLookupJson(domain, type, endpoint); + return performLookupJson(domain, type, endpoint, cdflag); case 'dns': - return performLookupDns(domain, type, endpoint); + return performLookupDns(domain, type, endpoint, cdflag); default: - return Promise.reject(new Error(`Unknown endpoint type: ${endpoint.type}`)); + return Promise.reject( + new Error(`Unknown endpoint type: ${endpoint.type}`), + ); } }; -const performLookup = async (domain, type, endpoint) => { +const performLookup = async (domain, type, endpoint, cdflag) => { // Make the request - const { Status, Question, Answer } = await performLookupRequest(domain, type, endpoint); + const { Status, Question, Answer } = await performLookupRequest(domain, type, endpoint, cdflag); // Return an error message for non-zero status if (Status !== 0) @@ -150,10 +155,10 @@ const performLookup = async (domain, type, endpoint) => { }; }; -export const performLookupWithCache = (domain, type, endpoint) => cache( +export const performLookupWithCache = (domain, type, endpoint, cdflag) => cache( performLookup, - [ domain, type, endpoint ], - `dns-${domain}-${type}-${endpoint.endpoint}`, + [ domain, type, endpoint, cdflag ], + `dns-${domain}-${type}-${endpoint.endpoint}-${cdflag}`, Number(process.env.CACHE_DNS_TTL) || 10, );