diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mail/modules/DNS.jsm | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | comm/mail/modules/DNS.jsm | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/comm/mail/modules/DNS.jsm b/comm/mail/modules/DNS.jsm new file mode 100644 index 0000000000..de913aa5cd --- /dev/null +++ b/comm/mail/modules/DNS.jsm @@ -0,0 +1,493 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This module is responsible for performing DNS queries using ctypes for + * loading system DNS libraries on Linux, Mac and Windows. + */ + +const EXPORTED_SYMBOLS = ["DNS", "SRVRecord"]; + +var DNS = null; + +if (typeof Components !== "undefined") { + var { ctypes } = ChromeUtils.importESModule( + "resource://gre/modules/ctypes.sys.mjs" + ); + var { BasePromiseWorker } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseWorker.sys.mjs" + ); +} + +var LOCATION = "resource:///modules/DNS.jsm"; + +// These constants are luckily shared, but with different names +var NS_T_TXT = 16; // DNS_TYPE_TXT +var NS_T_SRV = 33; // DNS_TYPE_SRV +var NS_T_MX = 15; // DNS_TYPE_MX + +// For Linux and Mac. +function load_libresolv(os) { + this._open(os); +} + +load_libresolv.prototype = { + library: null, + + // Tries to find and load library. + _open(os) { + function findLibrary() { + let lastException = null; + let candidates = []; + if (os == "FreeBSD") { + candidates = [{ name: "c", suffix: ".7" }]; + } else if (os == "OpenBSD") { + candidates = [{ name: "c", suffix: "" }]; + } else { + candidates = [ + { name: "resolv.9", suffix: "" }, + { name: "resolv", suffix: ".2" }, + { name: "resolv", suffix: "" }, + ]; + } + let tried = []; + for (let candidate of candidates) { + try { + let name = ctypes.libraryName(candidate.name) + candidate.suffix; + tried.push(name); + return ctypes.open(name); + } catch (ex) { + lastException = ex; + } + } + throw new Error( + "Could not find libresolv in any of " + + tried + + " Exception: " + + lastException + + "\n" + ); + } + + // Declaring functions to be able to call them. + function declare(aSymbolNames, ...aArgs) { + let lastException = null; + if (!Array.isArray(aSymbolNames)) { + aSymbolNames = [aSymbolNames]; + } + + for (let name of aSymbolNames) { + try { + return library.declare(name, ...aArgs); + } catch (ex) { + lastException = ex; + } + } + library.close(); + throw new Error( + "Failed to declare " + + aSymbolNames + + " Exception: " + + lastException + + "\n" + ); + } + + let library = (this.library = findLibrary()); + this.res_search = declare( + ["res_9_search", "res_search", "__res_search"], + ctypes.default_abi, + ctypes.int, + ctypes.char.ptr, + ctypes.int, + ctypes.int, + ctypes.unsigned_char.ptr, + ctypes.int + ); + this.res_query = declare( + ["res_9_query", "res_query", "__res_query"], + ctypes.default_abi, + ctypes.int, + ctypes.char.ptr, + ctypes.int, + ctypes.int, + ctypes.unsigned_char.ptr, + ctypes.int + ); + this.dn_expand = declare( + ["res_9_dn_expand", "dn_expand", "__dn_expand"], + ctypes.default_abi, + ctypes.int, + ctypes.unsigned_char.ptr, + ctypes.unsigned_char.ptr, + ctypes.unsigned_char.ptr, + ctypes.char.ptr, + ctypes.int + ); + this.dn_skipname = declare( + ["res_9_dn_skipname", "dn_skipname", "__dn_skipname"], + ctypes.default_abi, + ctypes.int, + ctypes.unsigned_char.ptr, + ctypes.unsigned_char.ptr + ); + this.ns_get16 = declare( + ["res_9_ns_get16", "ns_get16", "_getshort"], + ctypes.default_abi, + ctypes.unsigned_int, + ctypes.unsigned_char.ptr + ); + this.ns_get32 = declare( + ["res_9_ns_get32", "ns_get32", "_getlong"], + ctypes.default_abi, + ctypes.unsigned_long, + ctypes.unsigned_char.ptr + ); + + this.QUERYBUF_SIZE = 1024; + this.NS_MAXCDNAME = 255; + this.NS_HFIXEDSZ = 12; + this.NS_QFIXEDSZ = 4; + this.NS_RRFIXEDSZ = 10; + this.NS_C_IN = 1; + }, + + close() { + this.library.close(); + this.library = null; + }, + + // Maps record to SRVRecord, TXTRecord, or MXRecord according to aTypeID and + // returns it. + _mapAnswer(aTypeID, aAnswer, aIdx, aLength) { + if (aTypeID == NS_T_SRV) { + let prio = this.ns_get16(aAnswer.addressOfElement(aIdx)); + let weight = this.ns_get16(aAnswer.addressOfElement(aIdx + 2)); + let port = this.ns_get16(aAnswer.addressOfElement(aIdx + 4)); + + let hostbuf = ctypes.char.array(this.NS_MAXCDNAME)(); + let hostlen = this.dn_expand( + aAnswer.addressOfElement(0), + aAnswer.addressOfElement(aLength), + aAnswer.addressOfElement(aIdx + 6), + hostbuf, + this.NS_MAXCDNAME + ); + let host = hostlen > -1 ? hostbuf.readString() : null; + return new SRVRecord(prio, weight, host, port); + } else if (aTypeID == NS_T_TXT) { + // TODO should only read dataLength characters. + let data = ctypes.unsigned_char.ptr(aAnswer.addressOfElement(aIdx + 1)); + + return new TXTRecord(data.readString()); + } else if (aTypeID == NS_T_MX) { + let prio = this.ns_get16(aAnswer.addressOfElement(aIdx)); + + let hostbuf = ctypes.char.array(this.NS_MAXCDNAME)(); + let hostlen = this.dn_expand( + aAnswer.addressOfElement(0), + aAnswer.addressOfElement(aLength), + aAnswer.addressOfElement(aIdx + 2), + hostbuf, + this.NS_MAXCDNAME + ); + let host = hostlen > -1 ? hostbuf.readString() : null; + return new MXRecord(prio, host); + } + return {}; + }, + + // Performs a DNS query for aTypeID on a certain address (aName) and returns + // array of records of aTypeID. + lookup(aName, aTypeID) { + let qname = ctypes.char.array()(aName); + let answer = ctypes.unsigned_char.array(this.QUERYBUF_SIZE)(); + let length = this.res_search( + qname, + this.NS_C_IN, + aTypeID, + answer, + this.QUERYBUF_SIZE + ); + + // There is an error. + if (length < 0) { + return []; + } + + let results = []; + let idx = this.NS_HFIXEDSZ; + + let qdcount = this.ns_get16(answer.addressOfElement(4)); + let ancount = this.ns_get16(answer.addressOfElement(6)); + + for (let qdidx = 0; qdidx < qdcount && idx < length; qdidx++) { + idx += + this.NS_QFIXEDSZ + + this.dn_skipname( + answer.addressOfElement(idx), + answer.addressOfElement(length) + ); + } + + for (let anidx = 0; anidx < ancount && idx < length; anidx++) { + idx += this.dn_skipname( + answer.addressOfElement(idx), + answer.addressOfElement(length) + ); + let rridx = idx; + let type = this.ns_get16(answer.addressOfElement(rridx)); + let dataLength = this.ns_get16(answer.addressOfElement(rridx + 8)); + + idx += this.NS_RRFIXEDSZ; + + if (type === aTypeID) { + let resource = this._mapAnswer(aTypeID, answer, idx, length); + resource.type = type; + resource.nsclass = this.ns_get16(answer.addressOfElement(rridx + 2)); + resource.ttl = this.ns_get32(answer.addressOfElement(rridx + 4)) | 0; + results.push(resource); + } + idx += dataLength; + } + return results; + }, +}; + +// For Windows. +function load_dnsapi() { + this._open(); +} + +load_dnsapi.prototype = { + library: null, + + // Tries to find and load library. + _open() { + function declare(aSymbolName, ...aArgs) { + try { + return library.declare(aSymbolName, ...aArgs); + } catch (ex) { + throw new Error( + "Failed to declare " + aSymbolName + " Exception: " + ex + "\n" + ); + } + } + + let library = (this.library = ctypes.open(ctypes.libraryName("DnsAPI"))); + + this.DNS_SRV_DATA = ctypes.StructType("DNS_SRV_DATA", [ + { pNameTarget: ctypes.jschar.ptr }, + { wPriority: ctypes.unsigned_short }, + { wWeight: ctypes.unsigned_short }, + { wPort: ctypes.unsigned_short }, + { Pad: ctypes.unsigned_short }, + ]); + + this.DNS_TXT_DATA = ctypes.StructType("DNS_TXT_DATA", [ + { dwStringCount: ctypes.unsigned_long }, + { pStringArray: ctypes.jschar.ptr.array(1) }, + ]); + + this.DNS_MX_DATA = ctypes.StructType("DNS_MX_DATA", [ + { pNameTarget: ctypes.jschar.ptr }, + { wPriority: ctypes.unsigned_short }, + { Pad: ctypes.unsigned_short }, + ]); + + this.DNS_RECORD = ctypes.StructType("_DnsRecord"); + this.DNS_RECORD.define([ + { pNext: this.DNS_RECORD.ptr }, + { pName: ctypes.jschar.ptr }, + { wType: ctypes.unsigned_short }, + { wDataLength: ctypes.unsigned_short }, + { Flags: ctypes.unsigned_long }, + { dwTtl: ctypes.unsigned_long }, + { dwReserved: ctypes.unsigned_long }, + { Data: this.DNS_SRV_DATA }, // it's a union, can be cast to many things + ]); + + this.PDNS_RECORD = ctypes.PointerType(this.DNS_RECORD); + this.DnsQuery_W = declare( + "DnsQuery_W", + ctypes.winapi_abi, + ctypes.long, + ctypes.jschar.ptr, + ctypes.unsigned_short, + ctypes.unsigned_long, + ctypes.voidptr_t, + this.PDNS_RECORD.ptr, + ctypes.voidptr_t.ptr + ); + this.DnsRecordListFree = declare( + "DnsRecordListFree", + ctypes.winapi_abi, + ctypes.void_t, + this.PDNS_RECORD, + ctypes.int + ); + + this.ERROR_SUCCESS = ctypes.Int64(0); + this.DNS_QUERY_STANDARD = 0; + this.DnsFreeRecordList = 1; + }, + + close() { + this.library.close(); + this.library = null; + }, + + // Maps record to SRVRecord, TXTRecord, or MXRecord according to aTypeID and + // returns it. + _mapAnswer(aTypeID, aData) { + if (aTypeID == NS_T_SRV) { + let srvdata = ctypes.cast(aData, this.DNS_SRV_DATA); + + return new SRVRecord( + srvdata.wPriority, + srvdata.wWeight, + srvdata.pNameTarget.readString(), + srvdata.wPort + ); + } else if (aTypeID == NS_T_TXT) { + let txtdata = ctypes.cast(aData, this.DNS_TXT_DATA); + if (txtdata.dwStringCount > 0) { + return new TXTRecord(txtdata.pStringArray[0].readString()); + } + } else if (aTypeID == NS_T_MX) { + let mxdata = ctypes.cast(aData, this.DNS_MX_DATA); + + return new MXRecord(mxdata.wPriority, mxdata.pNameTarget.readString()); + } + return {}; + }, + + // Performs a DNS query for aTypeID on a certain address (aName) and returns + // array of records of aTypeID (e.g. SRVRecord, TXTRecord, or MXRecord). + lookup(aName, aTypeID) { + let queryResultsSet = this.PDNS_RECORD(); + let qname = ctypes.jschar.array()(aName); + let dnsStatus = this.DnsQuery_W( + qname, + aTypeID, + this.DNS_QUERY_STANDARD, + null, + queryResultsSet.address(), + null + ); + + // There is an error. + if (ctypes.Int64.compare(dnsStatus, this.ERROR_SUCCESS) != 0) { + return []; + } + + let results = []; + for ( + let presult = queryResultsSet; + presult && !presult.isNull(); + presult = presult.contents.pNext + ) { + let result = presult.contents; + if (result.wType == aTypeID) { + let resource = this._mapAnswer(aTypeID, result.Data); + resource.type = result.wType; + resource.nsclass = 0; + resource.ttl = result.dwTtl | 0; + results.push(resource); + } + } + + this.DnsRecordListFree(queryResultsSet, this.DnsFreeRecordList); + return results; + }, +}; + +// Used to make results of different libraries consistent for SRV queries. +function SRVRecord(aPrio, aWeight, aHost, aPort) { + this.prio = aPrio; + this.weight = aWeight; + this.host = aHost; + this.port = aPort; +} + +// Used to make results of different libraries consistent for TXT queries. +function TXTRecord(aData) { + this.data = aData; +} + +// Used to make results of different libraries consistent for MX queries. +function MXRecord(aPrio, aHost) { + this.prio = aPrio; + this.host = aHost; +} + +if (typeof Components === "undefined") { + /* eslint-env worker */ + + // We are in a worker, wait for our message then execute the wanted method. + /* import-globals-from /toolkit/components/workerloader/require.js */ + importScripts("resource://gre/modules/workers/require.js"); + let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); + + let worker = new PromiseWorker.AbstractWorker(); + worker.dispatch = function (aMethod, aArgs = []) { + return self[aMethod](...aArgs); + }; + worker.postMessage = function (...aArgs) { + self.postMessage(...aArgs); + }; + worker.close = function () { + self.close(); + }; + self.addEventListener("message", msg => worker.handleMessage(msg)); + + // eslint-disable-next-line no-unused-vars + function execute(aOS, aMethod, aArgs) { + let DNS = aOS == "WINNT" ? new load_dnsapi() : new load_libresolv(aOS); + return DNS[aMethod].apply(DNS, aArgs); + } +} else { + // We are loaded as a JSM, provide the async front that will start the + // worker. + var dns_async_front = { + /** + * Constants for use with the lookup function. + */ + TXT: NS_T_TXT, + SRV: NS_T_SRV, + MX: NS_T_MX, + + /** + * Do an asynchronous DNS lookup. The returned promise resolves with + * one of the Answer objects as defined above, or rejects with the + * error from the worker. + * + * Example: DNS.lookup("_caldavs._tcp.example.com", DNS.SRV) + * + * @param aName The aName to look up. + * @param aTypeID The RR type to look up as a constant. + * @returns A promise resolved when completed. + */ + lookup(aName, aTypeID) { + let worker = new BasePromiseWorker(LOCATION); + return worker.post("execute", [ + Services.appinfo.OS, + "lookup", + [...arguments], + ]); + }, + + /** Convenience functions */ + srv(aName) { + return this.lookup(aName, NS_T_SRV); + }, + txt(aName) { + return this.lookup(aName, NS_T_TXT); + }, + mx(aName) { + return this.lookup(aName, NS_T_MX); + }, + }; + DNS = dns_async_front; +} |