From 4f8b37051e6d9936284651c914f426df25783a2f Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Mon, 29 Apr 2024 23:27:52 -0700 Subject: [PATCH 1/9] feat: added HarakaMx - test: added get_implicit_mx tests #89 - change: get_mx: don't filter implicit MX errors #89 - fix(get_public_ip): set timeout in stun request, fixes #84 --- .release | 2 +- CHANGELOG.md | 66 +++++++------ CONTRIBUTORS.md | 2 +- README.md | 43 ++++++++ index.js | 164 +++++++++++++++++++++++-------- package.json | 8 +- test/net_utils.js | 245 ++++++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 447 insertions(+), 83 deletions(-) diff --git a/.release b/.release index 36bb27a..0fa4e69 160000 --- a/.release +++ b/.release @@ -1 +1 @@ -Subproject commit 36bb27a93862517943e04f24fd67b0df2da6cbbe +Subproject commit 0fa4e690ffabb0157e46d56f18e4f7cfe49ce291 diff --git a/CHANGELOG.md b/CHANGELOG.md index 761abc0..b96fe48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ### Unreleased +### [1.7.0] - 2024-04-29 + +- feat: added HarakaMx #89 +- test: added get_implicit_mx tests #89 +- change: get_mx: don't filter implicit MX errors #89 +- fix(get_public_ip): set timeout in stun request, fixes #84 + ### [1.6.0] - 2024-04-17 - feat: normalizeDomain, for punycode/IDN names -- feat: get_mx now _also_ returns implicit MX records +- feat: get*mx now \_also* returns implicit MX records - feat: added get_implicit_mx - feat: added resolve_mx_hosts - doc(Changes): fixed broken tag version links @@ -62,88 +69,88 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). - chore(ci): populate test matrix with Node.js LTS versions - chore(ci): limit dependabot updates to production deps -#### [1.3.5] - 2022-05-27 +### [1.3.5] - 2022-05-27 - chore(ci): use shared GHA workflows - style(es6): use dns.promises internally - dep(async): replace async dependency with Promise.all - doc(README): use code fences around examples (vs indention) -#### [1.3.4] - 2022-01-05 +### [1.3.4] - 2022-01-05 - promisify get_ips_by_host (backwards compatible) -#### [1.3.3] - 2020-01-05 +### [1.3.3] - 2020-01-05 - refactored is_local_host function to return a promise instead of using a callback #65 -#### [1.3.2] - 2021-12-20 +### [1.3.2] - 2021-12-20 - add is_local_host function #63 -#### [1.3.1] - 2021-10-13 +### [1.3.1] - 2021-10-13 - get_mx: wrap dns.resolveMx in a try haraka/Haraka#2985 - add .release scripts - add GH workflow, publish release to NPM upon merge to master -#### 1.3.0 - 2021-01-23 +### 1.3.0 - 2021-01-23 - Support passing an array to ip_in_list #60 -#### 1.2.4 - 2021-01-14 +### 1.2.4 - 2021-01-14 - add "any" IP to is_local_ip - add TEST-NET-[1-3] to is_private_ip -#### 1.2.3 - 2020-12-19 +### 1.2.3 - 2020-12-19 - fix: restore the tests wrapping the resolveMX iterable -#### 1.2.2 - 2020-12-15 +### 1.2.2 - 2020-12-15 - get_mx: do not include implicit MX -#### [1.2.1] - 2020-11-17 +### [1.2.1] - 2020-11-17 - bump ipaddr.js to 2.0.0 #56 -#### [1.2.0] - 2020-06-23 +### [1.2.0] - 2020-06-23 - added get_mx - remove deprecated load_tls_ini - remove deprecated tls_ini_section_with_defaults -#### 1.1.5 - 2020-04-11 +### 1.1.5 - 2020-04-11 - ipv6_bogus: handle parsing broken ipv6 addresses #49 - update async to version 3.0.1 #43 -#### 1.1.4 - 2019-04-04 +### 1.1.4 - 2019-04-04 - stop is_private_ip from checking if the IP is bound to a local network interface -#### 1.1.3 - 2019-03-01 +### 1.1.3 - 2019-03-01 - is_local_ip checks local network interfaces too -#### 1.1.2 - 2018-11-03 +### 1.1.2 - 2018-11-03 - add is_local_ip -#### 1.1.1 - 2018-07-19 +### 1.1.1 - 2018-07-19 - ip_in_list doesn't throw on empty list -#### 1.1.0 - 2018-04-11 +### 1.1.0 - 2018-04-11 - add get_primary_host_name haraka/Haraka#2380 -#### 1.0.14 - 2018-01-25 +### 1.0.14 - 2018-01-25 - restore tls_ini_section_with_defaults function (deprecated since Haraka 2.0.17) -#### 1.0.13 - 2018-01-19 +### 1.0.13 - 2018-01-19 - get_public_ip: assign timer before calling connect #29 - avoid race where timeout isn't cleared because stun connect errors immediately @@ -152,40 +159,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). - eslint updates #25, #27 - improved x509 parser #22 -#### 1.0.10 - 2017-07-27 +### 1.0.10 - 2017-07-27 - added vs-stun as optional dep (from Haraka) #21 -#### 1.0.9 - 2017-06-16 +### 1.0.9 - 2017-06-16 - lint fixes for compat with eslint 4 #18 -#### 1.0.8 - 2017-03-08 +### 1.0.8 - 2017-03-08 - skip loading expired x509 (TLS) certs - make TLS cert dir configurable - rename certs -> cert (be consistent with haraka/plugins/tls) - store cert/key as buffers (was strings) -#### 1.0.7 - 2017-03-08 +### 1.0.7 - 2017-03-08 - handle undefined tls.ini section -#### 1.0.6 - 2017-03-04 +### 1.0.6 - 2017-03-04 - add tls_ini_section_with_defaults() - add load_tls_dir() - add parse_x509_names() -#### 1.0.5 - 2016-11-20 +### 1.0.5 - 2016-11-20 - add enableSNI TLS option -#### 1.0.4 - 2016-10-25 +### 1.0.4 - 2016-10-25 - initialize TLS opts in (section != main) as booleans -#### 1.0.3 - 2016-10-25 +### 1.0.3 - 2016-10-25 - added tls.ini loading @@ -197,7 +204,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). [1.3.4]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.4 [1.3.5]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.5 [1.3.6]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.6 -[1.3.7]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.3.7 +[1.3.7]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.7 [1.4.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.4.0 [1.4.1]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.4.1 [1.5.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.0 @@ -206,3 +213,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). [1.5.3]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.3 [1.5.4]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.4 [1.6.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.6.0 +[1.7.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.7.0 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ac187dd..90ae255 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -2,7 +2,7 @@ This handcrafted artisinal software is brought to you by: -|
msimerson (57) |
baudehlo (4) |
DoobleD (2) |
lnedry (2) |
Juerd (1) |
olsonpm (1) |
typingArtist (1) | +|
msimerson (58) |
baudehlo (4) |
DoobleD (2) |
lnedry (2) |
Juerd (1) |
olsonpm (1) |
typingArtist (1) | | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | this file is maintained by [.release](https://github.com/msimerson/.release) diff --git a/README.md b/README.md index 96ee3d8..6d1f69d 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,49 @@ try { } ``` +### HarakaMx + +An object class representing a MX. HarakaMx objects may contain the following properties: + +```js +{ + exchange: '', // required: a FQDN or IP address + priority: 0, // integer, a MX priority. + port: 25, // integer: an alternate port + bind: '', // an outbound IP address to bind to + bind_helo: '', // an outbound helo hostname + using_lmtp: false, // boolean, specify LMTP delivery + auth_user: '', // an AUTH username (required if AUTH is desired) + auth_pass: '', // an AUTH password (required if AUTH is desired) + auth_type: '', // an AUTH type that should be used with the MX. + from_dns: '', // the DNS name from which the MX was queried +} +``` + +Create a HarakaMx object in The Usual Way: + +```js +const nu = require('haraka-net-utils') +const myMx = new nu.HarakaMx(parameter) +``` + +The parameter can be one of: + +- A string in any of the following formats: + - hostname + - hostname:port + - IPv4 + - IPv4:port + - [IPv6] + - [IPv6]: port +- A [URL](https://nodejs.org/docs/latest-v20.x/api/url.html) string + - smtp://mail.example.com:25 + - lmtp://int-mail.example.com:24 + - smtp://user:pass@host.example.com:587 +- An object, containing at least an exchange, and any of the other properties listed at the top of this section. + +An optional second parameter is an alias for from_dns. + [ci-img]: https://github.com/haraka/haraka-net-utils/actions/workflows/ci.yml/badge.svg [ci-url]: https://github.com/haraka/haraka-net-utils/actions/workflows/ci.yml [cov-img]: https://codecov.io/github/haraka/haraka-net-utils/coverage.svg diff --git a/index.js b/index.js index 77955e7..252e483 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,14 @@ 'use strict' -// node.js built-ins -const { Resolver } = require('dns').promises +const { Resolver } = require('node:dns').promises const dns = new Resolver({ timeout: 25000, tries: 1 }) -const net = require('net') -const os = require('os') -const punycode = require('punycode.js') +const net = require('node:net') +const os = require('node:os') +const url = require('node:url') // npm modules const ipaddr = require('ipaddr.js') +const punycode = require('punycode.js') const sprintf = require('sprintf-js').sprintf const tlds = require('haraka-tld') @@ -287,7 +287,9 @@ exports.get_public_ip_async = async function () { }, timeout * 1000) // Connect to STUN Server - const res = await this.stun.request(get_stun_server()) + const res = await this.stun.request(get_stun_server(), { + maxTimeout: (timeout - 1) * 1000, + }) this.public_ip = res.getXorAddress().address clearTimeout(timer) return this.public_ip @@ -296,22 +298,21 @@ exports.get_public_ip_async = async function () { exports.get_public_ip = async function (cb) { if (!cb) return exports.get_public_ip_async() - const nu = this - if (nu.public_ip !== undefined) return cb(null, nu.public_ip) // cache + if (this.public_ip !== undefined) return cb(null, this.public_ip) // cache // manual config override, for the cases where we can't figure it out const smtpIni = exports.config.get('smtp.ini').main if (smtpIni.public_ip) { - nu.public_ip = smtpIni.public_ip - return cb(null, nu.public_ip) + this.public_ip = smtpIni.public_ip + return cb(null, this.public_ip) } // Initialise cache value to null to prevent running // should we hit a timeout or the module isn't installed. - nu.public_ip = null + this.public_ip = null try { - nu.stun = require('@msimerson/stun') + this.stun = require('@msimerson/stun') } catch (e) { e.install = 'Please install stun: "npm install -g stun"' console.error(`${e.msg}\n${e.install}`) @@ -324,17 +325,20 @@ exports.get_public_ip = async function (cb) { }, timeout * 1000) // Connect to STUN Server - nu.stun.request(get_stun_server(), (error, res) => { - if (timer) clearTimeout(timer) - if (error) return cb(error) - - nu.public_ip = res.getXorAddress().address - cb(null, nu.public_ip) - }) + this.stun.request( + get_stun_server(), + { maxTimeout: (timeout - 1) * 1000 }, + (error, res) => { + if (timer) clearTimeout(timer) + if (error) return cb(error) + + this.public_ip = res.getXorAddress().address + cb(null, this.public_ip) + }, + ) } function get_stun_server() { - // STUN servers by Google const servers = [ 'stun.l.google.com:19302', 'stun1.l.google.com:19302', @@ -345,11 +349,7 @@ function get_stun_server() { return servers[Math.floor(Math.random() * servers.length)] } -exports.get_ipany_re = function (prefix, suffix, modifier) { - if (prefix === undefined) prefix = '' - if (suffix === undefined) suffix = '' - if (modifier === undefined) modifier = 'mg' - /* eslint-disable prefer-template */ +exports.get_ipany_re = function (prefix = '', suffix = '', modifier = 'mg') { return new RegExp( prefix + `(` + // capture group @@ -493,31 +493,24 @@ exports.get_mx = async (raw_domain, cb) => { const domain = normalizeDomain(raw_domain) try { - const exchanges = await dns.resolveMx(domain) + let exchanges = await dns.resolveMx(domain) if (exchanges && exchanges.length) { - exchanges.map((e) => (e.from_dns = domain)) + exchanges = exchanges.map((e) => new HarakaMx(e, domain)) if (cb) return cb(null, exchanges) return exchanges } + // no MX record(s), fall through } catch (err) { - // console.error(err.message) if (fatal_mx_err(err)) { if (cb) return cb(err, []) throw err } + // non-terminal DNS failure, fall through } - // no MX or non-fatal DNS failure - try { - const exchanges = await this.get_implicit_mx(domain) - if (cb) return cb(null, exchanges) - return exchanges - } catch (err) { - if (fatal_mx_err(err)) { - if (cb) return cb(err, []) - throw err - } - } + const exchanges = await this.get_implicit_mx(domain) + if (cb) return cb(null, exchanges) + return exchanges } exports.get_implicit_mx = async (domain) => { @@ -528,9 +521,7 @@ exports.get_implicit_mx = async (domain) => { return r .filter((a) => a.status === 'fulfilled') - .flatMap((a) => - a.value.map((ip) => ({ priority: 0, exchange: ip, from_dns: domain })), - ) + .flatMap((a) => a.value.map((ip) => new HarakaMx(ip, domain))) } exports.resolve_mx_hosts = async (mxes) => { @@ -570,3 +561,92 @@ exports.resolve_mx_hosts = async (mxes) => { return settled.filter((s) => s.status === 'fulfilled').flatMap((s) => s.value) } + +class HarakaMx { + constructor(obj = {}, domain) { + switch (typeof obj) { + case 'string': + /mtp:\/\//.test(obj) ? this.fromUrl(obj) : this.fromString(obj) + break + case 'object': + this.fromObject(obj) + break + } + + if (this.priority === undefined) this.priority = 0 + if (domain && this.from_dns === undefined) { + this.from_dns = domain.toLowerCase() + } + } + + fromObject(obj) { + for (const prop of [ + 'exchange', + 'priority', + 'port', + 'bind', + 'bind_helo', + 'using_lmtp', + 'auth_user', + 'auth_pass', + 'auth_type', + 'from_dns', + ]) { + if (obj[prop] !== undefined) this[prop] = obj[prop] + } + } + + fromString(str) { + const matches = /^\[?(.*?)\]?(?::(24|25|465|587|\d{4,5}))?$/.exec(str) + if (matches) { + this.exchange = matches[1].toLowerCase() + if (matches[2]) this.port = parseInt(matches[2]) + } else { + this.exchange = str + } + } + + fromUrl(str) { + const dest = new url.URL(str) + + switch (dest.protocol) { + case 'smtp:': + if (!dest.port) dest.port = 25 + break + case 'lmtp:': + this.using_lmtp = true + if (!dest.port) dest.port = 24 + break + } + + if (dest.hostname) this.exchange = dest.hostname.toLowerCase() + if (dest.port) this.port = parseInt(dest.port) + if (dest.username) this.auth_user = dest.username + if (dest.password) this.auth_pass = dest.password + } + + toUrl() { + const proto = this.using_lmtp ? 'lmtp://' : 'smtp://' + const auth = this.auth_user ? `${this.auth_user}:****@` : '' + const host = net.isIPv6(this.exchange) + ? `[${this.exchange}]` + : this.exchange + const port = this.port ? this.port : proto === 'lmtp://' ? 24 : 25 + return new url.URL(`${proto}${auth}${host}:${port}`) + } + + toString() { + const from_dns = this.from_dns ? ` (from ${this.from_dns})` : '' + return `MX ${this.priority} ${this.toUrl()}${from_dns}` + } +} + +/* + * A string of one of the following formats: + * hostname + * hostname:port + * ipaddress + * ipaddress:port + */ + +exports.HarakaMx = HarakaMx diff --git a/package.json b/package.json index dd51bb9..01b60f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "haraka-net-utils", - "version": "1.6.0", + "version": "1.7.0", "description": "haraka network utilities", "main": "index.js", "files": [ @@ -36,12 +36,12 @@ }, "homepage": "https://github.com/haraka/haraka-net-utils#readme", "devDependencies": { - "@haraka/eslint-config": "^1.1.3" + "@haraka/eslint-config": "^1.1.5" }, "dependencies": { - "haraka-config": "^1.1.0", + "haraka-config": "^1.2.4", "haraka-tld": "^1.2.1", - "ipaddr.js": "^2.1.0", + "ipaddr.js": "^2.2.0", "punycode.js": "^2.3.1", "openssl-wrapper": "^0.3.4", "sprintf-js": "^1.1.3" diff --git a/test/net_utils.js b/test/net_utils.js index a547a83..26da194 100644 --- a/test/net_utils.js +++ b/test/net_utils.js @@ -271,11 +271,9 @@ describe('get_public_ip', function () { it('normal', function (done) { this.net_utils.public_ip = undefined const cb = function (err, ip) { - // console.log(`ip: ${ip}`); - // console.log(`err: ${err}`); if (has_stun()) { if (err) { - console.log(err) + console.error(err) } else { console.log(`stun success: ${ip}`) assert.equal(null, err) @@ -1285,7 +1283,6 @@ describe('get_mx', function () { this.net_utils.get_mx(c, (err, mxlist) => { if (err) console.error(err) assert.ifError(err) - // assert.ok(mxlist.length); checkValid(validCases[c], mxlist) done() }) @@ -1294,7 +1291,6 @@ describe('get_mx', function () { it(`awaits MX records for ${c}`, async function () { this.timeout(12000) const mxlist = await this.net_utils.get_mx(c) - // assert.ok(mxlist.length); checkValid(validCases[c], mxlist) }) } @@ -1303,6 +1299,8 @@ describe('get_mx', function () { const invalidCases = { invalid: /queryMx (ENODATA|ENOTFOUND|ESERVFAIL) invalid/, 'gmail.xn--com-0da': /(ENOTFOUND|Cannot convert name to ASCII)/, + 'non-exist.haraka.tnpi.net': /ignore/, + 'haraka.non-exist': /ignore/, } function checkInvalid(expected, actual) { @@ -1316,8 +1314,8 @@ describe('get_mx', function () { for (const c in invalidCases) { it(`cb does not crash on invalid name: ${c}`, function () { this.net_utils.get_mx(c, (err, mxlist) => { + if (err) checkInvalid(invalidCases[c], err.message) assert.equal(mxlist.length, 0) - checkInvalid(invalidCases[c], err.message) }) }) @@ -1389,3 +1387,238 @@ describe('resolve_mx_hosts', function () { assert.equal(r.length, 8) }) }) + +describe('get_implicit_mx', function () { + this.timeout(5000) + + beforeEach(function (done) { + this.net_utils = require('../index') + done() + }) + + it('harakamail.com', async function () { + const mf = await this.net_utils.get_implicit_mx('harakamail.com') + assert.equal(mf.length, 1) + }) + + it('mx.theartfarm.com', async function () { + const mf = await this.net_utils.get_implicit_mx('mx.theartfarm.com') + assert.equal(mf.length, 0) + }) + + it('resolve-fail-definitive.josef-froehle.de', async function () { + const mf = await this.net_utils.get_implicit_mx( + 'resolve-fail-definitive.josef-froehle.de', + ) + assert.equal(mf.length, 0) + }) + it('resolve-fail-a.josef-froehle.de', async function () { + const mf = await this.net_utils.get_implicit_mx( + 'resolve-fail-a.josef-froehle.de', + ) + assert.equal(mf.length, 1) + }) + it('resolve-fail-aaaa.josef-froehle.de', async function () { + const mf = await this.net_utils.get_implicit_mx( + 'resolve-fail-aaaa.josef-froehle.de', + ) + assert.equal(mf.length, 0) + }) +}) + +describe('HarakaMx', () => { + beforeEach(function (done) { + this.nu = require('../index') + done() + }) + + describe('fromObject', () => { + it('accepts an object', function () { + assert.deepEqual( + new this.nu.HarakaMx({ + from_dns: 'example.com', + exchange: '.', + priority: 0, + }), + { from_dns: 'example.com', exchange: '.', priority: 0 }, + ) + }) + + it('sets default priority to 0', function () { + assert.deepEqual(new this.nu.HarakaMx({ exchange: '.' }), { + exchange: '.', + priority: 0, + }) + }) + + it('if optional domain provided, sets from_dns', function () { + assert.deepEqual(new this.nu.HarakaMx({ exchange: '.' }, 'example.com'), { + from_dns: 'example.com', + exchange: '.', + priority: 0, + }) + }) + }) + + describe('fromString', function () { + it('parses a hostname', function () { + assert.deepEqual(new this.nu.HarakaMx('mail.example.com'), { + exchange: 'mail.example.com', + priority: 0, + }) + }) + + it('parses a hostname:port', function () { + assert.deepEqual(new this.nu.HarakaMx('mail.example.com:25'), { + exchange: 'mail.example.com', + port: 25, + priority: 0, + }) + }) + + it('parses an IPv4', function () { + assert.deepEqual(new this.nu.HarakaMx('192.0.2.1'), { + exchange: '192.0.2.1', + priority: 0, + }) + }) + + it('parses an IPv4:port', function () { + assert.deepEqual(new this.nu.HarakaMx('192.0.2.1:25'), { + exchange: '192.0.2.1', + port: 25, + priority: 0, + }) + }) + + it('parses an IPv6', function () { + assert.deepEqual(new this.nu.HarakaMx('2001:db8::1'), { + exchange: '2001:db8::1', + priority: 0, + }) + }) + + it('parses an IPv6:port', function () { + assert.deepEqual(new this.nu.HarakaMx('2001:db8::1:25'), { + exchange: '2001:db8::1', + port: 25, + priority: 0, + }) + }) + + it('parses an [IPv6]:port', function () { + assert.deepEqual(new this.nu.HarakaMx('[2001:db8::1]:25'), { + exchange: '2001:db8::1', + port: 25, + priority: 0, + }) + }) + }) + + describe('fromUri', function () { + it('parses simple URIs', function () { + assert.deepEqual(new this.nu.HarakaMx('smtp://192.0.2.2'), { + exchange: '192.0.2.2', + port: 25, + priority: 0, + }) + + assert.deepEqual(new this.nu.HarakaMx('smtp://[2001:db8::1]:25'), { + exchange: '[2001:db8::1]', + port: 25, + priority: 0, + }) + }) + + it('parses more complex URIs', function () { + assert.deepEqual( + new this.nu.HarakaMx('smtp://authUser:sekretPass@[2001:db8::1]'), + { + exchange: '[2001:db8::1]', + port: 25, + priority: 0, + auth_pass: 'sekretPass', + auth_user: 'authUser', + }, + ) + + assert.deepEqual( + new this.nu.HarakaMx('lmtp://authUser:sekretPass@[2001:db8::1]:25'), + { + exchange: '[2001:db8::1]', + port: 25, + priority: 0, + using_lmtp: true, + auth_pass: 'sekretPass', + auth_user: 'authUser', + }, + ) + }) + }) + + describe('toUrl', function () { + it('has a reasonable toUrl()', function () { + assert.equal( + new this.nu.HarakaMx({ exchange: '.' }).toUrl(), + 'smtp://.:25', + ) + + assert.equal( + new this.nu.HarakaMx({ + from_dns: 'example.com', + exchange: '.', + priority: 10, + }).toUrl(), + 'smtp://.:25', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toUrl(), + 'smtp://au:****@192.0.2.3:25', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toUrl(), + 'smtp://au:****@192.0.2.3:465', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toUrl(), + 'smtp://[2001:db8::1]:25', + ) + }) + }) + + describe('toString', function () { + it('has a reasonable toString()', function () { + assert.equal( + new this.nu.HarakaMx({ exchange: '.' }).toString(), + 'MX 0 smtp://.:25', + ) + + assert.equal( + new this.nu.HarakaMx({ + from_dns: 'example.com', + exchange: '.', + priority: 10, + }).toString(), + 'MX 10 smtp://.:25 (from example.com)', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toString(), + 'MX 0 smtp://au:****@192.0.2.3:25', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toString(), + 'MX 0 smtp://au:****@192.0.2.3:465', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toString(), + 'MX 0 smtp://[2001:db8::1]:25', + ) + }) + }) +}) From 0784a88434002559b3295d1e9c9e2f9f9bc70695 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 30 Apr 2024 08:28:51 -0700 Subject: [PATCH 2/9] refactor bigger tests into files --- test/get_ip_any.js | 628 +++++++++++++++++++++++ test/get_mx.js | 181 +++++++ test/get_public_ip.js | 81 +++ test/harakaMx.js | 198 ++++++++ test/net_utils.js | 1095 +---------------------------------------- 5 files changed, 1091 insertions(+), 1092 deletions(-) create mode 100644 test/get_ip_any.js create mode 100644 test/get_mx.js create mode 100644 test/get_public_ip.js create mode 100644 test/harakaMx.js diff --git a/test/get_ip_any.js b/test/get_ip_any.js new file mode 100644 index 0000000..c1d61cb --- /dev/null +++ b/test/get_ip_any.js @@ -0,0 +1,628 @@ +const assert = require('node:assert') +const net = require('node:net') + + +const ip_fixtures = [ + [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876 '], + [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0'], + [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876'], + [false, ' 2001:0:1234::C1C0:ABCD:876 '], + [false, ' 2001:0:1234::C1C0:ABCD:876'], + [false, ''], + [false, "':10.0.0.1"], + [false, '---'], + [false, '02001:0000:1234:0000:0000:C1C0:ABCD:0876'], + [false, '1.2.3.4:1111:2222:3333:4444::5555'], + [false, '1.2.3.4:1111:2222:3333::5555'], + [false, '1.2.3.4:1111:2222::5555'], + [false, '1.2.3.4:1111::5555'], + [false, '1.2.3.4::'], + [false, '1.2.3.4::5555'], + [false, '1111'], + [false, '11112222:3333:4444:5555:6666:1.2.3.4'], + [false, '11112222:3333:4444:5555:6666:7777:8888'], + [false, '1111:'], + [false, '1111:1.2.3.4'], + [false, '1111:2222'], + [false, '1111:22223333:4444:5555:6666:1.2.3.4'], + [false, '1111:22223333:4444:5555:6666:7777:8888'], + [false, '1111:2222:'], + [false, '1111:2222:1.2.3.4'], + [false, '1111:2222:3333'], + [false, '1111:2222:33334444:5555:6666:1.2.3.4'], + [false, '1111:2222:33334444:5555:6666:7777:8888'], + [false, '1111:2222:3333:'], + [false, '1111:2222:3333:1.2.3.4'], + [false, '1111:2222:3333:4444'], + [false, '1111:2222:3333:44445555:6666:1.2.3.4'], + [false, '1111:2222:3333:44445555:6666:7777:8888'], + [false, '1111:2222:3333:4444:'], + [false, '1111:2222:3333:4444:1.2.3.4'], + [false, '1111:2222:3333:4444:5555'], + [false, '1111:2222:3333:4444:55556666:1.2.3.4'], + [false, '1111:2222:3333:4444:55556666:7777:8888'], + [false, '1111:2222:3333:4444:5555:'], + [false, '1111:2222:3333:4444:5555:1.2.3.4'], + [false, '1111:2222:3333:4444:5555:6666'], + [false, '1111:2222:3333:4444:5555:66661.2.3.4'], + [false, '1111:2222:3333:4444:5555:66667777:8888'], + [false, '1111:2222:3333:4444:5555:6666:'], + [false, '1111:2222:3333:4444:5555:6666:00.00.00.00'], + [false, '1111:2222:3333:4444:5555:6666:000.000.000.000'], + [false, '1111:2222:3333:4444:5555:6666:1.2.3.4.5'], + [false, '1111:2222:3333:4444:5555:6666:255.255.255255'], + [false, '1111:2222:3333:4444:5555:6666:255.255255.255'], + [false, '1111:2222:3333:4444:5555:6666:255255.255.255'], + [false, '1111:2222:3333:4444:5555:6666:256.256.256.256'], + [false, '1111:2222:3333:4444:5555:6666:7777'], + [false, '1111:2222:3333:4444:5555:6666:77778888'], + [false, '1111:2222:3333:4444:5555:6666:7777:'], + [false, '1111:2222:3333:4444:5555:6666:7777:1.2.3.4'], + [false, '1111:2222:3333:4444:5555:6666:7777:8888:'], + [false, '1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4'], + [false, '1111:2222:3333:4444:5555:6666:7777:8888:9999'], + [false, '1111:2222:3333:4444:5555:6666:7777:8888::'], + [false, '1111:2222:3333:4444:5555:6666:7777:::'], + [false, '1111:2222:3333:4444:5555:6666::1.2.3.4'], + [false, '1111:2222:3333:4444:5555:6666::8888:'], + [false, '1111:2222:3333:4444:5555:6666:::'], + [false, '1111:2222:3333:4444:5555:6666:::8888'], + [false, '1111:2222:3333:4444:5555::7777:8888:'], + [false, '1111:2222:3333:4444:5555::7777::'], + [false, '1111:2222:3333:4444:5555::8888:'], + [false, '1111:2222:3333:4444:5555:::'], + [false, '1111:2222:3333:4444:5555:::1.2.3.4'], + [false, '1111:2222:3333:4444:5555:::7777:8888'], + [false, '1111:2222:3333:4444::5555:'], + [false, '1111:2222:3333:4444::6666:7777:8888:'], + [false, '1111:2222:3333:4444::6666:7777::'], + [false, '1111:2222:3333:4444::6666::8888'], + [false, '1111:2222:3333:4444::7777:8888:'], + [false, '1111:2222:3333:4444::8888:'], + [false, '1111:2222:3333:4444:::'], + [false, '1111:2222:3333:4444:::6666:1.2.3.4'], + [false, '1111:2222:3333:4444:::6666:7777:8888'], + [false, '1111:2222:3333::5555:'], + [false, '1111:2222:3333::5555:6666:7777:8888:'], + [false, '1111:2222:3333::5555:6666:7777::'], + [false, '1111:2222:3333::5555:6666::8888'], + [false, '1111:2222:3333::5555::1.2.3.4'], + [false, '1111:2222:3333::5555::7777:8888'], + [false, '1111:2222:3333::6666:7777:8888:'], + [false, '1111:2222:3333::7777:8888:'], + [false, '1111:2222:3333::8888:'], + [false, '1111:2222:3333:::'], + [false, '1111:2222:3333:::5555:6666:1.2.3.4'], + [false, '1111:2222:3333:::5555:6666:7777:8888'], + [false, '1111:2222::4444:5555:6666:7777:8888:'], + [false, '1111:2222::4444:5555:6666:7777::'], + [false, '1111:2222::4444:5555:6666::8888'], + [false, '1111:2222::4444:5555::1.2.3.4'], + [false, '1111:2222::4444:5555::7777:8888'], + [false, '1111:2222::4444::6666:1.2.3.4'], + [false, '1111:2222::4444::6666:7777:8888'], + [false, '1111:2222::5555:'], + [false, '1111:2222::5555:6666:7777:8888:'], + [false, '1111:2222::6666:7777:8888:'], + [false, '1111:2222::7777:8888:'], + [false, '1111:2222::8888:'], + [false, '1111:2222:::'], + [false, '1111:2222:::4444:5555:6666:1.2.3.4'], + [false, '1111:2222:::4444:5555:6666:7777:8888'], + [false, '1111::3333:4444:5555:6666:7777:8888:'], + [false, '1111::3333:4444:5555:6666:7777::'], + [false, '1111::3333:4444:5555:6666::8888'], + [false, '1111::3333:4444:5555::1.2.3.4'], + [false, '1111::3333:4444:5555::7777:8888'], + [false, '1111::3333:4444::6666:1.2.3.4'], + [false, '1111::3333:4444::6666:7777:8888'], + [false, '1111::3333::5555:6666:1.2.3.4'], + [false, '1111::3333::5555:6666:7777:8888'], + [false, '1111::4444:5555:6666:7777:8888:'], + [false, '1111::5555:'], + [false, '1111::5555:6666:7777:8888:'], + [false, '1111::6666:7777:8888:'], + [false, '1111::7777:8888:'], + [false, '1111::8888:'], + [false, '1111:::'], + [false, '1111:::3333:4444:5555:6666:1.2.3.4'], + [false, '1111:::3333:4444:5555:6666:7777:8888'], + [false, '123'], + [false, '12345::6:7:8'], + [false, '192.168.0.256'], + [false, '192.168.256.0'], + [false, '1:2:3:4:5:6:7:8:9'], + [false, '1:2:3::4:5:6:7:8:9'], + [false, '1:2:3::4:5::7:8'], + [false, '1::1.2.256.4'], + [false, '1::1.2.3.256'], + [false, '1::1.2.3.300'], + [false, '1::1.2.3.900'], + [false, '1::1.2.300.4'], + [false, '1::1.2.900.4'], + [false, '1::1.256.3.4'], + [false, '1::1.300.3.4'], + [false, '1::1.900.3.4'], + [false, '1::256.2.3.4'], + [false, '1::260.2.3.4'], + [false, '1::2::3'], + [false, '1::300.2.3.4'], + [false, '1::300.300.300.300'], + [false, '1::3000.30.30.30'], + [false, '1::400.2.3.4'], + [false, '1::5:1.2.256.4'], + [false, '1::5:1.2.3.256'], + [false, '1::5:1.2.3.300'], + [false, '1::5:1.2.3.900'], + [false, '1::5:1.2.300.4'], + [false, '1::5:1.2.900.4'], + [false, '1::5:1.256.3.4'], + [false, '1::5:1.300.3.4'], + [false, '1::5:1.900.3.4'], + [false, '1::5:256.2.3.4'], + [false, '1::5:260.2.3.4'], + [false, '1::5:300.2.3.4'], + [false, '1::5:300.300.300.300'], + [false, '1::5:3000.30.30.30'], + [false, '1::5:400.2.3.4'], + [false, '1::5:900.2.3.4'], + [false, '1::900.2.3.4'], + [false, '1:::3:4:5'], + [false, '2001:0000:1234: 0000:0000:C1C0:ABCD:0876'], + [false, '2001:0000:1234:0000:00001:C1C0:ABCD:0876'], + [false, '2001:0000:1234:0000:0000:C1C0:ABCD:0876 0'], + [false, '2001:1:1:1:1:1:255Z255X255Y255'], + [false, '2001::FFD3::57ab'], + [false, '2001:DB8:0:0:8:800:200C:417A:221'], + [false, '2001:db8:85a3::8a2e:37023:7334'], + [false, '2001:db8:85a3::8a2e:370k:7334'], + [false, '255.256.255.255'], + [false, '256.255.255.255'], + [false, '3ffe:0b00:0000:0001:0000:0000:000a'], + [false, '3ffe:b00::1::a'], + [false, ':'], + [false, ':1.2.3.4'], + [false, ':1111:2222:3333:4444:5555:6666:1.2.3.4'], + [false, ':1111:2222:3333:4444:5555:6666:7777:8888'], + [false, ':1111:2222:3333:4444:5555:6666:7777::'], + [false, ':1111:2222:3333:4444:5555:6666::'], + [false, ':1111:2222:3333:4444:5555:6666::8888'], + [false, ':1111:2222:3333:4444:5555::'], + [false, ':1111:2222:3333:4444:5555::1.2.3.4'], + [false, ':1111:2222:3333:4444:5555::7777:8888'], + [false, ':1111:2222:3333:4444:5555::8888'], + [false, ':1111:2222:3333:4444::'], + [false, ':1111:2222:3333:4444::1.2.3.4'], + [false, ':1111:2222:3333:4444::5555'], + [false, ':1111:2222:3333:4444::6666:1.2.3.4'], + [false, ':1111:2222:3333:4444::6666:7777:8888'], + [false, ':1111:2222:3333:4444::7777:8888'], + [false, ':1111:2222:3333:4444::8888'], + [false, ':1111:2222:3333::'], + [false, ':1111:2222:3333::1.2.3.4'], + [false, ':1111:2222:3333::5555'], + [false, ':1111:2222:3333::5555:6666:1.2.3.4'], + [false, ':1111:2222:3333::5555:6666:7777:8888'], + [false, ':1111:2222:3333::6666:1.2.3.4'], + [false, ':1111:2222:3333::6666:7777:8888'], + [false, ':1111:2222:3333::7777:8888'], + [false, ':1111:2222:3333::8888'], + [false, ':1111:2222::'], + [false, ':1111:2222::1.2.3.4'], + [false, ':1111:2222::4444:5555:6666:1.2.3.4'], + [false, ':1111:2222::4444:5555:6666:7777:8888'], + [false, ':1111:2222::5555'], + [false, ':1111:2222::5555:6666:1.2.3.4'], + [false, ':1111:2222::5555:6666:7777:8888'], + [false, ':1111:2222::6666:1.2.3.4'], + [false, ':1111:2222::6666:7777:8888'], + [false, ':1111:2222::7777:8888'], + [false, ':1111:2222::8888'], + [false, ':1111::'], + [false, ':1111::1.2.3.4'], + [false, ':1111::3333:4444:5555:6666:1.2.3.4'], + [false, ':1111::3333:4444:5555:6666:7777:8888'], + [false, ':1111::4444:5555:6666:1.2.3.4'], + [false, ':1111::4444:5555:6666:7777:8888'], + [false, ':1111::5555'], + [false, ':1111::5555:6666:1.2.3.4'], + [false, ':1111::5555:6666:7777:8888'], + [false, ':1111::6666:1.2.3.4'], + [false, ':1111::6666:7777:8888'], + [false, ':1111::7777:8888'], + [false, ':1111::8888'], + [false, ':2222:3333:4444:5555:6666:1.2.3.4'], + [false, ':2222:3333:4444:5555:6666:7777:8888'], + [false, ':3333:4444:5555:6666:1.2.3.4'], + [false, ':3333:4444:5555:6666:7777:8888'], + [false, ':4444:5555:6666:1.2.3.4'], + [false, ':4444:5555:6666:7777:8888'], + [false, ':5555:6666:1.2.3.4'], + [false, ':5555:6666:7777:8888'], + [false, ':6666:1.2.3.4'], + [false, ':6666:7777:8888'], + [false, ':7777:8888'], + [false, ':8888'], + [false, '::.'], + [false, '::..'], + [false, '::...'], + [false, '::...4'], + [false, '::..3.'], + [false, '::..3.4'], + [false, '::.2..'], + [false, '::.2.3.'], + [false, '::.2.3.4'], + [false, '::1...'], + [false, '::1.2..'], + [false, '::1.2.256.4'], + [false, '::1.2.3.'], + [false, '::1.2.3.256'], + [false, '::1.2.3.300'], + [false, '::1.2.3.900'], + [false, '::1.2.300.4'], + [false, '::1.2.900.4'], + [false, '::1.256.3.4'], + [false, '::1.300.3.4'], + [false, '::1.900.3.4'], + [false, '::1111:2222:3333:4444:5555:6666::'], + [false, '::2222:3333:4444:5555:6666:7777:1.2.3.4'], + [false, '::2222:3333:4444:5555:6666:7777:8888:'], + [false, '::2222:3333:4444:5555:6666:7777:8888:9999'], + [false, '::2222:3333:4444:5555:7777:8888::'], + [false, '::2222:3333:4444:5555:7777::8888'], + [false, '::2222:3333:4444:5555::1.2.3.4'], + [false, '::2222:3333:4444:5555::7777:8888'], + [false, '::2222:3333:4444::6666:1.2.3.4'], + [false, '::2222:3333:4444::6666:7777:8888'], + [false, '::2222:3333::5555:6666:1.2.3.4'], + [false, '::2222:3333::5555:6666:7777:8888'], + [false, '::2222::4444:5555:6666:1.2.3.4'], + [false, '::2222::4444:5555:6666:7777:8888'], + [false, '::256.2.3.4'], + [false, '::260.2.3.4'], + [false, '::300.2.3.4'], + [false, '::300.300.300.300'], + [false, '::3000.30.30.30'], + [false, '::3333:4444:5555:6666:7777:8888:'], + [false, '::400.2.3.4'], + [false, '::4444:5555:6666:7777:8888:'], + [false, '::5555:'], + [false, '::5555:6666:7777:8888:'], + [false, '::6666:7777:8888:'], + [false, '::7777:8888:'], + [false, '::8888:'], + [false, '::900.2.3.4'], + [false, ':::'], + [false, ':::1.2.3.4'], + [false, ':::2222:3333:4444:5555:6666:1.2.3.4'], + [false, ':::2222:3333:4444:5555:6666:7777:8888'], + [false, ':::3333:4444:5555:6666:7777:8888'], + [false, ':::4444:5555:6666:1.2.3.4'], + [false, ':::4444:5555:6666:7777:8888'], + [false, ':::5555'], + [false, ':::5555:6666:1.2.3.4'], + [false, ':::5555:6666:7777:8888'], + [false, ':::6666:1.2.3.4'], + [false, ':::6666:7777:8888'], + [false, ':::7777:8888'], + [false, ':::8888'], + [false, '::ffff:192x168.1.26'], + [false, '::ffff:2.3.4'], + [false, '::ffff:257.1.2.3'], + [false, 'FF01::101::2'], + [false, 'FF02:0000:0000:0000:0000:0000:0000:0000:0001'], + [false, 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4'], + [false, 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX'], + [false, 'fe80:0000:0000:0000:0204:61ff:254.157.241.086'], + [false, 'fe80::4413:c8ae:2821:5852%10'], + [false, 'ldkfj'], + [false, 'mydomain.com'], + [false, 'test.mydomain.com'], + [true, '0000:0000:0000:0000:0000:0000:0000:0000'], + [true, '0000:0000:0000:0000:0000:0000:0000:0001'], + [true, '0:0:0:0:0:0:0:0'], + [true, '0:0:0:0:0:0:0:1'], + [true, '0:0:0:0:0:0:0::'], + [true, '0:0:0:0:0:0:13.1.68.3'], + [true, '0:0:0:0:0:0::'], + [true, '0:0:0:0:0::'], + [true, '0:0:0:0:0:FFFF:129.144.52.38'], + [true, '0:0:0:0::'], + [true, '0:0:0::'], + [true, '0:0::'], + [true, '0::'], + [true, '0:a:b:c:d:e:f::'], + [true, '1.2.3.4'], + [true, '1111:2222:3333:4444:5555:6666:123.123.123.123'], + [true, '1111:2222:3333:4444:5555:6666:7777:8888'], + [true, '1111:2222:3333:4444:5555:6666:7777::'], + [true, '1111:2222:3333:4444:5555:6666::'], + [true, '1111:2222:3333:4444:5555:6666::8888'], + [true, '1111:2222:3333:4444:5555::'], + [true, '1111:2222:3333:4444:5555::123.123.123.123'], + [true, '1111:2222:3333:4444:5555::7777:8888'], + [true, '1111:2222:3333:4444:5555::8888'], + [true, '1111:2222:3333:4444::'], + [true, '1111:2222:3333:4444::123.123.123.123'], + [true, '1111:2222:3333:4444::6666:123.123.123.123'], + [true, '1111:2222:3333:4444::6666:7777:8888'], + [true, '1111:2222:3333:4444::7777:8888'], + [true, '1111:2222:3333:4444::8888'], + [true, '1111:2222:3333::'], + [true, '1111:2222:3333::123.123.123.123'], + [true, '1111:2222:3333::5555:6666:123.123.123.123'], + [true, '1111:2222:3333::5555:6666:7777:8888'], + [true, '1111:2222:3333::6666:123.123.123.123'], + [true, '1111:2222:3333::6666:7777:8888'], + [true, '1111:2222:3333::7777:8888'], + [true, '1111:2222:3333::8888'], + [true, '1111:2222::'], + [true, '1111:2222::123.123.123.123'], + [true, '1111:2222::4444:5555:6666:123.123.123.123'], + [true, '1111:2222::4444:5555:6666:7777:8888'], + [true, '1111:2222::5555:6666:123.123.123.123'], + [true, '1111:2222::5555:6666:7777:8888'], + [true, '1111:2222::6666:123.123.123.123'], + [true, '1111:2222::6666:7777:8888'], + [true, '1111:2222::7777:8888'], + [true, '1111:2222::8888'], + [true, '1111::'], + [true, '1111::123.123.123.123'], + [true, '1111::3333:4444:5555:6666:123.123.123.123'], + [true, '1111::3333:4444:5555:6666:7777:8888'], + [true, '1111::4444:5555:6666:123.123.123.123'], + [true, '1111::4444:5555:6666:7777:8888'], + [true, '1111::5555:6666:123.123.123.123'], + [true, '1111::5555:6666:7777:8888'], + [true, '1111::6666:123.123.123.123'], + [true, '1111::6666:7777:8888'], + [true, '1111::7777:8888'], + [true, '1111::8888'], + [true, '123.23.34.2'], + [true, '172.26.168.134'], + [true, '192.168.0.0'], + [true, '192.168.128.255'], + [true, '1:2:3:4:5:6:1.2.3.4'], + [true, '1:2:3:4:5:6:7:8'], + [true, '1:2:3:4:5:6::'], + [true, '1:2:3:4:5:6::8'], + [true, '1:2:3:4:5::'], + [true, '1:2:3:4:5::1.2.3.4'], + [true, '1:2:3:4:5::7:8'], + [true, '1:2:3:4:5::8'], + [true, '1:2:3:4::'], + [true, '1:2:3:4::1.2.3.4'], + [true, '1:2:3:4::5:1.2.3.4'], + [true, '1:2:3:4::7:8'], + [true, '1:2:3:4::8'], + [true, '1:2:3::'], + [true, '1:2:3::1.2.3.4'], + [true, '1:2:3::5:1.2.3.4'], + [true, '1:2:3::7:8'], + [true, '1:2:3::8'], + [true, '1:2::'], + [true, '1:2::1.2.3.4'], + [true, '1:2::5:1.2.3.4'], + [true, '1:2::7:8'], + [true, '1:2::8'], + [true, '1::'], + [true, '1::1.2.3.4'], + [true, '1::2:3'], + [true, '1::2:3:4'], + [true, '1::2:3:4:5'], + [true, '1::2:3:4:5:6'], + [true, '1::2:3:4:5:6:7'], + [true, '1::5:1.2.3.4'], + [true, '1::5:11.22.33.44'], + [true, '1::7:8'], + [true, '1::8'], + [true, '2001:0000:1234:0000:0000:C1C0:ABCD:0876'], + [true, '2001:0:1234::C1C0:ABCD:876'], + [true, '2001:0db8:0000:0000:0000:0000:1428:57ab'], + [true, '2001:0db8:0000:0000:0000::1428:57ab'], + [true, '2001:0db8:0:0:0:0:1428:57ab'], + [true, '2001:0db8:0:0::1428:57ab'], + [true, '2001:0db8:1234:0000:0000:0000:0000:0000'], + [true, '2001:0db8:1234::'], + [true, '2001:0db8:1234:ffff:ffff:ffff:ffff:ffff'], + [true, '2001:0db8:85a3:0000:0000:8a2e:0370:7334'], + [true, '2001:0db8::1428:57ab'], + [true, '2001:2:3:4:5:6:7:134'], + [true, '2001:DB8:0:0:8:800:200C:417A'], + [true, '2001:DB8::8:800:200C:417A'], + [true, '2001:db8:85a3:0:0:8a2e:370:7334'], + [true, '2001:db8:85a3::8a2e:370:7334'], + [true, '2001:db8::'], + [true, '2001:db8::1428:57ab'], + [true, '2001:db8:a::123'], + [true, '2002::'], + [true, '2::10'], + [true, '3ffe:0b00:0000:0000:0001:0000:0000:000a'], + [true, '3ffe:b00::1:0:0:a'], + [true, '::'], + [true, '::0'], + [true, '::0:0'], + [true, '::0:0:0'], + [true, '::0:0:0:0'], + [true, '::0:0:0:0:0'], + [true, '::0:0:0:0:0:0'], + [true, '::0:0:0:0:0:0:0'], + [true, '::0:a:b:c:d:e:f'], + [true, '::1'], + [true, '::123.123.123.123'], + [true, '::13.1.68.3'], + [true, '::2222:3333:4444:5555:6666:123.123.123.123'], + [true, '::2222:3333:4444:5555:6666:7777:8888'], + [true, '::2:3'], + [true, '::2:3:4'], + [true, '::2:3:4:5'], + [true, '::2:3:4:5:6'], + [true, '::2:3:4:5:6:7'], + [true, '::2:3:4:5:6:7:8'], + [true, '::3333:4444:5555:6666:7777:8888'], + [true, '::4444:5555:6666:123.123.123.123'], + [true, '::4444:5555:6666:7777:8888'], + [true, '::5555:6666:123.123.123.123'], + [true, '::5555:6666:7777:8888'], + [true, '::6666:123.123.123.123'], + [true, '::6666:7777:8888'], + [true, '::7777:8888'], + [true, '::8'], + [true, '::8888'], + [true, '::FFFF:129.144.52.38'], + [true, '::ffff:0:0'], + [true, '::ffff:0c22:384e'], + [true, '::ffff:12.34.56.78'], + [true, '::ffff:192.0.2.128'], + [true, '::ffff:192.168.1.1'], + [true, '::ffff:192.168.1.26'], + [true, '::ffff:c000:280'], + [true, 'FF01:0:0:0:0:0:0:101'], + [true, 'FF01::101'], + [true, 'FF02:0000:0000:0000:0000:0000:0000:0001'], + [true, 'FF02::1'], + [true, 'a:b:c:d:e:f:0::'], + [true, 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'], + [true, 'fe80:0:0:0:204:61ff:254.157.241.86'], + [true, 'fe80:0:0:0:204:61ff:fe9d:f156'], + [true, 'fe80::'], + [true, 'fe80::1'], + [true, 'fe80::204:61ff:254.157.241.86'], + [true, 'fe80::204:61ff:fe9d:f156'], + [true, 'fe80::217:f2ff:254.7.237.98'], + [true, 'fe80::217:f2ff:fe07:ed62'], + [true, 'ff02::1'], +] + +describe('get_ipany_re', function () { + const net_utils = require('../index') + + it('IPv6, Prefix', function (done) { + // for x-*-ip headers + // it must fail as of not valide + assert.ok(!net.isIPv6('IPv6:2001:db8:85a3::8a2e:370:7334')) + // must okay! + assert.ok(net.isIPv6('2001:db8:85a3::8a2e:370:7334')) + done() + }) + + it('IP fixtures check', function (done) { + for (const i in ip_fixtures) { + const match = net_utils.get_ipany_re('^', '$').test(ip_fixtures[i][1]) + // console.log('IP:', `'${ip_fixtures[i][1]}'` , 'Expected:', ip_fixtures[i][0] , 'Match:' , match); + assert.ok( + match === ip_fixtures[i][0], + `${ip_fixtures[i][1]} - Expected: ${ip_fixtures[i][0]} - Match: ${match}`, + ) + } + done() + }) + + it('IPv4, bare', function (done) { + // for x-*-ip headers + const match = net_utils.get_ipany_re().exec('127.0.0.1') + assert.equal(match[1], '127.0.0.1') + assert.equal(match.length, 2) + done() + }) + + it('IPv4, Received header, parens', function (done) { + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(]', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from unknown (HELO mail.theartfarm.com) (127.0.0.30) by mail.theartfarm.com with SMTP; 5 Sep 2015 14:29:00 -0000', + ) + assert.equal(match[1], '127.0.0.30') + assert.equal(match.length, 2) + done() + }) + + it('IPv4, Received header, bracketed, expedia', function (done) { + const received_header = + 'Received: from mta2.expediamail.com (mta2.expediamail.com [66.231.89.19]) by mail.theartfarm.com (Haraka/2.6.2-toaster) with ESMTPS id C669CF18-1C1C-484C-8A5B-A89088B048CB.1 envelope-from (version=TLSv1/SSLv3 cipher=AES256-SHA verify=NO); Sat, 05 Sep 2015 07:28:57 -0700' + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(]', + '[\\]\\)]', + ) + const match = received_re.exec(received_header) + assert.equal(match[1], '66.231.89.19') + assert.equal(match.length, 2) + done() + }) + + it('IPv4, Received header, bracketed, github', function (done) { + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(]', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from github-smtp2a-ext-cp1-prd.iad.github.net (github-smtp2-ext5.iad.github.net [192.30.252.196])', + ) + assert.equal(match[1], '192.30.252.196') + assert.equal(match.length, 2) + done() + }) + + it('IPv6, Received header, bracketed', function (done) { + const received_header = + 'Received: from ?IPv6:2601:184:c001:5cf7:a53f:baf7:aaf3:bce7? ([2601:184:c001:5cf7:a53f:baf7:aaf3:bce7])' + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(]', + '[\\]\\)]', + ) + const match = received_re.exec(received_header) + assert.equal(match[1], '2601:184:c001:5cf7:a53f:baf7:aaf3:bce7') + assert.equal(match.length, 2) + done() + }) + + it('IPv6, Received header, bracketed, IPv6 prefix', function (done) { + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(](?:IPv6:)?', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from hub.freebsd.org (hub.freebsd.org [IPv6:2001:1900:2254:206c::16:88])', + ) + assert.equal(match[1], '2001:1900:2254:206c::16:88') + assert.equal(match.length, 2) + done() + }) + + it('IPv6, folded Received header, bracketed, IPv6 prefix', function (done) { + // note the use of [\s\S], '.' doesn't match newlines in JS regexp + const received_re = net_utils.get_ipany_re( + '^Received:[\\s\\S]*?[\\[\\(](?:IPv6:)?', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from freefall.freebsd.org (freefall.freebsd.org\r\n [IPv6:2001:1900:2254:206c::16:87])', + ) + if (match) { + assert.equal(match[1], '2001:1900:2254:206c::16:87') + assert.equal(match.length, 2) + } + done() + }) + + it('IPv6, Received header, bracketed, IPv6 prefix, localhost compressed', function (done) { + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(](?:IPv6:)?', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from ietfa.amsl.com (localhost [IPv6:::1])', + ) + assert.equal(match[1], '::1') + assert.equal(match.length, 2) + done() + }) + + it('IPv6 bogus', function (done) { + const is_bogus = net_utils.ipv6_bogus('::192.41.13.251') // From https://github.com/haraka/Haraka/issues/2763 + assert.equal(is_bogus, true) + done() + }) +}) diff --git a/test/get_mx.js b/test/get_mx.js new file mode 100644 index 0000000..7a0724b --- /dev/null +++ b/test/get_mx.js @@ -0,0 +1,181 @@ +const assert = require('assert') + +describe('get_mx', function () { + this.timeout(12000) + + beforeEach(function (done) { + this.net_utils = require('../index') + done() + }) + + const validCases = { + 'tnpi.net': 'mail.theartfarm.com', + 'matt@tnpi.net': 'mail.theartfarm.com', + 'matt.simerson@gmail.com': /google.com/, + 'example.com': '', + 'no-mx.haraka.tnpi.net': '192.0.99.5', + 'bad-mx.haraka.tnpi.net': /99/, + 'über.haraka.tnpi.net': 'no-mx.haraka.tnpi.net', + } + + function checkValid(c, mxlist) { + try { + if ('string' === typeof c) { + assert.equal(mxlist[0].exchange, c) + } else { + assert.ok(c.test(mxlist[0].exchange)) + } + } catch (err) { + console.error(err) + } + } + + for (const c in validCases) { + it(`gets MX records for ${c}`, function (done) { + this.timeout(12000) + this.net_utils.get_mx(c, (err, mxlist) => { + if (err) console.error(err) + assert.ifError(err) + checkValid(validCases[c], mxlist) + done() + }) + }) + + it(`awaits MX records for ${c}`, async function () { + this.timeout(12000) + const mxlist = await this.net_utils.get_mx(c) + checkValid(validCases[c], mxlist) + }) + } + + // macOS: ENODATA, win: ENOTOUND, ubuntu: ESERVFAIL + const invalidCases = { + invalid: /queryMx (ENODATA|ENOTFOUND|ESERVFAIL) invalid/, + 'gmail.xn--com-0da': /(ENOTFOUND|Cannot convert name to ASCII)/, + 'non-exist.haraka.tnpi.net': /ignore/, + 'haraka.non-exist': /ignore/, + } + + function checkInvalid(expected, actual) { + if ('string' === typeof expected) { + assert.strictEqual(actual, expected) + } else { + assert.equal(expected.test(actual), true) + } + } + + for (const c in invalidCases) { + it(`cb does not crash on invalid name: ${c}`, function () { + this.net_utils.get_mx(c, (err, mxlist) => { + if (err) checkInvalid(invalidCases[c], err.message) + assert.equal(mxlist.length, 0) + }) + }) + + it(`async does not crash on invalid name: ${c}`, async function () { + try { + const mxlist = await this.net_utils.get_mx(c) + assert.equal(mxlist.length, 0) + } catch (err) { + checkInvalid(invalidCases[c], err.message) + } + }) + } + + + describe('resolve_mx_hosts', function () { + this.timeout(12000) + + beforeEach((done) => { + this.net_utils = require('../index') + done() + }) + + const expectedResolvedMx = [ + { + exchange: '2605:ae00:329::14', + priority: 10, + from_dns: 'mail.theartfarm.com', + }, + { + exchange: '66.128.51.165', + priority: 10, + from_dns: 'mail.theartfarm.com', + }, + ] + + it('resolves mx hosts to IPs, tnpi.net', async () => { + const r = await this.net_utils.resolve_mx_hosts([ + { exchange: 'mail.theartfarm.com', priority: 10, from_dns: 'tnpi.net' }, + ]) + assert.deepEqual(r, expectedResolvedMx) + }) + + it('resolves mx hosts to IPs, gmail.com', async () => { + const mxes = await this.net_utils.get_mx('gmail.com') + assert.equal(mxes.length, 5) + const r = await this.net_utils.resolve_mx_hosts(mxes) + assert.equal(r.length, 10) + }) + + it('returns IPs as is', async () => { + const r = await this.net_utils.resolve_mx_hosts(expectedResolvedMx) + assert.deepEqual(r, expectedResolvedMx) + }) + + it('returns sockets as-is', async () => { + const r = await this.net_utils.resolve_mx_hosts([{ path: '/var/run/sock' }]) + assert.deepEqual(r, [{ path: '/var/run/sock' }]) + }) + + it('resolve_mx_hosts, gmail.com', async () => { + const mxes = await this.net_utils.get_mx('gmail.com') + const r = await this.net_utils.resolve_mx_hosts(mxes) + assert.equal(r.length, 10) + }) + + it('resolve_mx_hosts, yahoo.com', async () => { + const mxes = await this.net_utils.get_mx('yahoo.com') + const r = await this.net_utils.resolve_mx_hosts([mxes[0]]) + assert.equal(r.length, 8) + }) + }) + + describe('get_implicit_mx', function () { + this.timeout(5000) + + beforeEach(function (done) { + this.net_utils = require('../index') + done() + }) + + it('harakamail.com', async function () { + const mf = await this.net_utils.get_implicit_mx('harakamail.com') + assert.equal(mf.length, 1) + }) + + it('mx.theartfarm.com', async function () { + const mf = await this.net_utils.get_implicit_mx('mx.theartfarm.com') + assert.equal(mf.length, 0) + }) + + it('resolve-fail-definitive.josef-froehle.de', async function () { + const mf = await this.net_utils.get_implicit_mx( + 'resolve-fail-definitive.josef-froehle.de', + ) + assert.equal(mf.length, 0) + }) + it('resolve-fail-a.josef-froehle.de', async function () { + const mf = await this.net_utils.get_implicit_mx( + 'resolve-fail-a.josef-froehle.de', + ) + assert.equal(mf.length, 1) + }) + it('resolve-fail-aaaa.josef-froehle.de', async function () { + const mf = await this.net_utils.get_implicit_mx( + 'resolve-fail-aaaa.josef-froehle.de', + ) + assert.equal(mf.length, 0) + }) + }) +}) \ No newline at end of file diff --git a/test/get_public_ip.js b/test/get_public_ip.js new file mode 100644 index 0000000..a2a5ea6 --- /dev/null +++ b/test/get_public_ip.js @@ -0,0 +1,81 @@ +const assert = require('node:assert') +const path = require('node:path') + +function has_stun() { + try { + require('@msimerson/stun') + } catch (e) { + return false + } + return true +} + +describe('get_public_ip', function () { + beforeEach(function (done) { + this.net_utils = require('../index') + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('test'), + ) + done() + }) + + it('cached', function (done) { + this.net_utils.public_ip = '1.1.1.1' + this.net_utils.get_public_ip((err, ip) => { + assert.equal(null, err) + assert.equal('1.1.1.1', ip) + done() + }) + }) + + it('normal', function (done) { + this.net_utils.public_ip = undefined + this.net_utils.get_public_ip((err, ip) => { + if (has_stun()) { + if (err) { + console.error(err) + } else { + console.log(`stun success: ${ip}`) + assert.equal(null, err) + assert.ok(ip, ip) + } + } else { + console.log('stun skipped') + } + done() + }) + }) + + describe('get_public_ip_async', function () { + beforeEach((done) => { + this.net_utils = require('../index') + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('test'), + ) + done() + }) + + it('cached', async () => { + this.net_utils.public_ip = '1.1.1.1' + const ip = await this.net_utils.get_public_ip() + assert.equal('1.1.1.1', ip) + }) + + it('normal', async () => { + this.net_utils.public_ip = undefined + + if (!has_stun()) { + console.log('stun skipped') + return + } + + try { + const ip = await this.net_utils.get_public_ip() + console.log(`stun success: ${ip}`) + assert.ok(ip, ip) + } catch (e) { + console.error(e) + } + }) + }) +}) diff --git a/test/harakaMx.js b/test/harakaMx.js new file mode 100644 index 0000000..b91eeee --- /dev/null +++ b/test/harakaMx.js @@ -0,0 +1,198 @@ +const assert = require('assert') + +describe('HarakaMx', () => { + beforeEach(function (done) { + this.nu = require('../index') + done() + }) + + describe('fromObject', () => { + it('accepts an object', function () { + assert.deepEqual( + new this.nu.HarakaMx({ + from_dns: 'example.com', + exchange: '.', + priority: 0, + }), + { from_dns: 'example.com', exchange: '.', priority: 0 }, + ) + }) + + it('sets default priority to 0', function () { + assert.deepEqual(new this.nu.HarakaMx({ exchange: '.' }), { + exchange: '.', + priority: 0, + }) + }) + + it('if optional domain provided, sets from_dns', function () { + assert.deepEqual(new this.nu.HarakaMx({ exchange: '.' }, 'example.com'), { + from_dns: 'example.com', + exchange: '.', + priority: 0, + }) + }) + }) + + describe('fromString', function () { + it('parses a hostname', function () { + assert.deepEqual(new this.nu.HarakaMx('mail.example.com'), { + exchange: 'mail.example.com', + priority: 0, + }) + }) + + it('parses a hostname:port', function () { + assert.deepEqual(new this.nu.HarakaMx('mail.example.com:25'), { + exchange: 'mail.example.com', + port: 25, + priority: 0, + }) + }) + + it('parses an IPv4', function () { + assert.deepEqual(new this.nu.HarakaMx('192.0.2.1'), { + exchange: '192.0.2.1', + priority: 0, + }) + }) + + it('parses an IPv4:port', function () { + assert.deepEqual(new this.nu.HarakaMx('192.0.2.1:25'), { + exchange: '192.0.2.1', + port: 25, + priority: 0, + }) + }) + + it('parses an IPv6', function () { + assert.deepEqual(new this.nu.HarakaMx('2001:db8::1'), { + exchange: '2001:db8::1', + priority: 0, + }) + }) + + it('parses an IPv6:port', function () { + assert.deepEqual(new this.nu.HarakaMx('2001:db8::1:25'), { + exchange: '2001:db8::1', + port: 25, + priority: 0, + }) + }) + + it('parses an [IPv6]:port', function () { + assert.deepEqual(new this.nu.HarakaMx('[2001:db8::1]:25'), { + exchange: '2001:db8::1', + port: 25, + priority: 0, + }) + }) + }) + + describe('fromUri', function () { + it('parses simple URIs', function () { + assert.deepEqual(new this.nu.HarakaMx('smtp://192.0.2.2'), { + exchange: '192.0.2.2', + port: 25, + priority: 0, + }) + + assert.deepEqual(new this.nu.HarakaMx('smtp://[2001:db8::1]:25'), { + exchange: '[2001:db8::1]', + port: 25, + priority: 0, + }) + }) + + it('parses more complex URIs', function () { + assert.deepEqual( + new this.nu.HarakaMx('smtp://authUser:sekretPass@[2001:db8::1]'), + { + exchange: '[2001:db8::1]', + port: 25, + priority: 0, + auth_pass: 'sekretPass', + auth_user: 'authUser', + }, + ) + + assert.deepEqual( + new this.nu.HarakaMx('lmtp://authUser:sekretPass@[2001:db8::1]:25'), + { + exchange: '[2001:db8::1]', + port: 25, + priority: 0, + using_lmtp: true, + auth_pass: 'sekretPass', + auth_user: 'authUser', + }, + ) + }) + }) + + describe('toUrl', function () { + it('has a reasonable toUrl()', function () { + assert.equal( + new this.nu.HarakaMx({ exchange: '.' }).toUrl(), + 'smtp://.:25', + ) + + assert.equal( + new this.nu.HarakaMx({ + from_dns: 'example.com', + exchange: '.', + priority: 10, + }).toUrl(), + 'smtp://.:25', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toUrl(), + 'smtp://au:****@192.0.2.3:25', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toUrl(), + 'smtp://au:****@192.0.2.3:465', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toUrl(), + 'smtp://[2001:db8::1]:25', + ) + }) + }) + + describe('toString', function () { + it('has a reasonable toString()', function () { + assert.equal( + new this.nu.HarakaMx({ exchange: '.' }).toString(), + 'MX 0 smtp://.:25', + ) + + assert.equal( + new this.nu.HarakaMx({ + from_dns: 'example.com', + exchange: '.', + priority: 10, + }).toString(), + 'MX 10 smtp://.:25 (from example.com)', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toString(), + 'MX 0 smtp://au:****@192.0.2.3:25', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toString(), + 'MX 0 smtp://au:****@192.0.2.3:465', + ) + + assert.equal( + new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toString(), + 'MX 0 smtp://[2001:db8::1]:25', + ) + }) + }) +}) diff --git a/test/net_utils.js b/test/net_utils.js index 26da194..cb0aea9 100644 --- a/test/net_utils.js +++ b/test/net_utils.js @@ -1,7 +1,6 @@ -const assert = require('assert') -const net = require('net') -const os = require('os') -const path = require('path') +const assert = require('node:assert') +const os = require('node:os') +const path = require('node:path') require('haraka-config').watch_files = false const net_utils = require('../index') @@ -240,95 +239,6 @@ describe('is_private_ip', function () { }) }) -describe('get_public_ip', function () { - beforeEach(function (done) { - this.net_utils = require('../index') - this.net_utils.config = this.net_utils.config.module_config( - path.resolve('test'), - ) - done() - }) - - function has_stun() { - try { - require('stun') - } catch (e) { - return false - } - return true - } - - it('cached', function (done) { - this.net_utils.public_ip = '1.1.1.1' - const cb = function (err, ip) { - assert.equal(null, err) - assert.equal('1.1.1.1', ip) - done() - } - this.net_utils.get_public_ip(cb) - }) - - it('normal', function (done) { - this.net_utils.public_ip = undefined - const cb = function (err, ip) { - if (has_stun()) { - if (err) { - console.error(err) - } else { - console.log(`stun success: ${ip}`) - assert.equal(null, err) - assert.ok(ip, ip) - } - } else { - console.log('stun skipped') - } - done() - } - this.net_utils.get_public_ip(cb) - }) -}) - -describe('get_public_ip_async', function () { - beforeEach(() => { - this.net_utils = require('../index') - this.net_utils.config = this.net_utils.config.module_config( - path.resolve('test'), - ) - }) - - function has_stun() { - try { - require('stun') - } catch (e) { - return false - } - return true - } - - it('cached', async () => { - this.net_utils.public_ip = '1.1.1.1' - const ip = await this.net_utils.get_public_ip() - assert.equal('1.1.1.1', ip) - }) - - it('normal', async () => { - this.net_utils.public_ip = undefined - - if (!has_stun()) { - console.log('stun skipped') - return - } - - try { - const ip = await this.net_utils.get_public_ip() - console.log(`stun success: ${ip}`) - assert.ok(ip, ip) - } catch (e) { - console.error(e) - } - }) -}) - describe('octets_in_string', function () { it('c-24-18-98-14.hsd1.wa.comcast.net', function (done) { const str = 'c-24-18-98-14.hsd1.wa.comcast.net' @@ -469,629 +379,6 @@ describe('is_local_ipv6', function () { }) }) -const ip_fixtures = [ - [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876 '], - [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0'], - [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876'], - [false, ' 2001:0:1234::C1C0:ABCD:876 '], - [false, ' 2001:0:1234::C1C0:ABCD:876'], - [false, ''], - [false, "':10.0.0.1"], - [false, '---'], - [false, '02001:0000:1234:0000:0000:C1C0:ABCD:0876'], - [false, '1.2.3.4:1111:2222:3333:4444::5555'], - [false, '1.2.3.4:1111:2222:3333::5555'], - [false, '1.2.3.4:1111:2222::5555'], - [false, '1.2.3.4:1111::5555'], - [false, '1.2.3.4::'], - [false, '1.2.3.4::5555'], - [false, '1111'], - [false, '11112222:3333:4444:5555:6666:1.2.3.4'], - [false, '11112222:3333:4444:5555:6666:7777:8888'], - [false, '1111:'], - [false, '1111:1.2.3.4'], - [false, '1111:2222'], - [false, '1111:22223333:4444:5555:6666:1.2.3.4'], - [false, '1111:22223333:4444:5555:6666:7777:8888'], - [false, '1111:2222:'], - [false, '1111:2222:1.2.3.4'], - [false, '1111:2222:3333'], - [false, '1111:2222:33334444:5555:6666:1.2.3.4'], - [false, '1111:2222:33334444:5555:6666:7777:8888'], - [false, '1111:2222:3333:'], - [false, '1111:2222:3333:1.2.3.4'], - [false, '1111:2222:3333:4444'], - [false, '1111:2222:3333:44445555:6666:1.2.3.4'], - [false, '1111:2222:3333:44445555:6666:7777:8888'], - [false, '1111:2222:3333:4444:'], - [false, '1111:2222:3333:4444:1.2.3.4'], - [false, '1111:2222:3333:4444:5555'], - [false, '1111:2222:3333:4444:55556666:1.2.3.4'], - [false, '1111:2222:3333:4444:55556666:7777:8888'], - [false, '1111:2222:3333:4444:5555:'], - [false, '1111:2222:3333:4444:5555:1.2.3.4'], - [false, '1111:2222:3333:4444:5555:6666'], - [false, '1111:2222:3333:4444:5555:66661.2.3.4'], - [false, '1111:2222:3333:4444:5555:66667777:8888'], - [false, '1111:2222:3333:4444:5555:6666:'], - [false, '1111:2222:3333:4444:5555:6666:00.00.00.00'], - [false, '1111:2222:3333:4444:5555:6666:000.000.000.000'], - [false, '1111:2222:3333:4444:5555:6666:1.2.3.4.5'], - [false, '1111:2222:3333:4444:5555:6666:255.255.255255'], - [false, '1111:2222:3333:4444:5555:6666:255.255255.255'], - [false, '1111:2222:3333:4444:5555:6666:255255.255.255'], - [false, '1111:2222:3333:4444:5555:6666:256.256.256.256'], - [false, '1111:2222:3333:4444:5555:6666:7777'], - [false, '1111:2222:3333:4444:5555:6666:77778888'], - [false, '1111:2222:3333:4444:5555:6666:7777:'], - [false, '1111:2222:3333:4444:5555:6666:7777:1.2.3.4'], - [false, '1111:2222:3333:4444:5555:6666:7777:8888:'], - [false, '1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4'], - [false, '1111:2222:3333:4444:5555:6666:7777:8888:9999'], - [false, '1111:2222:3333:4444:5555:6666:7777:8888::'], - [false, '1111:2222:3333:4444:5555:6666:7777:::'], - [false, '1111:2222:3333:4444:5555:6666::1.2.3.4'], - [false, '1111:2222:3333:4444:5555:6666::8888:'], - [false, '1111:2222:3333:4444:5555:6666:::'], - [false, '1111:2222:3333:4444:5555:6666:::8888'], - [false, '1111:2222:3333:4444:5555::7777:8888:'], - [false, '1111:2222:3333:4444:5555::7777::'], - [false, '1111:2222:3333:4444:5555::8888:'], - [false, '1111:2222:3333:4444:5555:::'], - [false, '1111:2222:3333:4444:5555:::1.2.3.4'], - [false, '1111:2222:3333:4444:5555:::7777:8888'], - [false, '1111:2222:3333:4444::5555:'], - [false, '1111:2222:3333:4444::6666:7777:8888:'], - [false, '1111:2222:3333:4444::6666:7777::'], - [false, '1111:2222:3333:4444::6666::8888'], - [false, '1111:2222:3333:4444::7777:8888:'], - [false, '1111:2222:3333:4444::8888:'], - [false, '1111:2222:3333:4444:::'], - [false, '1111:2222:3333:4444:::6666:1.2.3.4'], - [false, '1111:2222:3333:4444:::6666:7777:8888'], - [false, '1111:2222:3333::5555:'], - [false, '1111:2222:3333::5555:6666:7777:8888:'], - [false, '1111:2222:3333::5555:6666:7777::'], - [false, '1111:2222:3333::5555:6666::8888'], - [false, '1111:2222:3333::5555::1.2.3.4'], - [false, '1111:2222:3333::5555::7777:8888'], - [false, '1111:2222:3333::6666:7777:8888:'], - [false, '1111:2222:3333::7777:8888:'], - [false, '1111:2222:3333::8888:'], - [false, '1111:2222:3333:::'], - [false, '1111:2222:3333:::5555:6666:1.2.3.4'], - [false, '1111:2222:3333:::5555:6666:7777:8888'], - [false, '1111:2222::4444:5555:6666:7777:8888:'], - [false, '1111:2222::4444:5555:6666:7777::'], - [false, '1111:2222::4444:5555:6666::8888'], - [false, '1111:2222::4444:5555::1.2.3.4'], - [false, '1111:2222::4444:5555::7777:8888'], - [false, '1111:2222::4444::6666:1.2.3.4'], - [false, '1111:2222::4444::6666:7777:8888'], - [false, '1111:2222::5555:'], - [false, '1111:2222::5555:6666:7777:8888:'], - [false, '1111:2222::6666:7777:8888:'], - [false, '1111:2222::7777:8888:'], - [false, '1111:2222::8888:'], - [false, '1111:2222:::'], - [false, '1111:2222:::4444:5555:6666:1.2.3.4'], - [false, '1111:2222:::4444:5555:6666:7777:8888'], - [false, '1111::3333:4444:5555:6666:7777:8888:'], - [false, '1111::3333:4444:5555:6666:7777::'], - [false, '1111::3333:4444:5555:6666::8888'], - [false, '1111::3333:4444:5555::1.2.3.4'], - [false, '1111::3333:4444:5555::7777:8888'], - [false, '1111::3333:4444::6666:1.2.3.4'], - [false, '1111::3333:4444::6666:7777:8888'], - [false, '1111::3333::5555:6666:1.2.3.4'], - [false, '1111::3333::5555:6666:7777:8888'], - [false, '1111::4444:5555:6666:7777:8888:'], - [false, '1111::5555:'], - [false, '1111::5555:6666:7777:8888:'], - [false, '1111::6666:7777:8888:'], - [false, '1111::7777:8888:'], - [false, '1111::8888:'], - [false, '1111:::'], - [false, '1111:::3333:4444:5555:6666:1.2.3.4'], - [false, '1111:::3333:4444:5555:6666:7777:8888'], - [false, '123'], - [false, '12345::6:7:8'], - [false, '192.168.0.256'], - [false, '192.168.256.0'], - [false, '1:2:3:4:5:6:7:8:9'], - [false, '1:2:3::4:5:6:7:8:9'], - [false, '1:2:3::4:5::7:8'], - [false, '1::1.2.256.4'], - [false, '1::1.2.3.256'], - [false, '1::1.2.3.300'], - [false, '1::1.2.3.900'], - [false, '1::1.2.300.4'], - [false, '1::1.2.900.4'], - [false, '1::1.256.3.4'], - [false, '1::1.300.3.4'], - [false, '1::1.900.3.4'], - [false, '1::256.2.3.4'], - [false, '1::260.2.3.4'], - [false, '1::2::3'], - [false, '1::300.2.3.4'], - [false, '1::300.300.300.300'], - [false, '1::3000.30.30.30'], - [false, '1::400.2.3.4'], - [false, '1::5:1.2.256.4'], - [false, '1::5:1.2.3.256'], - [false, '1::5:1.2.3.300'], - [false, '1::5:1.2.3.900'], - [false, '1::5:1.2.300.4'], - [false, '1::5:1.2.900.4'], - [false, '1::5:1.256.3.4'], - [false, '1::5:1.300.3.4'], - [false, '1::5:1.900.3.4'], - [false, '1::5:256.2.3.4'], - [false, '1::5:260.2.3.4'], - [false, '1::5:300.2.3.4'], - [false, '1::5:300.300.300.300'], - [false, '1::5:3000.30.30.30'], - [false, '1::5:400.2.3.4'], - [false, '1::5:900.2.3.4'], - [false, '1::900.2.3.4'], - [false, '1:::3:4:5'], - [false, '2001:0000:1234: 0000:0000:C1C0:ABCD:0876'], - [false, '2001:0000:1234:0000:00001:C1C0:ABCD:0876'], - [false, '2001:0000:1234:0000:0000:C1C0:ABCD:0876 0'], - [false, '2001:1:1:1:1:1:255Z255X255Y255'], - [false, '2001::FFD3::57ab'], - [false, '2001:DB8:0:0:8:800:200C:417A:221'], - [false, '2001:db8:85a3::8a2e:37023:7334'], - [false, '2001:db8:85a3::8a2e:370k:7334'], - [false, '255.256.255.255'], - [false, '256.255.255.255'], - [false, '3ffe:0b00:0000:0001:0000:0000:000a'], - [false, '3ffe:b00::1::a'], - [false, ':'], - [false, ':1.2.3.4'], - [false, ':1111:2222:3333:4444:5555:6666:1.2.3.4'], - [false, ':1111:2222:3333:4444:5555:6666:7777:8888'], - [false, ':1111:2222:3333:4444:5555:6666:7777::'], - [false, ':1111:2222:3333:4444:5555:6666::'], - [false, ':1111:2222:3333:4444:5555:6666::8888'], - [false, ':1111:2222:3333:4444:5555::'], - [false, ':1111:2222:3333:4444:5555::1.2.3.4'], - [false, ':1111:2222:3333:4444:5555::7777:8888'], - [false, ':1111:2222:3333:4444:5555::8888'], - [false, ':1111:2222:3333:4444::'], - [false, ':1111:2222:3333:4444::1.2.3.4'], - [false, ':1111:2222:3333:4444::5555'], - [false, ':1111:2222:3333:4444::6666:1.2.3.4'], - [false, ':1111:2222:3333:4444::6666:7777:8888'], - [false, ':1111:2222:3333:4444::7777:8888'], - [false, ':1111:2222:3333:4444::8888'], - [false, ':1111:2222:3333::'], - [false, ':1111:2222:3333::1.2.3.4'], - [false, ':1111:2222:3333::5555'], - [false, ':1111:2222:3333::5555:6666:1.2.3.4'], - [false, ':1111:2222:3333::5555:6666:7777:8888'], - [false, ':1111:2222:3333::6666:1.2.3.4'], - [false, ':1111:2222:3333::6666:7777:8888'], - [false, ':1111:2222:3333::7777:8888'], - [false, ':1111:2222:3333::8888'], - [false, ':1111:2222::'], - [false, ':1111:2222::1.2.3.4'], - [false, ':1111:2222::4444:5555:6666:1.2.3.4'], - [false, ':1111:2222::4444:5555:6666:7777:8888'], - [false, ':1111:2222::5555'], - [false, ':1111:2222::5555:6666:1.2.3.4'], - [false, ':1111:2222::5555:6666:7777:8888'], - [false, ':1111:2222::6666:1.2.3.4'], - [false, ':1111:2222::6666:7777:8888'], - [false, ':1111:2222::7777:8888'], - [false, ':1111:2222::8888'], - [false, ':1111::'], - [false, ':1111::1.2.3.4'], - [false, ':1111::3333:4444:5555:6666:1.2.3.4'], - [false, ':1111::3333:4444:5555:6666:7777:8888'], - [false, ':1111::4444:5555:6666:1.2.3.4'], - [false, ':1111::4444:5555:6666:7777:8888'], - [false, ':1111::5555'], - [false, ':1111::5555:6666:1.2.3.4'], - [false, ':1111::5555:6666:7777:8888'], - [false, ':1111::6666:1.2.3.4'], - [false, ':1111::6666:7777:8888'], - [false, ':1111::7777:8888'], - [false, ':1111::8888'], - [false, ':2222:3333:4444:5555:6666:1.2.3.4'], - [false, ':2222:3333:4444:5555:6666:7777:8888'], - [false, ':3333:4444:5555:6666:1.2.3.4'], - [false, ':3333:4444:5555:6666:7777:8888'], - [false, ':4444:5555:6666:1.2.3.4'], - [false, ':4444:5555:6666:7777:8888'], - [false, ':5555:6666:1.2.3.4'], - [false, ':5555:6666:7777:8888'], - [false, ':6666:1.2.3.4'], - [false, ':6666:7777:8888'], - [false, ':7777:8888'], - [false, ':8888'], - [false, '::.'], - [false, '::..'], - [false, '::...'], - [false, '::...4'], - [false, '::..3.'], - [false, '::..3.4'], - [false, '::.2..'], - [false, '::.2.3.'], - [false, '::.2.3.4'], - [false, '::1...'], - [false, '::1.2..'], - [false, '::1.2.256.4'], - [false, '::1.2.3.'], - [false, '::1.2.3.256'], - [false, '::1.2.3.300'], - [false, '::1.2.3.900'], - [false, '::1.2.300.4'], - [false, '::1.2.900.4'], - [false, '::1.256.3.4'], - [false, '::1.300.3.4'], - [false, '::1.900.3.4'], - [false, '::1111:2222:3333:4444:5555:6666::'], - [false, '::2222:3333:4444:5555:6666:7777:1.2.3.4'], - [false, '::2222:3333:4444:5555:6666:7777:8888:'], - [false, '::2222:3333:4444:5555:6666:7777:8888:9999'], - [false, '::2222:3333:4444:5555:7777:8888::'], - [false, '::2222:3333:4444:5555:7777::8888'], - [false, '::2222:3333:4444:5555::1.2.3.4'], - [false, '::2222:3333:4444:5555::7777:8888'], - [false, '::2222:3333:4444::6666:1.2.3.4'], - [false, '::2222:3333:4444::6666:7777:8888'], - [false, '::2222:3333::5555:6666:1.2.3.4'], - [false, '::2222:3333::5555:6666:7777:8888'], - [false, '::2222::4444:5555:6666:1.2.3.4'], - [false, '::2222::4444:5555:6666:7777:8888'], - [false, '::256.2.3.4'], - [false, '::260.2.3.4'], - [false, '::300.2.3.4'], - [false, '::300.300.300.300'], - [false, '::3000.30.30.30'], - [false, '::3333:4444:5555:6666:7777:8888:'], - [false, '::400.2.3.4'], - [false, '::4444:5555:6666:7777:8888:'], - [false, '::5555:'], - [false, '::5555:6666:7777:8888:'], - [false, '::6666:7777:8888:'], - [false, '::7777:8888:'], - [false, '::8888:'], - [false, '::900.2.3.4'], - [false, ':::'], - [false, ':::1.2.3.4'], - [false, ':::2222:3333:4444:5555:6666:1.2.3.4'], - [false, ':::2222:3333:4444:5555:6666:7777:8888'], - [false, ':::3333:4444:5555:6666:7777:8888'], - [false, ':::4444:5555:6666:1.2.3.4'], - [false, ':::4444:5555:6666:7777:8888'], - [false, ':::5555'], - [false, ':::5555:6666:1.2.3.4'], - [false, ':::5555:6666:7777:8888'], - [false, ':::6666:1.2.3.4'], - [false, ':::6666:7777:8888'], - [false, ':::7777:8888'], - [false, ':::8888'], - [false, '::ffff:192x168.1.26'], - [false, '::ffff:2.3.4'], - [false, '::ffff:257.1.2.3'], - [false, 'FF01::101::2'], - [false, 'FF02:0000:0000:0000:0000:0000:0000:0000:0001'], - [false, 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4'], - [false, 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX'], - [false, 'fe80:0000:0000:0000:0204:61ff:254.157.241.086'], - [false, 'fe80::4413:c8ae:2821:5852%10'], - [false, 'ldkfj'], - [false, 'mydomain.com'], - [false, 'test.mydomain.com'], - [true, '0000:0000:0000:0000:0000:0000:0000:0000'], - [true, '0000:0000:0000:0000:0000:0000:0000:0001'], - [true, '0:0:0:0:0:0:0:0'], - [true, '0:0:0:0:0:0:0:1'], - [true, '0:0:0:0:0:0:0::'], - [true, '0:0:0:0:0:0:13.1.68.3'], - [true, '0:0:0:0:0:0::'], - [true, '0:0:0:0:0::'], - [true, '0:0:0:0:0:FFFF:129.144.52.38'], - [true, '0:0:0:0::'], - [true, '0:0:0::'], - [true, '0:0::'], - [true, '0::'], - [true, '0:a:b:c:d:e:f::'], - [true, '1.2.3.4'], - [true, '1111:2222:3333:4444:5555:6666:123.123.123.123'], - [true, '1111:2222:3333:4444:5555:6666:7777:8888'], - [true, '1111:2222:3333:4444:5555:6666:7777::'], - [true, '1111:2222:3333:4444:5555:6666::'], - [true, '1111:2222:3333:4444:5555:6666::8888'], - [true, '1111:2222:3333:4444:5555::'], - [true, '1111:2222:3333:4444:5555::123.123.123.123'], - [true, '1111:2222:3333:4444:5555::7777:8888'], - [true, '1111:2222:3333:4444:5555::8888'], - [true, '1111:2222:3333:4444::'], - [true, '1111:2222:3333:4444::123.123.123.123'], - [true, '1111:2222:3333:4444::6666:123.123.123.123'], - [true, '1111:2222:3333:4444::6666:7777:8888'], - [true, '1111:2222:3333:4444::7777:8888'], - [true, '1111:2222:3333:4444::8888'], - [true, '1111:2222:3333::'], - [true, '1111:2222:3333::123.123.123.123'], - [true, '1111:2222:3333::5555:6666:123.123.123.123'], - [true, '1111:2222:3333::5555:6666:7777:8888'], - [true, '1111:2222:3333::6666:123.123.123.123'], - [true, '1111:2222:3333::6666:7777:8888'], - [true, '1111:2222:3333::7777:8888'], - [true, '1111:2222:3333::8888'], - [true, '1111:2222::'], - [true, '1111:2222::123.123.123.123'], - [true, '1111:2222::4444:5555:6666:123.123.123.123'], - [true, '1111:2222::4444:5555:6666:7777:8888'], - [true, '1111:2222::5555:6666:123.123.123.123'], - [true, '1111:2222::5555:6666:7777:8888'], - [true, '1111:2222::6666:123.123.123.123'], - [true, '1111:2222::6666:7777:8888'], - [true, '1111:2222::7777:8888'], - [true, '1111:2222::8888'], - [true, '1111::'], - [true, '1111::123.123.123.123'], - [true, '1111::3333:4444:5555:6666:123.123.123.123'], - [true, '1111::3333:4444:5555:6666:7777:8888'], - [true, '1111::4444:5555:6666:123.123.123.123'], - [true, '1111::4444:5555:6666:7777:8888'], - [true, '1111::5555:6666:123.123.123.123'], - [true, '1111::5555:6666:7777:8888'], - [true, '1111::6666:123.123.123.123'], - [true, '1111::6666:7777:8888'], - [true, '1111::7777:8888'], - [true, '1111::8888'], - [true, '123.23.34.2'], - [true, '172.26.168.134'], - [true, '192.168.0.0'], - [true, '192.168.128.255'], - [true, '1:2:3:4:5:6:1.2.3.4'], - [true, '1:2:3:4:5:6:7:8'], - [true, '1:2:3:4:5:6::'], - [true, '1:2:3:4:5:6::8'], - [true, '1:2:3:4:5::'], - [true, '1:2:3:4:5::1.2.3.4'], - [true, '1:2:3:4:5::7:8'], - [true, '1:2:3:4:5::8'], - [true, '1:2:3:4::'], - [true, '1:2:3:4::1.2.3.4'], - [true, '1:2:3:4::5:1.2.3.4'], - [true, '1:2:3:4::7:8'], - [true, '1:2:3:4::8'], - [true, '1:2:3::'], - [true, '1:2:3::1.2.3.4'], - [true, '1:2:3::5:1.2.3.4'], - [true, '1:2:3::7:8'], - [true, '1:2:3::8'], - [true, '1:2::'], - [true, '1:2::1.2.3.4'], - [true, '1:2::5:1.2.3.4'], - [true, '1:2::7:8'], - [true, '1:2::8'], - [true, '1::'], - [true, '1::1.2.3.4'], - [true, '1::2:3'], - [true, '1::2:3:4'], - [true, '1::2:3:4:5'], - [true, '1::2:3:4:5:6'], - [true, '1::2:3:4:5:6:7'], - [true, '1::5:1.2.3.4'], - [true, '1::5:11.22.33.44'], - [true, '1::7:8'], - [true, '1::8'], - [true, '2001:0000:1234:0000:0000:C1C0:ABCD:0876'], - [true, '2001:0:1234::C1C0:ABCD:876'], - [true, '2001:0db8:0000:0000:0000:0000:1428:57ab'], - [true, '2001:0db8:0000:0000:0000::1428:57ab'], - [true, '2001:0db8:0:0:0:0:1428:57ab'], - [true, '2001:0db8:0:0::1428:57ab'], - [true, '2001:0db8:1234:0000:0000:0000:0000:0000'], - [true, '2001:0db8:1234::'], - [true, '2001:0db8:1234:ffff:ffff:ffff:ffff:ffff'], - [true, '2001:0db8:85a3:0000:0000:8a2e:0370:7334'], - [true, '2001:0db8::1428:57ab'], - [true, '2001:2:3:4:5:6:7:134'], - [true, '2001:DB8:0:0:8:800:200C:417A'], - [true, '2001:DB8::8:800:200C:417A'], - [true, '2001:db8:85a3:0:0:8a2e:370:7334'], - [true, '2001:db8:85a3::8a2e:370:7334'], - [true, '2001:db8::'], - [true, '2001:db8::1428:57ab'], - [true, '2001:db8:a::123'], - [true, '2002::'], - [true, '2::10'], - [true, '3ffe:0b00:0000:0000:0001:0000:0000:000a'], - [true, '3ffe:b00::1:0:0:a'], - [true, '::'], - [true, '::0'], - [true, '::0:0'], - [true, '::0:0:0'], - [true, '::0:0:0:0'], - [true, '::0:0:0:0:0'], - [true, '::0:0:0:0:0:0'], - [true, '::0:0:0:0:0:0:0'], - [true, '::0:a:b:c:d:e:f'], - [true, '::1'], - [true, '::123.123.123.123'], - [true, '::13.1.68.3'], - [true, '::2222:3333:4444:5555:6666:123.123.123.123'], - [true, '::2222:3333:4444:5555:6666:7777:8888'], - [true, '::2:3'], - [true, '::2:3:4'], - [true, '::2:3:4:5'], - [true, '::2:3:4:5:6'], - [true, '::2:3:4:5:6:7'], - [true, '::2:3:4:5:6:7:8'], - [true, '::3333:4444:5555:6666:7777:8888'], - [true, '::4444:5555:6666:123.123.123.123'], - [true, '::4444:5555:6666:7777:8888'], - [true, '::5555:6666:123.123.123.123'], - [true, '::5555:6666:7777:8888'], - [true, '::6666:123.123.123.123'], - [true, '::6666:7777:8888'], - [true, '::7777:8888'], - [true, '::8'], - [true, '::8888'], - [true, '::FFFF:129.144.52.38'], - [true, '::ffff:0:0'], - [true, '::ffff:0c22:384e'], - [true, '::ffff:12.34.56.78'], - [true, '::ffff:192.0.2.128'], - [true, '::ffff:192.168.1.1'], - [true, '::ffff:192.168.1.26'], - [true, '::ffff:c000:280'], - [true, 'FF01:0:0:0:0:0:0:101'], - [true, 'FF01::101'], - [true, 'FF02:0000:0000:0000:0000:0000:0000:0001'], - [true, 'FF02::1'], - [true, 'a:b:c:d:e:f:0::'], - [true, 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'], - [true, 'fe80:0:0:0:204:61ff:254.157.241.86'], - [true, 'fe80:0:0:0:204:61ff:fe9d:f156'], - [true, 'fe80::'], - [true, 'fe80::1'], - [true, 'fe80::204:61ff:254.157.241.86'], - [true, 'fe80::204:61ff:fe9d:f156'], - [true, 'fe80::217:f2ff:254.7.237.98'], - [true, 'fe80::217:f2ff:fe07:ed62'], - [true, 'ff02::1'], -] - -describe('get_ipany_re', function () { - it('IPv6, Prefix', function (done) { - // for x-*-ip headers - // it must fail as of not valide - assert.ok(!net.isIPv6('IPv6:2001:db8:85a3::8a2e:370:7334')) - // must okay! - assert.ok(net.isIPv6('2001:db8:85a3::8a2e:370:7334')) - done() - }) - - it('IP fixtures check', function (done) { - for (const i in ip_fixtures) { - const match = net_utils.get_ipany_re('^', '$').test(ip_fixtures[i][1]) - // console.log('IP:', `'${ip_fixtures[i][1]}'` , 'Expected:', ip_fixtures[i][0] , 'Match:' , match); - assert.ok( - match === ip_fixtures[i][0], - `${ip_fixtures[i][1]} - Expected: ${ip_fixtures[i][0]} - Match: ${match}`, - ) - } - done() - }) - - it('IPv4, bare', function (done) { - // for x-*-ip headers - const match = net_utils.get_ipany_re().exec('127.0.0.1') - assert.equal(match[1], '127.0.0.1') - assert.equal(match.length, 2) - done() - }) - - it('IPv4, Received header, parens', function (done) { - const received_re = net_utils.get_ipany_re( - '^Received:.*?[\\[\\(]', - '[\\]\\)]', - ) - const match = received_re.exec( - 'Received: from unknown (HELO mail.theartfarm.com) (127.0.0.30) by mail.theartfarm.com with SMTP; 5 Sep 2015 14:29:00 -0000', - ) - assert.equal(match[1], '127.0.0.30') - assert.equal(match.length, 2) - done() - }) - - it('IPv4, Received header, bracketed, expedia', function (done) { - const received_header = - 'Received: from mta2.expediamail.com (mta2.expediamail.com [66.231.89.19]) by mail.theartfarm.com (Haraka/2.6.2-toaster) with ESMTPS id C669CF18-1C1C-484C-8A5B-A89088B048CB.1 envelope-from (version=TLSv1/SSLv3 cipher=AES256-SHA verify=NO); Sat, 05 Sep 2015 07:28:57 -0700' - const received_re = net_utils.get_ipany_re( - '^Received:.*?[\\[\\(]', - '[\\]\\)]', - ) - const match = received_re.exec(received_header) - assert.equal(match[1], '66.231.89.19') - assert.equal(match.length, 2) - done() - }) - - it('IPv4, Received header, bracketed, github', function (done) { - const received_re = net_utils.get_ipany_re( - '^Received:.*?[\\[\\(]', - '[\\]\\)]', - ) - const match = received_re.exec( - 'Received: from github-smtp2a-ext-cp1-prd.iad.github.net (github-smtp2-ext5.iad.github.net [192.30.252.196])', - ) - assert.equal(match[1], '192.30.252.196') - assert.equal(match.length, 2) - done() - }) - - it('IPv6, Received header, bracketed', function (done) { - const received_header = - 'Received: from ?IPv6:2601:184:c001:5cf7:a53f:baf7:aaf3:bce7? ([2601:184:c001:5cf7:a53f:baf7:aaf3:bce7])' - const received_re = net_utils.get_ipany_re( - '^Received:.*?[\\[\\(]', - '[\\]\\)]', - ) - const match = received_re.exec(received_header) - assert.equal(match[1], '2601:184:c001:5cf7:a53f:baf7:aaf3:bce7') - assert.equal(match.length, 2) - done() - }) - - it('IPv6, Received header, bracketed, IPv6 prefix', function (done) { - const received_re = net_utils.get_ipany_re( - '^Received:.*?[\\[\\(](?:IPv6:)?', - '[\\]\\)]', - ) - const match = received_re.exec( - 'Received: from hub.freebsd.org (hub.freebsd.org [IPv6:2001:1900:2254:206c::16:88])', - ) - assert.equal(match[1], '2001:1900:2254:206c::16:88') - assert.equal(match.length, 2) - done() - }) - - it('IPv6, folded Received header, bracketed, IPv6 prefix', function (done) { - // note the use of [\s\S], '.' doesn't match newlines in JS regexp - const received_re = net_utils.get_ipany_re( - '^Received:[\\s\\S]*?[\\[\\(](?:IPv6:)?', - '[\\]\\)]', - ) - const match = received_re.exec( - 'Received: from freefall.freebsd.org (freefall.freebsd.org\r\n [IPv6:2001:1900:2254:206c::16:87])', - ) - if (match) { - assert.equal(match[1], '2001:1900:2254:206c::16:87') - assert.equal(match.length, 2) - } - done() - }) - - it('IPv6, Received header, bracketed, IPv6 prefix, localhost compressed', function (done) { - const received_re = net_utils.get_ipany_re( - '^Received:.*?[\\[\\(](?:IPv6:)?', - '[\\]\\)]', - ) - const match = received_re.exec( - 'Received: from ietfa.amsl.com (localhost [IPv6:::1])', - ) - assert.equal(match[1], '::1') - assert.equal(match.length, 2) - done() - }) - - it('IPv6 bogus', function (done) { - const is_bogus = net_utils.ipv6_bogus('::192.41.13.251') // From https://github.com/haraka/Haraka/issues/2763 - assert.equal(is_bogus, true) - done() - }) -}) - describe('get_ips_by_host', function () { const tests = { 'servedby.tnpi.net': [ @@ -1246,379 +533,3 @@ describe('on_local_interface', function () { done() }) }) - -describe('get_mx', function () { - this.timeout(12000) - - beforeEach(function (done) { - this.net_utils = require('../index') - done() - }) - - const validCases = { - 'tnpi.net': 'mail.theartfarm.com', - 'matt@tnpi.net': 'mail.theartfarm.com', - 'matt.simerson@gmail.com': /google.com/, - 'example.com': '', - 'no-mx.haraka.tnpi.net': '192.0.99.5', - 'bad-mx.haraka.tnpi.net': /99/, - 'über.haraka.tnpi.net': 'no-mx.haraka.tnpi.net', - } - - function checkValid(c, mxlist) { - try { - if ('string' === typeof c) { - assert.equal(mxlist[0].exchange, c) - } else { - assert.ok(c.test(mxlist[0].exchange)) - } - } catch (err) { - console.error(err) - } - } - - for (const c in validCases) { - it(`gets MX records for ${c}`, function (done) { - this.timeout(12000) - this.net_utils.get_mx(c, (err, mxlist) => { - if (err) console.error(err) - assert.ifError(err) - checkValid(validCases[c], mxlist) - done() - }) - }) - - it(`awaits MX records for ${c}`, async function () { - this.timeout(12000) - const mxlist = await this.net_utils.get_mx(c) - checkValid(validCases[c], mxlist) - }) - } - - // macOS: ENODATA, win: ENOTOUND, ubuntu: ESERVFAIL - const invalidCases = { - invalid: /queryMx (ENODATA|ENOTFOUND|ESERVFAIL) invalid/, - 'gmail.xn--com-0da': /(ENOTFOUND|Cannot convert name to ASCII)/, - 'non-exist.haraka.tnpi.net': /ignore/, - 'haraka.non-exist': /ignore/, - } - - function checkInvalid(expected, actual) { - if ('string' === typeof expected) { - assert.strictEqual(actual, expected) - } else { - assert.equal(expected.test(actual), true) - } - } - - for (const c in invalidCases) { - it(`cb does not crash on invalid name: ${c}`, function () { - this.net_utils.get_mx(c, (err, mxlist) => { - if (err) checkInvalid(invalidCases[c], err.message) - assert.equal(mxlist.length, 0) - }) - }) - - it(`async does not crash on invalid name: ${c}`, async function () { - try { - const mxlist = await this.net_utils.get_mx(c) - assert.equal(mxlist.length, 0) - } catch (err) { - checkInvalid(invalidCases[c], err.message) - } - }) - } -}) - -describe('resolve_mx_hosts', function () { - this.timeout(12000) - - beforeEach((done) => { - this.net_utils = require('../index') - done() - }) - - const expectedResolvedMx = [ - { - exchange: '2605:ae00:329::14', - priority: 10, - from_dns: 'mail.theartfarm.com', - }, - { - exchange: '66.128.51.165', - priority: 10, - from_dns: 'mail.theartfarm.com', - }, - ] - - it('resolves mx hosts to IPs, tnpi.net', async () => { - const r = await this.net_utils.resolve_mx_hosts([ - { exchange: 'mail.theartfarm.com', priority: 10, from_dns: 'tnpi.net' }, - ]) - assert.deepEqual(r, expectedResolvedMx) - }) - - it('resolves mx hosts to IPs, gmail.com', async () => { - const mxes = await this.net_utils.get_mx('gmail.com') - assert.equal(mxes.length, 5) - const r = await this.net_utils.resolve_mx_hosts(mxes) - assert.equal(r.length, 10) - }) - - it('returns IPs as is', async () => { - const r = await this.net_utils.resolve_mx_hosts(expectedResolvedMx) - assert.deepEqual(r, expectedResolvedMx) - }) - - it('returns sockets as-is', async () => { - const r = await this.net_utils.resolve_mx_hosts([{ path: '/var/run/sock' }]) - assert.deepEqual(r, [{ path: '/var/run/sock' }]) - }) - - it('resolve_mx_hosts, gmail.com', async () => { - const mxes = await this.net_utils.get_mx('gmail.com') - const r = await this.net_utils.resolve_mx_hosts(mxes) - assert.equal(r.length, 10) - }) - - it('resolve_mx_hosts, yahoo.com', async () => { - const mxes = await this.net_utils.get_mx('yahoo.com') - const r = await this.net_utils.resolve_mx_hosts([mxes[0]]) - assert.equal(r.length, 8) - }) -}) - -describe('get_implicit_mx', function () { - this.timeout(5000) - - beforeEach(function (done) { - this.net_utils = require('../index') - done() - }) - - it('harakamail.com', async function () { - const mf = await this.net_utils.get_implicit_mx('harakamail.com') - assert.equal(mf.length, 1) - }) - - it('mx.theartfarm.com', async function () { - const mf = await this.net_utils.get_implicit_mx('mx.theartfarm.com') - assert.equal(mf.length, 0) - }) - - it('resolve-fail-definitive.josef-froehle.de', async function () { - const mf = await this.net_utils.get_implicit_mx( - 'resolve-fail-definitive.josef-froehle.de', - ) - assert.equal(mf.length, 0) - }) - it('resolve-fail-a.josef-froehle.de', async function () { - const mf = await this.net_utils.get_implicit_mx( - 'resolve-fail-a.josef-froehle.de', - ) - assert.equal(mf.length, 1) - }) - it('resolve-fail-aaaa.josef-froehle.de', async function () { - const mf = await this.net_utils.get_implicit_mx( - 'resolve-fail-aaaa.josef-froehle.de', - ) - assert.equal(mf.length, 0) - }) -}) - -describe('HarakaMx', () => { - beforeEach(function (done) { - this.nu = require('../index') - done() - }) - - describe('fromObject', () => { - it('accepts an object', function () { - assert.deepEqual( - new this.nu.HarakaMx({ - from_dns: 'example.com', - exchange: '.', - priority: 0, - }), - { from_dns: 'example.com', exchange: '.', priority: 0 }, - ) - }) - - it('sets default priority to 0', function () { - assert.deepEqual(new this.nu.HarakaMx({ exchange: '.' }), { - exchange: '.', - priority: 0, - }) - }) - - it('if optional domain provided, sets from_dns', function () { - assert.deepEqual(new this.nu.HarakaMx({ exchange: '.' }, 'example.com'), { - from_dns: 'example.com', - exchange: '.', - priority: 0, - }) - }) - }) - - describe('fromString', function () { - it('parses a hostname', function () { - assert.deepEqual(new this.nu.HarakaMx('mail.example.com'), { - exchange: 'mail.example.com', - priority: 0, - }) - }) - - it('parses a hostname:port', function () { - assert.deepEqual(new this.nu.HarakaMx('mail.example.com:25'), { - exchange: 'mail.example.com', - port: 25, - priority: 0, - }) - }) - - it('parses an IPv4', function () { - assert.deepEqual(new this.nu.HarakaMx('192.0.2.1'), { - exchange: '192.0.2.1', - priority: 0, - }) - }) - - it('parses an IPv4:port', function () { - assert.deepEqual(new this.nu.HarakaMx('192.0.2.1:25'), { - exchange: '192.0.2.1', - port: 25, - priority: 0, - }) - }) - - it('parses an IPv6', function () { - assert.deepEqual(new this.nu.HarakaMx('2001:db8::1'), { - exchange: '2001:db8::1', - priority: 0, - }) - }) - - it('parses an IPv6:port', function () { - assert.deepEqual(new this.nu.HarakaMx('2001:db8::1:25'), { - exchange: '2001:db8::1', - port: 25, - priority: 0, - }) - }) - - it('parses an [IPv6]:port', function () { - assert.deepEqual(new this.nu.HarakaMx('[2001:db8::1]:25'), { - exchange: '2001:db8::1', - port: 25, - priority: 0, - }) - }) - }) - - describe('fromUri', function () { - it('parses simple URIs', function () { - assert.deepEqual(new this.nu.HarakaMx('smtp://192.0.2.2'), { - exchange: '192.0.2.2', - port: 25, - priority: 0, - }) - - assert.deepEqual(new this.nu.HarakaMx('smtp://[2001:db8::1]:25'), { - exchange: '[2001:db8::1]', - port: 25, - priority: 0, - }) - }) - - it('parses more complex URIs', function () { - assert.deepEqual( - new this.nu.HarakaMx('smtp://authUser:sekretPass@[2001:db8::1]'), - { - exchange: '[2001:db8::1]', - port: 25, - priority: 0, - auth_pass: 'sekretPass', - auth_user: 'authUser', - }, - ) - - assert.deepEqual( - new this.nu.HarakaMx('lmtp://authUser:sekretPass@[2001:db8::1]:25'), - { - exchange: '[2001:db8::1]', - port: 25, - priority: 0, - using_lmtp: true, - auth_pass: 'sekretPass', - auth_user: 'authUser', - }, - ) - }) - }) - - describe('toUrl', function () { - it('has a reasonable toUrl()', function () { - assert.equal( - new this.nu.HarakaMx({ exchange: '.' }).toUrl(), - 'smtp://.:25', - ) - - assert.equal( - new this.nu.HarakaMx({ - from_dns: 'example.com', - exchange: '.', - priority: 10, - }).toUrl(), - 'smtp://.:25', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toUrl(), - 'smtp://au:****@192.0.2.3:25', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toUrl(), - 'smtp://au:****@192.0.2.3:465', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toUrl(), - 'smtp://[2001:db8::1]:25', - ) - }) - }) - - describe('toString', function () { - it('has a reasonable toString()', function () { - assert.equal( - new this.nu.HarakaMx({ exchange: '.' }).toString(), - 'MX 0 smtp://.:25', - ) - - assert.equal( - new this.nu.HarakaMx({ - from_dns: 'example.com', - exchange: '.', - priority: 10, - }).toString(), - 'MX 10 smtp://.:25 (from example.com)', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toString(), - 'MX 0 smtp://au:****@192.0.2.3:25', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toString(), - 'MX 0 smtp://au:****@192.0.2.3:465', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toString(), - 'MX 0 smtp://[2001:db8::1]:25', - ) - }) - }) -}) From 51f109b006d726e207db2e9f4a8660c03e474a31 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 30 Apr 2024 09:21:28 -0700 Subject: [PATCH 3/9] refactored larger chunks of code into lib/* --- index.js | 180 +----------------------------------------- lib/HarakaMx.js | 85 ++++++++++++++++++++ lib/get_public_ip.js | 93 ++++++++++++++++++++++ package.json | 2 +- test/get_public_ip.js | 30 +++---- test/harakaMx.js | 10 +++ 6 files changed, 208 insertions(+), 192 deletions(-) create mode 100644 lib/HarakaMx.js create mode 100644 lib/get_public_ip.js diff --git a/index.js b/index.js index 252e483..7337dd1 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,6 @@ const { Resolver } = require('node:dns').promises const dns = new Resolver({ timeout: 25000, tries: 1 }) const net = require('node:net') const os = require('node:os') -const url = require('node:url') // npm modules const ipaddr = require('ipaddr.js') @@ -12,6 +11,8 @@ const punycode = require('punycode.js') const sprintf = require('sprintf-js').sprintf const tlds = require('haraka-tld') +const HarakaMx = require('./lib/HarakaMx').HarakaMx + const locallyBoundIPs = [] // export config, so config base path can be overloaded by tests @@ -259,96 +260,6 @@ exports.same_ipv4_network = function (ip, ipList) { return false } -exports.get_public_ip_async = async function () { - if (this.public_ip !== undefined) return this.public_ip // cache - - // manual config override, for the cases where we can't figure it out - const smtpIni = exports.config.get('smtp.ini').main - if (smtpIni.public_ip) { - this.public_ip = smtpIni.public_ip - return this.public_ip - } - - // Initialise cache value to null to prevent running - // should we hit a timeout or the module isn't installed. - this.public_ip = null - - try { - this.stun = require('@msimerson/stun') - } catch (e) { - e.install = 'Please install stun: "npm install -g stun"' - console.error(`${e.msg}\n${e.install}`) - return - } - - const timeout = 10 - const timer = setTimeout(() => { - return new Error('STUN timeout') - }, timeout * 1000) - - // Connect to STUN Server - const res = await this.stun.request(get_stun_server(), { - maxTimeout: (timeout - 1) * 1000, - }) - this.public_ip = res.getXorAddress().address - clearTimeout(timer) - return this.public_ip -} - -exports.get_public_ip = async function (cb) { - if (!cb) return exports.get_public_ip_async() - - if (this.public_ip !== undefined) return cb(null, this.public_ip) // cache - - // manual config override, for the cases where we can't figure it out - const smtpIni = exports.config.get('smtp.ini').main - if (smtpIni.public_ip) { - this.public_ip = smtpIni.public_ip - return cb(null, this.public_ip) - } - - // Initialise cache value to null to prevent running - // should we hit a timeout or the module isn't installed. - this.public_ip = null - - try { - this.stun = require('@msimerson/stun') - } catch (e) { - e.install = 'Please install stun: "npm install -g stun"' - console.error(`${e.msg}\n${e.install}`) - return cb(e) - } - - const timeout = 10 - const timer = setTimeout(() => { - return cb(new Error('STUN timeout')) - }, timeout * 1000) - - // Connect to STUN Server - this.stun.request( - get_stun_server(), - { maxTimeout: (timeout - 1) * 1000 }, - (error, res) => { - if (timer) clearTimeout(timer) - if (error) return cb(error) - - this.public_ip = res.getXorAddress().address - cb(null, this.public_ip) - }, - ) -} - -function get_stun_server() { - const servers = [ - 'stun.l.google.com:19302', - 'stun1.l.google.com:19302', - 'stun2.l.google.com:19302', - 'stun3.l.google.com:19302', - 'stun4.l.google.com:19302', - ] - return servers[Math.floor(Math.random() * servers.length)] -} - exports.get_ipany_re = function (prefix = '', suffix = '', modifier = 'mg') { return new RegExp( prefix + @@ -562,91 +473,8 @@ exports.resolve_mx_hosts = async (mxes) => { return settled.filter((s) => s.status === 'fulfilled').flatMap((s) => s.value) } -class HarakaMx { - constructor(obj = {}, domain) { - switch (typeof obj) { - case 'string': - /mtp:\/\//.test(obj) ? this.fromUrl(obj) : this.fromString(obj) - break - case 'object': - this.fromObject(obj) - break - } - - if (this.priority === undefined) this.priority = 0 - if (domain && this.from_dns === undefined) { - this.from_dns = domain.toLowerCase() - } - } - - fromObject(obj) { - for (const prop of [ - 'exchange', - 'priority', - 'port', - 'bind', - 'bind_helo', - 'using_lmtp', - 'auth_user', - 'auth_pass', - 'auth_type', - 'from_dns', - ]) { - if (obj[prop] !== undefined) this[prop] = obj[prop] - } - } - - fromString(str) { - const matches = /^\[?(.*?)\]?(?::(24|25|465|587|\d{4,5}))?$/.exec(str) - if (matches) { - this.exchange = matches[1].toLowerCase() - if (matches[2]) this.port = parseInt(matches[2]) - } else { - this.exchange = str - } - } - - fromUrl(str) { - const dest = new url.URL(str) - - switch (dest.protocol) { - case 'smtp:': - if (!dest.port) dest.port = 25 - break - case 'lmtp:': - this.using_lmtp = true - if (!dest.port) dest.port = 24 - break - } - - if (dest.hostname) this.exchange = dest.hostname.toLowerCase() - if (dest.port) this.port = parseInt(dest.port) - if (dest.username) this.auth_user = dest.username - if (dest.password) this.auth_pass = dest.password - } - - toUrl() { - const proto = this.using_lmtp ? 'lmtp://' : 'smtp://' - const auth = this.auth_user ? `${this.auth_user}:****@` : '' - const host = net.isIPv6(this.exchange) - ? `[${this.exchange}]` - : this.exchange - const port = this.port ? this.port : proto === 'lmtp://' ? 24 : 25 - return new url.URL(`${proto}${auth}${host}:${port}`) - } - - toString() { - const from_dns = this.from_dns ? ` (from ${this.from_dns})` : '' - return `MX ${this.priority} ${this.toUrl()}${from_dns}` - } -} +exports.get_public_ip = require('./lib/get_public_ip').get_public_ip -/* - * A string of one of the following formats: - * hostname - * hostname:port - * ipaddress - * ipaddress:port - */ +exports.get_public_ip_async = require('./lib/get_public_ip').get_public_ip_async exports.HarakaMx = HarakaMx diff --git a/lib/HarakaMx.js b/lib/HarakaMx.js new file mode 100644 index 0000000..1dd0a33 --- /dev/null +++ b/lib/HarakaMx.js @@ -0,0 +1,85 @@ +'use strict' + +const net = require('node:net') +const url = require('node:url') + +class HarakaMx { + constructor(obj = {}, domain) { + switch (typeof obj) { + case 'string': + /mtp:\/\//.test(obj) ? this.fromUrl(obj) : this.fromString(obj) + break + case 'object': + this.fromObject(obj) + break + } + + if (this.priority === undefined) this.priority = 0 + if (domain && this.from_dns === undefined) { + this.from_dns = domain.toLowerCase() + } + } + + fromObject(obj) { + for (const prop of [ + 'exchange', + 'priority', + 'port', + 'bind', + 'bind_helo', + 'using_lmtp', + 'auth_user', + 'auth_pass', + 'auth_type', + 'from_dns', + ]) { + if (obj[prop] !== undefined) this[prop] = obj[prop] + } + } + + fromString(str) { + const matches = /^\[?(.*?)\]?(?::(24|25|465|587|\d{4,5}))?$/.exec(str) + if (matches) { + this.exchange = matches[1].toLowerCase() + if (matches[2]) this.port = parseInt(matches[2]) + } else { + this.exchange = str + } + } + + fromUrl(str) { + const dest = new url.URL(str) + + switch (dest.protocol) { + case 'smtp:': + if (!dest.port) dest.port = 25 + break + case 'lmtp:': + this.using_lmtp = true + if (!dest.port) dest.port = 24 + break + } + + if (dest.hostname) this.exchange = dest.hostname.toLowerCase() + if (dest.port) this.port = parseInt(dest.port) + if (dest.username) this.auth_user = dest.username + if (dest.password) this.auth_pass = dest.password + } + + toUrl() { + const proto = this.using_lmtp ? 'lmtp://' : 'smtp://' + const auth = this.auth_user ? `${this.auth_user}:****@` : '' + const host = net.isIPv6(this.exchange) + ? `[${this.exchange}]` + : this.exchange + const port = this.port ? this.port : proto === 'lmtp://' ? 24 : 25 + return new url.URL(`${proto}${auth}${host}:${port}`) + } + + toString() { + const from_dns = this.from_dns ? ` (from ${this.from_dns})` : '' + return `MX ${this.priority} ${this.toUrl()}${from_dns}` + } +} + +exports.HarakaMx = HarakaMx diff --git a/lib/get_public_ip.js b/lib/get_public_ip.js new file mode 100644 index 0000000..7012740 --- /dev/null +++ b/lib/get_public_ip.js @@ -0,0 +1,93 @@ +'use strict' + +exports.config = require('haraka-config') + +exports.get_public_ip_async = async function () { + if (this.public_ip !== undefined) return this.public_ip // cache + + // manual config override, for the cases where we can't figure it out + const smtpIni = exports.config.get('smtp.ini').main + if (smtpIni.public_ip) { + this.public_ip = smtpIni.public_ip + return this.public_ip + } + + // Initialise cache value to null to prevent running + // should we hit a timeout or the module isn't installed. + this.public_ip = null + + try { + this.stun = require('@msimerson/stun') + } catch (e) { + e.install = 'Please install stun: "npm install -g stun"' + console.error(`${e.msg}\n${e.install}`) + return + } + + const timeout = 10 + const timer = setTimeout(() => { + return new Error('STUN timeout') + }, timeout * 1000) + + // Connect to STUN Server + const res = await this.stun.request(get_stun_server(), { + maxTimeout: (timeout - 1) * 1000, + }) + this.public_ip = res.getXorAddress().address + clearTimeout(timer) + return this.public_ip +} + +exports.get_public_ip = async function (cb) { + if (!cb) return exports.get_public_ip_async() + + if (this.public_ip !== undefined) return cb(null, this.public_ip) // cache + + // manual config override, for the cases where we can't figure it out + const smtpIni = exports.config.get('smtp.ini').main + if (smtpIni.public_ip) { + this.public_ip = smtpIni.public_ip + return cb(null, this.public_ip) + } + + // Initialise cache value to null to prevent running + // should we hit a timeout or the module isn't installed. + this.public_ip = null + + try { + this.stun = require('@msimerson/stun') + } catch (e) { + e.install = 'Please install stun: "npm install -g stun"' + console.error(`${e.msg}\n${e.install}`) + return cb(e) + } + + const timeout = 10 + const timer = setTimeout(() => { + return cb(new Error('STUN timeout')) + }, timeout * 1000) + + // Connect to STUN Server + this.stun.request( + get_stun_server(), + { maxTimeout: (timeout - 1) * 1000 }, + (error, res) => { + if (timer) clearTimeout(timer) + if (error) return cb(error) + + this.public_ip = res.getXorAddress().address + cb(null, this.public_ip) + }, + ) +} + +function get_stun_server() { + const servers = [ + 'stun.l.google.com:19302', + 'stun1.l.google.com:19302', + 'stun2.l.google.com:19302', + 'stun3.l.google.com:19302', + 'stun4.l.google.com:19302', + ] + return servers[Math.floor(Math.random() * servers.length)] +} diff --git a/package.json b/package.json index 01b60f8..29a4531 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "haraka network utilities", "main": "index.js", "files": [ - "CHANGELOG.md" + "CHANGELOG.md", "lib" ], "scripts": { "format": "npm run prettier:fix && npm run lint:fix", diff --git a/test/get_public_ip.js b/test/get_public_ip.js index a2a5ea6..1162069 100644 --- a/test/get_public_ip.js +++ b/test/get_public_ip.js @@ -10,13 +10,20 @@ function has_stun() { return true } +beforeEach(function (done) { + this.net_utils = require('../lib/get_public_ip') + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('test'), + ) + done() +}) + describe('get_public_ip', function () { - beforeEach(function (done) { - this.net_utils = require('../index') - this.net_utils.config = this.net_utils.config.module_config( - path.resolve('test'), - ) - done() + + it('is accessible via main nu', function () { + const nu = require('../index') + assert.equal(typeof nu.get_public_ip, 'function') + assert.equal(typeof nu.get_public_ip_async, 'function') }) it('cached', function (done) { @@ -47,21 +54,14 @@ describe('get_public_ip', function () { }) describe('get_public_ip_async', function () { - beforeEach((done) => { - this.net_utils = require('../index') - this.net_utils.config = this.net_utils.config.module_config( - path.resolve('test'), - ) - done() - }) - it('cached', async () => { + it('cached', async function () { this.net_utils.public_ip = '1.1.1.1' const ip = await this.net_utils.get_public_ip() assert.equal('1.1.1.1', ip) }) - it('normal', async () => { + it('normal', async function () { this.net_utils.public_ip = undefined if (!has_stun()) { diff --git a/test/harakaMx.js b/test/harakaMx.js index b91eeee..438b4da 100644 --- a/test/harakaMx.js +++ b/test/harakaMx.js @@ -195,4 +195,14 @@ describe('HarakaMx', () => { ) }) }) + + it('is exported from nu', function () { + const nu = require('../index') + assert.equal(typeof nu.HarakaMx, 'function') + }) + + it('directly loadable', function () { + const nu = require('../lib/HarakaMx') + assert.equal(typeof nu.HarakaMx, 'function') + }) }) From 6bf182785f6aab5929b3011e40029fb9d1c70120 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 30 Apr 2024 09:49:41 -0700 Subject: [PATCH 4/9] refactored get_mx into lib/get_mx --- index.js | 121 +-------------------------------------- lib/HarakaMx.js | 4 +- lib/get_mx.js | 128 ++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- test/get_ip_any.js | 1 - test/get_mx.js | 7 ++- test/get_public_ip.js | 2 - test/harakaMx.js | 4 +- test/net_utils.js | 1 + 9 files changed, 142 insertions(+), 129 deletions(-) create mode 100644 lib/get_mx.js diff --git a/index.js b/index.js index 7337dd1..1054980 100644 --- a/index.js +++ b/index.js @@ -7,12 +7,9 @@ const os = require('node:os') // npm modules const ipaddr = require('ipaddr.js') -const punycode = require('punycode.js') const sprintf = require('sprintf-js').sprintf const tlds = require('haraka-tld') -const HarakaMx = require('./lib/HarakaMx').HarakaMx - const locallyBoundIPs = [] // export config, so config base path can be overloaded by tests @@ -357,124 +354,12 @@ exports.get_primary_host_name = function () { return exports.config.get('me') || os.hostname() } -function normalizeDomain(raw_domain) { - let domain = raw_domain - - if (/@/.test(domain)) { - domain = domain.split('@').pop() - // console.log(`\treduced ${raw_domain} to ${domain}.`) - } - - if (/^xn--/.test(domain)) { - // is punycode IDN with ACE, ASCII Compatible Encoding - } else if (domain !== punycode.toASCII(domain)) { - domain = punycode.toASCII(domain) - console.log(`\tACE encoded '${raw_domain}' to '${domain}'`) - } - - return domain -} - -function fatal_mx_err(err) { - // Possible DNS errors - // NODATA - // FORMERR - // BADRESP - // NOTFOUND - // BADNAME - // TIMEOUT - // CONNREFUSED - // NOMEM - // DESTRUCTION - // NOTIMP - // EREFUSED - // SERVFAIL - - switch (err.code) { - case 'ENODATA': - case 'ENOTFOUND': - // likely a hostname with no MX record, drop through - return false - default: - return err - } -} - -exports.get_mx = async (raw_domain, cb) => { - const domain = normalizeDomain(raw_domain) - - try { - let exchanges = await dns.resolveMx(domain) - if (exchanges && exchanges.length) { - exchanges = exchanges.map((e) => new HarakaMx(e, domain)) - if (cb) return cb(null, exchanges) - return exchanges - } - // no MX record(s), fall through - } catch (err) { - if (fatal_mx_err(err)) { - if (cb) return cb(err, []) - throw err - } - // non-terminal DNS failure, fall through - } - - const exchanges = await this.get_implicit_mx(domain) - if (cb) return cb(null, exchanges) - return exchanges -} - -exports.get_implicit_mx = async (domain) => { - // console.log(`No MX for ${domain}, trying AAAA & A records`) - - const promises = [dns.resolve6(domain), dns.resolve4(domain)] - const r = await Promise.allSettled(promises) - - return r - .filter((a) => a.status === 'fulfilled') - .flatMap((a) => a.value.map((ip) => new HarakaMx(ip, domain))) -} - -exports.resolve_mx_hosts = async (mxes) => { - // for the given list of MX exchanges, resolve the hostnames to IPs - const promises = [] - - for (const mx of mxes) { - if (!mx.exchange) { - promises.push(mx) - continue - } - - if (net.isIP(mx.exchange)) { - promises.push(mx) // already resolved - continue - } - - // resolve AAAA and A since mx.exchange is a hostname - promises.push( - dns - .resolve6(mx.exchange) - .then((ips) => - ips.map((ip) => ({ ...mx, exchange: ip, from_dns: mx.exchange })), - ), - ) - - promises.push( - dns - .resolve4(mx.exchange) - .then((ips) => - ips.map((ip) => ({ ...mx, exchange: ip, from_dns: mx.exchange })), - ), - ) - } - - const settled = await Promise.allSettled(promises) - - return settled.filter((s) => s.status === 'fulfilled').flatMap((s) => s.value) +for (const l of ['get_mx', 'get_implicit_mx', 'resolve_mx_hosts']) { + exports[l] = require('./lib/get_mx')[l] } exports.get_public_ip = require('./lib/get_public_ip').get_public_ip exports.get_public_ip_async = require('./lib/get_public_ip').get_public_ip_async -exports.HarakaMx = HarakaMx +exports.HarakaMx = require('./lib/HarakaMx') diff --git a/lib/HarakaMx.js b/lib/HarakaMx.js index 1dd0a33..c1cd37a 100644 --- a/lib/HarakaMx.js +++ b/lib/HarakaMx.js @@ -7,7 +7,7 @@ class HarakaMx { constructor(obj = {}, domain) { switch (typeof obj) { case 'string': - /mtp:\/\//.test(obj) ? this.fromUrl(obj) : this.fromString(obj) + ;/mtp:\/\//.test(obj) ? this.fromUrl(obj) : this.fromString(obj) break case 'object': this.fromObject(obj) @@ -82,4 +82,4 @@ class HarakaMx { } } -exports.HarakaMx = HarakaMx +module.exports = HarakaMx diff --git a/lib/get_mx.js b/lib/get_mx.js new file mode 100644 index 0000000..6789315 --- /dev/null +++ b/lib/get_mx.js @@ -0,0 +1,128 @@ +'use strict' + +const { Resolver } = require('node:dns').promises +const dns = new Resolver({ timeout: 25000, tries: 1 }) +const net = require('node:net') + +const punycode = require('punycode.js') + +const HarakaMx = require('./HarakaMx') + +function normalizeDomain(raw_domain) { + let domain = raw_domain + + if (/@/.test(domain)) { + domain = domain.split('@').pop() + // console.log(`\treduced ${raw_domain} to ${domain}.`) + } + + if (/^xn--/.test(domain)) { + // is punycode IDN with ACE, ASCII Compatible Encoding + } else if (domain !== punycode.toASCII(domain)) { + domain = punycode.toASCII(domain) + console.log(`\tACE encoded '${raw_domain}' to '${domain}'`) + } + + return domain +} + +function fatal_mx_err(err) { + // Possible DNS errors + // NODATA + // FORMERR + // BADRESP + // NOTFOUND + // BADNAME + // TIMEOUT + // CONNREFUSED + // NOMEM + // DESTRUCTION + // NOTIMP + // EREFUSED + // SERVFAIL + + switch (err.code) { + case 'ENODATA': + case 'ENOTFOUND': + // likely a hostname with no MX record, drop through + return false + default: + return err + } +} + +exports.get_mx = async (raw_domain, cb) => { + const domain = normalizeDomain(raw_domain) + + try { + let exchanges = await dns.resolveMx(domain) + if (exchanges && exchanges.length) { + exchanges = exchanges.map((e) => new HarakaMx(e, domain)) + if (cb) return cb(null, exchanges) + return exchanges + } + // no MX record(s), fall through + } catch (err) { + if (fatal_mx_err(err)) { + if (cb) return cb(err, []) + throw err + } + // non-terminal DNS failure, fall through + } + + const exchanges = await this.get_implicit_mx(domain) + if (cb) return cb(null, exchanges) + return exchanges +} + +exports.get_implicit_mx = async (domain) => { + // console.log(`No MX for ${domain}, trying AAAA & A records`) + + const r = await Promise.allSettled([ + dns.resolve6(domain), + dns.resolve4(domain), + ]) + + return r + .filter((a) => a.status === 'fulfilled') + .flatMap((a) => a.value.map((ip) => new HarakaMx(ip, domain))) +} + +exports.resolve_mx_hosts = async (mxes) => { + // for the given list of MX exchanges, resolve the hostnames to IPs + const promises = [] + + for (const mx of mxes) { + if (!mx.exchange) { + // console.error(`MX without an exchange. could be a socket`) + promises.push(mx) + continue + } + + if (net.isIP(mx.exchange)) { + promises.push(mx) // already resolved + continue + } + + // resolve AAAA and A since mx.exchange is a hostname + promises.push( + dns + .resolve6(mx.exchange) + .then((ips) => + ips.map((ip) => ({ ...mx, exchange: ip, from_dns: mx.exchange })), + ), + ) + + promises.push( + dns + .resolve4(mx.exchange) + .then((ips) => + ips.map((ip) => ({ ...mx, exchange: ip, from_dns: mx.exchange })), + ), + ) + } + + const settled = await Promise.allSettled(promises) + + return settled.filter((s) => s.status === 'fulfilled').flatMap((s) => s.value) +} diff --git a/package.json b/package.json index 29a4531..7b30189 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "haraka network utilities", "main": "index.js", "files": [ - "CHANGELOG.md", "lib" + "CHANGELOG.md", + "lib" ], "scripts": { "format": "npm run prettier:fix && npm run lint:fix", diff --git a/test/get_ip_any.js b/test/get_ip_any.js index c1d61cb..7314e58 100644 --- a/test/get_ip_any.js +++ b/test/get_ip_any.js @@ -1,7 +1,6 @@ const assert = require('node:assert') const net = require('node:net') - const ip_fixtures = [ [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876 '], [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0'], diff --git a/test/get_mx.js b/test/get_mx.js index 7a0724b..b08cbd4 100644 --- a/test/get_mx.js +++ b/test/get_mx.js @@ -82,7 +82,6 @@ describe('get_mx', function () { }) } - describe('resolve_mx_hosts', function () { this.timeout(12000) @@ -124,7 +123,9 @@ describe('get_mx', function () { }) it('returns sockets as-is', async () => { - const r = await this.net_utils.resolve_mx_hosts([{ path: '/var/run/sock' }]) + const r = await this.net_utils.resolve_mx_hosts([ + { path: '/var/run/sock' }, + ]) assert.deepEqual(r, [{ path: '/var/run/sock' }]) }) @@ -178,4 +179,4 @@ describe('get_mx', function () { assert.equal(mf.length, 0) }) }) -}) \ No newline at end of file +}) diff --git a/test/get_public_ip.js b/test/get_public_ip.js index 1162069..d61ed30 100644 --- a/test/get_public_ip.js +++ b/test/get_public_ip.js @@ -19,7 +19,6 @@ beforeEach(function (done) { }) describe('get_public_ip', function () { - it('is accessible via main nu', function () { const nu = require('../index') assert.equal(typeof nu.get_public_ip, 'function') @@ -54,7 +53,6 @@ describe('get_public_ip', function () { }) describe('get_public_ip_async', function () { - it('cached', async function () { this.net_utils.public_ip = '1.1.1.1' const ip = await this.net_utils.get_public_ip() diff --git a/test/harakaMx.js b/test/harakaMx.js index 438b4da..c29d904 100644 --- a/test/harakaMx.js +++ b/test/harakaMx.js @@ -202,7 +202,7 @@ describe('HarakaMx', () => { }) it('directly loadable', function () { - const nu = require('../lib/HarakaMx') - assert.equal(typeof nu.HarakaMx, 'function') + const hMx = require('../lib/HarakaMx') + assert.equal(typeof hMx, 'function') }) }) diff --git a/test/net_utils.js b/test/net_utils.js index cb0aea9..8620d01 100644 --- a/test/net_utils.js +++ b/test/net_utils.js @@ -407,6 +407,7 @@ describe('get_ips_by_host', function () { }) it(`get_ips_by_host, promise, ${t}`, async function () { + this.timeout(5000) try { const res = await net_utils.get_ips_by_host(t) assert.deepEqual(res.sort(), tests[t].sort()) From 2afdb46e387e9b713019ec928f04434c247f413a Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 30 Apr 2024 15:00:40 -0700 Subject: [PATCH 5/9] remove port additions --- README.md | 1 + lib/HarakaMx.js | 28 +++++++++--- lib/get_mx.js | 86 ++++++++++++++++++------------------- test/harakaMx.js | 109 ++++++++++++++++++++--------------------------- 4 files changed, 111 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 6d1f69d..404adff 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ An object class representing a MX. HarakaMx objects may contain the following pr ```js { exchange: '', // required: a FQDN or IP address + path: '', // the file path to a socket priority: 0, // integer, a MX priority. port: 25, // integer: an alternate port bind: '', // an outbound IP address to bind to diff --git a/lib/HarakaMx.js b/lib/HarakaMx.js index c1cd37a..239f298 100644 --- a/lib/HarakaMx.js +++ b/lib/HarakaMx.js @@ -1,10 +1,15 @@ 'use strict' const net = require('node:net') +const os = require('node:os') const url = require('node:url') +const config = require('haraka-config') + class HarakaMx { constructor(obj = {}, domain) { + if (obj instanceof HarakaMx) return obj + switch (typeof obj) { case 'string': ;/mtp:\/\//.test(obj) ? this.fromUrl(obj) : this.fromString(obj) @@ -15,14 +20,22 @@ class HarakaMx { } if (this.priority === undefined) this.priority = 0 + if (domain && this.from_dns === undefined) { this.from_dns = domain.toLowerCase() } + + if (process.env.NODE_ENV !== 'test') { + if (this.bind_helo === undefined) { + this.bind_helo = config.get('me') || os.hostname() + } + } } fromObject(obj) { for (const prop of [ 'exchange', + 'path', 'priority', 'port', 'bind', @@ -52,11 +65,9 @@ class HarakaMx { switch (dest.protocol) { case 'smtp:': - if (!dest.port) dest.port = 25 break case 'lmtp:': this.using_lmtp = true - if (!dest.port) dest.port = 24 break } @@ -67,17 +78,20 @@ class HarakaMx { } toUrl() { - const proto = this.using_lmtp ? 'lmtp://' : 'smtp://' - const auth = this.auth_user ? `${this.auth_user}:****@` : '' const host = net.isIPv6(this.exchange) ? `[${this.exchange}]` : this.exchange - const port = this.port ? this.port : proto === 'lmtp://' ? 24 : 25 - return new url.URL(`${proto}${auth}${host}:${port}`) + if (this.path) { + return new url.URL(`file://${host || 'localhost'}${this.path}`).href + } + const proto = this.using_lmtp ? 'lmtp://' : 'smtp://' + const auth = this.auth_user ? `${this.auth_user}:****@` : '' + const port = this.port ? `:${this.port}` : '' + return new url.URL(`${proto}${auth}${host}${port}`).href } toString() { - const from_dns = this.from_dns ? ` (from ${this.from_dns})` : '' + const from_dns = this.from_dns ? ` (via DNS)` : '' return `MX ${this.priority} ${this.toUrl()}${from_dns}` } } diff --git a/lib/get_mx.js b/lib/get_mx.js index 6789315..91bdc26 100644 --- a/lib/get_mx.js +++ b/lib/get_mx.js @@ -8,49 +8,6 @@ const punycode = require('punycode.js') const HarakaMx = require('./HarakaMx') -function normalizeDomain(raw_domain) { - let domain = raw_domain - - if (/@/.test(domain)) { - domain = domain.split('@').pop() - // console.log(`\treduced ${raw_domain} to ${domain}.`) - } - - if (/^xn--/.test(domain)) { - // is punycode IDN with ACE, ASCII Compatible Encoding - } else if (domain !== punycode.toASCII(domain)) { - domain = punycode.toASCII(domain) - console.log(`\tACE encoded '${raw_domain}' to '${domain}'`) - } - - return domain -} - -function fatal_mx_err(err) { - // Possible DNS errors - // NODATA - // FORMERR - // BADRESP - // NOTFOUND - // BADNAME - // TIMEOUT - // CONNREFUSED - // NOMEM - // DESTRUCTION - // NOTIMP - // EREFUSED - // SERVFAIL - - switch (err.code) { - case 'ENODATA': - case 'ENOTFOUND': - // likely a hostname with no MX record, drop through - return false - default: - return err - } -} - exports.get_mx = async (raw_domain, cb) => { const domain = normalizeDomain(raw_domain) @@ -126,3 +83,46 @@ exports.resolve_mx_hosts = async (mxes) => { return settled.filter((s) => s.status === 'fulfilled').flatMap((s) => s.value) } + +function normalizeDomain(raw_domain) { + let domain = raw_domain + + if (/@/.test(domain)) { + domain = domain.split('@').pop() + // console.log(`\treduced ${raw_domain} to ${domain}.`) + } + + if (/^xn--/.test(domain)) { + // is punycode IDN with ACE, ASCII Compatible Encoding + } else if (domain !== punycode.toASCII(domain)) { + domain = punycode.toASCII(domain) + console.log(`\tACE encoded '${raw_domain}' to '${domain}'`) + } + + return domain +} + +function fatal_mx_err(err) { + // Possible DNS errors + // NODATA + // FORMERR + // BADRESP + // NOTFOUND + // BADNAME + // TIMEOUT + // CONNREFUSED + // NOMEM + // DESTRUCTION + // NOTIMP + // EREFUSED + // SERVFAIL + + switch (err.code) { + case 'ENODATA': + case 'ENOTFOUND': + // likely a hostname with no MX record, drop through + return false + default: + return err + } +} diff --git a/test/harakaMx.js b/test/harakaMx.js index c29d904..3a338d5 100644 --- a/test/harakaMx.js +++ b/test/harakaMx.js @@ -1,5 +1,7 @@ const assert = require('assert') +process.env.NODE_ENV = 'test' + describe('HarakaMx', () => { beforeEach(function (done) { this.nu = require('../index') @@ -89,11 +91,10 @@ describe('HarakaMx', () => { }) }) - describe('fromUri', function () { + describe('fromUrl', function () { it('parses simple URIs', function () { assert.deepEqual(new this.nu.HarakaMx('smtp://192.0.2.2'), { exchange: '192.0.2.2', - port: 25, priority: 0, }) @@ -109,7 +110,6 @@ describe('HarakaMx', () => { new this.nu.HarakaMx('smtp://authUser:sekretPass@[2001:db8::1]'), { exchange: '[2001:db8::1]', - port: 25, priority: 0, auth_pass: 'sekretPass', auth_user: 'authUser', @@ -130,70 +130,53 @@ describe('HarakaMx', () => { }) }) - describe('toUrl', function () { - it('has a reasonable toUrl()', function () { - assert.equal( - new this.nu.HarakaMx({ exchange: '.' }).toUrl(), - 'smtp://.:25', - ) - - assert.equal( - new this.nu.HarakaMx({ - from_dns: 'example.com', - exchange: '.', - priority: 10, - }).toUrl(), - 'smtp://.:25', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toUrl(), - 'smtp://au:****@192.0.2.3:25', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toUrl(), - 'smtp://au:****@192.0.2.3:465', - ) + const testCases = [ + { in: { exchange: '.' }, url: 'smtp://.', str: 'MX 0 smtp://.' }, + { + in: { + from_dns: 'example.com', + exchange: '.', + priority: 10, + }, + url: 'smtp://.', + str: 'MX 10 smtp://. (via DNS)', + }, + { + in: 'smtp://au:ap@192.0.2.3:25', + url: 'smtp://au:****@192.0.2.3:25', + str: 'MX 0 smtp://au:****@192.0.2.3:25', + }, + { + in: 'smtp://au:ap@192.0.2.3:465', + url: 'smtp://au:****@192.0.2.3:465', + str: 'MX 0 smtp://au:****@192.0.2.3:465', + }, + { + in: 'smtp://[2001:db8::1]:25', + url: 'smtp://[2001:db8::1]:25', + str: 'MX 0 smtp://[2001:db8::1]:25', + }, + { + in: { path: '/var/run/sock' }, + url: 'file:///var/run/sock', + str: 'MX 0 file:///var/run/sock', + }, + ] - assert.equal( - new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toUrl(), - 'smtp://[2001:db8::1]:25', - ) - }) + describe('toUrl', function () { + for (const c of testCases) { + it(`${JSON.stringify(c.in)} -> ${c.url}`, function () { + assert.equal(new this.nu.HarakaMx(c.in).toUrl(), c.url) + }) + } }) describe('toString', function () { - it('has a reasonable toString()', function () { - assert.equal( - new this.nu.HarakaMx({ exchange: '.' }).toString(), - 'MX 0 smtp://.:25', - ) - - assert.equal( - new this.nu.HarakaMx({ - from_dns: 'example.com', - exchange: '.', - priority: 10, - }).toString(), - 'MX 10 smtp://.:25 (from example.com)', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toString(), - 'MX 0 smtp://au:****@192.0.2.3:25', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toString(), - 'MX 0 smtp://au:****@192.0.2.3:465', - ) - - assert.equal( - new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toString(), - 'MX 0 smtp://[2001:db8::1]:25', - ) - }) + for (const c of testCases) { + it(`${JSON.stringify(c.in)} -> ${c.str}`, function () { + assert.equal(new this.nu.HarakaMx(c.in).toString(), c.str) + }) + } }) it('is exported from nu', function () { From c8e9e1cdf3baa5f6af755a26cf82efa785f8edfa Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 2 May 2024 14:37:56 -0700 Subject: [PATCH 6/9] feat: add add_line_processor, aka line_socket.setup_line_processor --- CHANGELOG.md | 3 ++- index.js | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b96fe48..196194a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ### [1.7.0] - 2024-04-29 - feat: added HarakaMx #89 +- feat: add add_line_processor, aka line_socket.setup_line_processor +- fix(get_public_ip): set timeout in stun request, fixes #84 - test: added get_implicit_mx tests #89 - change: get_mx: don't filter implicit MX errors #89 -- fix(get_public_ip): set timeout in stun request, fixes #84 ### [1.6.0] - 2024-04-17 diff --git a/index.js b/index.js index 1054980..e717e52 100644 --- a/index.js +++ b/index.js @@ -363,3 +363,25 @@ exports.get_public_ip = require('./lib/get_public_ip').get_public_ip exports.get_public_ip_async = require('./lib/get_public_ip').get_public_ip_async exports.HarakaMx = require('./lib/HarakaMx') + +exports.add_line_processor = (socket) => { + const line_regexp = /^([^\n]*\n)/; // utils.line_regexp + let current_data = ''; + + socket.on('data', (data) => { + current_data += data; + let results; + while ((results = line_regexp.exec(current_data))) { + const this_line = results[1]; + current_data = current_data.slice(this_line.length); + socket.emit('line', this_line); + } + }) + + socket.on('end', () => { + if (current_data.length) { + socket.emit('line', current_data); + } + current_data = ''; + }) +} From d893bce0576af3646f39ff87cbfff737bf311536 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 2 May 2024 15:17:37 -0700 Subject: [PATCH 7/9] add test --- test/net_utils.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/net_utils.js b/test/net_utils.js index 8620d01..eb0c5ef 100644 --- a/test/net_utils.js +++ b/test/net_utils.js @@ -1,4 +1,5 @@ const assert = require('node:assert') +const EventEmitter = require('node:events'); const os = require('node:os') const path = require('node:path') @@ -534,3 +535,28 @@ describe('on_local_interface', function () { done() }) }) + +describe('add_line_processor', function () { + beforeEach(function (done) { + this.net_utils = require('../index') + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('test'), + ) + done() + }) + + it('adds a line processor', function (done) { + const socket = new EventEmitter() + let lines = 0 + socket.on('line', () => { + lines++ + }) + socket.on('end', (e) => { + assert.equal(lines, 3) + done() + }) + this.net_utils.add_line_processor(socket) + socket.emit('data', `multi\nline\nallThisDataIsLost\n`) + socket.emit('end') + }) +}) From 3394facbabd7bfbb13d9f64a6447ed13ebcc13f0 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 2 May 2024 15:19:48 -0700 Subject: [PATCH 8/9] lint --- index.js | 36 ++++++++++++++++++------------------ test/net_utils.js | 4 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index e717e52..fa9e7c8 100644 --- a/index.js +++ b/index.js @@ -365,23 +365,23 @@ exports.get_public_ip_async = require('./lib/get_public_ip').get_public_ip_async exports.HarakaMx = require('./lib/HarakaMx') exports.add_line_processor = (socket) => { - const line_regexp = /^([^\n]*\n)/; // utils.line_regexp - let current_data = ''; - - socket.on('data', (data) => { - current_data += data; - let results; - while ((results = line_regexp.exec(current_data))) { - const this_line = results[1]; - current_data = current_data.slice(this_line.length); - socket.emit('line', this_line); - } - }) + const line_regexp = /^([^\n]*\n)/ // utils.line_regexp + let current_data = '' + + socket.on('data', (data) => { + current_data += data + let results + while ((results = line_regexp.exec(current_data))) { + const this_line = results[1] + current_data = current_data.slice(this_line.length) + socket.emit('line', this_line) + } + }) - socket.on('end', () => { - if (current_data.length) { - socket.emit('line', current_data); - } - current_data = ''; - }) + socket.on('end', () => { + if (current_data.length) { + socket.emit('line', current_data) + } + current_data = '' + }) } diff --git a/test/net_utils.js b/test/net_utils.js index eb0c5ef..8222bb3 100644 --- a/test/net_utils.js +++ b/test/net_utils.js @@ -1,5 +1,5 @@ const assert = require('node:assert') -const EventEmitter = require('node:events'); +const EventEmitter = require('node:events') const os = require('node:os') const path = require('node:path') @@ -551,7 +551,7 @@ describe('add_line_processor', function () { socket.on('line', () => { lines++ }) - socket.on('end', (e) => { + socket.on('end', () => { assert.equal(lines, 3) done() }) From 586d73b3c159bbc98c9b24b162835ad5f03eea91 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 2 May 2024 15:21:40 -0700 Subject: [PATCH 9/9] prettier-ignore --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 196194a..2e64e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ### [1.6.0] - 2024-04-17 - feat: normalizeDomain, for punycode/IDN names -- feat: get*mx now \_also* returns implicit MX records + +- feat: get_mx now _also_ returns implicit MX records - feat: added get_implicit_mx - feat: added resolve_mx_hosts - doc(Changes): fixed broken tag version links