elit 3.6.4 → 3.6.6
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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +14 -1
- package/dist/build.d.ts +4 -1
- package/dist/cli.cjs +746 -166
- package/dist/cli.mjs +746 -166
- package/dist/config.d.ts +8 -1
- package/dist/coverage.d.ts +4 -1
- package/dist/desktop-auto-render.cjs +5 -4
- package/dist/desktop-auto-render.d.ts +4 -1
- package/dist/desktop-auto-render.js +5 -4
- package/dist/desktop-auto-render.mjs +5 -4
- package/dist/dom.cjs +5 -4
- package/dist/dom.d.ts +2 -0
- package/dist/dom.js +5 -4
- package/dist/dom.mjs +5 -4
- package/dist/el.d.ts +2 -0
- package/dist/index.cjs +5 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -4
- package/dist/index.mjs +5 -4
- package/dist/native.cjs +5 -4
- package/dist/native.d.ts +2 -0
- package/dist/native.js +5 -4
- package/dist/native.mjs +5 -4
- package/dist/render-context.d.ts +4 -1
- package/dist/router.cjs +5 -4
- package/dist/router.d.ts +2 -0
- package/dist/router.js +5 -4
- package/dist/router.mjs +5 -4
- package/dist/{server-CcBFc2F5.d.ts → server-uMQvZAll.d.ts} +9 -0
- package/dist/server.cjs +146 -4
- package/dist/server.d.ts +4 -1
- package/dist/server.js +4494 -285
- package/dist/server.mjs +146 -4
- package/dist/smtp-server.cjs +115 -0
- package/dist/smtp-server.d.ts +41 -0
- package/dist/smtp-server.js +4186 -0
- package/dist/smtp-server.mjs +87 -0
- package/dist/state.cjs +5 -4
- package/dist/state.d.ts +2 -0
- package/dist/state.js +5 -4
- package/dist/state.mjs +5 -4
- package/dist/test-runtime.cjs +184 -141
- package/dist/test-runtime.js +193 -150
- package/dist/test-runtime.mjs +184 -141
- package/dist/test.cjs +143 -134
- package/dist/test.js +152 -143
- package/dist/test.mjs +143 -134
- package/dist/types.d.ts +34 -2
- package/dist/universal.d.ts +2 -0
- package/package.json +9 -1
|
@@ -0,0 +1,4186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
11
|
+
}) : x)(function(x) {
|
|
12
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
13
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
|
+
});
|
|
15
|
+
var __esm = (fn, res) => function __init() {
|
|
16
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
17
|
+
};
|
|
18
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
19
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
20
|
+
};
|
|
21
|
+
var __export = (target, all) => {
|
|
22
|
+
for (var name in all)
|
|
23
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
|
+
};
|
|
25
|
+
var __copyProps = (to, from, except, desc) => {
|
|
26
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
27
|
+
for (let key of __getOwnPropNames(from))
|
|
28
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
29
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
30
|
+
}
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
34
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
35
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
36
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
37
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
38
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
39
|
+
mod
|
|
40
|
+
));
|
|
41
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
42
|
+
|
|
43
|
+
// node_modules/smtp-server/lib/smtp-stream.js
|
|
44
|
+
var require_smtp_stream = __commonJS({
|
|
45
|
+
"node_modules/smtp-server/lib/smtp-stream.js"(exports, module) {
|
|
46
|
+
"use strict";
|
|
47
|
+
var stream = __require("stream");
|
|
48
|
+
var Writable = stream.Writable;
|
|
49
|
+
var PassThrough = stream.PassThrough;
|
|
50
|
+
var SMTPStream = class extends Writable {
|
|
51
|
+
constructor(options) {
|
|
52
|
+
super(options);
|
|
53
|
+
this._dataMode = false;
|
|
54
|
+
this._dataStream = null;
|
|
55
|
+
this._maxBytes = Infinity;
|
|
56
|
+
this.dataBytes = 0;
|
|
57
|
+
this._continueCallback = false;
|
|
58
|
+
this._remainder = "";
|
|
59
|
+
this._lastBytes = false;
|
|
60
|
+
this._maxCommandLength = options && options.maxCommandLength || 4 * 1024;
|
|
61
|
+
this.isClosed = false;
|
|
62
|
+
this.on("finish", () => this._flushData());
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Placeholder command handler. Override this with your own.
|
|
66
|
+
*/
|
|
67
|
+
oncommand() {
|
|
68
|
+
throw new Error("Command handler is not set");
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Switch to data mode and return output stream. The dots in the stream are unescaped.
|
|
72
|
+
*
|
|
73
|
+
* @returns {Stream} Data stream
|
|
74
|
+
*/
|
|
75
|
+
startDataMode(maxBytes) {
|
|
76
|
+
this._dataMode = true;
|
|
77
|
+
this._maxBytes = maxBytes && Number(maxBytes) || Infinity;
|
|
78
|
+
this.dataBytes = 0;
|
|
79
|
+
this._dataStream = new PassThrough();
|
|
80
|
+
return this._dataStream;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Call this once data mode is over and you have finished processing the data stream
|
|
84
|
+
*/
|
|
85
|
+
continue() {
|
|
86
|
+
if (typeof this._continueCallback === "function") {
|
|
87
|
+
this._continueCallback();
|
|
88
|
+
this._continueCallback = false;
|
|
89
|
+
} else {
|
|
90
|
+
this._continueCallback = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// PRIVATE METHODS
|
|
94
|
+
/**
|
|
95
|
+
* Writable._write method.
|
|
96
|
+
*/
|
|
97
|
+
_write(chunk, encoding, next) {
|
|
98
|
+
if (!chunk || !chunk.length) {
|
|
99
|
+
return next();
|
|
100
|
+
}
|
|
101
|
+
let data;
|
|
102
|
+
let pos = 0;
|
|
103
|
+
let newlineRegex;
|
|
104
|
+
let called = false;
|
|
105
|
+
let done = (...args) => {
|
|
106
|
+
if (called) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
called = true;
|
|
110
|
+
next(...args);
|
|
111
|
+
};
|
|
112
|
+
if (this.isClosed) {
|
|
113
|
+
return done();
|
|
114
|
+
}
|
|
115
|
+
if (!this._dataMode) {
|
|
116
|
+
newlineRegex = /\r?\n/g;
|
|
117
|
+
data = this._remainder + chunk.toString("binary");
|
|
118
|
+
let readLine = () => {
|
|
119
|
+
let match;
|
|
120
|
+
let line;
|
|
121
|
+
let buf;
|
|
122
|
+
if (this._dataMode) {
|
|
123
|
+
buf = Buffer.from(data.substr(pos), "binary");
|
|
124
|
+
this._remainder = "";
|
|
125
|
+
return this._write(buf, "buffer", done);
|
|
126
|
+
}
|
|
127
|
+
if (match = newlineRegex.exec(data)) {
|
|
128
|
+
line = data.substr(pos, match.index - pos);
|
|
129
|
+
pos += line.length + match[0].length;
|
|
130
|
+
} else {
|
|
131
|
+
this._remainder = pos < data.length ? data.substr(pos) : "";
|
|
132
|
+
if (this._remainder.length > this._maxCommandLength) {
|
|
133
|
+
this._remainder = "";
|
|
134
|
+
return done(new Error("Command line too long"));
|
|
135
|
+
}
|
|
136
|
+
return done();
|
|
137
|
+
}
|
|
138
|
+
this.oncommand(Buffer.from(line, "binary"), readLine);
|
|
139
|
+
};
|
|
140
|
+
readLine();
|
|
141
|
+
} else {
|
|
142
|
+
this._feedDataStream(chunk, done);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Processes a chunk in data mode. Escape dots are removed and final dot ends the data mode.
|
|
147
|
+
*/
|
|
148
|
+
_feedDataStream(chunk, done) {
|
|
149
|
+
let i;
|
|
150
|
+
let endseq = Buffer.from("\r\n.\r\n");
|
|
151
|
+
let len;
|
|
152
|
+
let handled;
|
|
153
|
+
let buf;
|
|
154
|
+
if (this._lastBytes && this._lastBytes.length) {
|
|
155
|
+
chunk = Buffer.concat([this._lastBytes, chunk], this._lastBytes.length + chunk.length);
|
|
156
|
+
this._lastBytes = false;
|
|
157
|
+
}
|
|
158
|
+
len = chunk.length;
|
|
159
|
+
if (!this.dataBytes && len >= 3 && Buffer.compare(chunk.slice(0, 3), Buffer.from(".\r\n")) === 0) {
|
|
160
|
+
this._endDataMode(false, chunk.slice(3), done);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!this.dataBytes && len >= 2 && chunk[0] === 46 && chunk[1] === 46) {
|
|
164
|
+
chunk = chunk.slice(1);
|
|
165
|
+
len--;
|
|
166
|
+
}
|
|
167
|
+
for (i = 2; i < len - 2; i++) {
|
|
168
|
+
if (chunk[i] === 46 && chunk[i - 1] === 10) {
|
|
169
|
+
if (Buffer.compare(chunk.slice(i - 2, i + 3), endseq) === 0) {
|
|
170
|
+
if (i > 2) {
|
|
171
|
+
buf = chunk.slice(0, i);
|
|
172
|
+
this.dataBytes += buf.length;
|
|
173
|
+
this._endDataMode(buf, chunk.slice(i + 3), done);
|
|
174
|
+
} else {
|
|
175
|
+
this._endDataMode(false, chunk.slice(i + 3), done);
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (chunk[i + 1] === 46) {
|
|
180
|
+
buf = chunk.slice(0, i);
|
|
181
|
+
this._lastBytes = false;
|
|
182
|
+
this.dataBytes += buf.length;
|
|
183
|
+
if (this._dataStream.writable) {
|
|
184
|
+
this._dataStream.write(buf);
|
|
185
|
+
}
|
|
186
|
+
return setImmediate(() => this._feedDataStream(chunk.slice(i + 1), done));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (chunk.length < 4) {
|
|
191
|
+
this._lastBytes = chunk;
|
|
192
|
+
} else {
|
|
193
|
+
this._lastBytes = chunk.slice(chunk.length - 4);
|
|
194
|
+
}
|
|
195
|
+
if (this._lastBytes.length < chunk.length) {
|
|
196
|
+
buf = chunk.slice(0, chunk.length - this._lastBytes.length);
|
|
197
|
+
this.dataBytes += buf.length;
|
|
198
|
+
if (this._dataStream.writable) {
|
|
199
|
+
handled = this._dataStream.write(buf);
|
|
200
|
+
if (!handled) {
|
|
201
|
+
this._dataStream.once("drain", done);
|
|
202
|
+
} else {
|
|
203
|
+
return done();
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
return done();
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
return done();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Flushes remaining bytes
|
|
214
|
+
*/
|
|
215
|
+
_flushData() {
|
|
216
|
+
let line;
|
|
217
|
+
if (this._remainder && !this.isClosed) {
|
|
218
|
+
line = this._remainder;
|
|
219
|
+
this._remainder = "";
|
|
220
|
+
this.oncommand(Buffer.from(line, "binary"));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Ends data mode and returns to command mode. Stream is not resumed before #continue is called
|
|
225
|
+
*/
|
|
226
|
+
_endDataMode(chunk, remainder, callback) {
|
|
227
|
+
if (this._continueCallback === true) {
|
|
228
|
+
this._continueCallback = false;
|
|
229
|
+
this._dataStream.once("end", callback);
|
|
230
|
+
} else {
|
|
231
|
+
this._continueCallback = () => this._write(remainder, "buffer", callback);
|
|
232
|
+
}
|
|
233
|
+
this._dataStream.byteLength = this.dataBytes;
|
|
234
|
+
this._dataStream.sizeExceeded = this.dataBytes > this._maxBytes;
|
|
235
|
+
if (chunk && chunk.length && this._dataStream.writable) {
|
|
236
|
+
this._dataStream.end(chunk);
|
|
237
|
+
} else {
|
|
238
|
+
this._dataStream.end();
|
|
239
|
+
}
|
|
240
|
+
this._dataMode = false;
|
|
241
|
+
this._remainder = "";
|
|
242
|
+
this._dataStream = null;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
module.exports.SMTPStream = SMTPStream;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// node_modules/ipv6-normalize/index.js
|
|
250
|
+
var require_ipv6_normalize = __commonJS({
|
|
251
|
+
"node_modules/ipv6-normalize/index.js"(exports, module) {
|
|
252
|
+
"use strict";
|
|
253
|
+
module.exports = function(address) {
|
|
254
|
+
var _address = address.toLowerCase();
|
|
255
|
+
var segments = _address.split(":");
|
|
256
|
+
var length = segments.length;
|
|
257
|
+
var total = 8;
|
|
258
|
+
if (segments[0] === "" && segments[1] === "" && segments[2] === "") {
|
|
259
|
+
segments.shift();
|
|
260
|
+
segments.shift();
|
|
261
|
+
} else if (segments[0] === "" && segments[1] === "") {
|
|
262
|
+
segments.shift();
|
|
263
|
+
} else if (segments[length - 1] === "" && segments[length - 2] === "") {
|
|
264
|
+
segments.pop();
|
|
265
|
+
}
|
|
266
|
+
length = segments.length;
|
|
267
|
+
if (segments[length - 1].indexOf(".") !== -1) {
|
|
268
|
+
total = 7;
|
|
269
|
+
}
|
|
270
|
+
var pos;
|
|
271
|
+
for (pos = 0; pos < length; pos++) {
|
|
272
|
+
if (segments[pos] === "") {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (pos < total) {
|
|
277
|
+
segments.splice(pos, 1, "0000");
|
|
278
|
+
while (segments.length < total) {
|
|
279
|
+
segments.splice(pos, 0, "0000");
|
|
280
|
+
}
|
|
281
|
+
length = segments.length;
|
|
282
|
+
}
|
|
283
|
+
var _segments;
|
|
284
|
+
for (var i = 0; i < total; i++) {
|
|
285
|
+
_segments = segments[i].split("");
|
|
286
|
+
for (var j = 0; j < 3; j++) {
|
|
287
|
+
if (_segments[0] === "0" && _segments.length > 1) {
|
|
288
|
+
_segments.splice(0, 1);
|
|
289
|
+
} else {
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
segments[i] = _segments.join("");
|
|
294
|
+
}
|
|
295
|
+
var best = -1;
|
|
296
|
+
var _best = 0;
|
|
297
|
+
var _current = 0;
|
|
298
|
+
var current = -1;
|
|
299
|
+
var inzeroes = false;
|
|
300
|
+
for (i = 0; i < total; i++) {
|
|
301
|
+
if (inzeroes) {
|
|
302
|
+
if (segments[i] === "0") {
|
|
303
|
+
_current += 1;
|
|
304
|
+
} else {
|
|
305
|
+
inzeroes = false;
|
|
306
|
+
if (_current > _best) {
|
|
307
|
+
best = current;
|
|
308
|
+
_best = _current;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
if (segments[i] === "0") {
|
|
313
|
+
inzeroes = true;
|
|
314
|
+
current = i;
|
|
315
|
+
_current = 1;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (_current > _best) {
|
|
320
|
+
best = current;
|
|
321
|
+
_best = _current;
|
|
322
|
+
}
|
|
323
|
+
if (_best > 1) {
|
|
324
|
+
segments.splice(best, _best, "");
|
|
325
|
+
}
|
|
326
|
+
length = segments.length;
|
|
327
|
+
var result = "";
|
|
328
|
+
if (segments[0] === "") {
|
|
329
|
+
result = ":";
|
|
330
|
+
}
|
|
331
|
+
for (i = 0; i < length; i++) {
|
|
332
|
+
result += segments[i];
|
|
333
|
+
if (i === length - 1) {
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
result += ":";
|
|
337
|
+
}
|
|
338
|
+
if (segments[length - 1] === "") {
|
|
339
|
+
result += ":";
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// node_modules/smtp-server/lib/sasl.js
|
|
347
|
+
var require_sasl = __commonJS({
|
|
348
|
+
"node_modules/smtp-server/lib/sasl.js"(exports, module) {
|
|
349
|
+
"use strict";
|
|
350
|
+
var util = __require("util");
|
|
351
|
+
var crypto = __require("crypto");
|
|
352
|
+
var SASL = module.exports = {
|
|
353
|
+
SASL_PLAIN(args, callback) {
|
|
354
|
+
if (args.length > 1) {
|
|
355
|
+
this.send(501, "Error: syntax: AUTH PLAIN token");
|
|
356
|
+
return callback();
|
|
357
|
+
}
|
|
358
|
+
if (!args.length) {
|
|
359
|
+
this._nextHandler = SASL.PLAIN_token.bind(this, true);
|
|
360
|
+
this.send(334);
|
|
361
|
+
return callback();
|
|
362
|
+
}
|
|
363
|
+
SASL.PLAIN_token.call(this, false, args[0], callback);
|
|
364
|
+
},
|
|
365
|
+
SASL_LOGIN(args, callback) {
|
|
366
|
+
if (args.length > 1) {
|
|
367
|
+
this.send(501, "Error: syntax: AUTH LOGIN");
|
|
368
|
+
return callback();
|
|
369
|
+
}
|
|
370
|
+
if (!args.length) {
|
|
371
|
+
this._nextHandler = SASL.LOGIN_username.bind(this, true);
|
|
372
|
+
this.send(334, "VXNlcm5hbWU6");
|
|
373
|
+
return callback();
|
|
374
|
+
}
|
|
375
|
+
SASL.LOGIN_username.call(this, false, args[0], callback);
|
|
376
|
+
},
|
|
377
|
+
SASL_XOAUTH2(args, callback) {
|
|
378
|
+
if (args.length > 1) {
|
|
379
|
+
this.send(501, "Error: syntax: AUTH XOAUTH2 token");
|
|
380
|
+
return callback();
|
|
381
|
+
}
|
|
382
|
+
if (!args.length) {
|
|
383
|
+
this._nextHandler = SASL.XOAUTH2_token.bind(this, true);
|
|
384
|
+
this.send(334);
|
|
385
|
+
return callback();
|
|
386
|
+
}
|
|
387
|
+
SASL.XOAUTH2_token.call(this, false, args[0], callback);
|
|
388
|
+
},
|
|
389
|
+
"SASL_CRAM-MD5"(args, callback) {
|
|
390
|
+
if (args.length) {
|
|
391
|
+
this.send(501, "Error: syntax: AUTH CRAM-MD5");
|
|
392
|
+
return callback();
|
|
393
|
+
}
|
|
394
|
+
let challenge = util.format(
|
|
395
|
+
"<%s%s@%s>",
|
|
396
|
+
String(Math.random()).replace(/^[0.]+/, "").substr(0, 8),
|
|
397
|
+
// random numbers
|
|
398
|
+
Math.floor(Date.now() / 1e3),
|
|
399
|
+
// timestamp
|
|
400
|
+
this.name
|
|
401
|
+
// hostname
|
|
402
|
+
);
|
|
403
|
+
this._nextHandler = SASL["CRAM-MD5_token"].bind(this, true, challenge);
|
|
404
|
+
this.send(334, Buffer.from(challenge).toString("base64"));
|
|
405
|
+
return callback();
|
|
406
|
+
},
|
|
407
|
+
PLAIN_token(canAbort, token, callback) {
|
|
408
|
+
token = (token || "").toString().trim();
|
|
409
|
+
if (canAbort && token === "*") {
|
|
410
|
+
this.send(501, "Authentication aborted");
|
|
411
|
+
return callback();
|
|
412
|
+
}
|
|
413
|
+
let data = Buffer.from(token, "base64").toString().split("\0");
|
|
414
|
+
if (data.length !== 3) {
|
|
415
|
+
this.send(500, "Error: invalid userdata");
|
|
416
|
+
return callback();
|
|
417
|
+
}
|
|
418
|
+
let username = data[1] || data[0] || "";
|
|
419
|
+
let password = data[2] || "";
|
|
420
|
+
this._server.onAuth(
|
|
421
|
+
{
|
|
422
|
+
method: "PLAIN",
|
|
423
|
+
username,
|
|
424
|
+
password
|
|
425
|
+
},
|
|
426
|
+
this.session,
|
|
427
|
+
(err, response) => {
|
|
428
|
+
if (err) {
|
|
429
|
+
this._server.logger.info(
|
|
430
|
+
{
|
|
431
|
+
err,
|
|
432
|
+
tnx: "autherror",
|
|
433
|
+
cid: this.id,
|
|
434
|
+
method: "PLAIN",
|
|
435
|
+
user: username
|
|
436
|
+
},
|
|
437
|
+
"Authentication error for %s using %s. %s",
|
|
438
|
+
username,
|
|
439
|
+
"PLAIN",
|
|
440
|
+
err.message
|
|
441
|
+
);
|
|
442
|
+
this.send(err.responseCode || 535, err.message);
|
|
443
|
+
return callback();
|
|
444
|
+
}
|
|
445
|
+
if (!response.user) {
|
|
446
|
+
this._server.logger.info(
|
|
447
|
+
{
|
|
448
|
+
tnx: "authfail",
|
|
449
|
+
cid: this.id,
|
|
450
|
+
method: "PLAIN",
|
|
451
|
+
user: username
|
|
452
|
+
},
|
|
453
|
+
"Authentication failed for %s using %s",
|
|
454
|
+
username,
|
|
455
|
+
"PLAIN"
|
|
456
|
+
);
|
|
457
|
+
this.send(response.responseCode || 535, response.message || "Error: Authentication credentials invalid");
|
|
458
|
+
return callback();
|
|
459
|
+
}
|
|
460
|
+
this._server.logger.info(
|
|
461
|
+
{
|
|
462
|
+
tnx: "auth",
|
|
463
|
+
cid: this.id,
|
|
464
|
+
method: "PLAIN",
|
|
465
|
+
user: username
|
|
466
|
+
},
|
|
467
|
+
"%s authenticated using %s",
|
|
468
|
+
username,
|
|
469
|
+
"PLAIN"
|
|
470
|
+
);
|
|
471
|
+
this.session.user = response.user;
|
|
472
|
+
this.session.transmissionType = this._transmissionType();
|
|
473
|
+
this.send(235, "Authentication successful");
|
|
474
|
+
callback();
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
},
|
|
478
|
+
LOGIN_username(canAbort, username, callback) {
|
|
479
|
+
username = (username || "").toString().trim();
|
|
480
|
+
if (canAbort && username === "*") {
|
|
481
|
+
this.send(501, "Authentication aborted");
|
|
482
|
+
return callback();
|
|
483
|
+
}
|
|
484
|
+
username = Buffer.from(username, "base64").toString();
|
|
485
|
+
if (!username) {
|
|
486
|
+
this.send(500, "Error: missing username");
|
|
487
|
+
return callback();
|
|
488
|
+
}
|
|
489
|
+
this._nextHandler = SASL.LOGIN_password.bind(this, username);
|
|
490
|
+
this.send(334, "UGFzc3dvcmQ6");
|
|
491
|
+
return callback();
|
|
492
|
+
},
|
|
493
|
+
LOGIN_password(username, password, callback) {
|
|
494
|
+
password = (password || "").toString().trim();
|
|
495
|
+
if (password === "*") {
|
|
496
|
+
this.send(501, "Authentication aborted");
|
|
497
|
+
return callback();
|
|
498
|
+
}
|
|
499
|
+
password = Buffer.from(password, "base64").toString();
|
|
500
|
+
this._server.onAuth(
|
|
501
|
+
{
|
|
502
|
+
method: "LOGIN",
|
|
503
|
+
username,
|
|
504
|
+
password
|
|
505
|
+
},
|
|
506
|
+
this.session,
|
|
507
|
+
(err, response) => {
|
|
508
|
+
if (err) {
|
|
509
|
+
this._server.logger.info(
|
|
510
|
+
{
|
|
511
|
+
err,
|
|
512
|
+
tnx: "autherror",
|
|
513
|
+
cid: this.id,
|
|
514
|
+
method: "LOGIN",
|
|
515
|
+
user: username
|
|
516
|
+
},
|
|
517
|
+
"Authentication error for %s using %s. %s",
|
|
518
|
+
username,
|
|
519
|
+
"LOGIN",
|
|
520
|
+
err.message
|
|
521
|
+
);
|
|
522
|
+
this.send(err.responseCode || 535, err.message);
|
|
523
|
+
return callback();
|
|
524
|
+
}
|
|
525
|
+
if (!response.user) {
|
|
526
|
+
this._server.logger.info(
|
|
527
|
+
{
|
|
528
|
+
tnx: "authfail",
|
|
529
|
+
cid: this.id,
|
|
530
|
+
method: "LOGIN",
|
|
531
|
+
user: username
|
|
532
|
+
},
|
|
533
|
+
"Authentication failed for %s using %s",
|
|
534
|
+
username,
|
|
535
|
+
"LOGIN"
|
|
536
|
+
);
|
|
537
|
+
this.send(response.responseCode || 535, response.message || "Error: Authentication credentials invalid");
|
|
538
|
+
return callback();
|
|
539
|
+
}
|
|
540
|
+
this._server.logger.info(
|
|
541
|
+
{
|
|
542
|
+
tnx: "auth",
|
|
543
|
+
cid: this.id,
|
|
544
|
+
method: "PLAIN",
|
|
545
|
+
user: username
|
|
546
|
+
},
|
|
547
|
+
"%s authenticated using %s",
|
|
548
|
+
username,
|
|
549
|
+
"LOGIN"
|
|
550
|
+
);
|
|
551
|
+
this.session.user = response.user;
|
|
552
|
+
this.session.transmissionType = this._transmissionType();
|
|
553
|
+
this.send(235, "Authentication successful");
|
|
554
|
+
callback();
|
|
555
|
+
}
|
|
556
|
+
);
|
|
557
|
+
},
|
|
558
|
+
XOAUTH2_token(canAbort, token, callback) {
|
|
559
|
+
token = (token || "").toString().trim();
|
|
560
|
+
if (canAbort && token === "*") {
|
|
561
|
+
this.send(501, "Authentication aborted");
|
|
562
|
+
return callback();
|
|
563
|
+
}
|
|
564
|
+
let username;
|
|
565
|
+
let accessToken;
|
|
566
|
+
Buffer.from(token, "base64").toString().split("").forEach((part) => {
|
|
567
|
+
part = part.split("=");
|
|
568
|
+
let key = part.shift().toLowerCase();
|
|
569
|
+
let value = part.join("=").trim();
|
|
570
|
+
if (key === "user") {
|
|
571
|
+
username = value;
|
|
572
|
+
} else if (key === "auth") {
|
|
573
|
+
value = value.split(/\s+/);
|
|
574
|
+
if (value.shift().toLowerCase() === "bearer") {
|
|
575
|
+
accessToken = value.join(" ");
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
if (!username || !accessToken) {
|
|
580
|
+
this.send(500, "Error: invalid userdata");
|
|
581
|
+
return callback();
|
|
582
|
+
}
|
|
583
|
+
this._server.onAuth(
|
|
584
|
+
{
|
|
585
|
+
method: "XOAUTH2",
|
|
586
|
+
username,
|
|
587
|
+
accessToken
|
|
588
|
+
},
|
|
589
|
+
this.session,
|
|
590
|
+
(err, response) => {
|
|
591
|
+
if (err) {
|
|
592
|
+
this._server.logger.info(
|
|
593
|
+
{
|
|
594
|
+
err,
|
|
595
|
+
tnx: "autherror",
|
|
596
|
+
cid: this.id,
|
|
597
|
+
method: "XOAUTH2",
|
|
598
|
+
user: username
|
|
599
|
+
},
|
|
600
|
+
"Authentication error for %s using %s. %s",
|
|
601
|
+
username,
|
|
602
|
+
"XOAUTH2",
|
|
603
|
+
err.message
|
|
604
|
+
);
|
|
605
|
+
this.send(err.responseCode || 535, err.message);
|
|
606
|
+
return callback();
|
|
607
|
+
}
|
|
608
|
+
if (!response.user) {
|
|
609
|
+
this._server.logger.info(
|
|
610
|
+
{
|
|
611
|
+
tnx: "authfail",
|
|
612
|
+
cid: this.id,
|
|
613
|
+
method: "XOAUTH2",
|
|
614
|
+
user: username
|
|
615
|
+
},
|
|
616
|
+
"Authentication failed for %s using %s",
|
|
617
|
+
username,
|
|
618
|
+
"XOAUTH2"
|
|
619
|
+
);
|
|
620
|
+
this._nextHandler = SASL.XOAUTH2_error.bind(this);
|
|
621
|
+
this.send(response.responseCode || 334, Buffer.from(JSON.stringify(response.data || {})).toString("base64"));
|
|
622
|
+
return callback();
|
|
623
|
+
}
|
|
624
|
+
this._server.logger.info(
|
|
625
|
+
{
|
|
626
|
+
tnx: "auth",
|
|
627
|
+
cid: this.id,
|
|
628
|
+
method: "XOAUTH2",
|
|
629
|
+
user: username
|
|
630
|
+
},
|
|
631
|
+
"%s authenticated using %s",
|
|
632
|
+
username,
|
|
633
|
+
"XOAUTH2"
|
|
634
|
+
);
|
|
635
|
+
this.session.user = response.user;
|
|
636
|
+
this.session.transmissionType = this._transmissionType();
|
|
637
|
+
this.send(235, "Authentication successful");
|
|
638
|
+
callback();
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
},
|
|
642
|
+
XOAUTH2_error(data, callback) {
|
|
643
|
+
this.send(535, "Error: Username and Password not accepted");
|
|
644
|
+
return callback();
|
|
645
|
+
},
|
|
646
|
+
"CRAM-MD5_token"(canAbort, challenge, token, callback) {
|
|
647
|
+
token = (token || "").toString().trim();
|
|
648
|
+
if (canAbort && token === "*") {
|
|
649
|
+
this.send(501, "Authentication aborted");
|
|
650
|
+
return callback();
|
|
651
|
+
}
|
|
652
|
+
let tokenParts = Buffer.from(token, "base64").toString().split(" ");
|
|
653
|
+
let username = tokenParts.shift();
|
|
654
|
+
let challengeResponse = (tokenParts.shift() || "").toLowerCase();
|
|
655
|
+
this._server.onAuth(
|
|
656
|
+
{
|
|
657
|
+
method: "CRAM-MD5",
|
|
658
|
+
username,
|
|
659
|
+
challenge,
|
|
660
|
+
challengeResponse,
|
|
661
|
+
validatePassword(password) {
|
|
662
|
+
let hmac = crypto.createHmac("md5", password);
|
|
663
|
+
return hmac.update(challenge).digest("hex").toLowerCase() === challengeResponse;
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
this.session,
|
|
667
|
+
(err, response) => {
|
|
668
|
+
if (err) {
|
|
669
|
+
this._server.logger.info(
|
|
670
|
+
{
|
|
671
|
+
err,
|
|
672
|
+
tnx: "autherror",
|
|
673
|
+
cid: this.id,
|
|
674
|
+
method: "CRAM-MD5",
|
|
675
|
+
user: username
|
|
676
|
+
},
|
|
677
|
+
"Authentication error for %s using %s. %s",
|
|
678
|
+
username,
|
|
679
|
+
"CRAM-MD5",
|
|
680
|
+
err.message
|
|
681
|
+
);
|
|
682
|
+
this.send(err.responseCode || 535, err.message);
|
|
683
|
+
return callback();
|
|
684
|
+
}
|
|
685
|
+
if (!response.user) {
|
|
686
|
+
this._server.logger.info(
|
|
687
|
+
{
|
|
688
|
+
tnx: "authfail",
|
|
689
|
+
cid: this.id,
|
|
690
|
+
method: "CRAM-MD5",
|
|
691
|
+
user: username
|
|
692
|
+
},
|
|
693
|
+
"Authentication failed for %s using %s",
|
|
694
|
+
username,
|
|
695
|
+
"CRAM-MD5"
|
|
696
|
+
);
|
|
697
|
+
this.send(response.responseCode || 535, response.message || "Error: Authentication credentials invalid");
|
|
698
|
+
return callback();
|
|
699
|
+
}
|
|
700
|
+
this._server.logger.info(
|
|
701
|
+
{
|
|
702
|
+
tnx: "auth",
|
|
703
|
+
cid: this.id,
|
|
704
|
+
method: "CRAM-MD5",
|
|
705
|
+
user: username
|
|
706
|
+
},
|
|
707
|
+
"%s authenticated using %s",
|
|
708
|
+
username,
|
|
709
|
+
"CRAM-MD5"
|
|
710
|
+
);
|
|
711
|
+
this.session.user = response.user;
|
|
712
|
+
this.session.transmissionType = this._transmissionType();
|
|
713
|
+
this.send(235, "Authentication successful");
|
|
714
|
+
callback();
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
},
|
|
718
|
+
// this is not a real auth but a username validation initiated by SMTP proxy
|
|
719
|
+
SASL_XCLIENT(args, callback) {
|
|
720
|
+
const username = (args && args[0] || "").toString().trim();
|
|
721
|
+
this._server.onAuth(
|
|
722
|
+
{
|
|
723
|
+
method: "XCLIENT",
|
|
724
|
+
username,
|
|
725
|
+
password: null
|
|
726
|
+
},
|
|
727
|
+
this.session,
|
|
728
|
+
(err, response) => {
|
|
729
|
+
if (err) {
|
|
730
|
+
this._server.logger.info(
|
|
731
|
+
{
|
|
732
|
+
err,
|
|
733
|
+
tnx: "autherror",
|
|
734
|
+
cid: this.id,
|
|
735
|
+
method: "XCLIENT",
|
|
736
|
+
user: username
|
|
737
|
+
},
|
|
738
|
+
"Authentication error for %s using %s. %s",
|
|
739
|
+
username,
|
|
740
|
+
"XCLIENT",
|
|
741
|
+
err.message
|
|
742
|
+
);
|
|
743
|
+
return callback(err);
|
|
744
|
+
}
|
|
745
|
+
if (!response.user) {
|
|
746
|
+
this._server.logger.info(
|
|
747
|
+
{
|
|
748
|
+
tnx: "authfail",
|
|
749
|
+
cid: this.id,
|
|
750
|
+
method: "XCLIENT",
|
|
751
|
+
user: username
|
|
752
|
+
},
|
|
753
|
+
"Authentication failed for %s using %s",
|
|
754
|
+
username,
|
|
755
|
+
"XCLIENT"
|
|
756
|
+
);
|
|
757
|
+
return callback(new Error("Authentication credentials invalid"));
|
|
758
|
+
}
|
|
759
|
+
this._server.logger.info(
|
|
760
|
+
{
|
|
761
|
+
tnx: "auth",
|
|
762
|
+
cid: this.id,
|
|
763
|
+
method: "XCLIENT",
|
|
764
|
+
user: username
|
|
765
|
+
},
|
|
766
|
+
"%s authenticated using %s",
|
|
767
|
+
username,
|
|
768
|
+
"XCLIENT"
|
|
769
|
+
);
|
|
770
|
+
this.session.user = response.user;
|
|
771
|
+
this.session.transmissionType = this._transmissionType();
|
|
772
|
+
callback();
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
// node_modules/punycode.js/punycode.es6.js
|
|
781
|
+
var punycode_es6_exports = {};
|
|
782
|
+
__export(punycode_es6_exports, {
|
|
783
|
+
decode: () => decode,
|
|
784
|
+
default: () => punycode_es6_default,
|
|
785
|
+
encode: () => encode,
|
|
786
|
+
toASCII: () => toASCII,
|
|
787
|
+
toUnicode: () => toUnicode,
|
|
788
|
+
ucs2decode: () => ucs2decode,
|
|
789
|
+
ucs2encode: () => ucs2encode
|
|
790
|
+
});
|
|
791
|
+
function error(type) {
|
|
792
|
+
throw new RangeError(errors[type]);
|
|
793
|
+
}
|
|
794
|
+
function map(array, callback) {
|
|
795
|
+
const result = [];
|
|
796
|
+
let length = array.length;
|
|
797
|
+
while (length--) {
|
|
798
|
+
result[length] = callback(array[length]);
|
|
799
|
+
}
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
function mapDomain(domain, callback) {
|
|
803
|
+
const parts = domain.split("@");
|
|
804
|
+
let result = "";
|
|
805
|
+
if (parts.length > 1) {
|
|
806
|
+
result = parts[0] + "@";
|
|
807
|
+
domain = parts[1];
|
|
808
|
+
}
|
|
809
|
+
domain = domain.replace(regexSeparators, ".");
|
|
810
|
+
const labels = domain.split(".");
|
|
811
|
+
const encoded = map(labels, callback).join(".");
|
|
812
|
+
return result + encoded;
|
|
813
|
+
}
|
|
814
|
+
function ucs2decode(string) {
|
|
815
|
+
const output = [];
|
|
816
|
+
let counter = 0;
|
|
817
|
+
const length = string.length;
|
|
818
|
+
while (counter < length) {
|
|
819
|
+
const value = string.charCodeAt(counter++);
|
|
820
|
+
if (value >= 55296 && value <= 56319 && counter < length) {
|
|
821
|
+
const extra = string.charCodeAt(counter++);
|
|
822
|
+
if ((extra & 64512) == 56320) {
|
|
823
|
+
output.push(((value & 1023) << 10) + (extra & 1023) + 65536);
|
|
824
|
+
} else {
|
|
825
|
+
output.push(value);
|
|
826
|
+
counter--;
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
output.push(value);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return output;
|
|
833
|
+
}
|
|
834
|
+
var maxInt, base, tMin, tMax, skew, damp, initialBias, initialN, delimiter, regexPunycode, regexNonASCII, regexSeparators, errors, baseMinusTMin, floor, stringFromCharCode, ucs2encode, basicToDigit, digitToBasic, adapt, decode, encode, toUnicode, toASCII, punycode, punycode_es6_default;
|
|
835
|
+
var init_punycode_es6 = __esm({
|
|
836
|
+
"node_modules/punycode.js/punycode.es6.js"() {
|
|
837
|
+
"use strict";
|
|
838
|
+
maxInt = 2147483647;
|
|
839
|
+
base = 36;
|
|
840
|
+
tMin = 1;
|
|
841
|
+
tMax = 26;
|
|
842
|
+
skew = 38;
|
|
843
|
+
damp = 700;
|
|
844
|
+
initialBias = 72;
|
|
845
|
+
initialN = 128;
|
|
846
|
+
delimiter = "-";
|
|
847
|
+
regexPunycode = /^xn--/;
|
|
848
|
+
regexNonASCII = /[^\0-\x7F]/;
|
|
849
|
+
regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g;
|
|
850
|
+
errors = {
|
|
851
|
+
"overflow": "Overflow: input needs wider integers to process",
|
|
852
|
+
"not-basic": "Illegal input >= 0x80 (not a basic code point)",
|
|
853
|
+
"invalid-input": "Invalid input"
|
|
854
|
+
};
|
|
855
|
+
baseMinusTMin = base - tMin;
|
|
856
|
+
floor = Math.floor;
|
|
857
|
+
stringFromCharCode = String.fromCharCode;
|
|
858
|
+
ucs2encode = (codePoints) => String.fromCodePoint(...codePoints);
|
|
859
|
+
basicToDigit = function(codePoint) {
|
|
860
|
+
if (codePoint >= 48 && codePoint < 58) {
|
|
861
|
+
return 26 + (codePoint - 48);
|
|
862
|
+
}
|
|
863
|
+
if (codePoint >= 65 && codePoint < 91) {
|
|
864
|
+
return codePoint - 65;
|
|
865
|
+
}
|
|
866
|
+
if (codePoint >= 97 && codePoint < 123) {
|
|
867
|
+
return codePoint - 97;
|
|
868
|
+
}
|
|
869
|
+
return base;
|
|
870
|
+
};
|
|
871
|
+
digitToBasic = function(digit, flag) {
|
|
872
|
+
return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
|
|
873
|
+
};
|
|
874
|
+
adapt = function(delta, numPoints, firstTime) {
|
|
875
|
+
let k = 0;
|
|
876
|
+
delta = firstTime ? floor(delta / damp) : delta >> 1;
|
|
877
|
+
delta += floor(delta / numPoints);
|
|
878
|
+
for (; delta > baseMinusTMin * tMax >> 1; k += base) {
|
|
879
|
+
delta = floor(delta / baseMinusTMin);
|
|
880
|
+
}
|
|
881
|
+
return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
|
|
882
|
+
};
|
|
883
|
+
decode = function(input) {
|
|
884
|
+
const output = [];
|
|
885
|
+
const inputLength = input.length;
|
|
886
|
+
let i = 0;
|
|
887
|
+
let n = initialN;
|
|
888
|
+
let bias = initialBias;
|
|
889
|
+
let basic = input.lastIndexOf(delimiter);
|
|
890
|
+
if (basic < 0) {
|
|
891
|
+
basic = 0;
|
|
892
|
+
}
|
|
893
|
+
for (let j = 0; j < basic; ++j) {
|
|
894
|
+
if (input.charCodeAt(j) >= 128) {
|
|
895
|
+
error("not-basic");
|
|
896
|
+
}
|
|
897
|
+
output.push(input.charCodeAt(j));
|
|
898
|
+
}
|
|
899
|
+
for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; ) {
|
|
900
|
+
const oldi = i;
|
|
901
|
+
for (let w = 1, k = base; ; k += base) {
|
|
902
|
+
if (index >= inputLength) {
|
|
903
|
+
error("invalid-input");
|
|
904
|
+
}
|
|
905
|
+
const digit = basicToDigit(input.charCodeAt(index++));
|
|
906
|
+
if (digit >= base) {
|
|
907
|
+
error("invalid-input");
|
|
908
|
+
}
|
|
909
|
+
if (digit > floor((maxInt - i) / w)) {
|
|
910
|
+
error("overflow");
|
|
911
|
+
}
|
|
912
|
+
i += digit * w;
|
|
913
|
+
const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
|
|
914
|
+
if (digit < t) {
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
const baseMinusT = base - t;
|
|
918
|
+
if (w > floor(maxInt / baseMinusT)) {
|
|
919
|
+
error("overflow");
|
|
920
|
+
}
|
|
921
|
+
w *= baseMinusT;
|
|
922
|
+
}
|
|
923
|
+
const out = output.length + 1;
|
|
924
|
+
bias = adapt(i - oldi, out, oldi == 0);
|
|
925
|
+
if (floor(i / out) > maxInt - n) {
|
|
926
|
+
error("overflow");
|
|
927
|
+
}
|
|
928
|
+
n += floor(i / out);
|
|
929
|
+
i %= out;
|
|
930
|
+
output.splice(i++, 0, n);
|
|
931
|
+
}
|
|
932
|
+
return String.fromCodePoint(...output);
|
|
933
|
+
};
|
|
934
|
+
encode = function(input) {
|
|
935
|
+
const output = [];
|
|
936
|
+
input = ucs2decode(input);
|
|
937
|
+
const inputLength = input.length;
|
|
938
|
+
let n = initialN;
|
|
939
|
+
let delta = 0;
|
|
940
|
+
let bias = initialBias;
|
|
941
|
+
for (const currentValue of input) {
|
|
942
|
+
if (currentValue < 128) {
|
|
943
|
+
output.push(stringFromCharCode(currentValue));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const basicLength = output.length;
|
|
947
|
+
let handledCPCount = basicLength;
|
|
948
|
+
if (basicLength) {
|
|
949
|
+
output.push(delimiter);
|
|
950
|
+
}
|
|
951
|
+
while (handledCPCount < inputLength) {
|
|
952
|
+
let m = maxInt;
|
|
953
|
+
for (const currentValue of input) {
|
|
954
|
+
if (currentValue >= n && currentValue < m) {
|
|
955
|
+
m = currentValue;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const handledCPCountPlusOne = handledCPCount + 1;
|
|
959
|
+
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
|
|
960
|
+
error("overflow");
|
|
961
|
+
}
|
|
962
|
+
delta += (m - n) * handledCPCountPlusOne;
|
|
963
|
+
n = m;
|
|
964
|
+
for (const currentValue of input) {
|
|
965
|
+
if (currentValue < n && ++delta > maxInt) {
|
|
966
|
+
error("overflow");
|
|
967
|
+
}
|
|
968
|
+
if (currentValue === n) {
|
|
969
|
+
let q = delta;
|
|
970
|
+
for (let k = base; ; k += base) {
|
|
971
|
+
const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
|
|
972
|
+
if (q < t) {
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
const qMinusT = q - t;
|
|
976
|
+
const baseMinusT = base - t;
|
|
977
|
+
output.push(
|
|
978
|
+
stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
|
|
979
|
+
);
|
|
980
|
+
q = floor(qMinusT / baseMinusT);
|
|
981
|
+
}
|
|
982
|
+
output.push(stringFromCharCode(digitToBasic(q, 0)));
|
|
983
|
+
bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength);
|
|
984
|
+
delta = 0;
|
|
985
|
+
++handledCPCount;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
++delta;
|
|
989
|
+
++n;
|
|
990
|
+
}
|
|
991
|
+
return output.join("");
|
|
992
|
+
};
|
|
993
|
+
toUnicode = function(input) {
|
|
994
|
+
return mapDomain(input, function(string) {
|
|
995
|
+
return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string;
|
|
996
|
+
});
|
|
997
|
+
};
|
|
998
|
+
toASCII = function(input) {
|
|
999
|
+
return mapDomain(input, function(string) {
|
|
1000
|
+
return regexNonASCII.test(string) ? "xn--" + encode(string) : string;
|
|
1001
|
+
});
|
|
1002
|
+
};
|
|
1003
|
+
punycode = {
|
|
1004
|
+
/**
|
|
1005
|
+
* A string representing the current Punycode.js version number.
|
|
1006
|
+
* @memberOf punycode
|
|
1007
|
+
* @type String
|
|
1008
|
+
*/
|
|
1009
|
+
"version": "2.3.1",
|
|
1010
|
+
/**
|
|
1011
|
+
* An object of methods to convert from JavaScript's internal character
|
|
1012
|
+
* representation (UCS-2) to Unicode code points, and back.
|
|
1013
|
+
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
|
1014
|
+
* @memberOf punycode
|
|
1015
|
+
* @type Object
|
|
1016
|
+
*/
|
|
1017
|
+
"ucs2": {
|
|
1018
|
+
"decode": ucs2decode,
|
|
1019
|
+
"encode": ucs2encode
|
|
1020
|
+
},
|
|
1021
|
+
"decode": decode,
|
|
1022
|
+
"encode": encode,
|
|
1023
|
+
"toASCII": toASCII,
|
|
1024
|
+
"toUnicode": toUnicode
|
|
1025
|
+
};
|
|
1026
|
+
punycode_es6_default = punycode;
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
// node_modules/smtp-server/lib/smtp-connection.js
|
|
1031
|
+
var require_smtp_connection = __commonJS({
|
|
1032
|
+
"node_modules/smtp-server/lib/smtp-connection.js"(exports, module) {
|
|
1033
|
+
"use strict";
|
|
1034
|
+
var SMTPStream = require_smtp_stream().SMTPStream;
|
|
1035
|
+
var dns = __require("dns");
|
|
1036
|
+
var tls = __require("tls");
|
|
1037
|
+
var net = __require("net");
|
|
1038
|
+
var ipv6normalize = require_ipv6_normalize();
|
|
1039
|
+
var sasl = require_sasl();
|
|
1040
|
+
var crypto = __require("crypto");
|
|
1041
|
+
var os = __require("os");
|
|
1042
|
+
var punycode2 = (init_punycode_es6(), __toCommonJS(punycode_es6_exports));
|
|
1043
|
+
var EventEmitter = __require("events");
|
|
1044
|
+
var SOCKET_TIMEOUT = 60 * 1e3;
|
|
1045
|
+
var ENHANCED_STATUS_CODES = {
|
|
1046
|
+
// Success codes (2xx)
|
|
1047
|
+
200: "2.0.0",
|
|
1048
|
+
// System status, or system help reply
|
|
1049
|
+
211: "2.0.0",
|
|
1050
|
+
// System status, or system help reply
|
|
1051
|
+
214: "2.0.0",
|
|
1052
|
+
// Help message
|
|
1053
|
+
220: "2.0.0",
|
|
1054
|
+
// Service ready
|
|
1055
|
+
221: "2.0.0",
|
|
1056
|
+
// Service closing transmission channel
|
|
1057
|
+
235: "2.7.0",
|
|
1058
|
+
// Authentication successful
|
|
1059
|
+
250: "2.0.0",
|
|
1060
|
+
// Requested mail action okay, completed
|
|
1061
|
+
251: "2.1.5",
|
|
1062
|
+
// User not local; will forward
|
|
1063
|
+
252: "2.1.5",
|
|
1064
|
+
// Cannot VRFY user, but will accept message
|
|
1065
|
+
334: "3.7.0",
|
|
1066
|
+
// Server challenge for authentication
|
|
1067
|
+
354: "2.0.0",
|
|
1068
|
+
// Start mail input; end with <CRLF>.<CRLF>
|
|
1069
|
+
// Temporary failure codes (4xx)
|
|
1070
|
+
420: "4.4.2",
|
|
1071
|
+
// Timeout or connection lost (non-standard, used by some servers)
|
|
1072
|
+
421: "4.4.2",
|
|
1073
|
+
// Service not available, closing transmission channel
|
|
1074
|
+
450: "4.2.1",
|
|
1075
|
+
// Requested mail action not taken: mailbox unavailable
|
|
1076
|
+
451: "4.3.0",
|
|
1077
|
+
// Requested action aborted: local error in processing
|
|
1078
|
+
452: "4.2.2",
|
|
1079
|
+
// Requested action not taken: insufficient system storage
|
|
1080
|
+
454: "4.7.0",
|
|
1081
|
+
// Temporary authentication failure
|
|
1082
|
+
// Permanent failure codes (5xx)
|
|
1083
|
+
500: "5.5.2",
|
|
1084
|
+
// Syntax error, command unrecognized
|
|
1085
|
+
501: "5.5.4",
|
|
1086
|
+
// Syntax error in parameters or arguments
|
|
1087
|
+
502: "5.5.1",
|
|
1088
|
+
// Command not implemented
|
|
1089
|
+
503: "5.5.1",
|
|
1090
|
+
// Bad sequence of commands
|
|
1091
|
+
504: "5.5.4",
|
|
1092
|
+
// Command parameter not implemented
|
|
1093
|
+
521: "5.3.2",
|
|
1094
|
+
// Machine does not accept mail
|
|
1095
|
+
523: "5.3.4",
|
|
1096
|
+
// Message size exceeds server limit (non-standard, used by some servers)
|
|
1097
|
+
530: "5.7.0",
|
|
1098
|
+
// Authentication required
|
|
1099
|
+
535: "5.7.8",
|
|
1100
|
+
// Authentication credentials invalid
|
|
1101
|
+
538: "5.7.0",
|
|
1102
|
+
// Must issue a STARTTLS command first (non-standard)
|
|
1103
|
+
550: "5.1.1",
|
|
1104
|
+
// Requested action not taken: mailbox unavailable
|
|
1105
|
+
551: "5.1.6",
|
|
1106
|
+
// User not local; please try forwarding
|
|
1107
|
+
552: "5.2.2",
|
|
1108
|
+
// Requested mail action aborted: exceeded storage allocation
|
|
1109
|
+
553: "5.1.3",
|
|
1110
|
+
// Requested action not taken: mailbox name not allowed
|
|
1111
|
+
554: "5.6.0",
|
|
1112
|
+
// Transaction failed
|
|
1113
|
+
555: "5.5.4",
|
|
1114
|
+
// MAIL FROM/RCPT TO parameters not recognized or not implemented
|
|
1115
|
+
556: "5.1.10",
|
|
1116
|
+
// RCPT TO syntax error (non-standard)
|
|
1117
|
+
557: "5.7.1",
|
|
1118
|
+
// Delivery not authorized (non-standard, used by some servers)
|
|
1119
|
+
558: "5.2.3"
|
|
1120
|
+
// Message too large for recipient (non-standard, used by some servers)
|
|
1121
|
+
};
|
|
1122
|
+
var SKIPPED_COMMANDS_FOR_ENHANCED_STATUS_CODES = /* @__PURE__ */ new Set(["HELO", "EHLO", "LHLO"]);
|
|
1123
|
+
var CONTEXTUAL_STATUS_CODES = {
|
|
1124
|
+
// Mail transaction specific codes
|
|
1125
|
+
MAIL_FROM_OK: "2.1.0",
|
|
1126
|
+
// Originator address valid
|
|
1127
|
+
RCPT_TO_OK: "2.1.5",
|
|
1128
|
+
// Destination address valid
|
|
1129
|
+
DATA_OK: "2.6.0",
|
|
1130
|
+
// Message accepted for delivery
|
|
1131
|
+
// Authentication specific codes
|
|
1132
|
+
AUTH_SUCCESS: "2.7.0",
|
|
1133
|
+
// Authentication successful
|
|
1134
|
+
AUTH_REQUIRED: "5.7.0",
|
|
1135
|
+
// Authentication required
|
|
1136
|
+
AUTH_INVALID: "5.7.8",
|
|
1137
|
+
// Authentication credentials invalid
|
|
1138
|
+
// Policy specific codes
|
|
1139
|
+
POLICY_VIOLATION: "5.7.1",
|
|
1140
|
+
// Delivery not authorized
|
|
1141
|
+
SPAM_REJECTED: "5.7.1",
|
|
1142
|
+
// Message refused
|
|
1143
|
+
// Mailbox specific codes
|
|
1144
|
+
MAILBOX_FULL: "4.2.2",
|
|
1145
|
+
// Mailbox full
|
|
1146
|
+
MAILBOX_NOT_FOUND: "5.1.1",
|
|
1147
|
+
// Mailbox does not exist
|
|
1148
|
+
MAILBOX_SYNTAX_ERROR: "5.1.3",
|
|
1149
|
+
// Invalid mailbox syntax
|
|
1150
|
+
// System specific codes
|
|
1151
|
+
SYSTEM_ERROR: "4.3.0",
|
|
1152
|
+
// System error
|
|
1153
|
+
SYSTEM_FULL: "4.3.1",
|
|
1154
|
+
// System storage exceeded
|
|
1155
|
+
// Network specific codes
|
|
1156
|
+
NETWORK_ERROR: "4.4.0",
|
|
1157
|
+
// Network routing error
|
|
1158
|
+
CONNECTION_TIMEOUT: "4.4.2"
|
|
1159
|
+
// Connection timeout
|
|
1160
|
+
};
|
|
1161
|
+
var SMTPConnection = class extends EventEmitter {
|
|
1162
|
+
constructor(server, socket, options) {
|
|
1163
|
+
super();
|
|
1164
|
+
options = options || {};
|
|
1165
|
+
this.id = options.id || BigInt("0x" + crypto.randomBytes(10).toString("hex")).toString(32).padStart(16, "0");
|
|
1166
|
+
this.ignore = options.ignore;
|
|
1167
|
+
this._server = server;
|
|
1168
|
+
this._socket = socket;
|
|
1169
|
+
this.session = this.session = {
|
|
1170
|
+
id: this.id
|
|
1171
|
+
};
|
|
1172
|
+
this._transactionCounter = 0;
|
|
1173
|
+
this._ready = false;
|
|
1174
|
+
this._upgrading = false;
|
|
1175
|
+
this._nextHandler = false;
|
|
1176
|
+
this._parser = new SMTPStream({
|
|
1177
|
+
maxCommandLength: this._server.options.maxCommandLength
|
|
1178
|
+
});
|
|
1179
|
+
this._parser.oncommand = (...args) => this._onCommand(...args);
|
|
1180
|
+
this._dataStream = false;
|
|
1181
|
+
this.session.secure = this.secure = !!this._server.options.secure;
|
|
1182
|
+
this.needsUpgrade = !!this._server.options.needsUpgrade;
|
|
1183
|
+
this.tlsOptions = this.secure && !this.needsUpgrade && this._socket.getCipher ? this._socket.getCipher() : false;
|
|
1184
|
+
this.localAddress = (options.localAddress || this._socket.localAddress || "").replace(/^::ffff:/, "");
|
|
1185
|
+
this.localPort = Number(options.localPort || this._socket.localPort) || 0;
|
|
1186
|
+
this.remoteAddress = (options.remoteAddress || this._socket.remoteAddress || "").replace(/^::ffff:/, "");
|
|
1187
|
+
this.remotePort = Number(options.remotePort || this._socket.remotePort) || 0;
|
|
1188
|
+
if (this.localAddress && net.isIPv6(this.localAddress)) {
|
|
1189
|
+
this.localAddress = ipv6normalize(this.localAddress);
|
|
1190
|
+
}
|
|
1191
|
+
if (this.remoteAddress && net.isIPv6(this.remoteAddress)) {
|
|
1192
|
+
this.remoteAddress = ipv6normalize(this.remoteAddress);
|
|
1193
|
+
}
|
|
1194
|
+
this._unauthenticatedCommands = 0;
|
|
1195
|
+
this._maxAllowedUnauthenticatedCommands = this._server.options.maxAllowedUnauthenticatedCommands || 10;
|
|
1196
|
+
this._unrecognizedCommands = 0;
|
|
1197
|
+
this.name = this._server.options.name || os.hostname();
|
|
1198
|
+
this.clientHostname = false;
|
|
1199
|
+
this.openingCommand = false;
|
|
1200
|
+
this.hostNameAppearsAs = false;
|
|
1201
|
+
this._xClient = /* @__PURE__ */ new Map();
|
|
1202
|
+
this._xForward = /* @__PURE__ */ new Map();
|
|
1203
|
+
this._canEmitConnection = true;
|
|
1204
|
+
this._closing = false;
|
|
1205
|
+
this._closed = false;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Initiates the connection. Checks connection limits and reverse resolves client hostname. The client
|
|
1209
|
+
* is not allowed to send anything before init has finished otherwise 'You talk too soon' error is returned
|
|
1210
|
+
*/
|
|
1211
|
+
init() {
|
|
1212
|
+
this._setListeners(() => {
|
|
1213
|
+
if (this._server.options.maxClients && this._server.connections.size > this._server.options.maxClients) {
|
|
1214
|
+
return this.send(421, this.name + " Too many connected clients, try again in a moment", false);
|
|
1215
|
+
}
|
|
1216
|
+
let readyTimer = setTimeout(() => this.connectionReady(), 100);
|
|
1217
|
+
readyTimer.unref();
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
connectionReady(next) {
|
|
1221
|
+
let reverseCb = (err, hostnames) => {
|
|
1222
|
+
if (err) {
|
|
1223
|
+
this._server.logger.error(
|
|
1224
|
+
{
|
|
1225
|
+
tnx: "connection",
|
|
1226
|
+
cid: this.id,
|
|
1227
|
+
host: this.remoteAddress,
|
|
1228
|
+
hostname: this.clientHostname,
|
|
1229
|
+
err
|
|
1230
|
+
},
|
|
1231
|
+
"Reverse resolve for %s: %s",
|
|
1232
|
+
this.remoteAddress,
|
|
1233
|
+
err.message
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
if (this._closing || this._closed) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
this.clientHostname = hostnames && hostnames.shift() || "[" + this.remoteAddress + "]";
|
|
1240
|
+
this._resetSession();
|
|
1241
|
+
let onSecureIfNeeded = (next2) => {
|
|
1242
|
+
if (!this.session.secure) {
|
|
1243
|
+
return next2();
|
|
1244
|
+
}
|
|
1245
|
+
this.session.servername = this._socket.servername;
|
|
1246
|
+
this._server.onSecure(this._socket, this.session, (err2) => {
|
|
1247
|
+
if (err2) {
|
|
1248
|
+
return this._onError(err2);
|
|
1249
|
+
}
|
|
1250
|
+
next2();
|
|
1251
|
+
});
|
|
1252
|
+
};
|
|
1253
|
+
this._server.onConnect(this.session, (err2) => {
|
|
1254
|
+
this._server.logger.info(
|
|
1255
|
+
{
|
|
1256
|
+
tnx: "connection",
|
|
1257
|
+
cid: this.id,
|
|
1258
|
+
host: this.remoteAddress,
|
|
1259
|
+
hostname: this.clientHostname
|
|
1260
|
+
},
|
|
1261
|
+
"Connection from %s",
|
|
1262
|
+
this.clientHostname
|
|
1263
|
+
);
|
|
1264
|
+
if (err2) {
|
|
1265
|
+
this.send(err2.responseCode || 554, err2.message, false);
|
|
1266
|
+
return this.close();
|
|
1267
|
+
}
|
|
1268
|
+
onSecureIfNeeded(() => {
|
|
1269
|
+
this._ready = true;
|
|
1270
|
+
if (!this._server.options.useXClient && !this._server.options.useXForward) {
|
|
1271
|
+
this.emitConnection();
|
|
1272
|
+
}
|
|
1273
|
+
this.send(
|
|
1274
|
+
220,
|
|
1275
|
+
this.name + " " + (this._server.options.lmtp ? "LMTP" : "ESMTP") + (this._server.options.banner ? " " + this._server.options.banner : ""),
|
|
1276
|
+
false
|
|
1277
|
+
);
|
|
1278
|
+
if (typeof next === "function") {
|
|
1279
|
+
next();
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
});
|
|
1283
|
+
};
|
|
1284
|
+
if (this._server.options.disableReverseLookup) {
|
|
1285
|
+
return reverseCb(null, false);
|
|
1286
|
+
}
|
|
1287
|
+
let greetingSent = false;
|
|
1288
|
+
let reverseTimer = setTimeout(() => {
|
|
1289
|
+
clearTimeout(reverseTimer);
|
|
1290
|
+
if (greetingSent) {
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
greetingSent = true;
|
|
1294
|
+
reverseCb(new Error("Timeout"));
|
|
1295
|
+
}, 1500);
|
|
1296
|
+
reverseTimer.unref();
|
|
1297
|
+
const handleResolverResult = (...args) => {
|
|
1298
|
+
clearTimeout(reverseTimer);
|
|
1299
|
+
if (greetingSent) {
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
greetingSent = true;
|
|
1303
|
+
reverseCb(...args);
|
|
1304
|
+
};
|
|
1305
|
+
try {
|
|
1306
|
+
if (this._server.options.resolver && typeof this._server.options.resolver.reverse === "function") {
|
|
1307
|
+
this._server.options.resolver.reverse(this.remoteAddress.toString(), handleResolverResult);
|
|
1308
|
+
} else {
|
|
1309
|
+
dns.reverse(this.remoteAddress.toString(), handleResolverResult);
|
|
1310
|
+
}
|
|
1311
|
+
} catch (E) {
|
|
1312
|
+
clearTimeout(reverseTimer);
|
|
1313
|
+
if (greetingSent) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
greetingSent = true;
|
|
1317
|
+
reverseCb(E);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Send data to socket
|
|
1322
|
+
*
|
|
1323
|
+
* @param {Number} code Response code
|
|
1324
|
+
* @param {String|Array} data If data is Array, send a multi-line response
|
|
1325
|
+
* @param {String|Boolean} context Optional context for enhanced status codes
|
|
1326
|
+
*/
|
|
1327
|
+
send(code, data, context) {
|
|
1328
|
+
let payload;
|
|
1329
|
+
let enhancedCode = this._getEnhancedStatusCode(code, context);
|
|
1330
|
+
if (Array.isArray(data)) {
|
|
1331
|
+
payload = data.map((line, i, arr) => {
|
|
1332
|
+
let prefix = code + (i < arr.length - 1 ? "-" : " ");
|
|
1333
|
+
if (enhancedCode) {
|
|
1334
|
+
prefix += enhancedCode + " ";
|
|
1335
|
+
}
|
|
1336
|
+
return prefix + line;
|
|
1337
|
+
}).join("\r\n");
|
|
1338
|
+
} else {
|
|
1339
|
+
let parts = [code];
|
|
1340
|
+
if (enhancedCode) {
|
|
1341
|
+
parts.push(enhancedCode);
|
|
1342
|
+
}
|
|
1343
|
+
if (data) {
|
|
1344
|
+
parts.push(data);
|
|
1345
|
+
}
|
|
1346
|
+
payload = parts.join(" ");
|
|
1347
|
+
}
|
|
1348
|
+
if (code >= 400) {
|
|
1349
|
+
this.session.error = payload;
|
|
1350
|
+
}
|
|
1351
|
+
if (code === 334 && payload === "334") {
|
|
1352
|
+
payload += " ";
|
|
1353
|
+
}
|
|
1354
|
+
if (this._socket && !this._socket.destroyed && this._socket.readyState === "open") {
|
|
1355
|
+
this._socket.write(payload + "\r\n");
|
|
1356
|
+
this._server.logger.debug(
|
|
1357
|
+
{
|
|
1358
|
+
tnx: "send",
|
|
1359
|
+
cid: this.id,
|
|
1360
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
1361
|
+
},
|
|
1362
|
+
"S:",
|
|
1363
|
+
payload
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
if (code === 421) {
|
|
1367
|
+
this.close();
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Close socket
|
|
1372
|
+
*/
|
|
1373
|
+
close() {
|
|
1374
|
+
if (!this._socket.destroyed && this._socket.writable) {
|
|
1375
|
+
this._socket.end();
|
|
1376
|
+
}
|
|
1377
|
+
this._server.connections.delete(this);
|
|
1378
|
+
this._closing = true;
|
|
1379
|
+
}
|
|
1380
|
+
// PRIVATE METHODS
|
|
1381
|
+
/**
|
|
1382
|
+
* Setup socket event handlers
|
|
1383
|
+
*/
|
|
1384
|
+
_setListeners(callback) {
|
|
1385
|
+
this._socket.on("close", (hadError) => this._onCloseEvent(hadError));
|
|
1386
|
+
this._socket.on("error", (err) => this._onError(err));
|
|
1387
|
+
this._parser.on("error", (err) => {
|
|
1388
|
+
this.send(421, "Error: " + err.message);
|
|
1389
|
+
});
|
|
1390
|
+
this._socket.setTimeout(this._server.options.socketTimeout || SOCKET_TIMEOUT, () => this._onTimeout());
|
|
1391
|
+
this._socket.pipe(this._parser);
|
|
1392
|
+
if (!this.needsUpgrade) {
|
|
1393
|
+
return callback();
|
|
1394
|
+
}
|
|
1395
|
+
this.upgrade(() => false, callback);
|
|
1396
|
+
}
|
|
1397
|
+
_onCloseEvent(hadError) {
|
|
1398
|
+
this._server.logger.info(
|
|
1399
|
+
{
|
|
1400
|
+
tnx: "close",
|
|
1401
|
+
cid: this.id,
|
|
1402
|
+
host: this.remoteAddress,
|
|
1403
|
+
user: this.session.user && this.session.user.username || this.session.user,
|
|
1404
|
+
hadError
|
|
1405
|
+
},
|
|
1406
|
+
'%s received "close" event from %s' + (hadError ? " after error" : ""),
|
|
1407
|
+
this.id,
|
|
1408
|
+
this.remoteAddress
|
|
1409
|
+
);
|
|
1410
|
+
this._onClose();
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Fired when the socket is closed
|
|
1414
|
+
* @event
|
|
1415
|
+
*/
|
|
1416
|
+
_onClose() {
|
|
1417
|
+
if (this._parser) {
|
|
1418
|
+
this._parser.isClosed = true;
|
|
1419
|
+
this._socket.unpipe(this._parser);
|
|
1420
|
+
this._parser = false;
|
|
1421
|
+
}
|
|
1422
|
+
if (this._dataStream) {
|
|
1423
|
+
this._dataStream.unpipe();
|
|
1424
|
+
this._dataStream = null;
|
|
1425
|
+
}
|
|
1426
|
+
this._server.connections.delete(this);
|
|
1427
|
+
if (this._closed) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
this._closed = true;
|
|
1431
|
+
this._closing = false;
|
|
1432
|
+
this._server.logger.info(
|
|
1433
|
+
{
|
|
1434
|
+
tnx: "close",
|
|
1435
|
+
cid: this.id,
|
|
1436
|
+
host: this.remoteAddress,
|
|
1437
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
1438
|
+
},
|
|
1439
|
+
"Connection closed to %s",
|
|
1440
|
+
this.clientHostname || this.remoteAddress
|
|
1441
|
+
);
|
|
1442
|
+
setImmediate(() => this._server.onClose(this.session));
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Fired when an error occurs with the socket
|
|
1446
|
+
*
|
|
1447
|
+
* @event
|
|
1448
|
+
* @param {Error} err Error object
|
|
1449
|
+
*/
|
|
1450
|
+
_onError(err) {
|
|
1451
|
+
err.remoteAddress = this.remoteAddress;
|
|
1452
|
+
this._server.logger.error(
|
|
1453
|
+
{
|
|
1454
|
+
err,
|
|
1455
|
+
tnx: "error",
|
|
1456
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
1457
|
+
},
|
|
1458
|
+
"%s %s %s",
|
|
1459
|
+
this.id,
|
|
1460
|
+
this.remoteAddress,
|
|
1461
|
+
err.message
|
|
1462
|
+
);
|
|
1463
|
+
if ((err.code === "ECONNRESET" || err.code === "EPIPE") && (!this.session.envelope || !this.session.envelope.mailFrom)) {
|
|
1464
|
+
this.close();
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
this.emit("error", err);
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Fired when socket timeouts. Closes connection
|
|
1471
|
+
*
|
|
1472
|
+
* @event
|
|
1473
|
+
*/
|
|
1474
|
+
_onTimeout() {
|
|
1475
|
+
this.send(421, "Timeout - closing connection");
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Checks if a selected command is available and invokes it
|
|
1479
|
+
*
|
|
1480
|
+
* @param {Buffer} command Single line of data from the client
|
|
1481
|
+
* @param {Function} callback Callback to run once the command is processed
|
|
1482
|
+
*/
|
|
1483
|
+
_onCommand(command, callback) {
|
|
1484
|
+
let commandName = (command || "").toString().split(" ").shift().toUpperCase();
|
|
1485
|
+
this._server.logger.debug(
|
|
1486
|
+
{
|
|
1487
|
+
tnx: "command",
|
|
1488
|
+
cid: this.id,
|
|
1489
|
+
command: commandName,
|
|
1490
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
1491
|
+
},
|
|
1492
|
+
"C:",
|
|
1493
|
+
(command || "").toString()
|
|
1494
|
+
);
|
|
1495
|
+
let handler;
|
|
1496
|
+
callback = callback || (() => false);
|
|
1497
|
+
if (this._server._closeTimeout) {
|
|
1498
|
+
return this.send(421, "Server shutting down", commandName);
|
|
1499
|
+
}
|
|
1500
|
+
if (!this._ready) {
|
|
1501
|
+
return this.send(421, this.name + " You talk too soon", commandName);
|
|
1502
|
+
}
|
|
1503
|
+
if (/^(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) \/.* HTTP\/\d\.\d$/i.test(command)) {
|
|
1504
|
+
return this.send(421, "HTTP requests not allowed", commandName);
|
|
1505
|
+
}
|
|
1506
|
+
if (this._upgrading) {
|
|
1507
|
+
return callback();
|
|
1508
|
+
}
|
|
1509
|
+
if (this._nextHandler) {
|
|
1510
|
+
handler = this._nextHandler;
|
|
1511
|
+
this._nextHandler = false;
|
|
1512
|
+
} else {
|
|
1513
|
+
switch (commandName) {
|
|
1514
|
+
case "HELO":
|
|
1515
|
+
case "EHLO":
|
|
1516
|
+
case "LHLO":
|
|
1517
|
+
this.openingCommand = commandName;
|
|
1518
|
+
break;
|
|
1519
|
+
}
|
|
1520
|
+
if (this._server.options.lmtp) {
|
|
1521
|
+
switch (commandName) {
|
|
1522
|
+
case "HELO":
|
|
1523
|
+
case "EHLO":
|
|
1524
|
+
this.send(500, "Error: " + commandName + " not allowed in LMTP server", false);
|
|
1525
|
+
return setImmediate(callback);
|
|
1526
|
+
case "LHLO":
|
|
1527
|
+
commandName = "EHLO";
|
|
1528
|
+
break;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (this._isSupported(commandName)) {
|
|
1532
|
+
handler = this["handler_" + commandName];
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
if (!handler) {
|
|
1536
|
+
this._unrecognizedCommands++;
|
|
1537
|
+
if (this._unrecognizedCommands >= 10) {
|
|
1538
|
+
return this.send(421, "Error: too many unrecognized commands", commandName);
|
|
1539
|
+
}
|
|
1540
|
+
this.send(500, "Error: command not recognized", commandName);
|
|
1541
|
+
return setImmediate(callback);
|
|
1542
|
+
}
|
|
1543
|
+
if (!this.session.user && this._isSupported("AUTH") && !this._server.options.authOptional && commandName !== "AUTH" && this._maxAllowedUnauthenticatedCommands !== false) {
|
|
1544
|
+
this._unauthenticatedCommands++;
|
|
1545
|
+
if (this._unauthenticatedCommands >= this._maxAllowedUnauthenticatedCommands) {
|
|
1546
|
+
return this.send(421, "Error: too many unauthenticated commands", commandName);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
if (!this.hostNameAppearsAs && commandName && ["MAIL", "RCPT", "DATA", "AUTH"].includes(commandName)) {
|
|
1550
|
+
this.send(503, "Error: send " + (this._server.options.lmtp ? "LHLO" : "HELO/EHLO") + " first");
|
|
1551
|
+
return setImmediate(callback);
|
|
1552
|
+
}
|
|
1553
|
+
if (!this.session.user && this._isSupported("AUTH") && ["MAIL", "RCPT", "DATA"].includes(commandName) && !this._server.options.authOptional) {
|
|
1554
|
+
this.send(
|
|
1555
|
+
530,
|
|
1556
|
+
typeof this._server.options.authRequiredMessage === "string" ? this._server.options.authRequiredMessage : "Error: authentication Required"
|
|
1557
|
+
);
|
|
1558
|
+
return setImmediate(callback);
|
|
1559
|
+
}
|
|
1560
|
+
handler.call(this, command, callback);
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Checks that a command is available and is not listed in the disabled commands array
|
|
1564
|
+
*
|
|
1565
|
+
* @param {String} command Command name
|
|
1566
|
+
* @returns {Boolean} Returns true if the command can be used
|
|
1567
|
+
*/
|
|
1568
|
+
_isSupported(command) {
|
|
1569
|
+
command = (command || "").toString().trim().toUpperCase();
|
|
1570
|
+
return !this._server.options.disabledCommands.includes(command) && typeof this["handler_" + command] === "function";
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Determines if enhanced status codes should be used
|
|
1574
|
+
* @returns {Boolean} True if enhanced status codes should be included in responses
|
|
1575
|
+
*/
|
|
1576
|
+
_useEnhancedStatusCodes() {
|
|
1577
|
+
return !this._server.options.hideENHANCEDSTATUSCODES;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Gets the appropriate enhanced status code for a given SMTP response code and context
|
|
1581
|
+
* @param {Number} code SMTP response code
|
|
1582
|
+
* @param {String|Boolean} context Optional context for more specific status codes
|
|
1583
|
+
* @returns {String} Enhanced status code or empty string if not applicable
|
|
1584
|
+
*/
|
|
1585
|
+
_getEnhancedStatusCode(code, context) {
|
|
1586
|
+
if (context === false || !this._useEnhancedStatusCodes()) {
|
|
1587
|
+
return "";
|
|
1588
|
+
}
|
|
1589
|
+
if (code >= 300 && code < 400) {
|
|
1590
|
+
return "";
|
|
1591
|
+
}
|
|
1592
|
+
if (context && SKIPPED_COMMANDS_FOR_ENHANCED_STATUS_CODES.has(context)) {
|
|
1593
|
+
return "";
|
|
1594
|
+
}
|
|
1595
|
+
if (context && CONTEXTUAL_STATUS_CODES[context]) {
|
|
1596
|
+
return CONTEXTUAL_STATUS_CODES[context];
|
|
1597
|
+
}
|
|
1598
|
+
if (ENHANCED_STATUS_CODES[code]) {
|
|
1599
|
+
return ENHANCED_STATUS_CODES[code];
|
|
1600
|
+
}
|
|
1601
|
+
if (code >= 200 && code < 300) {
|
|
1602
|
+
return "2.0.0";
|
|
1603
|
+
}
|
|
1604
|
+
if (code >= 400 && code < 500) {
|
|
1605
|
+
return "4.0.0";
|
|
1606
|
+
}
|
|
1607
|
+
if (code >= 500) {
|
|
1608
|
+
return "5.0.0";
|
|
1609
|
+
}
|
|
1610
|
+
return "";
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Parses commands like MAIL FROM and RCPT TO. Returns an object with the address and optional arguments.
|
|
1614
|
+
*
|
|
1615
|
+
* @param {[type]} name Address type, eg 'mail from' or 'rcpt to'
|
|
1616
|
+
* @param {[type]} command Data payload to parse
|
|
1617
|
+
* @returns {Object|Boolean} Parsed address in the form of {address:, args: {}} or false if parsing failed
|
|
1618
|
+
*/
|
|
1619
|
+
_parseAddressCommand(name, command) {
|
|
1620
|
+
command = (command || "").toString();
|
|
1621
|
+
name = (name || "").toString().trim().toUpperCase();
|
|
1622
|
+
let parts = command.split(":");
|
|
1623
|
+
command = parts.shift().trim().toUpperCase();
|
|
1624
|
+
parts = parts.join(":").trim().split(/\s+/);
|
|
1625
|
+
let address = parts.shift();
|
|
1626
|
+
let args = false;
|
|
1627
|
+
let invalid = false;
|
|
1628
|
+
if (name !== command) {
|
|
1629
|
+
return false;
|
|
1630
|
+
}
|
|
1631
|
+
if (!/^<[^<>]*>$/.test(address)) {
|
|
1632
|
+
invalid = true;
|
|
1633
|
+
} else {
|
|
1634
|
+
address = address.substr(1, address.length - 2);
|
|
1635
|
+
}
|
|
1636
|
+
parts.forEach((part) => {
|
|
1637
|
+
part = part.split("=");
|
|
1638
|
+
let key = part.shift().toUpperCase();
|
|
1639
|
+
let value = part.join("=") || true;
|
|
1640
|
+
if (!key || key.trim() === "") {
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
if (typeof value === "string") {
|
|
1644
|
+
value = value.replace(/\+([0-9A-F]{2})/g, (match, hex) => unescape("%" + hex));
|
|
1645
|
+
}
|
|
1646
|
+
if (!args) {
|
|
1647
|
+
args = {};
|
|
1648
|
+
}
|
|
1649
|
+
args[key] = value;
|
|
1650
|
+
});
|
|
1651
|
+
if (address) {
|
|
1652
|
+
address = address.split("@");
|
|
1653
|
+
if (address.length !== 2 || !address[0] || !address[1]) {
|
|
1654
|
+
invalid = true;
|
|
1655
|
+
} else {
|
|
1656
|
+
let localPart = address[0];
|
|
1657
|
+
let domain = address[1];
|
|
1658
|
+
if (localPart.length + 1 + domain.length > 253) {
|
|
1659
|
+
invalid = true;
|
|
1660
|
+
} else {
|
|
1661
|
+
if (localPart.startsWith(".") || localPart.endsWith(".") || localPart.includes("..")) {
|
|
1662
|
+
invalid = true;
|
|
1663
|
+
}
|
|
1664
|
+
let isIPLiteral = !invalid && domain.startsWith("[") && domain.endsWith("]");
|
|
1665
|
+
if (isIPLiteral) {
|
|
1666
|
+
let ipContent = domain.slice(1, -1);
|
|
1667
|
+
let isValidIP = false;
|
|
1668
|
+
if (ipContent.toUpperCase().startsWith("IPV6:")) {
|
|
1669
|
+
let ipv6Addr = ipContent.slice(5);
|
|
1670
|
+
if (net.isIPv6(ipv6Addr)) {
|
|
1671
|
+
isValidIP = true;
|
|
1672
|
+
try {
|
|
1673
|
+
ipv6Addr = ipv6normalize(ipv6Addr);
|
|
1674
|
+
domain = "[IPv6:" + ipv6Addr + "]";
|
|
1675
|
+
} catch {
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
} else {
|
|
1679
|
+
if (net.isIPv4(ipContent)) {
|
|
1680
|
+
isValidIP = true;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
if (!isValidIP) {
|
|
1684
|
+
invalid = true;
|
|
1685
|
+
}
|
|
1686
|
+
if (!invalid) {
|
|
1687
|
+
address = [localPart, "@", domain].join("");
|
|
1688
|
+
}
|
|
1689
|
+
} else {
|
|
1690
|
+
if (!invalid && (domain.startsWith(".") || domain.endsWith(".") || domain.includes("..") || domain.includes(".-") || domain.includes("-.") || !/^[a-zA-Z0-9\u0080-\uFFFF.-]+$/.test(domain))) {
|
|
1691
|
+
invalid = true;
|
|
1692
|
+
}
|
|
1693
|
+
if (!invalid) {
|
|
1694
|
+
try {
|
|
1695
|
+
address = [localPart, "@", punycode2.toUnicode(domain)].join("");
|
|
1696
|
+
} catch (E) {
|
|
1697
|
+
this._server.logger.error(
|
|
1698
|
+
{
|
|
1699
|
+
tnx: "punycode",
|
|
1700
|
+
cid: this.id,
|
|
1701
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
1702
|
+
},
|
|
1703
|
+
'Failed to process punycode domain "%s". error=%s',
|
|
1704
|
+
domain,
|
|
1705
|
+
E.message
|
|
1706
|
+
);
|
|
1707
|
+
invalid = true;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return invalid ? false : {
|
|
1715
|
+
address,
|
|
1716
|
+
args
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Resets or sets up a new session. We reuse existing session object to keep
|
|
1721
|
+
* application specific data.
|
|
1722
|
+
*/
|
|
1723
|
+
_resetSession() {
|
|
1724
|
+
let session = this.session;
|
|
1725
|
+
session.localAddress = this.localAddress;
|
|
1726
|
+
session.localPort = this.localPort;
|
|
1727
|
+
session.remoteAddress = this.remoteAddress;
|
|
1728
|
+
session.remotePort = this.remotePort;
|
|
1729
|
+
session.clientHostname = this.clientHostname;
|
|
1730
|
+
session.openingCommand = this.openingCommand;
|
|
1731
|
+
session.hostNameAppearsAs = this.hostNameAppearsAs;
|
|
1732
|
+
session.xClient = this._xClient;
|
|
1733
|
+
session.xForward = this._xForward;
|
|
1734
|
+
session.transmissionType = this._transmissionType();
|
|
1735
|
+
session.tlsOptions = this.tlsOptions;
|
|
1736
|
+
session.envelope = {
|
|
1737
|
+
mailFrom: false,
|
|
1738
|
+
rcptTo: [],
|
|
1739
|
+
/** @property {boolean} requireTLS - RFC 8689: Indicates client requires TLS for entire delivery chain */
|
|
1740
|
+
requireTLS: false,
|
|
1741
|
+
/** @property {string} bodyType - RFC 6152: Message body encoding type (7bit or 8bitmime) */
|
|
1742
|
+
bodyType: "7bit",
|
|
1743
|
+
/** @property {boolean} smtpUtf8 - RFC 6531: Indicates UTF-8 support is requested */
|
|
1744
|
+
smtpUtf8: false
|
|
1745
|
+
};
|
|
1746
|
+
if (!this._server.options.hideDSN)
|
|
1747
|
+
session.envelope.dsn = {
|
|
1748
|
+
ret: null,
|
|
1749
|
+
// RET parameter from MAIL FROM (FULL or HDRS)
|
|
1750
|
+
envid: null
|
|
1751
|
+
// ENVID parameter from MAIL FROM
|
|
1752
|
+
};
|
|
1753
|
+
session.transaction = this._transactionCounter + 1;
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Returns current transmission type
|
|
1757
|
+
*
|
|
1758
|
+
* @return {String} Transmission type
|
|
1759
|
+
*/
|
|
1760
|
+
_transmissionType() {
|
|
1761
|
+
let type = this._server.options.lmtp ? "LMTP" : "SMTP";
|
|
1762
|
+
if (this.openingCommand === "EHLO") {
|
|
1763
|
+
type = "E" + type;
|
|
1764
|
+
}
|
|
1765
|
+
if (this.secure) {
|
|
1766
|
+
type += "S";
|
|
1767
|
+
}
|
|
1768
|
+
if (this.session.user) {
|
|
1769
|
+
type += "A";
|
|
1770
|
+
}
|
|
1771
|
+
return type;
|
|
1772
|
+
}
|
|
1773
|
+
emitConnection() {
|
|
1774
|
+
if (!this._canEmitConnection) {
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
this._canEmitConnection = false;
|
|
1778
|
+
this.emit("connect", {
|
|
1779
|
+
id: this.id,
|
|
1780
|
+
localAddress: this.localAddress,
|
|
1781
|
+
localPort: this.localPort,
|
|
1782
|
+
remoteAddress: this.remoteAddress,
|
|
1783
|
+
remotePort: this.remotePort,
|
|
1784
|
+
hostNameAppearsAs: this.hostNameAppearsAs,
|
|
1785
|
+
clientHostname: this.clientHostname
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
// COMMAND HANDLERS
|
|
1789
|
+
/**
|
|
1790
|
+
* Processes EHLO. Requires valid hostname as the single argument.
|
|
1791
|
+
*/
|
|
1792
|
+
handler_EHLO(command, callback) {
|
|
1793
|
+
let parts = command.toString().trim().split(/\s+/);
|
|
1794
|
+
let hostname = parts[1] || "";
|
|
1795
|
+
if (parts.length !== 2) {
|
|
1796
|
+
this.send(501, "Error: syntax: " + (this._server.options.lmtp ? "LHLO" : "EHLO") + " hostname", false);
|
|
1797
|
+
return callback();
|
|
1798
|
+
}
|
|
1799
|
+
this.hostNameAppearsAs = hostname.toLowerCase();
|
|
1800
|
+
let features = ["PIPELINING", "8BITMIME", "SMTPUTF8", "ENHANCEDSTATUSCODES", "DSN"].filter((feature) => !this._server.options["hide" + feature]);
|
|
1801
|
+
if (this._server.options.authMethods.length && this._isSupported("AUTH") && !this.session.user) {
|
|
1802
|
+
features.push(["AUTH"].concat(this._server.options.authMethods).join(" "));
|
|
1803
|
+
}
|
|
1804
|
+
if (!this.secure && this._isSupported("STARTTLS") && !this._server.options.hideSTARTTLS) {
|
|
1805
|
+
features.push("STARTTLS");
|
|
1806
|
+
}
|
|
1807
|
+
if (this.secure && !this._server.options.hideREQUIRETLS) {
|
|
1808
|
+
features.push("REQUIRETLS");
|
|
1809
|
+
}
|
|
1810
|
+
if (this._server.options.size) {
|
|
1811
|
+
features.push("SIZE" + (this._server.options.hideSize ? "" : " " + this._server.options.size));
|
|
1812
|
+
}
|
|
1813
|
+
if (!this._xClient.has("ADDR") && this._server.options.useXClient && this._isSupported("XCLIENT")) {
|
|
1814
|
+
features.push("XCLIENT NAME ADDR PORT PROTO HELO LOGIN");
|
|
1815
|
+
}
|
|
1816
|
+
if (!this._xClient.has("ADDR") && this._server.options.useXForward && this._isSupported("XFORWARD")) {
|
|
1817
|
+
features.push("XFORWARD NAME ADDR PORT PROTO HELO IDENT SOURCE");
|
|
1818
|
+
}
|
|
1819
|
+
this._resetSession();
|
|
1820
|
+
let heloResponse = this._server.options.heloResponse || "%s Nice to meet you, %s";
|
|
1821
|
+
let replacements = [this.name, this.clientHostname];
|
|
1822
|
+
let replacementIndex = 0;
|
|
1823
|
+
let formattedResponse = heloResponse.replace(/%s/g, () => replacements[replacementIndex++] || "");
|
|
1824
|
+
this.send(250, [formattedResponse].concat(features || []), false);
|
|
1825
|
+
callback();
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Processes HELO. Requires valid hostname as the single argument.
|
|
1829
|
+
*/
|
|
1830
|
+
handler_HELO(command, callback) {
|
|
1831
|
+
let parts = command.toString().trim().split(/\s+/);
|
|
1832
|
+
let hostname = parts[1] || "";
|
|
1833
|
+
if (parts.length !== 2) {
|
|
1834
|
+
this.send(501, "Error: Syntax: HELO hostname", false);
|
|
1835
|
+
return callback();
|
|
1836
|
+
}
|
|
1837
|
+
this.hostNameAppearsAs = hostname.toLowerCase();
|
|
1838
|
+
this._resetSession();
|
|
1839
|
+
let heloResponse = this._server.options.heloResponse || "%s Nice to meet you, %s";
|
|
1840
|
+
let replacements = [this.name, this.clientHostname];
|
|
1841
|
+
let replacementIndex = 0;
|
|
1842
|
+
let formattedResponse = heloResponse.replace(/%s/g, () => replacements[replacementIndex++] || "");
|
|
1843
|
+
this.send(250, formattedResponse, false);
|
|
1844
|
+
callback();
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Processes QUIT. Closes the connection
|
|
1848
|
+
*/
|
|
1849
|
+
handler_QUIT(command, callback) {
|
|
1850
|
+
this.send(221, "Bye");
|
|
1851
|
+
this.close();
|
|
1852
|
+
callback();
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Processes NOOP. Does nothing but keeps the connection alive
|
|
1856
|
+
*/
|
|
1857
|
+
handler_NOOP(command, callback) {
|
|
1858
|
+
this.send(250, "OK");
|
|
1859
|
+
callback();
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Processes RSET. Resets user and session info
|
|
1863
|
+
*/
|
|
1864
|
+
handler_RSET(command, callback) {
|
|
1865
|
+
this._resetSession();
|
|
1866
|
+
this.send(250, "Flushed");
|
|
1867
|
+
callback();
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Processes HELP. Responds with url to RFC
|
|
1871
|
+
*/
|
|
1872
|
+
handler_HELP(command, callback) {
|
|
1873
|
+
this.send(214, "See https://tools.ietf.org/html/rfc5321 for details");
|
|
1874
|
+
callback();
|
|
1875
|
+
}
|
|
1876
|
+
/**
|
|
1877
|
+
* Processes VRFY. Does not verify anything
|
|
1878
|
+
*/
|
|
1879
|
+
handler_VRFY(command, callback) {
|
|
1880
|
+
this.send(252, "Try to send something. No promises though");
|
|
1881
|
+
callback();
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Overrides connection info
|
|
1885
|
+
* http://www.postfix.org/XCLIENT_README.html
|
|
1886
|
+
*
|
|
1887
|
+
* TODO: add unit tests
|
|
1888
|
+
*/
|
|
1889
|
+
handler_XCLIENT(command, callback) {
|
|
1890
|
+
if (this._xClient.has("ADDR") || !this._server.options.useXClient) {
|
|
1891
|
+
this.send(550, "Error: Not allowed");
|
|
1892
|
+
return callback();
|
|
1893
|
+
}
|
|
1894
|
+
if (this.session.envelope.mailFrom) {
|
|
1895
|
+
this.send(503, "Error: Mail transaction in progress");
|
|
1896
|
+
return callback();
|
|
1897
|
+
}
|
|
1898
|
+
let allowedKeys = ["NAME", "ADDR", "PORT", "PROTO", "HELO", "LOGIN"];
|
|
1899
|
+
let parts = command.toString().trim().split(/\s+/);
|
|
1900
|
+
let key, value;
|
|
1901
|
+
let data = /* @__PURE__ */ new Map();
|
|
1902
|
+
parts.shift();
|
|
1903
|
+
if (!parts.length) {
|
|
1904
|
+
this.send(501, "Error: Bad command parameter syntax");
|
|
1905
|
+
return callback();
|
|
1906
|
+
}
|
|
1907
|
+
let loginValue = false;
|
|
1908
|
+
for (let i = 0, len = parts.length; i < len; i++) {
|
|
1909
|
+
value = parts[i].split("=");
|
|
1910
|
+
key = value.shift();
|
|
1911
|
+
if (value.length !== 1 || !allowedKeys.includes(key.toUpperCase())) {
|
|
1912
|
+
this.send(501, "Error: Bad command parameter syntax");
|
|
1913
|
+
return callback();
|
|
1914
|
+
}
|
|
1915
|
+
key = key.toUpperCase();
|
|
1916
|
+
value = (value[0] || "").replace(/\+([0-9A-F]{2})/g, (match, hex) => unescape("%" + hex));
|
|
1917
|
+
if (["[UNAVAILABLE]", "[TEMPUNAVAIL]"].includes(value.toUpperCase())) {
|
|
1918
|
+
value = false;
|
|
1919
|
+
}
|
|
1920
|
+
if (data.has(key)) {
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
data.set(key, value);
|
|
1924
|
+
switch (key) {
|
|
1925
|
+
// handled outside the switch
|
|
1926
|
+
case "LOGIN":
|
|
1927
|
+
loginValue = value;
|
|
1928
|
+
break;
|
|
1929
|
+
case "ADDR":
|
|
1930
|
+
if (value) {
|
|
1931
|
+
value = value.replace(/^IPV6:/i, "");
|
|
1932
|
+
if (!net.isIP(value)) {
|
|
1933
|
+
this.send(501, "Error: Bad command parameter syntax. Invalid address");
|
|
1934
|
+
return callback();
|
|
1935
|
+
}
|
|
1936
|
+
if (net.isIPv6(value)) {
|
|
1937
|
+
value = ipv6normalize(value);
|
|
1938
|
+
}
|
|
1939
|
+
this._server.logger.info(
|
|
1940
|
+
{
|
|
1941
|
+
tnx: "xclient",
|
|
1942
|
+
cid: this.id,
|
|
1943
|
+
xclientKey: "ADDR",
|
|
1944
|
+
xclient: value,
|
|
1945
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
1946
|
+
},
|
|
1947
|
+
"XCLIENT from %s through %s",
|
|
1948
|
+
value,
|
|
1949
|
+
this.remoteAddress
|
|
1950
|
+
);
|
|
1951
|
+
if (!this._xClient.has("ADDR:DEFAULT")) {
|
|
1952
|
+
this._xClient.set("ADDR:DEFAULT", this.remoteAddress);
|
|
1953
|
+
}
|
|
1954
|
+
this.remoteAddress = value;
|
|
1955
|
+
this.hostNameAppearsAs = false;
|
|
1956
|
+
}
|
|
1957
|
+
break;
|
|
1958
|
+
case "NAME":
|
|
1959
|
+
value = value || "";
|
|
1960
|
+
this._server.logger.info(
|
|
1961
|
+
{
|
|
1962
|
+
tnx: "xclient",
|
|
1963
|
+
cid: this.id,
|
|
1964
|
+
xclientKey: "NAME",
|
|
1965
|
+
xclient: value,
|
|
1966
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
1967
|
+
},
|
|
1968
|
+
'XCLIENT hostname resolved as "%s"',
|
|
1969
|
+
value
|
|
1970
|
+
);
|
|
1971
|
+
if (!this._xClient.has("NAME:DEFAULT")) {
|
|
1972
|
+
this._xClient.set("NAME:DEFAULT", this.clientHostname || "");
|
|
1973
|
+
}
|
|
1974
|
+
this.clientHostname = value.toLowerCase();
|
|
1975
|
+
break;
|
|
1976
|
+
case "PORT":
|
|
1977
|
+
value = Number(value) || "";
|
|
1978
|
+
this._server.logger.info(
|
|
1979
|
+
{
|
|
1980
|
+
tnx: "xclient",
|
|
1981
|
+
cid: this.id,
|
|
1982
|
+
xclientKey: "PORT",
|
|
1983
|
+
xclient: value,
|
|
1984
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
1985
|
+
},
|
|
1986
|
+
'XCLIENT remote port resolved as "%s"',
|
|
1987
|
+
value
|
|
1988
|
+
);
|
|
1989
|
+
if (!this._xClient.has("PORT:DEFAULT")) {
|
|
1990
|
+
this._xClient.set("PORT:DEFAULT", this.remotePort || "");
|
|
1991
|
+
}
|
|
1992
|
+
this.remotePort = value;
|
|
1993
|
+
break;
|
|
1994
|
+
default:
|
|
1995
|
+
}
|
|
1996
|
+
this._xClient.set(key, value);
|
|
1997
|
+
}
|
|
1998
|
+
let checkLogin = (done) => {
|
|
1999
|
+
if (typeof loginValue !== "string") {
|
|
2000
|
+
return done();
|
|
2001
|
+
}
|
|
2002
|
+
if (!loginValue) {
|
|
2003
|
+
this._server.logger.info(
|
|
2004
|
+
{
|
|
2005
|
+
tnx: "deauth",
|
|
2006
|
+
cid: this.id,
|
|
2007
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
2008
|
+
},
|
|
2009
|
+
"User deauthenticated using %s",
|
|
2010
|
+
"XCLIENT"
|
|
2011
|
+
);
|
|
2012
|
+
this.session.user = false;
|
|
2013
|
+
return done();
|
|
2014
|
+
}
|
|
2015
|
+
let method = "SASL_XCLIENT";
|
|
2016
|
+
sasl[method].call(this, [loginValue], (err) => {
|
|
2017
|
+
if (err) {
|
|
2018
|
+
this.send(550, err.message);
|
|
2019
|
+
this.close();
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
done();
|
|
2023
|
+
});
|
|
2024
|
+
};
|
|
2025
|
+
if (this.remoteAddress && !this.clientHostname) {
|
|
2026
|
+
this.clientHostname = "[" + this.remoteAddress + "]";
|
|
2027
|
+
}
|
|
2028
|
+
if (data.has("ADDR")) {
|
|
2029
|
+
this.emitConnection();
|
|
2030
|
+
}
|
|
2031
|
+
checkLogin(() => {
|
|
2032
|
+
this.send(
|
|
2033
|
+
220,
|
|
2034
|
+
this.name + " " + (this._server.options.lmtp ? "LMTP" : "ESMTP") + (this._server.options.banner ? " " + this._server.options.banner : "")
|
|
2035
|
+
);
|
|
2036
|
+
callback();
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Processes XFORWARD data
|
|
2041
|
+
* http://www.postfix.org/XFORWARD_README.html
|
|
2042
|
+
*
|
|
2043
|
+
* TODO: add unit tests
|
|
2044
|
+
*/
|
|
2045
|
+
handler_XFORWARD(command, callback) {
|
|
2046
|
+
if (!this._server.options.useXForward) {
|
|
2047
|
+
this.send(550, "Error: Not allowed");
|
|
2048
|
+
return callback();
|
|
2049
|
+
}
|
|
2050
|
+
if (this.session.envelope.mailFrom) {
|
|
2051
|
+
this.send(503, "Error: Mail transaction in progress");
|
|
2052
|
+
return callback();
|
|
2053
|
+
}
|
|
2054
|
+
let allowedKeys = ["NAME", "ADDR", "PORT", "PROTO", "HELO", "IDENT", "SOURCE"];
|
|
2055
|
+
let parts = command.toString().trim().split(/\s+/);
|
|
2056
|
+
let key, value;
|
|
2057
|
+
let data = /* @__PURE__ */ new Map();
|
|
2058
|
+
let hasAddr = false;
|
|
2059
|
+
parts.shift();
|
|
2060
|
+
if (!parts.length) {
|
|
2061
|
+
this.send(501, "Error: Bad command parameter syntax");
|
|
2062
|
+
return callback();
|
|
2063
|
+
}
|
|
2064
|
+
for (let i = 0, len = parts.length; i < len; i++) {
|
|
2065
|
+
value = parts[i].split("=");
|
|
2066
|
+
key = value.shift();
|
|
2067
|
+
if (value.length !== 1 || !allowedKeys.includes(key.toUpperCase())) {
|
|
2068
|
+
this.send(501, "Error: Bad command parameter syntax");
|
|
2069
|
+
return callback();
|
|
2070
|
+
}
|
|
2071
|
+
key = key.toUpperCase();
|
|
2072
|
+
if (data.has(key)) {
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
value = (value[0] || "").replace(/\+([0-9A-F]{2})/g, (match, hex) => unescape("%" + hex));
|
|
2076
|
+
if (value.toUpperCase() === "[UNAVAILABLE]") {
|
|
2077
|
+
value = false;
|
|
2078
|
+
}
|
|
2079
|
+
data.set(key, value);
|
|
2080
|
+
switch (key) {
|
|
2081
|
+
case "ADDR":
|
|
2082
|
+
if (value) {
|
|
2083
|
+
value = value.replace(/^IPV6:/i, "");
|
|
2084
|
+
if (!net.isIP(value)) {
|
|
2085
|
+
this.send(501, "Error: Bad command parameter syntax. Invalid address");
|
|
2086
|
+
return callback();
|
|
2087
|
+
}
|
|
2088
|
+
if (net.isIPv6(value)) {
|
|
2089
|
+
value = ipv6normalize(value);
|
|
2090
|
+
}
|
|
2091
|
+
this._server.logger.info(
|
|
2092
|
+
{
|
|
2093
|
+
tnx: "xforward",
|
|
2094
|
+
cid: this.id,
|
|
2095
|
+
xforwardKey: "ADDR",
|
|
2096
|
+
xforward: value,
|
|
2097
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
2098
|
+
},
|
|
2099
|
+
"XFORWARD from %s through %s",
|
|
2100
|
+
value,
|
|
2101
|
+
this.remoteAddress
|
|
2102
|
+
);
|
|
2103
|
+
if (!this._xClient.has("ADDR:DEFAULT")) {
|
|
2104
|
+
this._xClient.set("ADDR:DEFAULT", this.remoteAddress);
|
|
2105
|
+
}
|
|
2106
|
+
hasAddr = true;
|
|
2107
|
+
this.remoteAddress = value;
|
|
2108
|
+
}
|
|
2109
|
+
break;
|
|
2110
|
+
case "NAME":
|
|
2111
|
+
value = value || "";
|
|
2112
|
+
this._server.logger.info(
|
|
2113
|
+
{
|
|
2114
|
+
tnx: "xforward",
|
|
2115
|
+
cid: this.id,
|
|
2116
|
+
xforwardKey: "NAME",
|
|
2117
|
+
xforward: value,
|
|
2118
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
2119
|
+
},
|
|
2120
|
+
'XFORWARD hostname resolved as "%s"',
|
|
2121
|
+
value
|
|
2122
|
+
);
|
|
2123
|
+
this.clientHostname = value.toLowerCase();
|
|
2124
|
+
break;
|
|
2125
|
+
case "PORT":
|
|
2126
|
+
value = Number(value) || 0;
|
|
2127
|
+
this._server.logger.info(
|
|
2128
|
+
{
|
|
2129
|
+
tnx: "xforward",
|
|
2130
|
+
cid: this.id,
|
|
2131
|
+
xforwardKey: "PORT",
|
|
2132
|
+
xforward: value,
|
|
2133
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
2134
|
+
},
|
|
2135
|
+
'XFORWARD port resolved as "%s"',
|
|
2136
|
+
value
|
|
2137
|
+
);
|
|
2138
|
+
this.remotePort = value;
|
|
2139
|
+
break;
|
|
2140
|
+
case "HELO":
|
|
2141
|
+
value = (value || "").toString().toLowerCase();
|
|
2142
|
+
this._server.logger.info(
|
|
2143
|
+
{
|
|
2144
|
+
tnx: "xforward",
|
|
2145
|
+
cid: this.id,
|
|
2146
|
+
xforwardKey: "HELO",
|
|
2147
|
+
xforward: value,
|
|
2148
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
2149
|
+
},
|
|
2150
|
+
'XFORWARD HELO name resolved as "%s"',
|
|
2151
|
+
value
|
|
2152
|
+
);
|
|
2153
|
+
this.hostNameAppearsAs = value;
|
|
2154
|
+
break;
|
|
2155
|
+
default:
|
|
2156
|
+
}
|
|
2157
|
+
this._xForward.set(key, value);
|
|
2158
|
+
}
|
|
2159
|
+
if (hasAddr) {
|
|
2160
|
+
this._canEmitConnection = true;
|
|
2161
|
+
this.emitConnection();
|
|
2162
|
+
}
|
|
2163
|
+
this.send(250, "OK");
|
|
2164
|
+
callback();
|
|
2165
|
+
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Upgrades connection to TLS if possible
|
|
2168
|
+
*/
|
|
2169
|
+
handler_STARTTLS(command, callback) {
|
|
2170
|
+
if (this.secure) {
|
|
2171
|
+
this.send(503, "Error: TLS already active");
|
|
2172
|
+
return callback();
|
|
2173
|
+
}
|
|
2174
|
+
this.send(220, "Ready to start TLS");
|
|
2175
|
+
this.upgrade(callback);
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Check if selected authentication is available and delegate auth data to SASL
|
|
2179
|
+
*/
|
|
2180
|
+
handler_AUTH(command, callback) {
|
|
2181
|
+
let args = command.toString().trim().split(/\s+/);
|
|
2182
|
+
let method;
|
|
2183
|
+
let handler;
|
|
2184
|
+
args.shift();
|
|
2185
|
+
method = (args.shift() || "").toString().toUpperCase();
|
|
2186
|
+
handler = sasl["SASL_" + method];
|
|
2187
|
+
handler = handler ? handler.bind(this) : handler;
|
|
2188
|
+
if (!this.secure && this._isSupported("STARTTLS") && !this._server.options.hideSTARTTLS && !this._server.options.allowInsecureAuth) {
|
|
2189
|
+
this.send(538, "Error: Must issue a STARTTLS command first");
|
|
2190
|
+
return callback();
|
|
2191
|
+
}
|
|
2192
|
+
if (this.session.user) {
|
|
2193
|
+
this.send(503, "Error: No identity changes permitted");
|
|
2194
|
+
return callback();
|
|
2195
|
+
}
|
|
2196
|
+
if (!this._server.options.authMethods.includes(method) || typeof handler !== "function") {
|
|
2197
|
+
this.send(504, "Error: Unrecognized authentication type");
|
|
2198
|
+
return callback();
|
|
2199
|
+
}
|
|
2200
|
+
handler(args, callback);
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Validates MAIL FROM parameters
|
|
2204
|
+
* @param {Object} parsed - Parsed address command with args
|
|
2205
|
+
* @returns {Object|null} Returns error object {code, message, enhancedCode} if validation fails, null if successful
|
|
2206
|
+
*/
|
|
2207
|
+
_validateMailParams(parsed) {
|
|
2208
|
+
if (parsed.args.BODY) {
|
|
2209
|
+
const bodyType = parsed.args.BODY.toLowerCase();
|
|
2210
|
+
if (bodyType !== "7bit" && bodyType !== "8bitmime") {
|
|
2211
|
+
return {
|
|
2212
|
+
code: 501,
|
|
2213
|
+
message: "Invalid BODY parameter value. Must be 7BIT or 8BITMIME",
|
|
2214
|
+
enhancedCode: null
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
if (parsed.args.SMTPUTF8 !== void 0) {
|
|
2219
|
+
if (parsed.args.SMTPUTF8 !== true) {
|
|
2220
|
+
return {
|
|
2221
|
+
code: 501,
|
|
2222
|
+
message: "Invalid SMTPUTF8 parameter. This flag does not accept a value",
|
|
2223
|
+
enhancedCode: null
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
if (parsed.args.REQUIRETLS !== void 0) {
|
|
2228
|
+
if (!this.secure) {
|
|
2229
|
+
return {
|
|
2230
|
+
code: 530,
|
|
2231
|
+
message: "REQUIRETLS not permitted on non-TLS connections",
|
|
2232
|
+
enhancedCode: null
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
if (parsed.args.REQUIRETLS !== true) {
|
|
2236
|
+
return {
|
|
2237
|
+
code: 501,
|
|
2238
|
+
message: "Invalid REQUIRETLS parameter. This flag does not accept a value",
|
|
2239
|
+
enhancedCode: null
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
if (!this._server.options.hideDSN) {
|
|
2244
|
+
if (parsed.args.RET) {
|
|
2245
|
+
const ret = parsed.args.RET.toUpperCase();
|
|
2246
|
+
if (ret !== "FULL" && ret !== "HDRS") {
|
|
2247
|
+
return {
|
|
2248
|
+
code: 501,
|
|
2249
|
+
message: "Invalid RET parameter value. Must be FULL or HDRS",
|
|
2250
|
+
enhancedCode: null
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
return null;
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Applies validated MAIL FROM parameters to session envelope
|
|
2259
|
+
* @param {Object} parsed - Parsed address command with args
|
|
2260
|
+
*/
|
|
2261
|
+
_applyMailParams(parsed) {
|
|
2262
|
+
if (parsed.args.BODY) {
|
|
2263
|
+
this.session.envelope.bodyType = parsed.args.BODY.toLowerCase();
|
|
2264
|
+
}
|
|
2265
|
+
if (parsed.args.SMTPUTF8 === true) {
|
|
2266
|
+
this.session.envelope.smtpUtf8 = true;
|
|
2267
|
+
}
|
|
2268
|
+
if (parsed.args.REQUIRETLS === true) {
|
|
2269
|
+
this.session.envelope.requireTLS = true;
|
|
2270
|
+
}
|
|
2271
|
+
if (!this._server.options.hideDSN) {
|
|
2272
|
+
if (parsed.args.RET) {
|
|
2273
|
+
this.session.envelope.dsn.ret = parsed.args.RET.toUpperCase();
|
|
2274
|
+
}
|
|
2275
|
+
if (parsed.args.ENVID) {
|
|
2276
|
+
this.session.envelope.dsn.envid = parsed.args.ENVID;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Processes MAIL FROM command, parses address and extra arguments
|
|
2282
|
+
*/
|
|
2283
|
+
handler_MAIL(command, callback) {
|
|
2284
|
+
let parsed = this._parseAddressCommand("mail from", command);
|
|
2285
|
+
this.emitConnection();
|
|
2286
|
+
if (!parsed) {
|
|
2287
|
+
this.send(501, "Error: Bad sender address syntax", "MAILBOX_SYNTAX_ERROR");
|
|
2288
|
+
return callback();
|
|
2289
|
+
}
|
|
2290
|
+
if (this.session.envelope.mailFrom) {
|
|
2291
|
+
this.send(503, "Error: nested MAIL command");
|
|
2292
|
+
return callback();
|
|
2293
|
+
}
|
|
2294
|
+
if (!this._server.options.hideSize && this._server.options.size && parsed.args.SIZE && Number(parsed.args.SIZE) > this._server.options.size) {
|
|
2295
|
+
this.send(552, "Error: message exceeds fixed maximum message size " + this._server.options.size, "SYSTEM_FULL");
|
|
2296
|
+
return callback();
|
|
2297
|
+
}
|
|
2298
|
+
const validationError = this._validateMailParams(parsed);
|
|
2299
|
+
if (validationError) {
|
|
2300
|
+
this.send(validationError.code, validationError.message, validationError.enhancedCode);
|
|
2301
|
+
return callback();
|
|
2302
|
+
}
|
|
2303
|
+
this._applyMailParams(parsed);
|
|
2304
|
+
this._server.onMailFrom(parsed, this.session, (err) => {
|
|
2305
|
+
if (err) {
|
|
2306
|
+
this.send(err.responseCode || 550, err.message);
|
|
2307
|
+
return callback();
|
|
2308
|
+
}
|
|
2309
|
+
this.session.envelope.mailFrom = parsed;
|
|
2310
|
+
this.send(250, "Accepted", "MAIL_FROM_OK");
|
|
2311
|
+
callback();
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Processes RCPT TO command, parses address and extra arguments
|
|
2316
|
+
*/
|
|
2317
|
+
handler_RCPT(command, callback) {
|
|
2318
|
+
let parsed = this._parseAddressCommand("rcpt to", command);
|
|
2319
|
+
if (!parsed || !parsed.address) {
|
|
2320
|
+
this.send(501, "Error: Bad recipient address syntax", "MAILBOX_SYNTAX_ERROR");
|
|
2321
|
+
return callback();
|
|
2322
|
+
}
|
|
2323
|
+
if (!this.session.envelope.mailFrom) {
|
|
2324
|
+
this.send(503, "Error: need MAIL command");
|
|
2325
|
+
return callback();
|
|
2326
|
+
}
|
|
2327
|
+
if (!this._server.options.hideDSN) {
|
|
2328
|
+
if (parsed.args.NOTIFY) {
|
|
2329
|
+
const notify = parsed.args.NOTIFY.toUpperCase();
|
|
2330
|
+
const validNotifyValues = ["NEVER", "SUCCESS", "FAILURE", "DELAY"];
|
|
2331
|
+
const notifyValues = notify.split(",");
|
|
2332
|
+
for (let value of notifyValues) {
|
|
2333
|
+
if (!validNotifyValues.includes(value)) {
|
|
2334
|
+
this.send(501, "Error: NOTIFY parameter must be NEVER, SUCCESS, FAILURE, or DELAY");
|
|
2335
|
+
return callback();
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
if (notifyValues.includes("NEVER") && notifyValues.length > 1) {
|
|
2339
|
+
this.send(501, "Error: NOTIFY=NEVER cannot be combined with other values");
|
|
2340
|
+
return callback();
|
|
2341
|
+
}
|
|
2342
|
+
parsed.dsn = parsed.dsn || {};
|
|
2343
|
+
parsed.dsn.notify = notifyValues;
|
|
2344
|
+
}
|
|
2345
|
+
if (parsed.args.ORCPT) {
|
|
2346
|
+
parsed.dsn = parsed.dsn || {};
|
|
2347
|
+
parsed.dsn.orcpt = parsed.args.ORCPT;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
this._server.onRcptTo(parsed, this.session, (err) => {
|
|
2351
|
+
if (err) {
|
|
2352
|
+
this.send(err.responseCode || 550, err.message);
|
|
2353
|
+
return callback();
|
|
2354
|
+
}
|
|
2355
|
+
for (let i = 0, len = this.session.envelope.rcptTo.length; i < len; i++) {
|
|
2356
|
+
if (this.session.envelope.rcptTo[i].address.toLowerCase() === parsed.address.toLowerCase()) {
|
|
2357
|
+
this.session.envelope.rcptTo[i] = parsed;
|
|
2358
|
+
parsed = false;
|
|
2359
|
+
break;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
if (parsed) {
|
|
2363
|
+
this.session.envelope.rcptTo.push(parsed);
|
|
2364
|
+
}
|
|
2365
|
+
this.send(250, "Accepted", "RCPT_TO_OK");
|
|
2366
|
+
callback();
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Processes DATA by forwarding incoming stream to the onData handler
|
|
2371
|
+
*/
|
|
2372
|
+
handler_DATA(command, callback) {
|
|
2373
|
+
if (!this.session.envelope.rcptTo.length) {
|
|
2374
|
+
this.send(503, "Error: need RCPT command");
|
|
2375
|
+
return callback();
|
|
2376
|
+
}
|
|
2377
|
+
if (!this._parser) {
|
|
2378
|
+
return callback();
|
|
2379
|
+
}
|
|
2380
|
+
this._dataStream = this._parser.startDataMode(this._server.options.size);
|
|
2381
|
+
let close = (err, message) => {
|
|
2382
|
+
let i, len;
|
|
2383
|
+
this._server.logger.debug(
|
|
2384
|
+
{
|
|
2385
|
+
tnx: "data",
|
|
2386
|
+
cid: this.id,
|
|
2387
|
+
bytes: this._parser.dataBytes,
|
|
2388
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
2389
|
+
},
|
|
2390
|
+
"C: <%s bytes of DATA>",
|
|
2391
|
+
this._parser.dataBytes
|
|
2392
|
+
);
|
|
2393
|
+
if (typeof this._dataStream === "object" && this._dataStream && this._dataStream.readable) {
|
|
2394
|
+
this._dataStream.removeAllListeners();
|
|
2395
|
+
}
|
|
2396
|
+
if (err) {
|
|
2397
|
+
if (this._server.options.lmtp) {
|
|
2398
|
+
for (i = 0, len = this.session.envelope.rcptTo.length; i < len; i++) {
|
|
2399
|
+
this.send(err.responseCode || 450, err.message);
|
|
2400
|
+
}
|
|
2401
|
+
} else {
|
|
2402
|
+
this.send(err.responseCode || 450, err.message);
|
|
2403
|
+
}
|
|
2404
|
+
} else if (Array.isArray(message)) {
|
|
2405
|
+
message.forEach((response) => {
|
|
2406
|
+
if (/Error\]$/i.test(Object.prototype.toString.call(response))) {
|
|
2407
|
+
this.send(response.responseCode || 450, response.message);
|
|
2408
|
+
} else {
|
|
2409
|
+
this.send(250, typeof response === "string" ? response : "OK: message accepted", "DATA_OK");
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2412
|
+
} else if (this._server.options.lmtp) {
|
|
2413
|
+
for (i = 0, len = this.session.envelope.rcptTo.length; i < len; i++) {
|
|
2414
|
+
this.send(250, typeof message === "string" ? message : "OK: message accepted", "DATA_OK");
|
|
2415
|
+
}
|
|
2416
|
+
} else {
|
|
2417
|
+
this.send(250, typeof message === "string" ? message : "OK: message queued", "DATA_OK");
|
|
2418
|
+
}
|
|
2419
|
+
this._transactionCounter++;
|
|
2420
|
+
this._unrecognizedCommands = 0;
|
|
2421
|
+
this._resetSession();
|
|
2422
|
+
if (typeof this._parser === "object" && this._parser) {
|
|
2423
|
+
this._parser.continue();
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
this._server.onData(this._dataStream, this.session, (err, message) => {
|
|
2427
|
+
if (typeof this._dataStream === "object" && this._dataStream && this._dataStream.readable) {
|
|
2428
|
+
this._dataStream.on("end", () => close(err, message));
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
close(err, message);
|
|
2432
|
+
});
|
|
2433
|
+
this.send(354, "End data with <CR><LF>.<CR><LF>");
|
|
2434
|
+
callback();
|
|
2435
|
+
}
|
|
2436
|
+
// Dummy handlers for some old sendmail specific commands
|
|
2437
|
+
/**
|
|
2438
|
+
* Processes sendmail WIZ command, upgrades to "wizard mode"
|
|
2439
|
+
*/
|
|
2440
|
+
handler_WIZ(command, callback) {
|
|
2441
|
+
let args = command.toString().trim().split(/\s+/);
|
|
2442
|
+
let password;
|
|
2443
|
+
args.shift();
|
|
2444
|
+
password = (args.shift() || "").toString();
|
|
2445
|
+
if (!password) {
|
|
2446
|
+
this.send(500, "You are no wizard!");
|
|
2447
|
+
return callback();
|
|
2448
|
+
}
|
|
2449
|
+
this.session.isWizard = true;
|
|
2450
|
+
this.send(200, "Please pass, oh mighty wizard");
|
|
2451
|
+
callback();
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Processes sendmail SHELL command, should return interactive shell but this is a dummy function
|
|
2455
|
+
* so no actual shell is provided to the client
|
|
2456
|
+
*/
|
|
2457
|
+
handler_SHELL(command, callback) {
|
|
2458
|
+
this._server.logger.info(
|
|
2459
|
+
{
|
|
2460
|
+
tnx: "shell",
|
|
2461
|
+
cid: this.id,
|
|
2462
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
2463
|
+
},
|
|
2464
|
+
"Client tried to invoke SHELL"
|
|
2465
|
+
);
|
|
2466
|
+
if (!this.session.isWizard) {
|
|
2467
|
+
this.send(500, "Mere mortals must not mutter that mantra");
|
|
2468
|
+
return callback();
|
|
2469
|
+
}
|
|
2470
|
+
this.send(500, "Error: Invoking shell is not allowed. This incident will be reported.");
|
|
2471
|
+
callback();
|
|
2472
|
+
}
|
|
2473
|
+
/**
|
|
2474
|
+
* Processes sendmail KILL command
|
|
2475
|
+
*/
|
|
2476
|
+
handler_KILL(command, callback) {
|
|
2477
|
+
this._server.logger.info(
|
|
2478
|
+
{
|
|
2479
|
+
tnx: "kill",
|
|
2480
|
+
cid: this.id,
|
|
2481
|
+
user: this.session.user && this.session.user.username || this.session.user
|
|
2482
|
+
},
|
|
2483
|
+
"Client tried to invoke KILL"
|
|
2484
|
+
);
|
|
2485
|
+
this.send(500, "Can not kill Mom");
|
|
2486
|
+
callback();
|
|
2487
|
+
}
|
|
2488
|
+
upgrade(callback, secureCallback) {
|
|
2489
|
+
this._socket.unpipe(this._parser);
|
|
2490
|
+
this._upgrading = true;
|
|
2491
|
+
setImmediate(callback);
|
|
2492
|
+
let secureSocket;
|
|
2493
|
+
let onError = (err) => {
|
|
2494
|
+
const meta = {};
|
|
2495
|
+
if (secureSocket) {
|
|
2496
|
+
meta.tlsProtocol = secureSocket.getProtocol();
|
|
2497
|
+
}
|
|
2498
|
+
meta.protocol = "smtp";
|
|
2499
|
+
meta.stage = "connect";
|
|
2500
|
+
if (err) {
|
|
2501
|
+
err.meta = meta;
|
|
2502
|
+
}
|
|
2503
|
+
if (err && /SSL[23]*_GET_CLIENT_HELLO|ssl[23]*_read_bytes|ssl_bytes_to_cipher_list/i.test(err.message)) {
|
|
2504
|
+
let message = err.message;
|
|
2505
|
+
err.message = "Failed to establish TLS session";
|
|
2506
|
+
err.responseCode = 500;
|
|
2507
|
+
err.code = err.code || "TLSError";
|
|
2508
|
+
meta.message = message;
|
|
2509
|
+
}
|
|
2510
|
+
if (!err || !err.message) {
|
|
2511
|
+
err = new Error("Socket closed while initiating TLS");
|
|
2512
|
+
err.responseCode = 500;
|
|
2513
|
+
err.code = "SocketError";
|
|
2514
|
+
err.report = false;
|
|
2515
|
+
err.meta = meta;
|
|
2516
|
+
}
|
|
2517
|
+
this._onError(err);
|
|
2518
|
+
};
|
|
2519
|
+
let secureContext = this._server.secureContext.get("*");
|
|
2520
|
+
let socketOptions = {
|
|
2521
|
+
secureContext,
|
|
2522
|
+
isServer: true,
|
|
2523
|
+
server: this._server.server,
|
|
2524
|
+
SNICallback: this._server.options.SNICallback
|
|
2525
|
+
};
|
|
2526
|
+
["requestCert", "rejectUnauthorized", "NPNProtocols", "SNICallback", "session", "requestOCSP"].forEach((key) => {
|
|
2527
|
+
if (key in this._server.options) {
|
|
2528
|
+
socketOptions[key] = this._server.options[key];
|
|
2529
|
+
}
|
|
2530
|
+
});
|
|
2531
|
+
this._socket.removeAllListeners();
|
|
2532
|
+
this._socket.on("error", (err) => this._onError(err));
|
|
2533
|
+
secureSocket = new tls.TLSSocket(this._socket, socketOptions);
|
|
2534
|
+
secureSocket.once("close", (hadError) => this._onCloseEvent(hadError));
|
|
2535
|
+
secureSocket.once("error", (err) => onError(err));
|
|
2536
|
+
secureSocket.once("_tlsError", (err) => onError(err));
|
|
2537
|
+
secureSocket.once("clientError", (err) => onError(err));
|
|
2538
|
+
secureSocket.setTimeout(this._server.options.socketTimeout || SOCKET_TIMEOUT, () => this._onTimeout());
|
|
2539
|
+
secureSocket.on("secure", () => {
|
|
2540
|
+
this.session.secure = this.secure = true;
|
|
2541
|
+
this._socket = secureSocket;
|
|
2542
|
+
this._upgrading = false;
|
|
2543
|
+
this.session.tlsOptions = this.tlsOptions = this._socket.getCipher();
|
|
2544
|
+
this.session.servername = this._socket.servername;
|
|
2545
|
+
let cipher = this.session.tlsOptions && this.session.tlsOptions.name;
|
|
2546
|
+
this._server.logger.info(
|
|
2547
|
+
{
|
|
2548
|
+
tnx: "starttls",
|
|
2549
|
+
cid: this.id,
|
|
2550
|
+
user: this.session.user && this.session.user.username || this.session.user,
|
|
2551
|
+
cipher
|
|
2552
|
+
},
|
|
2553
|
+
"Connection upgraded to TLS using",
|
|
2554
|
+
cipher || "N/A"
|
|
2555
|
+
);
|
|
2556
|
+
this._server.onSecure(this._socket, this.session, (err) => {
|
|
2557
|
+
if (err) {
|
|
2558
|
+
return this._onError(err);
|
|
2559
|
+
}
|
|
2560
|
+
this._socket.pipe(this._parser);
|
|
2561
|
+
if (typeof secureCallback === "function") {
|
|
2562
|
+
secureCallback();
|
|
2563
|
+
}
|
|
2564
|
+
});
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
};
|
|
2568
|
+
module.exports.SMTPConnection = SMTPConnection;
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2572
|
+
// node_modules/smtp-server/lib/tls-options.js
|
|
2573
|
+
var require_tls_options = __commonJS({
|
|
2574
|
+
"node_modules/smtp-server/lib/tls-options.js"(exports, module) {
|
|
2575
|
+
"use strict";
|
|
2576
|
+
var crypto = __require("crypto");
|
|
2577
|
+
module.exports = getTLSOptions;
|
|
2578
|
+
var tlsDefaults = {
|
|
2579
|
+
// pregenerated default certificates for localhost
|
|
2580
|
+
// obviusly, do not use in production
|
|
2581
|
+
key: "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA6Z5Qqhw+oWfhtEiMHE32Ht94mwTBpAfjt3vPpX8M7DMCTwHs\n1xcXvQ4lQ3rwreDTOWdoJeEEy7gMxXqH0jw0WfBx+8IIJU69xstOyT7FRFDvA1yT\nRXY2yt9K5s6SKken/ebMfmZR+03ND4UFsDzkz0FfgcjrkXmrMF5Eh5UXX/+9YHeU\nxlp0gMAt+/SumSmgCaysxZLjLpd4uXz+X+JVxsk1ACg1NoEO7lWJC/3WBP7MIcu2\nwVsMd2XegLT0gWYfT1/jsIH64U/mS/SVXC9QhxMl9Yfko2kx1OiYhDxhHs75RJZh\nrNRxgfiwgSb50Gw4NAQaDIxr/DJPdLhgnpY6UQIDAQABAoIBAE+tfzWFjJbgJ0ql\ns6Ozs020Sh4U8TZQuonJ4HhBbNbiTtdDgNObPK1uNadeNtgW5fOeIRdKN6iDjVeN\nAuXhQrmqGDYVZ1HSGUfD74sTrZQvRlWPLWtzdhybK6Css41YAyPFo9k4bJ2ZW2b/\np4EEQ8WsNja9oBpttMU6YYUchGxo1gujN8hmfDdXUQx3k5Xwx4KA68dveJ8GasIt\nd+0Jd/FVwCyyx8HTiF1FF8QZYQeAXxbXJgLBuCsMQJghlcpBEzWkscBR3Ap1U0Zi\n4oat8wrPZGCblaA6rNkRUVbc/+Vw0stnuJ/BLHbPxyBs6w495yBSjBqUWZMvljNz\nm9/aK0ECgYEA9oVIVAd0enjSVIyAZNbw11ElidzdtBkeIJdsxqhmXzeIFZbB39Gd\nbjtAVclVbq5mLsI1j22ER2rHA4Ygkn6vlLghK3ZMPxZa57oJtmL3oP0RvOjE4zRV\ndzKexNGo9gU/x9SQbuyOmuauvAYhXZxeLpv+lEfsZTqqrvPUGeBiEQcCgYEA8poG\nWVnykWuTmCe0bMmvYDsWpAEiZnFLDaKcSbz3O7RMGbPy1cypmqSinIYUpURBT/WY\nwVPAGtjkuTXtd1Cy58m7PqziB7NNWMcsMGj+lWrTPZ6hCHIBcAImKEPpd+Y9vGJX\noatFJguqAGOz7rigBq6iPfeQOCWpmprNAuah++cCgYB1gcybOT59TnA7mwlsh8Qf\nbm+tSllnin2A3Y0dGJJLmsXEPKtHS7x2Gcot2h1d98V/TlWHe5WNEUmx1VJbYgXB\npw8wj2ACxl4ojNYqWPxegaLd4DpRbtW6Tqe9e47FTnU7hIggR6QmFAWAXI+09l8y\namssNShqjE9lu5YDi6BTKwKBgQCuIlKGViLfsKjrYSyHnajNWPxiUhIgGBf4PI0T\n/Jg1ea/aDykxv0rKHnw9/5vYGIsM2st/kR7l5mMecg/2Qa145HsLfMptHo1ZOPWF\n9gcuttPTegY6aqKPhGthIYX2MwSDMM+X0ri6m0q2JtqjclAjG7yG4CjbtGTt/UlE\nWMlSZwKBgQDslGeLUnkW0bsV5EG3AKRUyPKz/6DVNuxaIRRhOeWVKV101claqXAT\nwXOpdKrvkjZbT4AzcNrlGtRl3l7dEVXTu+dN7/ZieJRu7zaStlAQZkIyP9O3DdQ3\nrIcetQpfrJ1cAqz6Ng0pD0mh77vQ13WG1BBmDFa2A9BuzLoBituf4g==\n-----END RSA PRIVATE KEY-----",
|
|
2582
|
+
cert: "-----BEGIN CERTIFICATE-----\nMIICpDCCAYwCCQCuVLVKVTXnAjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwls\nb2NhbGhvc3QwHhcNMTUwMjEyMTEzMjU4WhcNMjUwMjA5MTEzMjU4WjAUMRIwEAYD\nVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDp\nnlCqHD6hZ+G0SIwcTfYe33ibBMGkB+O3e8+lfwzsMwJPAezXFxe9DiVDevCt4NM5\nZ2gl4QTLuAzFeofSPDRZ8HH7wgglTr3Gy07JPsVEUO8DXJNFdjbK30rmzpIqR6f9\n5sx+ZlH7Tc0PhQWwPOTPQV+ByOuReaswXkSHlRdf/71gd5TGWnSAwC379K6ZKaAJ\nrKzFkuMul3i5fP5f4lXGyTUAKDU2gQ7uVYkL/dYE/swhy7bBWwx3Zd6AtPSBZh9P\nX+OwgfrhT+ZL9JVcL1CHEyX1h+SjaTHU6JiEPGEezvlElmGs1HGB+LCBJvnQbDg0\nBBoMjGv8Mk90uGCeljpRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABXm8GPdY0sc\nmMUFlgDqFzcevjdGDce0QfboR+M7WDdm512Jz2SbRTgZD/4na42ThODOZz9z1AcM\nzLgx2ZNZzVhBz0odCU4JVhOCEks/OzSyKeGwjIb4JAY7dh+Kju1+6MNfQJ4r1Hza\nSVXH0+JlpJDaJ73NQ2JyfqELmJ1mTcptkA/N6rQWhlzycTBSlfogwf9xawgVPATP\n4AuwgjHl12JI2HVVs1gu65Y3slvaHRCr0B4+Kg1GYNLLcbFcK+NEHrHmPxy9TnTh\nZwp1dsNQU+Xkylz8IUANWSLHYZOMtN2e5SKIdwTtl5C8YxveuY8YKb1gDExnMraT\nVGXQDqPleug=\n-----END CERTIFICATE-----",
|
|
2583
|
+
honorCipherOrder: true,
|
|
2584
|
+
requestOCSP: false,
|
|
2585
|
+
sessionIdContext: crypto.createHash("sha1").update(process.argv.join(" ")).digest("hex").slice(0, 32),
|
|
2586
|
+
minVersion: "TLSv1"
|
|
2587
|
+
// sadly there are very old SMTP clients out there
|
|
2588
|
+
};
|
|
2589
|
+
function getTLSOptions(opts) {
|
|
2590
|
+
return Object.assign({}, tlsDefaults, opts || {});
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
});
|
|
2594
|
+
|
|
2595
|
+
// node_modules/nodemailer/lib/fetch/cookies.js
|
|
2596
|
+
var require_cookies = __commonJS({
|
|
2597
|
+
"node_modules/nodemailer/lib/fetch/cookies.js"(exports, module) {
|
|
2598
|
+
"use strict";
|
|
2599
|
+
var urllib = __require("url");
|
|
2600
|
+
var SESSION_TIMEOUT = 1800;
|
|
2601
|
+
var Cookies = class {
|
|
2602
|
+
constructor(options) {
|
|
2603
|
+
this.options = options || {};
|
|
2604
|
+
this.cookies = [];
|
|
2605
|
+
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Stores a cookie string to the cookie storage
|
|
2608
|
+
*
|
|
2609
|
+
* @param {String} cookieStr Value from the 'Set-Cookie:' header
|
|
2610
|
+
* @param {String} url Current URL
|
|
2611
|
+
*/
|
|
2612
|
+
set(cookieStr, url) {
|
|
2613
|
+
const urlparts = urllib.parse(url || "");
|
|
2614
|
+
const cookie = this.parse(cookieStr);
|
|
2615
|
+
let domain;
|
|
2616
|
+
if (cookie.domain) {
|
|
2617
|
+
domain = cookie.domain.replace(/^\./, "");
|
|
2618
|
+
if (
|
|
2619
|
+
// can't be valid if the requested domain is shorter than current hostname
|
|
2620
|
+
urlparts.hostname.length < domain.length || // prefix domains with dot to be sure that partial matches are not used
|
|
2621
|
+
("." + urlparts.hostname).substr(-domain.length + 1) !== "." + domain
|
|
2622
|
+
) {
|
|
2623
|
+
cookie.domain = urlparts.hostname;
|
|
2624
|
+
}
|
|
2625
|
+
} else {
|
|
2626
|
+
cookie.domain = urlparts.hostname;
|
|
2627
|
+
}
|
|
2628
|
+
if (!cookie.path) {
|
|
2629
|
+
cookie.path = this.getPath(urlparts.pathname);
|
|
2630
|
+
}
|
|
2631
|
+
if (!cookie.expires) {
|
|
2632
|
+
cookie.expires = new Date(Date.now() + (Number(this.options.sessionTimeout || SESSION_TIMEOUT) || SESSION_TIMEOUT) * 1e3);
|
|
2633
|
+
}
|
|
2634
|
+
return this.add(cookie);
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Returns cookie string for the 'Cookie:' header.
|
|
2638
|
+
*
|
|
2639
|
+
* @param {String} url URL to check for
|
|
2640
|
+
* @returns {String} Cookie header or empty string if no matches were found
|
|
2641
|
+
*/
|
|
2642
|
+
get(url) {
|
|
2643
|
+
return this.list(url).map((cookie) => cookie.name + "=" + cookie.value).join("; ");
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Lists all valied cookie objects for the specified URL
|
|
2647
|
+
*
|
|
2648
|
+
* @param {String} url URL to check for
|
|
2649
|
+
* @returns {Array} An array of cookie objects
|
|
2650
|
+
*/
|
|
2651
|
+
list(url) {
|
|
2652
|
+
const result = [];
|
|
2653
|
+
for (let i = this.cookies.length - 1; i >= 0; i--) {
|
|
2654
|
+
const cookie = this.cookies[i];
|
|
2655
|
+
if (this.isExpired(cookie)) {
|
|
2656
|
+
this.cookies.splice(i, 1);
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
if (this.match(cookie, url)) {
|
|
2660
|
+
result.unshift(cookie);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
return result;
|
|
2664
|
+
}
|
|
2665
|
+
/**
|
|
2666
|
+
* Parses cookie string from the 'Set-Cookie:' header
|
|
2667
|
+
*
|
|
2668
|
+
* @param {String} cookieStr String from the 'Set-Cookie:' header
|
|
2669
|
+
* @returns {Object} Cookie object
|
|
2670
|
+
*/
|
|
2671
|
+
parse(cookieStr) {
|
|
2672
|
+
const cookie = {};
|
|
2673
|
+
(cookieStr || "").toString().split(";").forEach((cookiePart) => {
|
|
2674
|
+
const valueParts = cookiePart.split("=");
|
|
2675
|
+
const key = valueParts.shift().trim().toLowerCase();
|
|
2676
|
+
let value = valueParts.join("=").trim();
|
|
2677
|
+
let domain;
|
|
2678
|
+
if (!key) {
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
switch (key) {
|
|
2682
|
+
case "expires":
|
|
2683
|
+
value = new Date(value);
|
|
2684
|
+
if (value.toString() !== "Invalid Date") {
|
|
2685
|
+
cookie.expires = value;
|
|
2686
|
+
}
|
|
2687
|
+
break;
|
|
2688
|
+
case "path":
|
|
2689
|
+
cookie.path = value;
|
|
2690
|
+
break;
|
|
2691
|
+
case "domain":
|
|
2692
|
+
domain = value.toLowerCase();
|
|
2693
|
+
if (domain.length && domain.charAt(0) !== ".") {
|
|
2694
|
+
domain = "." + domain;
|
|
2695
|
+
}
|
|
2696
|
+
cookie.domain = domain;
|
|
2697
|
+
break;
|
|
2698
|
+
case "max-age":
|
|
2699
|
+
cookie.expires = new Date(Date.now() + (Number(value) || 0) * 1e3);
|
|
2700
|
+
break;
|
|
2701
|
+
case "secure":
|
|
2702
|
+
cookie.secure = true;
|
|
2703
|
+
break;
|
|
2704
|
+
case "httponly":
|
|
2705
|
+
cookie.httponly = true;
|
|
2706
|
+
break;
|
|
2707
|
+
default:
|
|
2708
|
+
if (!cookie.name) {
|
|
2709
|
+
cookie.name = key;
|
|
2710
|
+
cookie.value = value;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2714
|
+
return cookie;
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Checks if a cookie object is valid for a specified URL
|
|
2718
|
+
*
|
|
2719
|
+
* @param {Object} cookie Cookie object
|
|
2720
|
+
* @param {String} url URL to check for
|
|
2721
|
+
* @returns {Boolean} true if cookie is valid for specifiec URL
|
|
2722
|
+
*/
|
|
2723
|
+
match(cookie, url) {
|
|
2724
|
+
const urlparts = urllib.parse(url || "");
|
|
2725
|
+
if (urlparts.hostname !== cookie.domain && (cookie.domain.charAt(0) !== "." || ("." + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)) {
|
|
2726
|
+
return false;
|
|
2727
|
+
}
|
|
2728
|
+
const path = this.getPath(urlparts.pathname);
|
|
2729
|
+
if (path.substr(0, cookie.path.length) !== cookie.path) {
|
|
2730
|
+
return false;
|
|
2731
|
+
}
|
|
2732
|
+
if (cookie.secure && urlparts.protocol !== "https:") {
|
|
2733
|
+
return false;
|
|
2734
|
+
}
|
|
2735
|
+
return true;
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Adds (or updates/removes if needed) a cookie object to the cookie storage
|
|
2739
|
+
*
|
|
2740
|
+
* @param {Object} cookie Cookie value to be stored
|
|
2741
|
+
*/
|
|
2742
|
+
add(cookie) {
|
|
2743
|
+
if (!cookie || !cookie.name) {
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
for (let i = 0, len = this.cookies.length; i < len; i++) {
|
|
2747
|
+
if (this.compare(this.cookies[i], cookie)) {
|
|
2748
|
+
if (this.isExpired(cookie)) {
|
|
2749
|
+
this.cookies.splice(i, 1);
|
|
2750
|
+
return false;
|
|
2751
|
+
}
|
|
2752
|
+
this.cookies[i] = cookie;
|
|
2753
|
+
return true;
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
if (!this.isExpired(cookie)) {
|
|
2757
|
+
this.cookies.push(cookie);
|
|
2758
|
+
}
|
|
2759
|
+
return true;
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Checks if two cookie objects are the same
|
|
2763
|
+
*
|
|
2764
|
+
* @param {Object} a Cookie to check against
|
|
2765
|
+
* @param {Object} b Cookie to check against
|
|
2766
|
+
* @returns {Boolean} True, if the cookies are the same
|
|
2767
|
+
*/
|
|
2768
|
+
compare(a, b) {
|
|
2769
|
+
return a.name === b.name && a.path === b.path && a.domain === b.domain && a.secure === b.secure && a.httponly === b.httponly;
|
|
2770
|
+
}
|
|
2771
|
+
/**
|
|
2772
|
+
* Checks if a cookie is expired
|
|
2773
|
+
*
|
|
2774
|
+
* @param {Object} cookie Cookie object to check against
|
|
2775
|
+
* @returns {Boolean} True, if the cookie is expired
|
|
2776
|
+
*/
|
|
2777
|
+
isExpired(cookie) {
|
|
2778
|
+
return cookie.expires && cookie.expires < /* @__PURE__ */ new Date() || !cookie.value;
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Returns normalized cookie path for an URL path argument
|
|
2782
|
+
*
|
|
2783
|
+
* @param {String} pathname
|
|
2784
|
+
* @returns {String} Normalized path
|
|
2785
|
+
*/
|
|
2786
|
+
getPath(pathname) {
|
|
2787
|
+
let path = (pathname || "/").split("/");
|
|
2788
|
+
path.pop();
|
|
2789
|
+
path = path.join("/").trim();
|
|
2790
|
+
if (path.charAt(0) !== "/") {
|
|
2791
|
+
path = "/" + path;
|
|
2792
|
+
}
|
|
2793
|
+
if (path.substr(-1) !== "/") {
|
|
2794
|
+
path += "/";
|
|
2795
|
+
}
|
|
2796
|
+
return path;
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
module.exports = Cookies;
|
|
2800
|
+
}
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
// node_modules/nodemailer/package.json
|
|
2804
|
+
var require_package = __commonJS({
|
|
2805
|
+
"node_modules/nodemailer/package.json"(exports, module) {
|
|
2806
|
+
module.exports = {
|
|
2807
|
+
name: "nodemailer",
|
|
2808
|
+
version: "8.0.5",
|
|
2809
|
+
description: "Easy as cake e-mail sending from your Node.js applications",
|
|
2810
|
+
main: "lib/nodemailer.js",
|
|
2811
|
+
scripts: {
|
|
2812
|
+
test: "node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js",
|
|
2813
|
+
"test:coverage": "c8 node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js",
|
|
2814
|
+
format: 'prettier --write "**/*.{js,json,md}"',
|
|
2815
|
+
"format:check": 'prettier --check "**/*.{js,json,md}"',
|
|
2816
|
+
lint: "eslint .",
|
|
2817
|
+
"lint:fix": "eslint . --fix",
|
|
2818
|
+
update: "rm -rf node_modules/ package-lock.json && ncu -u && npm install",
|
|
2819
|
+
"test:syntax": 'docker run --rm -v "$PWD:/app:ro" -w /app node:6-alpine node test/syntax-compat.js'
|
|
2820
|
+
},
|
|
2821
|
+
repository: {
|
|
2822
|
+
type: "git",
|
|
2823
|
+
url: "https://github.com/nodemailer/nodemailer.git"
|
|
2824
|
+
},
|
|
2825
|
+
keywords: [
|
|
2826
|
+
"Nodemailer"
|
|
2827
|
+
],
|
|
2828
|
+
author: "Andris Reinman",
|
|
2829
|
+
license: "MIT-0",
|
|
2830
|
+
bugs: {
|
|
2831
|
+
url: "https://github.com/nodemailer/nodemailer/issues"
|
|
2832
|
+
},
|
|
2833
|
+
homepage: "https://nodemailer.com/",
|
|
2834
|
+
devDependencies: {
|
|
2835
|
+
"@aws-sdk/client-sesv2": "3.1025.0",
|
|
2836
|
+
bunyan: "1.8.15",
|
|
2837
|
+
c8: "11.0.0",
|
|
2838
|
+
eslint: "10.2.0",
|
|
2839
|
+
"eslint-config-prettier": "10.1.8",
|
|
2840
|
+
globals: "17.4.0",
|
|
2841
|
+
libbase64: "1.3.0",
|
|
2842
|
+
libmime: "5.3.7",
|
|
2843
|
+
libqp: "2.1.1",
|
|
2844
|
+
"nodemailer-ntlm-auth": "1.0.4",
|
|
2845
|
+
prettier: "3.8.1",
|
|
2846
|
+
proxy: "1.0.2",
|
|
2847
|
+
"proxy-test-server": "1.0.0",
|
|
2848
|
+
"smtp-server": "3.18.3"
|
|
2849
|
+
},
|
|
2850
|
+
engines: {
|
|
2851
|
+
node: ">=6.0.0"
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
});
|
|
2856
|
+
|
|
2857
|
+
// node_modules/nodemailer/lib/errors.js
|
|
2858
|
+
var require_errors = __commonJS({
|
|
2859
|
+
"node_modules/nodemailer/lib/errors.js"(exports, module) {
|
|
2860
|
+
"use strict";
|
|
2861
|
+
var ERROR_CODES = {
|
|
2862
|
+
// Connection errors
|
|
2863
|
+
ECONNECTION: "Connection closed unexpectedly",
|
|
2864
|
+
ETIMEDOUT: "Connection or operation timed out",
|
|
2865
|
+
ESOCKET: "Socket-level error",
|
|
2866
|
+
EDNS: "DNS resolution failed",
|
|
2867
|
+
// TLS/Security errors
|
|
2868
|
+
ETLS: "TLS handshake or STARTTLS failed",
|
|
2869
|
+
EREQUIRETLS: "REQUIRETLS not supported by server (RFC 8689)",
|
|
2870
|
+
// Protocol errors
|
|
2871
|
+
EPROTOCOL: "Invalid SMTP server response",
|
|
2872
|
+
EENVELOPE: "Invalid mail envelope (sender or recipients)",
|
|
2873
|
+
EMESSAGE: "Message delivery error",
|
|
2874
|
+
ESTREAM: "Stream processing error",
|
|
2875
|
+
// Authentication errors
|
|
2876
|
+
EAUTH: "Authentication failed",
|
|
2877
|
+
ENOAUTH: "Authentication credentials not provided",
|
|
2878
|
+
EOAUTH2: "OAuth2 token generation or refresh error",
|
|
2879
|
+
// Resource errors
|
|
2880
|
+
EMAXLIMIT: "Pool resource limit reached (max messages per connection)",
|
|
2881
|
+
// Transport-specific errors
|
|
2882
|
+
ESENDMAIL: "Sendmail command error",
|
|
2883
|
+
ESES: "AWS SES transport error",
|
|
2884
|
+
// Configuration and access errors
|
|
2885
|
+
ECONFIG: "Invalid configuration",
|
|
2886
|
+
EPROXY: "Proxy connection error",
|
|
2887
|
+
EFILEACCESS: "File access rejected (disableFileAccess is set)",
|
|
2888
|
+
EURLACCESS: "URL access rejected (disableUrlAccess is set)",
|
|
2889
|
+
EFETCH: "HTTP fetch error"
|
|
2890
|
+
};
|
|
2891
|
+
module.exports = { ERROR_CODES };
|
|
2892
|
+
for (const code of Object.keys(ERROR_CODES)) {
|
|
2893
|
+
module.exports[code] = code;
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
});
|
|
2897
|
+
|
|
2898
|
+
// node_modules/nodemailer/lib/fetch/index.js
|
|
2899
|
+
var require_fetch = __commonJS({
|
|
2900
|
+
"node_modules/nodemailer/lib/fetch/index.js"(exports, module) {
|
|
2901
|
+
"use strict";
|
|
2902
|
+
var http = __require("http");
|
|
2903
|
+
var https = __require("https");
|
|
2904
|
+
var urllib = __require("url");
|
|
2905
|
+
var zlib = __require("zlib");
|
|
2906
|
+
var { PassThrough } = __require("stream");
|
|
2907
|
+
var Cookies = require_cookies();
|
|
2908
|
+
var packageData = require_package();
|
|
2909
|
+
var net = __require("net");
|
|
2910
|
+
var errors2 = require_errors();
|
|
2911
|
+
var MAX_REDIRECTS = 5;
|
|
2912
|
+
module.exports = function(url, options) {
|
|
2913
|
+
return nmfetch(url, options);
|
|
2914
|
+
};
|
|
2915
|
+
module.exports.Cookies = Cookies;
|
|
2916
|
+
function nmfetch(url, options) {
|
|
2917
|
+
options = options || {};
|
|
2918
|
+
options.fetchRes = options.fetchRes || new PassThrough();
|
|
2919
|
+
options.cookies = options.cookies || new Cookies();
|
|
2920
|
+
options.redirects = options.redirects || 0;
|
|
2921
|
+
options.maxRedirects = isNaN(options.maxRedirects) ? MAX_REDIRECTS : options.maxRedirects;
|
|
2922
|
+
if (options.cookie) {
|
|
2923
|
+
[].concat(options.cookie || []).forEach((cookie) => {
|
|
2924
|
+
options.cookies.set(cookie, url);
|
|
2925
|
+
});
|
|
2926
|
+
options.cookie = false;
|
|
2927
|
+
}
|
|
2928
|
+
const fetchRes = options.fetchRes;
|
|
2929
|
+
const parsed = urllib.parse(url);
|
|
2930
|
+
let method = (options.method || "").toString().trim().toUpperCase() || "GET";
|
|
2931
|
+
let finished = false;
|
|
2932
|
+
let cookies;
|
|
2933
|
+
let body;
|
|
2934
|
+
const handler = parsed.protocol === "https:" ? https : http;
|
|
2935
|
+
const headers = {
|
|
2936
|
+
"accept-encoding": "gzip,deflate",
|
|
2937
|
+
"user-agent": "nodemailer/" + packageData.version
|
|
2938
|
+
};
|
|
2939
|
+
Object.keys(options.headers || {}).forEach((key) => {
|
|
2940
|
+
headers[key.toLowerCase().trim()] = options.headers[key];
|
|
2941
|
+
});
|
|
2942
|
+
if (options.userAgent) {
|
|
2943
|
+
headers["user-agent"] = options.userAgent;
|
|
2944
|
+
}
|
|
2945
|
+
if (parsed.auth) {
|
|
2946
|
+
headers.Authorization = "Basic " + Buffer.from(parsed.auth).toString("base64");
|
|
2947
|
+
}
|
|
2948
|
+
if (cookies = options.cookies.get(url)) {
|
|
2949
|
+
headers.cookie = cookies;
|
|
2950
|
+
}
|
|
2951
|
+
if (options.body) {
|
|
2952
|
+
if (options.contentType !== false) {
|
|
2953
|
+
headers["Content-Type"] = options.contentType || "application/x-www-form-urlencoded";
|
|
2954
|
+
}
|
|
2955
|
+
if (typeof options.body.pipe === "function") {
|
|
2956
|
+
headers["Transfer-Encoding"] = "chunked";
|
|
2957
|
+
body = options.body;
|
|
2958
|
+
body.on("error", (err) => {
|
|
2959
|
+
if (finished) {
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
finished = true;
|
|
2963
|
+
err.code = errors2.EFETCH;
|
|
2964
|
+
err.sourceUrl = url;
|
|
2965
|
+
fetchRes.emit("error", err);
|
|
2966
|
+
});
|
|
2967
|
+
} else {
|
|
2968
|
+
if (options.body instanceof Buffer) {
|
|
2969
|
+
body = options.body;
|
|
2970
|
+
} else if (typeof options.body === "object") {
|
|
2971
|
+
try {
|
|
2972
|
+
body = Buffer.from(
|
|
2973
|
+
Object.keys(options.body).map((key) => {
|
|
2974
|
+
const value = options.body[key].toString().trim();
|
|
2975
|
+
return encodeURIComponent(key) + "=" + encodeURIComponent(value);
|
|
2976
|
+
}).join("&")
|
|
2977
|
+
);
|
|
2978
|
+
} catch (E) {
|
|
2979
|
+
if (finished) {
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
finished = true;
|
|
2983
|
+
E.code = errors2.EFETCH;
|
|
2984
|
+
E.sourceUrl = url;
|
|
2985
|
+
fetchRes.emit("error", E);
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
} else {
|
|
2989
|
+
body = Buffer.from(options.body.toString().trim());
|
|
2990
|
+
}
|
|
2991
|
+
headers["Content-Type"] = options.contentType || "application/x-www-form-urlencoded";
|
|
2992
|
+
headers["Content-Length"] = body.length;
|
|
2993
|
+
}
|
|
2994
|
+
method = (options.method || "").toString().trim().toUpperCase() || "POST";
|
|
2995
|
+
}
|
|
2996
|
+
let req;
|
|
2997
|
+
const reqOptions = {
|
|
2998
|
+
method,
|
|
2999
|
+
host: parsed.hostname,
|
|
3000
|
+
path: parsed.path,
|
|
3001
|
+
port: parsed.port ? parsed.port : parsed.protocol === "https:" ? 443 : 80,
|
|
3002
|
+
headers,
|
|
3003
|
+
rejectUnauthorized: false,
|
|
3004
|
+
agent: false
|
|
3005
|
+
};
|
|
3006
|
+
if (options.tls) {
|
|
3007
|
+
Object.assign(reqOptions, options.tls);
|
|
3008
|
+
}
|
|
3009
|
+
if (parsed.protocol === "https:" && parsed.hostname && parsed.hostname !== reqOptions.host && !net.isIP(parsed.hostname) && !reqOptions.servername) {
|
|
3010
|
+
reqOptions.servername = parsed.hostname;
|
|
3011
|
+
}
|
|
3012
|
+
try {
|
|
3013
|
+
req = handler.request(reqOptions);
|
|
3014
|
+
} catch (E) {
|
|
3015
|
+
finished = true;
|
|
3016
|
+
setImmediate(() => {
|
|
3017
|
+
E.code = errors2.EFETCH;
|
|
3018
|
+
E.sourceUrl = url;
|
|
3019
|
+
fetchRes.emit("error", E);
|
|
3020
|
+
});
|
|
3021
|
+
return fetchRes;
|
|
3022
|
+
}
|
|
3023
|
+
if (options.timeout) {
|
|
3024
|
+
req.setTimeout(options.timeout, () => {
|
|
3025
|
+
if (finished) {
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
finished = true;
|
|
3029
|
+
req.abort();
|
|
3030
|
+
const err = new Error("Request Timeout");
|
|
3031
|
+
err.code = errors2.EFETCH;
|
|
3032
|
+
err.sourceUrl = url;
|
|
3033
|
+
fetchRes.emit("error", err);
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
3036
|
+
req.on("error", (err) => {
|
|
3037
|
+
if (finished) {
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
finished = true;
|
|
3041
|
+
err.code = errors2.EFETCH;
|
|
3042
|
+
err.sourceUrl = url;
|
|
3043
|
+
fetchRes.emit("error", err);
|
|
3044
|
+
});
|
|
3045
|
+
req.on("response", (res) => {
|
|
3046
|
+
let inflate;
|
|
3047
|
+
if (finished) {
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
switch (res.headers["content-encoding"]) {
|
|
3051
|
+
case "gzip":
|
|
3052
|
+
case "deflate":
|
|
3053
|
+
inflate = zlib.createUnzip();
|
|
3054
|
+
break;
|
|
3055
|
+
}
|
|
3056
|
+
if (res.headers["set-cookie"]) {
|
|
3057
|
+
[].concat(res.headers["set-cookie"] || []).forEach((cookie) => {
|
|
3058
|
+
options.cookies.set(cookie, url);
|
|
3059
|
+
});
|
|
3060
|
+
}
|
|
3061
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
3062
|
+
options.redirects++;
|
|
3063
|
+
if (options.redirects > options.maxRedirects) {
|
|
3064
|
+
finished = true;
|
|
3065
|
+
const err = new Error("Maximum redirect count exceeded");
|
|
3066
|
+
err.code = errors2.EFETCH;
|
|
3067
|
+
err.sourceUrl = url;
|
|
3068
|
+
fetchRes.emit("error", err);
|
|
3069
|
+
req.abort();
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
options.method = "GET";
|
|
3073
|
+
options.body = false;
|
|
3074
|
+
return nmfetch(urllib.resolve(url, res.headers.location), options);
|
|
3075
|
+
}
|
|
3076
|
+
fetchRes.statusCode = res.statusCode;
|
|
3077
|
+
fetchRes.headers = res.headers;
|
|
3078
|
+
if (res.statusCode >= 300 && !options.allowErrorResponse) {
|
|
3079
|
+
finished = true;
|
|
3080
|
+
const err = new Error("Invalid status code " + res.statusCode);
|
|
3081
|
+
err.code = errors2.EFETCH;
|
|
3082
|
+
err.sourceUrl = url;
|
|
3083
|
+
fetchRes.emit("error", err);
|
|
3084
|
+
req.abort();
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
res.on("error", (err) => {
|
|
3088
|
+
if (finished) {
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
finished = true;
|
|
3092
|
+
err.code = errors2.EFETCH;
|
|
3093
|
+
err.sourceUrl = url;
|
|
3094
|
+
fetchRes.emit("error", err);
|
|
3095
|
+
req.abort();
|
|
3096
|
+
});
|
|
3097
|
+
if (inflate) {
|
|
3098
|
+
res.pipe(inflate).pipe(fetchRes);
|
|
3099
|
+
inflate.on("error", (err) => {
|
|
3100
|
+
if (finished) {
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
finished = true;
|
|
3104
|
+
err.code = errors2.EFETCH;
|
|
3105
|
+
err.sourceUrl = url;
|
|
3106
|
+
fetchRes.emit("error", err);
|
|
3107
|
+
req.abort();
|
|
3108
|
+
});
|
|
3109
|
+
} else {
|
|
3110
|
+
res.pipe(fetchRes);
|
|
3111
|
+
}
|
|
3112
|
+
});
|
|
3113
|
+
setImmediate(() => {
|
|
3114
|
+
if (body) {
|
|
3115
|
+
try {
|
|
3116
|
+
if (typeof body.pipe === "function") {
|
|
3117
|
+
return body.pipe(req);
|
|
3118
|
+
}
|
|
3119
|
+
req.write(body);
|
|
3120
|
+
} catch (err) {
|
|
3121
|
+
finished = true;
|
|
3122
|
+
err.code = errors2.EFETCH;
|
|
3123
|
+
err.sourceUrl = url;
|
|
3124
|
+
fetchRes.emit("error", err);
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
req.end();
|
|
3129
|
+
});
|
|
3130
|
+
return fetchRes;
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
});
|
|
3134
|
+
|
|
3135
|
+
// node_modules/nodemailer/lib/shared/index.js
|
|
3136
|
+
var require_shared = __commonJS({
|
|
3137
|
+
"node_modules/nodemailer/lib/shared/index.js"(exports, module) {
|
|
3138
|
+
"use strict";
|
|
3139
|
+
var urllib = __require("url");
|
|
3140
|
+
var util = __require("util");
|
|
3141
|
+
var fs = __require("fs");
|
|
3142
|
+
var nmfetch = require_fetch();
|
|
3143
|
+
var dns = __require("dns");
|
|
3144
|
+
var net = __require("net");
|
|
3145
|
+
var os = __require("os");
|
|
3146
|
+
var DNS_TTL = 5 * 60 * 1e3;
|
|
3147
|
+
var CACHE_CLEANUP_INTERVAL = 30 * 1e3;
|
|
3148
|
+
var MAX_CACHE_SIZE = 1e3;
|
|
3149
|
+
var lastCacheCleanup = 0;
|
|
3150
|
+
module.exports._lastCacheCleanup = () => lastCacheCleanup;
|
|
3151
|
+
module.exports._resetCacheCleanup = () => {
|
|
3152
|
+
lastCacheCleanup = 0;
|
|
3153
|
+
};
|
|
3154
|
+
var networkInterfaces;
|
|
3155
|
+
try {
|
|
3156
|
+
networkInterfaces = os.networkInterfaces();
|
|
3157
|
+
} catch (_err) {
|
|
3158
|
+
}
|
|
3159
|
+
module.exports.networkInterfaces = networkInterfaces;
|
|
3160
|
+
var isFamilySupported = (family, allowInternal) => {
|
|
3161
|
+
const ifaces = module.exports.networkInterfaces;
|
|
3162
|
+
if (!ifaces) {
|
|
3163
|
+
return true;
|
|
3164
|
+
}
|
|
3165
|
+
return Object.keys(ifaces).map((key) => ifaces[key]).reduce((acc, val) => acc.concat(val), []).filter((i) => !i.internal || allowInternal).some((i) => i.family === "IPv" + family || i.family === family);
|
|
3166
|
+
};
|
|
3167
|
+
var resolve = (family, hostname, options, callback) => {
|
|
3168
|
+
options = options || {};
|
|
3169
|
+
if (!isFamilySupported(family, options.allowInternalNetworkInterfaces)) {
|
|
3170
|
+
return callback(null, []);
|
|
3171
|
+
}
|
|
3172
|
+
const dnsResolver = dns.Resolver ? new dns.Resolver(options) : dns;
|
|
3173
|
+
dnsResolver["resolve" + family](hostname, (err, addresses) => {
|
|
3174
|
+
if (err) {
|
|
3175
|
+
switch (err.code) {
|
|
3176
|
+
case dns.NODATA:
|
|
3177
|
+
case dns.NOTFOUND:
|
|
3178
|
+
case dns.NOTIMP:
|
|
3179
|
+
case dns.SERVFAIL:
|
|
3180
|
+
case dns.CONNREFUSED:
|
|
3181
|
+
case dns.REFUSED:
|
|
3182
|
+
case "EAI_AGAIN":
|
|
3183
|
+
return callback(null, []);
|
|
3184
|
+
}
|
|
3185
|
+
return callback(err);
|
|
3186
|
+
}
|
|
3187
|
+
return callback(null, Array.isArray(addresses) ? addresses : [].concat(addresses || []));
|
|
3188
|
+
});
|
|
3189
|
+
};
|
|
3190
|
+
var dnsCache = module.exports.dnsCache = /* @__PURE__ */ new Map();
|
|
3191
|
+
var formatDNSValue = (value, extra) => {
|
|
3192
|
+
if (!value) {
|
|
3193
|
+
return Object.assign({}, extra || {});
|
|
3194
|
+
}
|
|
3195
|
+
const addresses = value.addresses || [];
|
|
3196
|
+
const host = addresses.length > 0 ? addresses[Math.floor(Math.random() * addresses.length)] : null;
|
|
3197
|
+
return Object.assign(
|
|
3198
|
+
{
|
|
3199
|
+
servername: value.servername,
|
|
3200
|
+
host,
|
|
3201
|
+
// Include all addresses for connection fallback support
|
|
3202
|
+
_addresses: addresses
|
|
3203
|
+
},
|
|
3204
|
+
extra || {}
|
|
3205
|
+
);
|
|
3206
|
+
};
|
|
3207
|
+
module.exports.resolveHostname = (options, callback) => {
|
|
3208
|
+
options = options || {};
|
|
3209
|
+
if (!options.host && options.servername) {
|
|
3210
|
+
options.host = options.servername;
|
|
3211
|
+
}
|
|
3212
|
+
if (!options.host || net.isIP(options.host)) {
|
|
3213
|
+
const value = {
|
|
3214
|
+
addresses: [options.host],
|
|
3215
|
+
servername: options.servername || false
|
|
3216
|
+
};
|
|
3217
|
+
return callback(
|
|
3218
|
+
null,
|
|
3219
|
+
formatDNSValue(value, {
|
|
3220
|
+
cached: false
|
|
3221
|
+
})
|
|
3222
|
+
);
|
|
3223
|
+
}
|
|
3224
|
+
let cached;
|
|
3225
|
+
if (dnsCache.has(options.host)) {
|
|
3226
|
+
cached = dnsCache.get(options.host);
|
|
3227
|
+
const now = Date.now();
|
|
3228
|
+
if (now - lastCacheCleanup > CACHE_CLEANUP_INTERVAL) {
|
|
3229
|
+
lastCacheCleanup = now;
|
|
3230
|
+
for (const [host, entry] of dnsCache.entries()) {
|
|
3231
|
+
if (entry.expires && entry.expires < now) {
|
|
3232
|
+
dnsCache.delete(host);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
if (dnsCache.size > MAX_CACHE_SIZE) {
|
|
3236
|
+
const toDelete = Math.floor(MAX_CACHE_SIZE * 0.1);
|
|
3237
|
+
const keys = Array.from(dnsCache.keys()).slice(0, toDelete);
|
|
3238
|
+
keys.forEach((key) => dnsCache.delete(key));
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
if (!cached.expires || cached.expires >= now) {
|
|
3242
|
+
return callback(
|
|
3243
|
+
null,
|
|
3244
|
+
formatDNSValue(cached.value, {
|
|
3245
|
+
cached: true
|
|
3246
|
+
})
|
|
3247
|
+
);
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
let ipv4Addresses = [];
|
|
3251
|
+
let ipv6Addresses = [];
|
|
3252
|
+
let ipv4Error = null;
|
|
3253
|
+
let ipv6Error = null;
|
|
3254
|
+
resolve(4, options.host, options, (err, addresses) => {
|
|
3255
|
+
if (err) {
|
|
3256
|
+
ipv4Error = err;
|
|
3257
|
+
} else {
|
|
3258
|
+
ipv4Addresses = addresses || [];
|
|
3259
|
+
}
|
|
3260
|
+
resolve(6, options.host, options, (err2, addresses2) => {
|
|
3261
|
+
if (err2) {
|
|
3262
|
+
ipv6Error = err2;
|
|
3263
|
+
} else {
|
|
3264
|
+
ipv6Addresses = addresses2 || [];
|
|
3265
|
+
}
|
|
3266
|
+
const allAddresses = ipv4Addresses.concat(ipv6Addresses);
|
|
3267
|
+
if (allAddresses.length) {
|
|
3268
|
+
const value = {
|
|
3269
|
+
addresses: allAddresses,
|
|
3270
|
+
servername: options.servername || options.host
|
|
3271
|
+
};
|
|
3272
|
+
dnsCache.set(options.host, {
|
|
3273
|
+
value,
|
|
3274
|
+
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
3275
|
+
});
|
|
3276
|
+
return callback(
|
|
3277
|
+
null,
|
|
3278
|
+
formatDNSValue(value, {
|
|
3279
|
+
cached: false
|
|
3280
|
+
})
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3283
|
+
if (ipv4Error && ipv6Error) {
|
|
3284
|
+
if (cached) {
|
|
3285
|
+
dnsCache.set(options.host, {
|
|
3286
|
+
value: cached.value,
|
|
3287
|
+
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
3288
|
+
});
|
|
3289
|
+
return callback(
|
|
3290
|
+
null,
|
|
3291
|
+
formatDNSValue(cached.value, {
|
|
3292
|
+
cached: true,
|
|
3293
|
+
error: ipv4Error
|
|
3294
|
+
})
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
try {
|
|
3299
|
+
dns.lookup(options.host, { all: true }, (err3, addresses3) => {
|
|
3300
|
+
if (err3) {
|
|
3301
|
+
if (cached) {
|
|
3302
|
+
dnsCache.set(options.host, {
|
|
3303
|
+
value: cached.value,
|
|
3304
|
+
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
3305
|
+
});
|
|
3306
|
+
return callback(
|
|
3307
|
+
null,
|
|
3308
|
+
formatDNSValue(cached.value, {
|
|
3309
|
+
cached: true,
|
|
3310
|
+
error: err3
|
|
3311
|
+
})
|
|
3312
|
+
);
|
|
3313
|
+
}
|
|
3314
|
+
return callback(err3);
|
|
3315
|
+
}
|
|
3316
|
+
const supportedAddresses = addresses3 ? addresses3.filter((addr) => isFamilySupported(addr.family)).map((addr) => addr.address) : [];
|
|
3317
|
+
if (addresses3 && addresses3.length && !supportedAddresses.length) {
|
|
3318
|
+
console.warn(`Failed to resolve IPv${addresses3[0].family} addresses with current network`);
|
|
3319
|
+
}
|
|
3320
|
+
if (!supportedAddresses.length && cached) {
|
|
3321
|
+
return callback(
|
|
3322
|
+
null,
|
|
3323
|
+
formatDNSValue(cached.value, {
|
|
3324
|
+
cached: true
|
|
3325
|
+
})
|
|
3326
|
+
);
|
|
3327
|
+
}
|
|
3328
|
+
const value = {
|
|
3329
|
+
addresses: supportedAddresses.length ? supportedAddresses : [options.host],
|
|
3330
|
+
servername: options.servername || options.host
|
|
3331
|
+
};
|
|
3332
|
+
dnsCache.set(options.host, {
|
|
3333
|
+
value,
|
|
3334
|
+
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
3335
|
+
});
|
|
3336
|
+
return callback(
|
|
3337
|
+
null,
|
|
3338
|
+
formatDNSValue(value, {
|
|
3339
|
+
cached: false
|
|
3340
|
+
})
|
|
3341
|
+
);
|
|
3342
|
+
});
|
|
3343
|
+
} catch (lookupErr) {
|
|
3344
|
+
if (cached) {
|
|
3345
|
+
dnsCache.set(options.host, {
|
|
3346
|
+
value: cached.value,
|
|
3347
|
+
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
3348
|
+
});
|
|
3349
|
+
return callback(
|
|
3350
|
+
null,
|
|
3351
|
+
formatDNSValue(cached.value, {
|
|
3352
|
+
cached: true,
|
|
3353
|
+
error: lookupErr
|
|
3354
|
+
})
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
return callback(ipv4Error || ipv6Error || lookupErr);
|
|
3358
|
+
}
|
|
3359
|
+
});
|
|
3360
|
+
});
|
|
3361
|
+
};
|
|
3362
|
+
module.exports.parseConnectionUrl = (str) => {
|
|
3363
|
+
str = str || "";
|
|
3364
|
+
const options = {};
|
|
3365
|
+
const url = urllib.parse(str, true);
|
|
3366
|
+
switch (url.protocol) {
|
|
3367
|
+
case "smtp:":
|
|
3368
|
+
options.secure = false;
|
|
3369
|
+
break;
|
|
3370
|
+
case "smtps:":
|
|
3371
|
+
options.secure = true;
|
|
3372
|
+
break;
|
|
3373
|
+
case "direct:":
|
|
3374
|
+
options.direct = true;
|
|
3375
|
+
break;
|
|
3376
|
+
}
|
|
3377
|
+
if (!isNaN(url.port) && Number(url.port)) {
|
|
3378
|
+
options.port = Number(url.port);
|
|
3379
|
+
}
|
|
3380
|
+
if (url.hostname) {
|
|
3381
|
+
options.host = url.hostname;
|
|
3382
|
+
}
|
|
3383
|
+
if (url.auth) {
|
|
3384
|
+
const auth = url.auth.split(":");
|
|
3385
|
+
options.auth = {
|
|
3386
|
+
user: auth.shift(),
|
|
3387
|
+
pass: auth.join(":")
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
Object.keys(url.query || {}).forEach((key) => {
|
|
3391
|
+
let obj = options;
|
|
3392
|
+
let lKey = key;
|
|
3393
|
+
let value = url.query[key];
|
|
3394
|
+
if (!isNaN(value)) {
|
|
3395
|
+
value = Number(value);
|
|
3396
|
+
}
|
|
3397
|
+
switch (value) {
|
|
3398
|
+
case "true":
|
|
3399
|
+
value = true;
|
|
3400
|
+
break;
|
|
3401
|
+
case "false":
|
|
3402
|
+
value = false;
|
|
3403
|
+
break;
|
|
3404
|
+
}
|
|
3405
|
+
if (key.indexOf("tls.") === 0) {
|
|
3406
|
+
lKey = key.substr(4);
|
|
3407
|
+
if (!options.tls) {
|
|
3408
|
+
options.tls = {};
|
|
3409
|
+
}
|
|
3410
|
+
obj = options.tls;
|
|
3411
|
+
} else if (key.indexOf(".") >= 0) {
|
|
3412
|
+
return;
|
|
3413
|
+
}
|
|
3414
|
+
if (!(lKey in obj)) {
|
|
3415
|
+
obj[lKey] = value;
|
|
3416
|
+
}
|
|
3417
|
+
});
|
|
3418
|
+
return options;
|
|
3419
|
+
};
|
|
3420
|
+
module.exports._logFunc = (logger, level, defaults, data, message, ...args) => {
|
|
3421
|
+
const entry = Object.assign({}, defaults || {}, data || {});
|
|
3422
|
+
delete entry.level;
|
|
3423
|
+
logger[level](entry, message, ...args);
|
|
3424
|
+
};
|
|
3425
|
+
module.exports.getLogger = (options, defaults) => {
|
|
3426
|
+
options = options || {};
|
|
3427
|
+
const response = {};
|
|
3428
|
+
const levels = ["trace", "debug", "info", "warn", "error", "fatal"];
|
|
3429
|
+
if (!options.logger) {
|
|
3430
|
+
levels.forEach((level) => {
|
|
3431
|
+
response[level] = () => false;
|
|
3432
|
+
});
|
|
3433
|
+
return response;
|
|
3434
|
+
}
|
|
3435
|
+
const logger = options.logger === true ? createDefaultLogger(levels) : options.logger;
|
|
3436
|
+
levels.forEach((level) => {
|
|
3437
|
+
response[level] = (data, message, ...args) => {
|
|
3438
|
+
module.exports._logFunc(logger, level, defaults, data, message, ...args);
|
|
3439
|
+
};
|
|
3440
|
+
});
|
|
3441
|
+
return response;
|
|
3442
|
+
};
|
|
3443
|
+
module.exports.callbackPromise = (resolve2, reject) => function() {
|
|
3444
|
+
const args = Array.from(arguments);
|
|
3445
|
+
const err = args.shift();
|
|
3446
|
+
if (err) {
|
|
3447
|
+
reject(err);
|
|
3448
|
+
} else {
|
|
3449
|
+
resolve2(...args);
|
|
3450
|
+
}
|
|
3451
|
+
};
|
|
3452
|
+
module.exports.parseDataURI = (uri) => {
|
|
3453
|
+
if (typeof uri !== "string") {
|
|
3454
|
+
return null;
|
|
3455
|
+
}
|
|
3456
|
+
if (!uri.startsWith("data:")) {
|
|
3457
|
+
return null;
|
|
3458
|
+
}
|
|
3459
|
+
const commaPos = uri.indexOf(",");
|
|
3460
|
+
if (commaPos === -1) {
|
|
3461
|
+
return null;
|
|
3462
|
+
}
|
|
3463
|
+
const data = uri.substring(commaPos + 1);
|
|
3464
|
+
const metaStr = uri.substring("data:".length, commaPos);
|
|
3465
|
+
let encoding;
|
|
3466
|
+
const metaEntries = metaStr.split(";");
|
|
3467
|
+
if (metaEntries.length > 0) {
|
|
3468
|
+
const lastEntry = metaEntries[metaEntries.length - 1].toLowerCase().trim();
|
|
3469
|
+
if (["base64", "utf8", "utf-8"].includes(lastEntry) && lastEntry.indexOf("=") === -1) {
|
|
3470
|
+
encoding = lastEntry;
|
|
3471
|
+
metaEntries.pop();
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
const contentType = metaEntries.length > 0 ? metaEntries.shift() : "application/octet-stream";
|
|
3475
|
+
const params = {};
|
|
3476
|
+
for (let i = 0; i < metaEntries.length; i++) {
|
|
3477
|
+
const entry = metaEntries[i];
|
|
3478
|
+
const sepPos = entry.indexOf("=");
|
|
3479
|
+
if (sepPos > 0) {
|
|
3480
|
+
const key = entry.substring(0, sepPos).trim();
|
|
3481
|
+
const value = entry.substring(sepPos + 1).trim();
|
|
3482
|
+
if (key) {
|
|
3483
|
+
params[key] = value;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
let bufferData;
|
|
3488
|
+
try {
|
|
3489
|
+
if (encoding === "base64") {
|
|
3490
|
+
bufferData = Buffer.from(data, "base64");
|
|
3491
|
+
} else {
|
|
3492
|
+
try {
|
|
3493
|
+
bufferData = Buffer.from(decodeURIComponent(data));
|
|
3494
|
+
} catch (_decodeError) {
|
|
3495
|
+
bufferData = Buffer.from(data);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
} catch (_bufferError) {
|
|
3499
|
+
bufferData = Buffer.alloc(0);
|
|
3500
|
+
}
|
|
3501
|
+
return {
|
|
3502
|
+
data: bufferData,
|
|
3503
|
+
encoding: encoding || null,
|
|
3504
|
+
contentType: contentType || "application/octet-stream",
|
|
3505
|
+
params
|
|
3506
|
+
};
|
|
3507
|
+
};
|
|
3508
|
+
module.exports.resolveContent = (data, key, callback) => {
|
|
3509
|
+
let promise;
|
|
3510
|
+
if (!callback) {
|
|
3511
|
+
promise = new Promise((resolve2, reject) => {
|
|
3512
|
+
callback = module.exports.callbackPromise(resolve2, reject);
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
let content = data && data[key] && data[key].content || data[key];
|
|
3516
|
+
const encoding = (typeof data[key] === "object" && data[key].encoding || "utf8").toString().toLowerCase().replace(/[-_\s]/g, "");
|
|
3517
|
+
if (!content) {
|
|
3518
|
+
return callback(null, content);
|
|
3519
|
+
}
|
|
3520
|
+
if (typeof content === "object") {
|
|
3521
|
+
if (typeof content.pipe === "function") {
|
|
3522
|
+
return resolveStream(content, (err, value) => {
|
|
3523
|
+
if (err) {
|
|
3524
|
+
return callback(err);
|
|
3525
|
+
}
|
|
3526
|
+
if (data[key].content) {
|
|
3527
|
+
data[key].content = value;
|
|
3528
|
+
} else {
|
|
3529
|
+
data[key] = value;
|
|
3530
|
+
}
|
|
3531
|
+
callback(null, value);
|
|
3532
|
+
});
|
|
3533
|
+
} else if (/^https?:\/\//i.test(content.path || content.href)) {
|
|
3534
|
+
return resolveStream(nmfetch(content.path || content.href), callback);
|
|
3535
|
+
} else if (/^data:/i.test(content.path || content.href)) {
|
|
3536
|
+
const parsedDataUri = module.exports.parseDataURI(content.path || content.href);
|
|
3537
|
+
if (!parsedDataUri || !parsedDataUri.data) {
|
|
3538
|
+
return callback(null, Buffer.from(0));
|
|
3539
|
+
}
|
|
3540
|
+
return callback(null, parsedDataUri.data);
|
|
3541
|
+
} else if (content.path) {
|
|
3542
|
+
return resolveStream(fs.createReadStream(content.path), callback);
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
if (typeof data[key].content === "string" && !["utf8", "usascii", "ascii"].includes(encoding)) {
|
|
3546
|
+
content = Buffer.from(data[key].content, encoding);
|
|
3547
|
+
}
|
|
3548
|
+
setImmediate(() => callback(null, content));
|
|
3549
|
+
return promise;
|
|
3550
|
+
};
|
|
3551
|
+
module.exports.assign = function() {
|
|
3552
|
+
const args = Array.from(arguments);
|
|
3553
|
+
const target = args.shift() || {};
|
|
3554
|
+
args.forEach((source) => {
|
|
3555
|
+
Object.keys(source || {}).forEach((key) => {
|
|
3556
|
+
if (["tls", "auth"].includes(key) && source[key] && typeof source[key] === "object") {
|
|
3557
|
+
target[key] = Object.assign(target[key] || {}, source[key]);
|
|
3558
|
+
} else {
|
|
3559
|
+
target[key] = source[key];
|
|
3560
|
+
}
|
|
3561
|
+
});
|
|
3562
|
+
});
|
|
3563
|
+
return target;
|
|
3564
|
+
};
|
|
3565
|
+
module.exports.encodeXText = (str) => {
|
|
3566
|
+
if (!/[^\x21-\x2A\x2C-\x3C\x3E-\x7E]/.test(str)) {
|
|
3567
|
+
return str;
|
|
3568
|
+
}
|
|
3569
|
+
const buf = Buffer.from(str);
|
|
3570
|
+
let result = "";
|
|
3571
|
+
for (let i = 0, len = buf.length; i < len; i++) {
|
|
3572
|
+
const c = buf[i];
|
|
3573
|
+
if (c < 33 || c > 126 || c === 43 || c === 61) {
|
|
3574
|
+
result += "+" + (c < 16 ? "0" : "") + c.toString(16).toUpperCase();
|
|
3575
|
+
} else {
|
|
3576
|
+
result += String.fromCharCode(c);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
return result;
|
|
3580
|
+
};
|
|
3581
|
+
function resolveStream(stream, callback) {
|
|
3582
|
+
let responded = false;
|
|
3583
|
+
const chunks = [];
|
|
3584
|
+
let chunklen = 0;
|
|
3585
|
+
stream.on("error", (err) => {
|
|
3586
|
+
if (responded) {
|
|
3587
|
+
return;
|
|
3588
|
+
}
|
|
3589
|
+
responded = true;
|
|
3590
|
+
callback(err);
|
|
3591
|
+
});
|
|
3592
|
+
stream.on("readable", () => {
|
|
3593
|
+
let chunk;
|
|
3594
|
+
while ((chunk = stream.read()) !== null) {
|
|
3595
|
+
chunks.push(chunk);
|
|
3596
|
+
chunklen += chunk.length;
|
|
3597
|
+
}
|
|
3598
|
+
});
|
|
3599
|
+
stream.on("end", () => {
|
|
3600
|
+
if (responded) {
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
responded = true;
|
|
3604
|
+
let value;
|
|
3605
|
+
try {
|
|
3606
|
+
value = Buffer.concat(chunks, chunklen);
|
|
3607
|
+
} catch (E) {
|
|
3608
|
+
return callback(E);
|
|
3609
|
+
}
|
|
3610
|
+
callback(null, value);
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
function createDefaultLogger(levels) {
|
|
3614
|
+
const levelMaxLen = levels.reduce((max, level) => Math.max(max, level.length), 0);
|
|
3615
|
+
const levelNames = /* @__PURE__ */ new Map();
|
|
3616
|
+
levels.forEach((level) => {
|
|
3617
|
+
let levelName = level.toUpperCase();
|
|
3618
|
+
if (levelName.length < levelMaxLen) {
|
|
3619
|
+
levelName += " ".repeat(levelMaxLen - levelName.length);
|
|
3620
|
+
}
|
|
3621
|
+
levelNames.set(level, levelName);
|
|
3622
|
+
});
|
|
3623
|
+
const print = (level, entry, message, ...args) => {
|
|
3624
|
+
let prefix = "";
|
|
3625
|
+
if (entry) {
|
|
3626
|
+
if (entry.tnx === "server") {
|
|
3627
|
+
prefix = "S: ";
|
|
3628
|
+
} else if (entry.tnx === "client") {
|
|
3629
|
+
prefix = "C: ";
|
|
3630
|
+
}
|
|
3631
|
+
if (entry.sid) {
|
|
3632
|
+
prefix = "[" + entry.sid + "] " + prefix;
|
|
3633
|
+
}
|
|
3634
|
+
if (entry.cid) {
|
|
3635
|
+
prefix = "[#" + entry.cid + "] " + prefix;
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
message = util.format(message, ...args);
|
|
3639
|
+
message.split(/\r?\n/).forEach((line) => {
|
|
3640
|
+
console.log("[%s] %s %s", (/* @__PURE__ */ new Date()).toISOString().substr(0, 19).replace(/T/, " "), levelNames.get(level), prefix + line);
|
|
3641
|
+
});
|
|
3642
|
+
};
|
|
3643
|
+
const logger = {};
|
|
3644
|
+
levels.forEach((level) => {
|
|
3645
|
+
logger[level] = print.bind(null, level);
|
|
3646
|
+
});
|
|
3647
|
+
return logger;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
});
|
|
3651
|
+
|
|
3652
|
+
// node_modules/smtp-server/lib/smtp-server.js
|
|
3653
|
+
var require_smtp_server = __commonJS({
|
|
3654
|
+
"node_modules/smtp-server/lib/smtp-server.js"(exports, module) {
|
|
3655
|
+
"use strict";
|
|
3656
|
+
var net = __require("net");
|
|
3657
|
+
var tls = __require("tls");
|
|
3658
|
+
var SMTPConnection = require_smtp_connection().SMTPConnection;
|
|
3659
|
+
var tlsOptions = require_tls_options();
|
|
3660
|
+
var EventEmitter = __require("events");
|
|
3661
|
+
var shared = require_shared();
|
|
3662
|
+
var punycode2 = (init_punycode_es6(), __toCommonJS(punycode_es6_exports));
|
|
3663
|
+
var crypto = __require("crypto");
|
|
3664
|
+
var CLOSE_TIMEOUT = 30 * 1e3;
|
|
3665
|
+
var SMTPServer2 = class extends EventEmitter {
|
|
3666
|
+
constructor(options) {
|
|
3667
|
+
super();
|
|
3668
|
+
this.options = options || {};
|
|
3669
|
+
this.updateSecureContext();
|
|
3670
|
+
this.options.disabledCommands = [].concat(this.options.disabledCommands || []).map((command) => (command || "").toString().toUpperCase().trim());
|
|
3671
|
+
this.options.authMethods = [].concat(this.options.authMethods || []).map((method) => (method || "").toString().toUpperCase().trim());
|
|
3672
|
+
if (!this.options.authMethods.length) {
|
|
3673
|
+
this.options.authMethods = ["LOGIN", "PLAIN"];
|
|
3674
|
+
}
|
|
3675
|
+
if (this.options.hideENHANCEDSTATUSCODES === void 0) {
|
|
3676
|
+
this.options.hideENHANCEDSTATUSCODES = true;
|
|
3677
|
+
}
|
|
3678
|
+
if (this.options.hideDSN === void 0) {
|
|
3679
|
+
this.options.hideDSN = true;
|
|
3680
|
+
}
|
|
3681
|
+
if (this.options.hideREQUIRETLS === void 0) {
|
|
3682
|
+
this.options.hideREQUIRETLS = true;
|
|
3683
|
+
}
|
|
3684
|
+
this.logger = shared.getLogger(this.options, {
|
|
3685
|
+
component: this.options.component || "smtp-server"
|
|
3686
|
+
});
|
|
3687
|
+
["onConnect", "onSecure", "onAuth", "onMailFrom", "onRcptTo", "onData", "onClose"].forEach((handler) => {
|
|
3688
|
+
if (typeof this.options[handler] === "function") {
|
|
3689
|
+
this[handler] = this.options[handler];
|
|
3690
|
+
}
|
|
3691
|
+
});
|
|
3692
|
+
this._closeTimeout = false;
|
|
3693
|
+
this.connections = /* @__PURE__ */ new Set();
|
|
3694
|
+
if (this.options.secure && !this.options.needsUpgrade) {
|
|
3695
|
+
this.server = net.createServer(this.options, (socket) => {
|
|
3696
|
+
this._handleProxy(socket, (err, socketOptions) => {
|
|
3697
|
+
if (err) {
|
|
3698
|
+
}
|
|
3699
|
+
if (this.options.secured) {
|
|
3700
|
+
return this.connect(socket, socketOptions);
|
|
3701
|
+
}
|
|
3702
|
+
this._upgrade(socket, (err2, tlsSocket) => {
|
|
3703
|
+
if (err2) {
|
|
3704
|
+
return this._onError(err2);
|
|
3705
|
+
}
|
|
3706
|
+
this.connect(tlsSocket, socketOptions);
|
|
3707
|
+
});
|
|
3708
|
+
});
|
|
3709
|
+
});
|
|
3710
|
+
} else {
|
|
3711
|
+
this.server = net.createServer(
|
|
3712
|
+
this.options,
|
|
3713
|
+
(socket) => this._handleProxy(socket, (err, socketOptions) => {
|
|
3714
|
+
if (err) {
|
|
3715
|
+
}
|
|
3716
|
+
this.connect(socket, socketOptions);
|
|
3717
|
+
})
|
|
3718
|
+
);
|
|
3719
|
+
}
|
|
3720
|
+
this._setListeners();
|
|
3721
|
+
}
|
|
3722
|
+
connect(socket, socketOptions) {
|
|
3723
|
+
let connection = new SMTPConnection(this, socket, socketOptions);
|
|
3724
|
+
this.connections.add(connection);
|
|
3725
|
+
connection.on("error", (err) => this._onError(err));
|
|
3726
|
+
connection.on("connect", (data) => this._onClientConnect(data));
|
|
3727
|
+
connection.init();
|
|
3728
|
+
}
|
|
3729
|
+
/**
|
|
3730
|
+
* Start listening on selected port and interface
|
|
3731
|
+
*/
|
|
3732
|
+
listen(...args) {
|
|
3733
|
+
return this.server.listen(...args);
|
|
3734
|
+
}
|
|
3735
|
+
/**
|
|
3736
|
+
* Closes the server
|
|
3737
|
+
*
|
|
3738
|
+
* @param {Function} callback Callback to run once the server is fully closed
|
|
3739
|
+
*/
|
|
3740
|
+
close(callback) {
|
|
3741
|
+
let connections = this.connections.size;
|
|
3742
|
+
let timeout = this.options.closeTimeout || CLOSE_TIMEOUT;
|
|
3743
|
+
this.server.close(() => {
|
|
3744
|
+
clearTimeout(this._closeTimeout);
|
|
3745
|
+
if (typeof callback === "function") {
|
|
3746
|
+
return callback();
|
|
3747
|
+
}
|
|
3748
|
+
});
|
|
3749
|
+
if (connections) {
|
|
3750
|
+
this.logger.info(
|
|
3751
|
+
{
|
|
3752
|
+
tnx: "close"
|
|
3753
|
+
},
|
|
3754
|
+
"Server closing with %s pending connection%s, waiting %s seconds before terminating",
|
|
3755
|
+
connections,
|
|
3756
|
+
connections !== 1 ? "s" : "",
|
|
3757
|
+
timeout / 1e3
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3760
|
+
this._closeTimeout = setTimeout(() => {
|
|
3761
|
+
connections = this.connections.size;
|
|
3762
|
+
if (connections) {
|
|
3763
|
+
this.logger.info(
|
|
3764
|
+
{
|
|
3765
|
+
tnx: "close"
|
|
3766
|
+
},
|
|
3767
|
+
"Closing %s pending connection%s to close the server",
|
|
3768
|
+
connections,
|
|
3769
|
+
connections !== 1 ? "s" : ""
|
|
3770
|
+
);
|
|
3771
|
+
this.connections.forEach((connection) => {
|
|
3772
|
+
connection.send(421, "Server shutting down");
|
|
3773
|
+
connection.close();
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
if (typeof callback === "function") {
|
|
3777
|
+
const realCallback = callback;
|
|
3778
|
+
callback = null;
|
|
3779
|
+
return realCallback();
|
|
3780
|
+
}
|
|
3781
|
+
}, timeout);
|
|
3782
|
+
this._closeTimeout.unref();
|
|
3783
|
+
}
|
|
3784
|
+
/**
|
|
3785
|
+
* Authentication handler. Override this
|
|
3786
|
+
*
|
|
3787
|
+
* @param {Object} auth Authentication options
|
|
3788
|
+
* @param {Function} callback Callback to run once the user is authenticated
|
|
3789
|
+
*/
|
|
3790
|
+
onAuth(auth, session, callback) {
|
|
3791
|
+
if (auth.method === "XOAUTH2") {
|
|
3792
|
+
return callback(null, {
|
|
3793
|
+
data: {
|
|
3794
|
+
status: "401",
|
|
3795
|
+
schemes: "bearer mac",
|
|
3796
|
+
scope: "https://mail.google.com/"
|
|
3797
|
+
}
|
|
3798
|
+
});
|
|
3799
|
+
}
|
|
3800
|
+
if (auth.method === "XCLIENT") {
|
|
3801
|
+
return callback();
|
|
3802
|
+
}
|
|
3803
|
+
return callback(null, {
|
|
3804
|
+
message: "Authentication not implemented"
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3807
|
+
onConnect(session, callback) {
|
|
3808
|
+
setImmediate(callback);
|
|
3809
|
+
}
|
|
3810
|
+
onMailFrom(address, session, callback) {
|
|
3811
|
+
setImmediate(callback);
|
|
3812
|
+
}
|
|
3813
|
+
onRcptTo(address, session, callback) {
|
|
3814
|
+
setImmediate(callback);
|
|
3815
|
+
}
|
|
3816
|
+
onSecure(socket, session, callback) {
|
|
3817
|
+
setImmediate(callback);
|
|
3818
|
+
}
|
|
3819
|
+
onData(stream, session, callback) {
|
|
3820
|
+
let chunklen = 0;
|
|
3821
|
+
stream.on("data", (chunk) => {
|
|
3822
|
+
chunklen += chunk.length;
|
|
3823
|
+
});
|
|
3824
|
+
stream.on("end", () => {
|
|
3825
|
+
this.logger.info(
|
|
3826
|
+
{
|
|
3827
|
+
tnx: "message",
|
|
3828
|
+
size: chunklen
|
|
3829
|
+
},
|
|
3830
|
+
"<received %s bytes>",
|
|
3831
|
+
chunklen
|
|
3832
|
+
);
|
|
3833
|
+
callback();
|
|
3834
|
+
});
|
|
3835
|
+
}
|
|
3836
|
+
onClose() {
|
|
3837
|
+
}
|
|
3838
|
+
updateSecureContext(options) {
|
|
3839
|
+
Object.keys(options || {}).forEach((key) => {
|
|
3840
|
+
this.options[key] = options[key];
|
|
3841
|
+
});
|
|
3842
|
+
let defaultTlsOptions = tlsOptions(this.options);
|
|
3843
|
+
this.secureContext = /* @__PURE__ */ new Map();
|
|
3844
|
+
this.secureContext.set("*", tls.createSecureContext(defaultTlsOptions));
|
|
3845
|
+
let ctxMap = this.options.sniOptions || {};
|
|
3846
|
+
if (typeof ctxMap.get === "function") {
|
|
3847
|
+
ctxMap.forEach((ctx, servername) => {
|
|
3848
|
+
this.secureContext.set(this._normalizeHostname(servername), tls.createSecureContext(tlsOptions(ctx)));
|
|
3849
|
+
});
|
|
3850
|
+
} else {
|
|
3851
|
+
Object.keys(ctxMap).forEach((servername) => {
|
|
3852
|
+
this.secureContext.set(this._normalizeHostname(servername), tls.createSecureContext(tlsOptions(ctxMap[servername])));
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
if (this.options.secure) {
|
|
3856
|
+
Object.keys(defaultTlsOptions || {}).forEach((key) => {
|
|
3857
|
+
if (!(key in this.options)) {
|
|
3858
|
+
this.options[key] = defaultTlsOptions[key];
|
|
3859
|
+
}
|
|
3860
|
+
});
|
|
3861
|
+
if (typeof this.options.SNICallback !== "function") {
|
|
3862
|
+
this.options.SNICallback = (servername, cb) => {
|
|
3863
|
+
cb(null, this.secureContext.get(servername));
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
// PRIVATE METHODS
|
|
3869
|
+
/**
|
|
3870
|
+
* Setup server event handlers
|
|
3871
|
+
*/
|
|
3872
|
+
_setListeners() {
|
|
3873
|
+
let server = this.server;
|
|
3874
|
+
server.once("listening", (...args) => this._onListening(...args));
|
|
3875
|
+
server.once("close", (...args) => this._onClose(server, ...args));
|
|
3876
|
+
server.on("error", (...args) => this._onError(...args));
|
|
3877
|
+
}
|
|
3878
|
+
/**
|
|
3879
|
+
* Called when server started listening
|
|
3880
|
+
*
|
|
3881
|
+
* @event
|
|
3882
|
+
*/
|
|
3883
|
+
_onListening() {
|
|
3884
|
+
let address = this.server.address();
|
|
3885
|
+
if (address === null) {
|
|
3886
|
+
address = { address: null, port: null, family: null };
|
|
3887
|
+
}
|
|
3888
|
+
this.logger.info(
|
|
3889
|
+
//
|
|
3890
|
+
{
|
|
3891
|
+
tnx: "listen",
|
|
3892
|
+
host: address.address,
|
|
3893
|
+
port: address.port,
|
|
3894
|
+
secure: !!this.options.secure,
|
|
3895
|
+
protocol: this.options.lmtp ? "LMTP" : "SMTP"
|
|
3896
|
+
},
|
|
3897
|
+
"%s%s Server listening on %s:%s",
|
|
3898
|
+
this.options.secure ? "Secure " : "",
|
|
3899
|
+
this.options.lmtp ? "LMTP" : "SMTP",
|
|
3900
|
+
address.family === "IPv4" ? address.address : "[" + address.address + "]",
|
|
3901
|
+
address.port
|
|
3902
|
+
);
|
|
3903
|
+
}
|
|
3904
|
+
/**
|
|
3905
|
+
* Called when server is closed
|
|
3906
|
+
*
|
|
3907
|
+
* @event
|
|
3908
|
+
*/
|
|
3909
|
+
_onClose(server) {
|
|
3910
|
+
this.logger.info(
|
|
3911
|
+
{
|
|
3912
|
+
tnx: "closed"
|
|
3913
|
+
},
|
|
3914
|
+
(this.options.lmtp ? "LMTP" : "SMTP") + " Server closed"
|
|
3915
|
+
);
|
|
3916
|
+
if (server !== this.server) {
|
|
3917
|
+
return;
|
|
3918
|
+
}
|
|
3919
|
+
this.emit("close");
|
|
3920
|
+
}
|
|
3921
|
+
/**
|
|
3922
|
+
* Called when an error occurs with the server
|
|
3923
|
+
*
|
|
3924
|
+
* @event
|
|
3925
|
+
*/
|
|
3926
|
+
_onError(err) {
|
|
3927
|
+
this.emit("error", err);
|
|
3928
|
+
}
|
|
3929
|
+
_handleProxy(socket, callback) {
|
|
3930
|
+
let socketOptions = {
|
|
3931
|
+
id: BigInt("0x" + crypto.randomBytes(10).toString("hex")).toString(32).padStart(16, "0")
|
|
3932
|
+
};
|
|
3933
|
+
if (!this.options.useProxy || Array.isArray(this.options.useProxy) && !this.options.useProxy.includes(socket.remoteAddress) && !this.options.useProxy.includes("*")) {
|
|
3934
|
+
socketOptions.ignore = this.options.ignoredHosts && this.options.ignoredHosts.includes(socket.remoteAddress);
|
|
3935
|
+
return setImmediate(() => callback(null, socketOptions));
|
|
3936
|
+
}
|
|
3937
|
+
let chunks = [];
|
|
3938
|
+
let chunklen = 0;
|
|
3939
|
+
let socketReader = () => {
|
|
3940
|
+
let chunk;
|
|
3941
|
+
while ((chunk = socket.read()) !== null) {
|
|
3942
|
+
for (let i = 0, len = chunk.length; i < len; i++) {
|
|
3943
|
+
let chr = chunk[i];
|
|
3944
|
+
if (chr === 10) {
|
|
3945
|
+
socket.removeListener("readable", socketReader);
|
|
3946
|
+
chunks.push(chunk.slice(0, i + 1));
|
|
3947
|
+
chunklen += i + 1;
|
|
3948
|
+
let remainder = chunk.slice(i + 1);
|
|
3949
|
+
if (remainder.length) {
|
|
3950
|
+
socket.unshift(remainder);
|
|
3951
|
+
}
|
|
3952
|
+
let header = Buffer.concat(chunks, chunklen).toString().trim();
|
|
3953
|
+
let params = (header || "").toString().split(" ");
|
|
3954
|
+
let commandName = params.shift().toUpperCase();
|
|
3955
|
+
if (commandName !== "PROXY") {
|
|
3956
|
+
try {
|
|
3957
|
+
socket.end("* BAD Invalid PROXY header\r\n");
|
|
3958
|
+
} catch {
|
|
3959
|
+
}
|
|
3960
|
+
return;
|
|
3961
|
+
}
|
|
3962
|
+
if (params[1]) {
|
|
3963
|
+
socketOptions.remoteAddress = params[1].trim().toLowerCase();
|
|
3964
|
+
socketOptions.ignore = this.options.ignoredHosts && this.options.ignoredHosts.includes(socketOptions.remoteAddress);
|
|
3965
|
+
try {
|
|
3966
|
+
if (!socketOptions.ignore) {
|
|
3967
|
+
this.logger.info(
|
|
3968
|
+
{
|
|
3969
|
+
tnx: "proxy",
|
|
3970
|
+
cid: socketOptions.id,
|
|
3971
|
+
proxy: params[1].trim().toLowerCase()
|
|
3972
|
+
},
|
|
3973
|
+
"[%s] PROXY from %s through %s (%s)",
|
|
3974
|
+
socketOptions.id,
|
|
3975
|
+
params[1].trim().toLowerCase(),
|
|
3976
|
+
params[2].trim().toLowerCase(),
|
|
3977
|
+
JSON.stringify(params)
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
} catch {
|
|
3981
|
+
socket.end("* BAD Invalid PROXY header\r\n");
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
if (params[3]) {
|
|
3985
|
+
socketOptions.remotePort = Number(params[3].trim()) || socketOptions.remotePort;
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
return callback(null, socketOptions);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
chunks.push(chunk);
|
|
3992
|
+
chunklen += chunk.length;
|
|
3993
|
+
}
|
|
3994
|
+
};
|
|
3995
|
+
socket.on("readable", socketReader);
|
|
3996
|
+
}
|
|
3997
|
+
/**
|
|
3998
|
+
* Called when a new connection is established. This might not be the same time the socket is opened
|
|
3999
|
+
*
|
|
4000
|
+
* @event
|
|
4001
|
+
*/
|
|
4002
|
+
_onClientConnect(data) {
|
|
4003
|
+
this.emit("connect", data);
|
|
4004
|
+
}
|
|
4005
|
+
/**
|
|
4006
|
+
* Normalize hostname
|
|
4007
|
+
*
|
|
4008
|
+
* @event
|
|
4009
|
+
*/
|
|
4010
|
+
_normalizeHostname(hostname) {
|
|
4011
|
+
try {
|
|
4012
|
+
hostname = punycode2.toUnicode((hostname || "").toString().trim()).toLowerCase();
|
|
4013
|
+
} catch (E) {
|
|
4014
|
+
this.logger.error(
|
|
4015
|
+
{
|
|
4016
|
+
tnx: "punycode"
|
|
4017
|
+
},
|
|
4018
|
+
'Failed to process punycode domain "%s". error=%s',
|
|
4019
|
+
hostname,
|
|
4020
|
+
E.message
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
return hostname;
|
|
4024
|
+
}
|
|
4025
|
+
_upgrade(socket, callback) {
|
|
4026
|
+
let socketOptions = {
|
|
4027
|
+
secureContext: this.secureContext.get("*"),
|
|
4028
|
+
isServer: true,
|
|
4029
|
+
server: this.server,
|
|
4030
|
+
SNICallback: (servername, cb) => {
|
|
4031
|
+
this.options.SNICallback(this._normalizeHostname(servername), (err, context) => {
|
|
4032
|
+
if (err) {
|
|
4033
|
+
this.logger.error(
|
|
4034
|
+
{
|
|
4035
|
+
tnx: "sni",
|
|
4036
|
+
servername,
|
|
4037
|
+
err
|
|
4038
|
+
},
|
|
4039
|
+
"Failed to fetch SNI context for servername %s",
|
|
4040
|
+
servername
|
|
4041
|
+
);
|
|
4042
|
+
}
|
|
4043
|
+
return cb(null, context || this.secureContext.get("*"));
|
|
4044
|
+
});
|
|
4045
|
+
}
|
|
4046
|
+
};
|
|
4047
|
+
let returned = false;
|
|
4048
|
+
let tlsSocket;
|
|
4049
|
+
let onError = (err) => {
|
|
4050
|
+
if (returned) {
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
returned = true;
|
|
4054
|
+
const meta = {};
|
|
4055
|
+
if (tlsSocket) {
|
|
4056
|
+
meta.tlsProtocol = tlsSocket.getProtocol();
|
|
4057
|
+
}
|
|
4058
|
+
meta.protocol = "smtp";
|
|
4059
|
+
meta.stage = "connect";
|
|
4060
|
+
meta.remoteAddress = socket.remoteAddress;
|
|
4061
|
+
if (err) {
|
|
4062
|
+
err.meta = meta;
|
|
4063
|
+
}
|
|
4064
|
+
if (err && /SSL[23]*_GET_CLIENT_HELLO|ssl[23]*_read_bytes|ssl_bytes_to_cipher_list/i.test(err.message)) {
|
|
4065
|
+
let message = err.message;
|
|
4066
|
+
err.message = "Failed to establish TLS session";
|
|
4067
|
+
err.responseCode = 500;
|
|
4068
|
+
err.code = err.code || "TLSError";
|
|
4069
|
+
meta.message = message;
|
|
4070
|
+
}
|
|
4071
|
+
if (!err || !err.message) {
|
|
4072
|
+
err = new Error("Socket closed while initiating TLS");
|
|
4073
|
+
err.responseCode = 500;
|
|
4074
|
+
err.code = "SocketError";
|
|
4075
|
+
err.report = false;
|
|
4076
|
+
err.meta = meta;
|
|
4077
|
+
}
|
|
4078
|
+
callback(err || new Error("Socket closed unexpectedly"));
|
|
4079
|
+
};
|
|
4080
|
+
socket.once("error", onError);
|
|
4081
|
+
tlsSocket = new tls.TLSSocket(socket, socketOptions);
|
|
4082
|
+
tlsSocket.once("close", onError);
|
|
4083
|
+
tlsSocket.once("error", onError);
|
|
4084
|
+
tlsSocket.once("_tlsError", onError);
|
|
4085
|
+
tlsSocket.once("clientError", onError);
|
|
4086
|
+
tlsSocket.once("tlsClientError", onError);
|
|
4087
|
+
tlsSocket.on("secure", () => {
|
|
4088
|
+
socket.removeListener("error", onError);
|
|
4089
|
+
tlsSocket.removeListener("close", onError);
|
|
4090
|
+
tlsSocket.removeListener("error", onError);
|
|
4091
|
+
tlsSocket.removeListener("_tlsError", onError);
|
|
4092
|
+
tlsSocket.removeListener("clientError", onError);
|
|
4093
|
+
tlsSocket.removeListener("tlsClientError", onError);
|
|
4094
|
+
if (returned) {
|
|
4095
|
+
try {
|
|
4096
|
+
tlsSocket.end();
|
|
4097
|
+
} catch {
|
|
4098
|
+
}
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
returned = true;
|
|
4102
|
+
return callback(null, tlsSocket);
|
|
4103
|
+
});
|
|
4104
|
+
}
|
|
4105
|
+
};
|
|
4106
|
+
module.exports.SMTPServer = SMTPServer2;
|
|
4107
|
+
}
|
|
4108
|
+
});
|
|
4109
|
+
|
|
4110
|
+
// src/smtp-server.ts
|
|
4111
|
+
var import_smtp_server = __toESM(require_smtp_server());
|
|
4112
|
+
var DEFAULT_SMTP_PORT = 2525;
|
|
4113
|
+
var DEFAULT_SMTP_HOST = "127.0.0.1";
|
|
4114
|
+
function resolveSmtpServerConfig(config = {}) {
|
|
4115
|
+
const { port = DEFAULT_SMTP_PORT, host = DEFAULT_SMTP_HOST, label, ...serverOptions } = config;
|
|
4116
|
+
return {
|
|
4117
|
+
...serverOptions,
|
|
4118
|
+
port,
|
|
4119
|
+
host,
|
|
4120
|
+
label
|
|
4121
|
+
};
|
|
4122
|
+
}
|
|
4123
|
+
function normalizeSmtpServerConfigs(input) {
|
|
4124
|
+
const configs = Array.isArray(input) ? input : input ? [input] : [];
|
|
4125
|
+
return configs.map((config) => resolveSmtpServerConfig(config));
|
|
4126
|
+
}
|
|
4127
|
+
function closeSmtpServer(server) {
|
|
4128
|
+
return new Promise((resolve, reject) => {
|
|
4129
|
+
let settled = false;
|
|
4130
|
+
const finish = (error2) => {
|
|
4131
|
+
if (settled) {
|
|
4132
|
+
return;
|
|
4133
|
+
}
|
|
4134
|
+
settled = true;
|
|
4135
|
+
if (error2) {
|
|
4136
|
+
reject(error2);
|
|
4137
|
+
return;
|
|
4138
|
+
}
|
|
4139
|
+
resolve();
|
|
4140
|
+
};
|
|
4141
|
+
const handleCloseError = (error2) => {
|
|
4142
|
+
const errorCode = error2.code;
|
|
4143
|
+
if (errorCode === "ERR_SERVER_NOT_RUNNING") {
|
|
4144
|
+
finish();
|
|
4145
|
+
return;
|
|
4146
|
+
}
|
|
4147
|
+
finish(error2);
|
|
4148
|
+
};
|
|
4149
|
+
try {
|
|
4150
|
+
server.close(() => finish());
|
|
4151
|
+
} catch (error2) {
|
|
4152
|
+
handleCloseError(error2);
|
|
4153
|
+
}
|
|
4154
|
+
});
|
|
4155
|
+
}
|
|
4156
|
+
function createSmtpServer(config = {}) {
|
|
4157
|
+
const resolvedConfig = resolveSmtpServerConfig(config);
|
|
4158
|
+
const { port, host, label: _label, ...serverOptions } = resolvedConfig;
|
|
4159
|
+
const server = new import_smtp_server.SMTPServer(serverOptions);
|
|
4160
|
+
return {
|
|
4161
|
+
server,
|
|
4162
|
+
config: resolvedConfig,
|
|
4163
|
+
listen(callback) {
|
|
4164
|
+
return callback ? server.listen(port, host, callback) : server.listen(port, host);
|
|
4165
|
+
},
|
|
4166
|
+
address() {
|
|
4167
|
+
return server.server.address();
|
|
4168
|
+
},
|
|
4169
|
+
close() {
|
|
4170
|
+
return closeSmtpServer(server);
|
|
4171
|
+
}
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
4174
|
+
function startSmtpServer(config = {}) {
|
|
4175
|
+
const handle = createSmtpServer(config);
|
|
4176
|
+
handle.listen();
|
|
4177
|
+
return handle;
|
|
4178
|
+
}
|
|
4179
|
+
var smtp_server_default = {
|
|
4180
|
+
SMTPServer: import_smtp_server.SMTPServer,
|
|
4181
|
+
createSmtpServer,
|
|
4182
|
+
startSmtpServer,
|
|
4183
|
+
DEFAULT_SMTP_HOST,
|
|
4184
|
+
DEFAULT_SMTP_PORT
|
|
4185
|
+
};
|
|
4186
|
+
})();
|