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..2e64e9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,9 +4,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
### Unreleased
+### [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
+
### [1.6.0] - 2024-04-17
- feat: normalizeDomain, for punycode/IDN names
+
- feat: get_mx now _also_ returns implicit MX records
- feat: added get_implicit_mx
- feat: added resolve_mx_hosts
@@ -62,88 +71,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 +161,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 +206,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 +215,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..404adff 100644
--- a/README.md
+++ b/README.md
@@ -120,6 +120,50 @@ try {
}
```
+### HarakaMx
+
+An object class representing a MX. HarakaMx objects may contain the following properties:
+
+```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
+ 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..fa9e7c8 100644
--- a/index.js
+++ b/index.js
@@ -1,11 +1,9 @@
'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')
// npm modules
const ipaddr = require('ipaddr.js')
@@ -259,97 +257,7 @@ 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())
- 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()
-
- const nu = this
- if (nu.public_ip !== undefined) return cb(null, nu.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)
- }
-
- // Initialise cache value to null to prevent running
- // should we hit a timeout or the module isn't installed.
- nu.public_ip = null
-
- try {
- nu.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
- 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)
- })
-}
-
-function get_stun_server() {
- // STUN servers by Google
- 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) {
- 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
@@ -446,127 +354,34 @@ 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
+for (const l of ['get_mx', 'get_implicit_mx', 'resolve_mx_hosts']) {
+ exports[l] = require('./lib/get_mx')[l]
}
-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_public_ip = require('./lib/get_public_ip').get_public_ip
-exports.get_mx = async (raw_domain, cb) => {
- const domain = normalizeDomain(raw_domain)
+exports.get_public_ip_async = require('./lib/get_public_ip').get_public_ip_async
- try {
- const exchanges = await dns.resolveMx(domain)
- if (exchanges && exchanges.length) {
- exchanges.map((e) => (e.from_dns = domain))
- if (cb) return cb(null, exchanges)
- return exchanges
- }
- } catch (err) {
- // console.error(err.message)
- if (fatal_mx_err(err)) {
- if (cb) return cb(err, [])
- throw err
- }
- }
+exports.HarakaMx = require('./lib/HarakaMx')
- // 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
- }
- }
-}
+exports.add_line_processor = (socket) => {
+ const line_regexp = /^([^\n]*\n)/ // utils.line_regexp
+ let current_data = ''
-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) => ({ priority: 0, exchange: ip, from_dns: 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
+ 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)
}
+ })
- if (net.isIP(mx.exchange)) {
- promises.push(mx) // already resolved
- continue
+ socket.on('end', () => {
+ if (current_data.length) {
+ socket.emit('line', current_data)
}
-
- // 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)
+ current_data = ''
+ })
}
diff --git a/lib/HarakaMx.js b/lib/HarakaMx.js
new file mode 100644
index 0000000..239f298
--- /dev/null
+++ b/lib/HarakaMx.js
@@ -0,0 +1,99 @@
+'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)
+ 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()
+ }
+
+ 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',
+ '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:':
+ break
+ case 'lmtp:':
+ this.using_lmtp = true
+ 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 host = net.isIPv6(this.exchange)
+ ? `[${this.exchange}]`
+ : this.exchange
+ 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 ? ` (via DNS)` : ''
+ return `MX ${this.priority} ${this.toUrl()}${from_dns}`
+ }
+}
+
+module.exports = HarakaMx
diff --git a/lib/get_mx.js b/lib/get_mx.js
new file mode 100644
index 0000000..91bdc26
--- /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')
+
+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)
+}
+
+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/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 dd51bb9..7b30189 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,11 @@
{
"name": "haraka-net-utils",
- "version": "1.6.0",
+ "version": "1.7.0",
"description": "haraka network utilities",
"main": "index.js",
"files": [
- "CHANGELOG.md"
+ "CHANGELOG.md",
+ "lib"
],
"scripts": {
"format": "npm run prettier:fix && npm run lint:fix",
@@ -36,12 +37,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/get_ip_any.js b/test/get_ip_any.js
new file mode 100644
index 0000000..7314e58
--- /dev/null
+++ b/test/get_ip_any.js
@@ -0,0 +1,627 @@
+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..b08cbd4
--- /dev/null
+++ b/test/get_mx.js
@@ -0,0 +1,182 @@
+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)
+ })
+ })
+})
diff --git a/test/get_public_ip.js b/test/get_public_ip.js
new file mode 100644
index 0000000..d61ed30
--- /dev/null
+++ b/test/get_public_ip.js
@@ -0,0 +1,79 @@
+const assert = require('node:assert')
+const path = require('node:path')
+
+function has_stun() {
+ try {
+ require('@msimerson/stun')
+ } catch (e) {
+ return false
+ }
+ 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 () {
+ 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) {
+ 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 () {
+ 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 function () {
+ 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..3a338d5
--- /dev/null
+++ b/test/harakaMx.js
@@ -0,0 +1,191 @@
+const assert = require('assert')
+
+process.env.NODE_ENV = 'test'
+
+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('fromUrl', function () {
+ it('parses simple URIs', function () {
+ assert.deepEqual(new this.nu.HarakaMx('smtp://192.0.2.2'), {
+ exchange: '192.0.2.2',
+ 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]',
+ 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',
+ },
+ )
+ })
+ })
+
+ 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',
+ },
+ ]
+
+ 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 () {
+ 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 () {
+ const nu = require('../index')
+ assert.equal(typeof nu.HarakaMx, 'function')
+ })
+
+ it('directly loadable', function () {
+ const hMx = require('../lib/HarakaMx')
+ assert.equal(typeof hMx, 'function')
+ })
+})
diff --git a/test/net_utils.js b/test/net_utils.js
index a547a83..8222bb3 100644
--- a/test/net_utils.js
+++ b/test/net_utils.js
@@ -1,7 +1,7 @@
-const assert = require('assert')
-const net = require('net')
-const os = require('os')
-const path = require('path')
+const assert = require('node:assert')
+const EventEmitter = require('node:events')
+const os = require('node:os')
+const path = require('node:path')
require('haraka-config').watch_files = false
const net_utils = require('../index')
@@ -240,97 +240,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) {
- // console.log(`ip: ${ip}`);
- // console.log(`err: ${err}`);
- if (has_stun()) {
- if (err) {
- console.log(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'
@@ -471,629 +380,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': [
@@ -1122,6 +408,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())
@@ -1249,143 +536,27 @@ describe('on_local_interface', function () {
})
})
-describe('get_mx', function () {
- this.timeout(12000)
-
+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()
})
- 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)
- // assert.ok(mxlist.length);
- 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)
- // assert.ok(mxlist.length);
- checkValid(validCases[c], mxlist)
+ it('adds a line processor', function (done) {
+ const socket = new EventEmitter()
+ let lines = 0
+ socket.on('line', () => {
+ lines++
})
- }
-
- // macOS: ENODATA, win: ENOTOUND, ubuntu: ESERVFAIL
- const invalidCases = {
- invalid: /queryMx (ENODATA|ENOTFOUND|ESERVFAIL) invalid/,
- 'gmail.xn--com-0da': /(ENOTFOUND|Cannot convert name to ASCII)/,
- }
-
- 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) => {
- assert.equal(mxlist.length, 0)
- checkInvalid(invalidCases[c], err.message)
- })
- })
-
- 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)
- }
+ socket.on('end', () => {
+ assert.equal(lines, 3)
+ done()
})
- }
-})
-
-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)
+ this.net_utils.add_line_processor(socket)
+ socket.emit('data', `multi\nline\nallThisDataIsLost\n`)
+ socket.emit('end')
})
})