mailauth 2.3.3 → 2.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dkim/body/relaxed.js +219 -85
- package/lib/dkim/body/simple.js +6 -1
- package/licenses.txt +3 -3
- package/man/mailauth.1 +1 -1
- package/package.json +4 -4
package/lib/dkim/body/relaxed.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint no-control-regex: 0 */
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
'use strict';
|
|
4
4
|
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
|
|
7
|
+
const CHAR_CR = 0x0d;
|
|
8
|
+
const CHAR_LF = 0x0a;
|
|
9
|
+
const CHAR_SPACE = 0x20;
|
|
10
|
+
const CHAR_TAB = 0x09;
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* Class for calculating body hash of an email message body stream
|
|
9
14
|
* using the "relaxed" canonicalization
|
|
@@ -16,139 +21,244 @@ class RelaxedHash {
|
|
|
16
21
|
* @param {Number} [maxBodyLength] Allowed body length count, the value from the l= parameter
|
|
17
22
|
*/
|
|
18
23
|
constructor(algorithm, maxBodyLength) {
|
|
19
|
-
algorithm = (algorithm || 'sha256').split('-').pop();
|
|
24
|
+
algorithm = (algorithm || 'sha256').split('-').pop().toLowerCase();
|
|
25
|
+
|
|
20
26
|
this.bodyHash = crypto.createHash(algorithm);
|
|
21
27
|
|
|
22
|
-
this.remainder =
|
|
28
|
+
this.remainder = false;
|
|
23
29
|
this.byteLength = 0;
|
|
24
30
|
|
|
25
31
|
this.bodyHashedBytes = 0;
|
|
26
32
|
this.maxBodyLength = maxBodyLength;
|
|
33
|
+
|
|
34
|
+
this.maxSizeReached = false;
|
|
35
|
+
|
|
36
|
+
this.emptyLinesQueue = [];
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
_updateBodyHash(chunk) {
|
|
30
|
-
|
|
40
|
+
if (this.maxSizeReached) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// the following is needed for the l= option
|
|
31
45
|
if (
|
|
32
46
|
typeof this.maxBodyLength === 'number' &&
|
|
33
47
|
!isNaN(this.maxBodyLength) &&
|
|
34
48
|
this.maxBodyLength >= 0 &&
|
|
35
49
|
this.bodyHashedBytes + chunk.length > this.maxBodyLength
|
|
36
50
|
) {
|
|
51
|
+
this.maxSizeReached = true;
|
|
37
52
|
if (this.bodyHashedBytes >= this.maxBodyLength) {
|
|
38
53
|
// nothing to do here, skip entire chunk
|
|
39
54
|
return;
|
|
40
55
|
}
|
|
56
|
+
|
|
41
57
|
// only use allowed size of bytes
|
|
42
|
-
chunk = chunk.
|
|
58
|
+
chunk = chunk.subarray(0, this.maxBodyLength - this.bodyHashedBytes);
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
this.bodyHashedBytes += chunk.length;
|
|
46
62
|
this.bodyHash.update(chunk);
|
|
63
|
+
|
|
64
|
+
//process.stdout.write(chunk);
|
|
47
65
|
}
|
|
48
66
|
|
|
49
|
-
|
|
50
|
-
this.
|
|
67
|
+
_drainPendingEmptyLines() {
|
|
68
|
+
if (this.emptyLinesQueue.length) {
|
|
69
|
+
for (let emptyLine of this.emptyLinesQueue) {
|
|
70
|
+
this._updateBodyHash(emptyLine);
|
|
71
|
+
}
|
|
72
|
+
this.emptyLinesQueue = [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
51
75
|
|
|
52
|
-
|
|
76
|
+
_pushBodyHash(chunk) {
|
|
77
|
+
if (!chunk || !chunk.length) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
53
80
|
|
|
54
|
-
//
|
|
55
|
-
let
|
|
81
|
+
// remove line endings
|
|
82
|
+
let foundNonLn = false;
|
|
56
83
|
|
|
57
|
-
//
|
|
58
|
-
// If we get another chunk that does not match this description then we can restore the previously processed data
|
|
59
|
-
let state = 'file';
|
|
84
|
+
// buffer line endings and empty lines
|
|
60
85
|
for (let i = chunk.length - 1; i >= 0; i--) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// switch to line ending mode, this is the last non-empty line
|
|
67
|
-
state = 'line';
|
|
68
|
-
} else if (state === 'line' && (c === 0x09 || c === 0x20)) {
|
|
69
|
-
// do nothing, found ' ' or \t at the end of line, keep processing the last non-empty line
|
|
70
|
-
} else if (state === 'file' || state === 'line') {
|
|
71
|
-
// non line/file ending character found, switch to body mode
|
|
72
|
-
state = 'body';
|
|
73
|
-
if (i === chunk.length - 1) {
|
|
74
|
-
// final char is not part of line end or file end, so do nothing
|
|
75
|
-
break;
|
|
86
|
+
if (chunk[i] !== CHAR_LF && chunk[i] !== CHAR_CR) {
|
|
87
|
+
this._drainPendingEmptyLines();
|
|
88
|
+
if (i < chunk.length - 1) {
|
|
89
|
+
this.emptyLinesQueue.push(chunk.subarray(i + 1));
|
|
90
|
+
chunk = chunk.subarray(0, i + 1);
|
|
76
91
|
}
|
|
92
|
+
foundNonLn = true;
|
|
93
|
+
break;
|
|
77
94
|
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!foundNonLn) {
|
|
98
|
+
this.emptyLinesQueue.push(chunk);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
78
101
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
this._updateBodyHash(chunk);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fixLineBuffer(line) {
|
|
106
|
+
let resultLine = [];
|
|
107
|
+
|
|
108
|
+
let nonWspFound = false;
|
|
109
|
+
let prevWsp = false;
|
|
110
|
+
|
|
111
|
+
for (let i = line.length - 1; i >= 0; i--) {
|
|
112
|
+
if (line[i] === CHAR_LF) {
|
|
113
|
+
resultLine.unshift(line[i]);
|
|
114
|
+
if (i === 0 || line[i - 1] !== CHAR_CR) {
|
|
115
|
+
// add missing carriage return
|
|
116
|
+
resultLine.unshift(CHAR_CR);
|
|
94
117
|
}
|
|
118
|
+
continue;
|
|
95
119
|
}
|
|
96
120
|
|
|
97
|
-
if (
|
|
121
|
+
if (line[i] === CHAR_CR) {
|
|
122
|
+
resultLine.unshift(line[i]);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (line[i] === CHAR_SPACE || line[i] === CHAR_TAB) {
|
|
127
|
+
if (nonWspFound) {
|
|
128
|
+
prevWsp = true;
|
|
129
|
+
}
|
|
98
130
|
continue;
|
|
99
131
|
}
|
|
100
132
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
133
|
+
if (prevWsp) {
|
|
134
|
+
resultLine.unshift(CHAR_SPACE);
|
|
135
|
+
prevWsp = false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
nonWspFound = true;
|
|
139
|
+
resultLine.unshift(line[i]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (prevWsp && nonWspFound) {
|
|
143
|
+
resultLine.unshift(CHAR_SPACE);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return Buffer.from(resultLine);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
update(chunk, final) {
|
|
150
|
+
this.byteLength += (chunk && chunk.length) || 0;
|
|
151
|
+
if (this.maxSizeReached) {
|
|
152
|
+
return;
|
|
105
153
|
}
|
|
106
154
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
155
|
+
// Canonicalize content by applying a and b in order:
|
|
156
|
+
// a.1. Ignore all whitespace at the end of lines.
|
|
157
|
+
// a.2. Reduce all sequences of WSP within a line to a single SP character.
|
|
158
|
+
|
|
159
|
+
// b.1. Ignore all empty lines at the end of the message body.
|
|
160
|
+
// b.2. If the body is non-empty but does not end with a CRLF, a CRLF is added.
|
|
161
|
+
|
|
162
|
+
let lineEndPos = -1;
|
|
163
|
+
let lineNeedsFixing = false;
|
|
164
|
+
let cursorPos = 0;
|
|
165
|
+
|
|
166
|
+
if (this.remainder && this.remainder.length) {
|
|
167
|
+
if (chunk) {
|
|
168
|
+
// concatting chunks might be bad for performance :S
|
|
169
|
+
chunk = Buffer.concat([this.remainder, chunk]);
|
|
170
|
+
} else {
|
|
171
|
+
chunk = this.remainder;
|
|
172
|
+
}
|
|
173
|
+
this.remainder = false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (chunk && chunk.length) {
|
|
177
|
+
for (let pos = 0; pos < chunk.length; pos++) {
|
|
178
|
+
switch (chunk[pos]) {
|
|
179
|
+
case CHAR_LF:
|
|
180
|
+
if (
|
|
181
|
+
!lineNeedsFixing &&
|
|
182
|
+
// previous character is not <CR>
|
|
183
|
+
((pos >= 1 && chunk[pos - 1] !== CHAR_CR) ||
|
|
184
|
+
// LF is the first byte on the line
|
|
185
|
+
pos === 0 ||
|
|
186
|
+
// there's a space before line break
|
|
187
|
+
(pos >= 2 && chunk[pos - 1] === CHAR_CR && chunk[pos - 2] === CHAR_SPACE))
|
|
188
|
+
) {
|
|
189
|
+
lineNeedsFixing = true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// line break
|
|
193
|
+
if (lineNeedsFixing) {
|
|
194
|
+
// emit pending bytes up to the last line break before current line
|
|
195
|
+
if (lineEndPos >= 0 && lineEndPos >= cursorPos) {
|
|
196
|
+
let chunkPart = chunk.subarray(cursorPos, lineEndPos + 1);
|
|
197
|
+
this._pushBodyHash(chunkPart);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let line = chunk.subarray(lineEndPos + 1, pos + 1);
|
|
201
|
+
this._pushBodyHash(this.fixLineBuffer(line));
|
|
202
|
+
|
|
203
|
+
lineNeedsFixing = false;
|
|
204
|
+
|
|
205
|
+
// move cursor to the start of next line
|
|
206
|
+
cursorPos = pos + 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
lineEndPos = pos;
|
|
210
|
+
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case CHAR_SPACE:
|
|
214
|
+
if (!lineNeedsFixing && pos && chunk[pos - 1] === CHAR_SPACE) {
|
|
215
|
+
lineNeedsFixing = true;
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
|
|
219
|
+
case CHAR_TAB:
|
|
220
|
+
// non-space WSP always needs replacing
|
|
221
|
+
lineNeedsFixing = true;
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
default:
|
|
127
225
|
}
|
|
128
226
|
}
|
|
129
227
|
}
|
|
130
228
|
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
229
|
+
if (chunk && cursorPos < chunk.length && cursorPos !== lineEndPos) {
|
|
230
|
+
// emit data from chunk
|
|
231
|
+
|
|
232
|
+
let chunkPart = chunk.subarray(cursorPos, lineEndPos + 1);
|
|
233
|
+
|
|
234
|
+
if (chunkPart.length) {
|
|
235
|
+
this._pushBodyHash(lineNeedsFixing ? this.fixLineBuffer(chunkPart) : chunkPart);
|
|
236
|
+
lineNeedsFixing = false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
cursorPos = lineEndPos + 1;
|
|
142
240
|
}
|
|
143
241
|
|
|
144
|
-
|
|
242
|
+
if (chunk && !final && cursorPos < chunk.length) {
|
|
243
|
+
this.remainder = chunk.subarray(cursorPos);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (final) {
|
|
247
|
+
let chunkPart = (cursorPos && chunk && chunk.subarray(cursorPos)) || chunk;
|
|
248
|
+
if (chunkPart && chunkPart.length) {
|
|
249
|
+
this._pushBodyHash(lineNeedsFixing ? this.fixLineBuffer(chunkPart) : chunkPart);
|
|
250
|
+
lineNeedsFixing = false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (this.bodyHashedBytes) {
|
|
254
|
+
// terminating line break for non-empty messages
|
|
255
|
+
this._updateBodyHash(Buffer.from([CHAR_CR, CHAR_LF]));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
145
258
|
}
|
|
146
259
|
|
|
147
260
|
digest(encoding) {
|
|
148
|
-
|
|
149
|
-
// add terminating line end
|
|
150
|
-
this._updateBodyHash(Buffer.from('\r\n'));
|
|
151
|
-
}
|
|
261
|
+
this.update(null, true);
|
|
152
262
|
|
|
153
263
|
// finalize
|
|
154
264
|
return this.bodyHash.digest(encoding);
|
|
@@ -156,3 +266,27 @@ class RelaxedHash {
|
|
|
156
266
|
}
|
|
157
267
|
|
|
158
268
|
module.exports = { RelaxedHash };
|
|
269
|
+
|
|
270
|
+
/*
|
|
271
|
+
let fs = require('fs');
|
|
272
|
+
|
|
273
|
+
const getBody = message => {
|
|
274
|
+
message = message.toString('binary');
|
|
275
|
+
let match = message.match(/\r?\n\r?\n/);
|
|
276
|
+
if (match) {
|
|
277
|
+
message = message.substr(match.index + match[0].length);
|
|
278
|
+
}
|
|
279
|
+
return Buffer.from(message, 'binary');
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
let s = fs.readFileSync(process.argv[2]);
|
|
283
|
+
|
|
284
|
+
let k = new RelaxedHash('rsa-sha256', -1);
|
|
285
|
+
|
|
286
|
+
for (let byte of getBody(s)) {
|
|
287
|
+
k.update(Buffer.from([byte]));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.error(k.digest('base64'));
|
|
291
|
+
console.error(k.byteLength, k.bodyHashedBytes);
|
|
292
|
+
*/
|
package/lib/dkim/body/simple.js
CHANGED
|
@@ -22,6 +22,8 @@ class SimpleHash {
|
|
|
22
22
|
|
|
23
23
|
this.bodyHashedBytes = 0;
|
|
24
24
|
this.maxBodyLength = maxBodyLength;
|
|
25
|
+
|
|
26
|
+
this.lastNewline = false;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
_updateBodyHash(chunk) {
|
|
@@ -42,6 +44,8 @@ class SimpleHash {
|
|
|
42
44
|
|
|
43
45
|
this.bodyHashedBytes += chunk.length;
|
|
44
46
|
this.bodyHash.update(chunk);
|
|
47
|
+
|
|
48
|
+
//process.stdout.write(chunk);
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
update(chunk) {
|
|
@@ -81,10 +85,11 @@ class SimpleHash {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
this._updateBodyHash(chunk);
|
|
88
|
+
this.lastNewline = chunk[chunk.length - 1] === 0x0a;
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
digest(encoding) {
|
|
87
|
-
if (this.
|
|
92
|
+
if (!this.lastNewline || !this.bodyHashedBytes) {
|
|
88
93
|
// emit empty line buffer to keep the stream flowing
|
|
89
94
|
this._updateBodyHash(Buffer.from('\r\n'));
|
|
90
95
|
}
|
package/licenses.txt
CHANGED
|
@@ -3,9 +3,9 @@ name license type link
|
|
|
3
3
|
@fidm/x509 MIT git+ssh://git@github.com/fidm/x509.git 1.2.1
|
|
4
4
|
ipaddr.js MIT git://github.com/whitequark/ipaddr.js.git 2.0.1 whitequark whitequark@whitequark.org
|
|
5
5
|
joi BSD-3-Clause git://github.com/sideway/joi.git 17.6.0
|
|
6
|
-
libmime MIT git://github.com/andris9/libmime.git 5.
|
|
6
|
+
libmime MIT git://github.com/andris9/libmime.git 5.1.0 Andris Reinman andris@kreata.ee
|
|
7
7
|
node-forge (BSD-3-Clause OR GPL-2.0) git+https://github.com/digitalbazaar/forge.git 1.3.1 Digital Bazaar, Inc. support@digitalbazaar.com http://digitalbazaar.com/
|
|
8
|
-
nodemailer MIT git+https://github.com/nodemailer/nodemailer.git 6.7.
|
|
8
|
+
nodemailer MIT git+https://github.com/nodemailer/nodemailer.git 6.7.5 Andris Reinman
|
|
9
9
|
psl MIT git+ssh://git@github.com/lupomontero/psl.git 1.8.0 Lupo Montero lupomontero@gmail.com https://lupomontero.com/
|
|
10
10
|
punycode MIT git+https://github.com/bestiejs/punycode.js.git 2.1.1 Mathias Bynens https://mathiasbynens.be/
|
|
11
|
-
yargs MIT git+https://github.com/yargs/yargs.git 17.
|
|
11
|
+
yargs MIT git+https://github.com/yargs/yargs.git 17.5.0
|
package/man/mailauth.1
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailauth",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.4",
|
|
4
4
|
"description": "Email authentication library for Node.js",
|
|
5
5
|
"main": "lib/mailauth.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"homepage": "https://github.com/postalsys/mailauth",
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"chai": "4.3.6",
|
|
36
|
-
"eslint": "8.
|
|
36
|
+
"eslint": "8.17.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "8.5.0",
|
|
39
39
|
"js-yaml": "4.1.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"marked-man": "0.7.0",
|
|
43
43
|
"mbox-reader": "1.1.5",
|
|
44
44
|
"mocha": "10.0.0",
|
|
45
|
-
"pkg": "5.
|
|
45
|
+
"pkg": "5.7.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@fidm/x509": "1.2.1",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"nodemailer": "6.7.5",
|
|
54
54
|
"psl": "1.8.0",
|
|
55
55
|
"punycode": "2.1.1",
|
|
56
|
-
"yargs": "17.5.
|
|
56
|
+
"yargs": "17.5.1"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=14.0.0"
|