aboutsummaryrefslogtreecommitdiff
path: root/mx-aaaa/modules/mx-aaaa.js
diff options
context:
space:
mode:
Diffstat (limited to 'mx-aaaa/modules/mx-aaaa.js')
-rw-r--r--mx-aaaa/modules/mx-aaaa.js190
1 files changed, 190 insertions, 0 deletions
diff --git a/mx-aaaa/modules/mx-aaaa.js b/mx-aaaa/modules/mx-aaaa.js
new file mode 100644
index 0000000..807f691
--- /dev/null
+++ b/mx-aaaa/modules/mx-aaaa.js
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2019-2022 David Timber <dxdt@dev.snart.me>
+ *
+ * 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, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * 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.
+ */
+export class Resolver {
+ async resolve (type, rname) {} // abstract
+};
+
+function getRTypeNum (typestrd) {
+ switch (typestrd) {
+ case 'A': return 1;
+ case 'AAAA': return 28;
+ case 'MX': return 15;
+ }
+ throw new Error();
+}
+
+export class GooglePublicDNSResolver {
+ _mk_query_url (type, rname) {
+ return `https://dns.google/resolve?name=${rname}&type=${type}`;
+ }
+
+ async _resolve_inner (url) {
+ let rsp;
+
+ for (let i = 0; i < 3; i += 1) {
+ rsp = await fetch(url);
+
+ if (rsp.status / 100 === 5) {
+ await new Promise(r => setTimeout(r, 500));
+ continue;
+ }
+ break;
+ }
+
+ return rsp;
+ }
+
+ async resolve (type, rname) {
+ if (!rname.endsWith('.')) {
+ rname += '.';
+ }
+ const url = this._mk_query_url(type, rname);
+ const rsp = await this._resolve_inner(url);
+ const doc = await rsp.json();
+ const nrtype = getRTypeNum(type);
+ let ret = [];
+
+ if ('Answer' in doc) {
+ for (const rr of doc['Answer']) {
+ // ignore other extra answers like CNAME
+ if ('data' in rr &&
+ 'name' in rr &&
+ 'type' in rr &&
+ rr['name'] === rname &&
+ rr['type'] === nrtype)
+ {
+ ret.push(rr['data']);
+ }
+ }
+ }
+
+ return ret;
+ }
+};
+
+export function parseList (txt) {
+ let ret = [];
+ const lines = txt.split(/[\r\n]+/gm);
+
+ for (let l of lines) {
+ l = l.trim();
+
+ const d = l.search('#');
+ if (d >= 0) {
+ l = l.substring(0, d).trim();
+ }
+
+ if (l) {
+ ret.push(l);
+ }
+ }
+
+ return ret;
+};
+
+const defaultResolver = GooglePublicDNSResolver;
+
+export class ResolveMXAAAA {
+ constructor (onresolve) {
+ this.onresolve = onresolve;
+ }
+
+ async doResolve (list, resolver = null) {
+ let ret = {
+ mxmap: {},
+ counts: {
+ list: list.length,
+ mx: 0,
+ has_a: 0,
+ has_aaaa: 0
+ }
+ };
+
+ if (!resolver) {
+ resolver = new defaultResolver();
+ }
+
+ // init the map
+ for (const domain of list) {
+ ret.mxmap[domain] = {
+ mx: [],
+ amap: {},
+ acnt: 0,
+ aaaamap: {},
+ aaaacnt: 0
+ };
+ }
+
+ // Get all MX
+ for (const domain of list) {
+ const q_mx = await resolver.resolve('MX', domain);
+ let has_a = 0, has_aaaa = 0;
+
+ for (let mx of q_mx) {
+ // don't need the number part
+ const d = mx.search(/\s+/);
+ if (d > 0) {
+ mx = mx.substring(d).trim();
+ }
+
+ ret.mxmap[domain].mx.push(mx);
+ ret.counts.mx += 1;
+
+ this.onresolve('mx', {
+ domain: domain,
+ mx: mx
+ }, ret);
+
+ // Resolve all A of MX
+ const q_a = await resolver.resolve('A', mx);
+ if (q_a.length) {
+ has_a = 1;
+ ret.mxmap[domain].acnt += 1;
+ }
+ ret.mxmap[domain].amap[mx] = q_a;
+ this.onresolve('a', {
+ domain: domain,
+ mx: mx,
+ a: q_a
+ }, ret);
+
+ // Resolve all AAAA of MX
+ const q_aaaa = await resolver.resolve('AAAA', mx);
+ if (q_aaaa.length) {
+ has_aaaa = 1;
+ ret.mxmap[domain].aaaacnt += 1;
+ }
+ ret.mxmap[domain].aaaamap[mx] = q_aaaa;
+ this.onresolve('aaaa', {
+ domain: domain,
+ mx: mx,
+ aaaa: q_aaaa
+ }, ret);
+ }
+
+ ret.counts.has_a += has_a;
+ ret.counts.has_aaaa += has_aaaa;
+ }
+
+ return ret;
+ }
+};