Comparing version 2.1.3 to 2.2.0
@@ -12,2 +12,3 @@ #!/usr/bin/env node | ||
const commandSeal = require('../lib/commands/seal'); | ||
const commandSpf = require('../lib/commands/spf'); | ||
@@ -45,2 +46,8 @@ const argv = yargs(hideBin(process.argv)) | ||
description: 'Path to a JSON file with cached DNS responses. If this file is given then no actual DNS requests are performed' | ||
}) | ||
.option('max-lookups', { | ||
alias: 'x', | ||
type: 'number', | ||
description: 'Maximum allowed DNS lookups', | ||
default: 50 | ||
}); | ||
@@ -226,2 +233,59 @@ yargs.positional('email', { | ||
) | ||
.command( | ||
['spf'], | ||
'Validate SPF for an email address and MTA IP address', | ||
yargs => { | ||
yargs | ||
.option('sender', { | ||
alias: 'f', | ||
type: 'string', | ||
description: 'Email address from the MAIL FROM command', | ||
demandOption: true | ||
}) | ||
.option('client-ip', { | ||
alias: 'i', | ||
type: 'string', | ||
description: 'Client IP used for SPF checks. If not set then parsed from the latest Received header', | ||
demandOption: true | ||
}) | ||
.option('helo', { | ||
alias: 'e', | ||
type: 'string', | ||
description: 'Client hostname from the EHLO/HELO command, used in some specific SPF checks' | ||
}) | ||
.option('mta', { | ||
alias: 'm', | ||
type: 'string', | ||
description: 'Hostname of this machine, used in the Authentication-Results header', | ||
default: os.hostname() | ||
}) | ||
.option('dns-cache', { | ||
alias: 'n', | ||
type: 'string', | ||
description: 'Path to a JSON file with cached DNS responses. If this file is given then no actual DNS requests are performed' | ||
}) | ||
.option('headers-only', { | ||
alias: 'o', | ||
type: 'boolean', | ||
description: 'Return signing headers only' | ||
}) | ||
.option('max-lookups', { | ||
alias: 'x', | ||
type: 'number', | ||
description: 'Maximum allowed DNS lookups', | ||
default: 50 | ||
}); | ||
}, | ||
argv => { | ||
commandSpf(argv) | ||
.then(() => { | ||
process.exit(); | ||
}) | ||
.catch(err => { | ||
console.error('Failed to verify SPF for an email address'); | ||
console.error(err); | ||
process.exit(1); | ||
}); | ||
} | ||
) | ||
.option('verbose', { | ||
@@ -228,0 +292,0 @@ alias: 'v', |
42
cli.md
@@ -12,2 +12,3 @@ # CLI USAGE | ||
- [seal](#seal) - to seal an email with ARC | ||
- [spt](#spf) - to validate SPF for an IP address and email address | ||
- [DNS cache file](#dns-cache-file) | ||
@@ -36,2 +37,3 @@ | ||
$ mailauth seal --help | ||
$ mailauth spf --help | ||
``` | ||
@@ -68,2 +70,3 @@ | ||
- `--verbose` or `-v` if this flag is set then mailauth writes some debugging info to standard error | ||
- `--max-lookups nr` or `-x nr` defines the allowed DNS lookup limit for SPF checks. Defaults to 50. | ||
@@ -175,2 +178,41 @@ **Example** | ||
### spf | ||
`spf` command takes an email address and an IP address and returns a JSON formatted SPF report. | ||
``` | ||
$ mailauth spf [options] | ||
``` | ||
Where | ||
- **options** are option flags and arguments | ||
**Options** | ||
- `--sender user@example.com` or `-f address` is the email address from the MAIL FROM command. Required. | ||
- `--client-ip x.x.x.x` or `-i x.x.x.x` is the IP of the remote client that sent the email. Required. | ||
- `--helo hostname` or `-e hostname` is the client hostname from the HELO/EHLO command. Used in some obscure SPF validation operations | ||
- `--mta hostname` or `-m hostname` is the server hostname doing the validation checks. Defaults to `os.hostname()`. Used in authentication headers. | ||
- `--dns-cache /path/to/dns.json` or `-n path` is the path to a file with cached DNS query responses. If this file is provided then no actual DNS requests are performed, only cached values from this file are used. | ||
- `--verbose` or `-v` if this flag is set then mailauth writes some debugging info to standard error | ||
- `--headers-only` or `-o` If set return SPF authentication header only. Default is to return a JSON structure. | ||
- `--max-lookups nr` or `-x nr` defines the allowed DNS lookup limit for SPF checks. Defaults to 50. | ||
**Example** | ||
``` | ||
$ mailauth spf --verbose -f andris@wildduck.email -i 217.146.76.20 | ||
Checking SPF for andris@wildduck.email | ||
Maximum DNS lookups: 50 | ||
-------- | ||
DNS query for TXT wildduck.email: [["v=spf1 mx a -all"]] | ||
DNS query for MX wildduck.email: [{"exchange":"mail.wildduck.email","priority":1}] | ||
DNS query for A mail.wildduck.email: ["217.146.76.20"] | ||
{ | ||
"domain": "wildduck.email", | ||
"client-ip": "217.146.76.20", | ||
... | ||
``` | ||
## DNS cache file | ||
@@ -177,0 +219,0 @@ |
@@ -34,2 +34,6 @@ 'use strict'; | ||
if (argv.maxLookups) { | ||
opts.maxResolveCount = argv.maxLookups; | ||
} | ||
for (let key of ['mta', 'helo', 'sender']) { | ||
@@ -36,0 +40,0 @@ if (argv[key]) { |
@@ -57,2 +57,5 @@ 'use strict'; | ||
} | ||
if (argv.dnsCache) { | ||
console.error(`Using DNS cache: ${argv.dnsCache}`); | ||
} | ||
console.error('--------'); | ||
@@ -59,0 +62,0 @@ } |
@@ -27,4 +27,15 @@ 'use strict'; | ||
return async (domain, type) => { | ||
if (!domain && type === 'resolveCount') { | ||
// special condition to get the counter | ||
return resolveCount; | ||
} | ||
if (!domain && type === 'resolveLimit') { | ||
// special condition to get the limit | ||
return maxResolveCount; | ||
} | ||
// do not allow to make more that MAX_RESOLVE_COUNT DNS requests per SPF check | ||
resolveCount++; | ||
if (resolveCount > maxResolveCount) { | ||
@@ -103,7 +114,8 @@ let error = new Error('Too many DNS requests'); | ||
* @param {String} opts.ip Client IP address | ||
* @param {String} opts.helo Client EHLO/HELO hostname | ||
* @param {String} [opts.mta] Hostname of the MTA or MX server that processes the message | ||
* @param {String} opts.helo Client EHLO/HELO hostname | ||
* @param {String} [opts.maxResolveCount=50] Maximum DNS lookups allowed | ||
*/ | ||
const verify = async opts => { | ||
let { sender, ip, mta, helo, resolver, maxResolveCount } = opts || {}; | ||
let { sender, ip, helo, mta, maxResolveCount, resolver } = opts || {}; | ||
@@ -141,2 +153,4 @@ mta = mta || os.hostname(); | ||
let verifyResolver = limitedResolver(resolver, maxResolveCount); | ||
let result; | ||
@@ -161,3 +175,3 @@ try { | ||
// generate DNS handler | ||
resolver: limitedResolver(resolver, maxResolveCount) | ||
resolver: verifyResolver | ||
}); | ||
@@ -175,2 +189,9 @@ } catch (err) { | ||
if (result && typeof result === 'object') { | ||
result.lookups = { | ||
limit: await verifyResolver(false, 'resolveLimit'), | ||
count: await verifyResolver(false, 'resolveCount') | ||
}; | ||
} | ||
let response = { domain, 'client-ip': ip }; | ||
@@ -241,2 +262,6 @@ if (helo) { | ||
if (result.lookups) { | ||
response.lookups = result.lookups; | ||
} | ||
return response; | ||
@@ -243,0 +268,0 @@ }; |
@@ -28,2 +28,5 @@ # mailauth(1) | ||
**spf**\ | ||
Authenticates SPF for an IP address and email address | ||
## Website | ||
@@ -39,4 +42,8 @@ | ||
`cat /path/to/email.eml | mailauth report` | ||
`mailauth sign /path/to/email.eml -d kreata.ee -s test -k /path/to/key` | ||
`mailauth spf -f andris@wildduck.email -i 217.146.76.20` | ||
## EMAIL ARGUMENT | ||
@@ -56,15 +63,15 @@ | ||
- `--client-ip`, `-i <ip>` | ||
Client IP used for SPF checks. If not set then parsed from the latest Received header. (`report`, `seal`) | ||
Client IP used for SPF checks. If not set then parsed from the latest Received header. (`report`, `seal`, `spf`) | ||
- `--mta`, `-m <hostname>` | ||
Hostname of this machine, used in the Authentication-Results header. (`report`, `seal`) | ||
Hostname of this machine, used in the Authentication-Results header. (`report`, `seal`, `spf`) | ||
- `--helo`, `-e <hostname>` | ||
Client hostname from the EHLO/HELO command, used in some specific SPF checks. (`report`, `seal`) | ||
Client hostname from the EHLO/HELO command, used in some specific SPF checks. (`report`, `seal`, `spf`) | ||
- `--sender`, `-f <address>` | ||
Email address from the `MAIL FROM` command. If not set then the address from the latest _Return-Path_ header is used instead. (`report`, `seal`) | ||
Email address from the `MAIL FROM` command. If not set then the address from the latest _Return-Path_ header is used instead. (`report`, `seal`, `spf`) | ||
- `--dns-cache`, `-n <file>` | ||
Path to a JSON file with cached DNS responses. If this file is given then no actual DNS requests are performed. (`report`, `seal`) | ||
Path to a JSON file with cached DNS responses. If this file is given then no actual DNS requests are performed. (`report`, `seal`, `spf`) | ||
@@ -96,4 +103,7 @@ - `--private-key`, `-k <file>` | ||
- `--headers-only`, `-o` | ||
Return signing headers only. By default the entire message is printed to console. (`sign`, `seal`) | ||
Return signing headers only. By default the entire message is printed to console. (`sign`, `seal`, `spf`) | ||
- `--max-lookups`, `-x` | ||
How many DNS lookups allowed for SPF validation. Defaults to 50. (`report`, `spf`) | ||
## DNS CACHE | ||
@@ -100,0 +110,0 @@ |
{ | ||
"name": "mailauth", | ||
"version": "2.1.3", | ||
"version": "2.2.0", | ||
"description": "Email authentication library for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "lib/mailauth.js", |
@@ -51,2 +51,3 @@ ![](https://github.com/andris9/mailauth/raw/master/assets/mailauth.png) | ||
- **resolver** (_async function_) is an optional async function for DNS requests. Defaults to [dns.promises.resolve](https://nodejs.org/api/dns.html#dns_dnspromises_resolve_hostname_rrtype) | ||
- **maxResolveCount** (_number_ defaults to _50_) is the DNS lookup limit for SPF. [RFC7208](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4) requires this limit to be 10, mailauth is less strict and defaults to 50. | ||
@@ -53,0 +54,0 @@ **Example** |
Sorry, the diff of this file is not supported yet
230241
39
4455
448
4
13