@mavogel/awscdk-rootmail 0.1.15 → 0.1.16
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/.jsii +4 -4
- package/lib/rootmail.js +1 -1
- package/lib/ses-receive.js +1 -1
- package/node_modules/mailparser/.release-please-manifest.json +1 -1
- package/node_modules/mailparser/CHANGELOG.md +7 -0
- package/node_modules/mailparser/package.json +2 -2
- package/node_modules/nodemailer/CHANGELOG.md +27 -0
- package/node_modules/nodemailer/dist/cjs/base64/index.d.ts +3 -0
- package/node_modules/nodemailer/dist/cjs/base64/index.js +80 -41
- package/node_modules/nodemailer/dist/cjs/dkim/index.js +43 -29
- package/node_modules/nodemailer/dist/cjs/dkim/message-parser.js +2 -2
- package/node_modules/nodemailer/dist/cjs/errors.d.ts +17 -0
- package/node_modules/nodemailer/dist/cjs/errors.js +12 -1
- package/node_modules/nodemailer/dist/cjs/fetch/index.js +23 -3
- package/node_modules/nodemailer/dist/cjs/mailer/index.js +23 -7
- package/node_modules/nodemailer/dist/cjs/mailer/mail-message.d.ts +5 -0
- package/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +20 -0
- package/node_modules/nodemailer/dist/cjs/mime-node/index.d.ts +1 -1
- package/node_modules/nodemailer/dist/cjs/mime-node/index.js +165 -105
- package/node_modules/nodemailer/dist/cjs/nodemailer.js +12 -9
- package/node_modules/nodemailer/dist/cjs/package-info.d.ts +1 -1
- package/node_modules/nodemailer/dist/cjs/package-info.js +1 -1
- package/node_modules/nodemailer/dist/cjs/qp/index.d.ts +2 -0
- package/node_modules/nodemailer/dist/cjs/qp/index.js +103 -33
- package/node_modules/nodemailer/dist/cjs/sendmail-transport/index.js +54 -14
- package/node_modules/nodemailer/dist/cjs/shared/index.d.ts +32 -1
- package/node_modules/nodemailer/dist/cjs/shared/index.js +212 -115
- package/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.d.ts +3 -1
- package/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.js +88 -68
- package/node_modules/nodemailer/dist/cjs/smtp-connection/index.d.ts +7 -1
- package/node_modules/nodemailer/dist/cjs/smtp-connection/index.js +348 -136
- package/node_modules/nodemailer/dist/cjs/smtp-pool/index.d.ts +3 -0
- package/node_modules/nodemailer/dist/cjs/smtp-pool/index.js +7 -1
- package/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.js +106 -17
- package/node_modules/nodemailer/dist/cjs/smtp-transport/index.js +8 -2
- package/node_modules/nodemailer/dist/esm/base64/index.d.ts +3 -0
- package/node_modules/nodemailer/dist/esm/base64/index.js +80 -41
- package/node_modules/nodemailer/dist/esm/dkim/index.js +43 -29
- package/node_modules/nodemailer/dist/esm/dkim/message-parser.js +2 -2
- package/node_modules/nodemailer/dist/esm/errors.d.ts +17 -0
- package/node_modules/nodemailer/dist/esm/errors.js +10 -0
- package/node_modules/nodemailer/dist/esm/fetch/index.js +23 -3
- package/node_modules/nodemailer/dist/esm/mailer/index.js +23 -7
- package/node_modules/nodemailer/dist/esm/mailer/mail-message.d.ts +5 -0
- package/node_modules/nodemailer/dist/esm/mailer/mail-message.js +20 -0
- package/node_modules/nodemailer/dist/esm/mime-node/index.d.ts +1 -1
- package/node_modules/nodemailer/dist/esm/mime-node/index.js +166 -106
- package/node_modules/nodemailer/dist/esm/nodemailer.js +12 -9
- package/node_modules/nodemailer/dist/esm/package-info.d.ts +1 -1
- package/node_modules/nodemailer/dist/esm/package-info.js +1 -1
- package/node_modules/nodemailer/dist/esm/qp/index.d.ts +2 -0
- package/node_modules/nodemailer/dist/esm/qp/index.js +103 -33
- package/node_modules/nodemailer/dist/esm/sendmail-transport/index.js +54 -14
- package/node_modules/nodemailer/dist/esm/shared/index.d.ts +32 -1
- package/node_modules/nodemailer/dist/esm/shared/index.js +207 -115
- package/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.d.ts +3 -1
- package/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.js +88 -68
- package/node_modules/nodemailer/dist/esm/smtp-connection/index.d.ts +7 -1
- package/node_modules/nodemailer/dist/esm/smtp-connection/index.js +348 -136
- package/node_modules/nodemailer/dist/esm/smtp-pool/index.d.ts +3 -0
- package/node_modules/nodemailer/dist/esm/smtp-pool/index.js +7 -1
- package/node_modules/nodemailer/dist/esm/smtp-pool/pool-resource.js +106 -17
- package/node_modules/nodemailer/dist/esm/smtp-transport/index.js +8 -2
- package/node_modules/nodemailer/package.json +1 -1
- package/package.json +3 -3
|
@@ -45,12 +45,30 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
|
45
45
|
const data_stream_js_1 = __importDefault(require("./data-stream.js"));
|
|
46
46
|
const node_stream_1 = require("node:stream");
|
|
47
47
|
const shared = __importStar(require("../shared/index.js"));
|
|
48
|
+
const errors_js_1 = require("../errors.js");
|
|
48
49
|
// default timeout values in ms
|
|
49
50
|
const CONNECTION_TIMEOUT = 2 * 60 * 1000; // how much to wait for the connection to be established
|
|
50
51
|
const SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity before disconnecting the client
|
|
51
52
|
const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved
|
|
52
53
|
const DNS_TIMEOUT = 30 * 1000; // how much to wait for resolveHostname
|
|
54
|
+
const CLOSE_TIMEOUT = 5 * 1000; // how much to wait for the server to close its side after we closed ours
|
|
55
|
+
const KEEPALIVE_DELAY = 30 * 1000; // idle time before TCP keepalive probes start, keeps NAT mappings of idle connections alive
|
|
53
56
|
const TEARDOWN_NOOP = () => { }; // reusable no-op handler for absorbing errors during socket teardown
|
|
57
|
+
// Random order, so that connections spread over the addresses of a host
|
|
58
|
+
function shuffle(list) {
|
|
59
|
+
const result = list.slice();
|
|
60
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
61
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
62
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
// Every timeout is reported with the ETIMEDOUT code, timeoutType tells which one it was
|
|
67
|
+
function timeoutError(message, timeoutType) {
|
|
68
|
+
const err = new Error(message);
|
|
69
|
+
err.timeoutType = timeoutType;
|
|
70
|
+
return err;
|
|
71
|
+
}
|
|
54
72
|
// how many bytes a single server response may occupy while it is still being received.
|
|
55
73
|
// Generous compared to any real reply, it only stops a peer that never completes one
|
|
56
74
|
const MAX_RESPONSE_SIZE = 1024 * 1024;
|
|
@@ -173,8 +191,8 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
173
191
|
this._maxAllowedSize = 0;
|
|
174
192
|
this._responseActions = [];
|
|
175
193
|
this._recipientQueue = [];
|
|
176
|
-
this.
|
|
177
|
-
this.
|
|
194
|
+
this._phaseTimer = false;
|
|
195
|
+
this._plaintextEhlo = false;
|
|
178
196
|
this._destroyed = false;
|
|
179
197
|
this._closing = false;
|
|
180
198
|
this._currentDataStream = false;
|
|
@@ -185,14 +203,25 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
185
203
|
this._onSocketClose = () => this._onClose();
|
|
186
204
|
this._onSocketEnd = () => this._onEnd();
|
|
187
205
|
this._onSocketTimeout = () => this._onTimeout();
|
|
188
|
-
this._onConnectionSocketError = err => this.
|
|
189
|
-
this._connectionAttemptId = 0;
|
|
206
|
+
this._onConnectionSocketError = err => this._onError(err, 'ESOCKET', false, 'CONN');
|
|
190
207
|
}
|
|
191
208
|
/**
|
|
192
209
|
* Creates a connection to a SMTP server and sets up connection
|
|
193
210
|
* listener
|
|
194
211
|
*/
|
|
195
212
|
connect(connectCallback) {
|
|
213
|
+
if (this._connectCalled && !this._destroyed) {
|
|
214
|
+
// A connection is opened once. A second call would open a second socket over the
|
|
215
|
+
// first one and run the session handlers of both against the same state
|
|
216
|
+
const err = this._formatError('Cannot connect - connect() was already called for this connection', 'ECONNECTION', false, 'API');
|
|
217
|
+
if (typeof connectCallback === 'function') {
|
|
218
|
+
setImmediate(() => connectCallback(err));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.logger.warn({ tnx: 'smtp' }, '%s', err.message);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this._connectCalled = true;
|
|
196
225
|
if (typeof connectCallback === 'function') {
|
|
197
226
|
this._connectCallback = connectCallback;
|
|
198
227
|
this.once('connect', () => {
|
|
@@ -207,11 +236,15 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
207
236
|
return connectCallback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'CONN'));
|
|
208
237
|
}
|
|
209
238
|
}
|
|
239
|
+
// connectionTimeout covers the whole of connecting: the DNS lookup and every address tried
|
|
240
|
+
const connectionTimeout = this.options.connectionTimeout || CONNECTION_TIMEOUT;
|
|
241
|
+
// a transport that first opened a proxy connection for this one sets when that started
|
|
242
|
+
this._connectionDeadline = (this._connectStartedAt || Date.now()) + connectionTimeout;
|
|
210
243
|
let opts = {
|
|
211
244
|
port: this.port,
|
|
212
245
|
host: this.host,
|
|
213
246
|
allowInternalNetworkInterfaces: this.allowInternalNetworkInterfaces,
|
|
214
|
-
timeout: this.options.dnsTimeout || DNS_TIMEOUT
|
|
247
|
+
timeout: Math.min(this.options.dnsTimeout || DNS_TIMEOUT, connectionTimeout)
|
|
215
248
|
};
|
|
216
249
|
if (this.options.localAddress) {
|
|
217
250
|
opts.localAddress = this.options.localAddress;
|
|
@@ -240,7 +273,6 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
240
273
|
return this._resolveAndConnect(opts, _resolved => {
|
|
241
274
|
try {
|
|
242
275
|
this._socket.connect(this.port, this.host, () => {
|
|
243
|
-
this._socket.setKeepAlive(true);
|
|
244
276
|
// a `secure` connection over a caller-provided socket must still
|
|
245
277
|
// perform the TLS handshake, otherwise AUTH and the message body
|
|
246
278
|
// would be sent in cleartext despite the caller requesting TLS
|
|
@@ -272,9 +304,40 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
272
304
|
}
|
|
273
305
|
}
|
|
274
306
|
return this._resolveAndConnect(opts, resolved => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
307
|
+
let addresses = resolved._addresses || [];
|
|
308
|
+
if (opts.localAddress) {
|
|
309
|
+
// a socket bound to an address of one family can not reach the other one
|
|
310
|
+
const localFamily = node_net_1.default.isIPv6(opts.localAddress) ? 6 : 4;
|
|
311
|
+
const sameFamily = addresses.filter(addr => node_net_1.default.isIP(addr) === localFamily);
|
|
312
|
+
addresses = sameFamily.length ? sameFamily : addresses;
|
|
313
|
+
}
|
|
314
|
+
if (addresses.length > 1) {
|
|
315
|
+
// net.connect tries the addresses in turn and moves on to the next one when an
|
|
316
|
+
// attempt fails or takes too long. With both families it starts on IPv6 and
|
|
317
|
+
// alternates (RFC 8305), so a host with a broken IPv6 path costs a fraction of
|
|
318
|
+
// a second instead of a whole connection timeout
|
|
319
|
+
const ipv6 = addresses.filter(addr => node_net_1.default.isIPv6(addr));
|
|
320
|
+
const ipv4 = addresses.filter(addr => !node_net_1.default.isIPv6(addr));
|
|
321
|
+
const ordered = shuffle(ipv6).concat(shuffle(ipv4));
|
|
322
|
+
opts.host = this.host;
|
|
323
|
+
opts.autoSelectFamily = true;
|
|
324
|
+
if (!ipv6.length || !ipv4.length) {
|
|
325
|
+
// a slow address of the only family gets its share of the time, not the
|
|
326
|
+
// quarter second meant for an address family that does not work
|
|
327
|
+
const remaining = this._connectionDeadline - Date.now();
|
|
328
|
+
opts.autoSelectFamilyAttemptTimeout = Math.max(Math.floor(remaining / ordered.length), 10);
|
|
329
|
+
}
|
|
330
|
+
opts.lookup = ((hostname, lookupOptions, callback) => {
|
|
331
|
+
if (lookupOptions && lookupOptions.all) {
|
|
332
|
+
const all = ordered.map(address => ({ address, family: node_net_1.default.isIPv6(address) ? 6 : 4 }));
|
|
333
|
+
return setImmediate(() => callback(null, all));
|
|
334
|
+
}
|
|
335
|
+
setImmediate(() => callback(null, ordered[0], node_net_1.default.isIPv6(ordered[0]) ? 6 : 4));
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
else if (addresses.length) {
|
|
339
|
+
opts.host = addresses[0];
|
|
340
|
+
}
|
|
278
341
|
this._connectToHost(opts, this.secureConnection);
|
|
279
342
|
});
|
|
280
343
|
}
|
|
@@ -321,20 +384,11 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
321
384
|
if (this._destroyed || this._closing) {
|
|
322
385
|
return;
|
|
323
386
|
}
|
|
324
|
-
this._connectionAttemptId++;
|
|
325
|
-
const currentAttemptId = this._connectionAttemptId;
|
|
326
387
|
const connectFn = secure
|
|
327
388
|
? node_tls_1.default.connect
|
|
328
389
|
: node_net_1.default.connect;
|
|
329
390
|
try {
|
|
330
|
-
this._socket = connectFn(opts, () =>
|
|
331
|
-
// Ignore callback if this is a stale connection attempt
|
|
332
|
-
if (this._connectionAttemptId !== currentAttemptId) {
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
this._socket.setKeepAlive(true);
|
|
336
|
-
this._onConnect();
|
|
337
|
-
});
|
|
391
|
+
this._socket = connectFn(opts, () => this._onConnect());
|
|
338
392
|
this._setupConnectionHandlers();
|
|
339
393
|
}
|
|
340
394
|
catch (E) {
|
|
@@ -347,51 +401,37 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
347
401
|
* @internal
|
|
348
402
|
*/
|
|
349
403
|
_setupConnectionHandlers() {
|
|
350
|
-
this.
|
|
351
|
-
this._onConnectionError('Connection timeout', 'ETIMEDOUT');
|
|
352
|
-
}, this.options.connectionTimeout || CONNECTION_TIMEOUT);
|
|
404
|
+
this._startPhase(Math.max((this._connectionDeadline || Date.now()) - Date.now(), 0), timeoutError('Connection timeout', 'CONNECT_TIMEOUT'));
|
|
353
405
|
this._socket.on('error', this._onConnectionSocketError);
|
|
354
406
|
}
|
|
355
407
|
/**
|
|
356
|
-
*
|
|
408
|
+
* Starts the timer of a connection phase: connecting, waiting for the greeting or a TLS
|
|
409
|
+
* upgrade. The phases follow one another, so starting one ends the one before
|
|
357
410
|
*
|
|
358
|
-
* @param
|
|
359
|
-
* @param
|
|
411
|
+
* @param timeout Time the phase may take
|
|
412
|
+
* @param err Error to fail with when it takes longer
|
|
360
413
|
* @internal
|
|
361
414
|
*/
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// Absorb any late teardown error (e.g. a TLS fallback socket emitting
|
|
383
|
-
// after destroy), mirroring the guard used in close()
|
|
384
|
-
this._socket.on('error', TEARDOWN_NOOP);
|
|
385
|
-
this._socket.destroy();
|
|
386
|
-
}
|
|
387
|
-
catch (_E) {
|
|
388
|
-
// ignore
|
|
389
|
-
}
|
|
390
|
-
this._socket = null;
|
|
391
|
-
}
|
|
392
|
-
// Update host and retry
|
|
393
|
-
this._connectOpts.host = nextHost;
|
|
394
|
-
this._connectToHost(this._connectOpts, this.secureConnection);
|
|
415
|
+
_startPhase(timeout, err) {
|
|
416
|
+
this._clearPhase();
|
|
417
|
+
this._phaseTimer = setTimeout(() => {
|
|
418
|
+
this._phaseTimer = false;
|
|
419
|
+
this._onError(err, 'ETIMEDOUT', false, 'CONN');
|
|
420
|
+
}, timeout);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Time the greeting or a STARTTLS upgrade may take: greetingTimeout, cut short by what is left
|
|
424
|
+
* of connectionTimeout
|
|
425
|
+
*
|
|
426
|
+
* @internal
|
|
427
|
+
*/
|
|
428
|
+
_remainingSetupTime() {
|
|
429
|
+
return Math.min(this.options.greetingTimeout || GREETING_TIMEOUT, Math.max((this._connectionDeadline || Infinity) - Date.now(), 1));
|
|
430
|
+
}
|
|
431
|
+
/** @internal */
|
|
432
|
+
_clearPhase() {
|
|
433
|
+
clearTimeout(this._phaseTimer);
|
|
434
|
+
this._phaseTimer = false;
|
|
395
435
|
}
|
|
396
436
|
/**
|
|
397
437
|
* Sends QUIT
|
|
@@ -404,8 +444,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
404
444
|
* Closes the connection to the server
|
|
405
445
|
*/
|
|
406
446
|
close() {
|
|
407
|
-
|
|
408
|
-
clearTimeout(this._greetingTimeout);
|
|
447
|
+
this._clearPhase();
|
|
409
448
|
this._responseActions = [];
|
|
410
449
|
// allow to run this function only once
|
|
411
450
|
if (this._closing) {
|
|
@@ -428,13 +467,15 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
428
467
|
}
|
|
429
468
|
this._currentDataStream = false;
|
|
430
469
|
}
|
|
431
|
-
// Detach from the message stream as well
|
|
432
|
-
//
|
|
470
|
+
// Detach from the message stream as well and release whatever it reads from, the message
|
|
471
|
+
// can not be sent over this connection anymore. The listener is swapped for a no-op rather
|
|
472
|
+
// than removed, a stream destroyed with an error would otherwise throw it as unhandled
|
|
433
473
|
if (this._pendingSend) {
|
|
434
474
|
const { stream, onStreamError } = this._pendingSend;
|
|
435
475
|
if (stream) {
|
|
436
476
|
stream.removeListener('error', onStreamError);
|
|
437
477
|
stream.on('error', TEARDOWN_NOOP);
|
|
478
|
+
stream.destroy();
|
|
438
479
|
}
|
|
439
480
|
this._pendingSend = false;
|
|
440
481
|
}
|
|
@@ -453,6 +494,15 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
453
494
|
// sending cleartext after TLS shutdown triggers ERR_SSL_BAD_RECORD_TYPE)
|
|
454
495
|
socket.on('error', TEARDOWN_NOOP);
|
|
455
496
|
socket[closeMethod]();
|
|
497
|
+
if (closeMethod === 'end') {
|
|
498
|
+
// end() only closes our side, a server that never closes its own would keep
|
|
499
|
+
// the socket, and the process with it, around for good
|
|
500
|
+
const closeTimer = setTimeout(() => socket.destroy(), CLOSE_TIMEOUT);
|
|
501
|
+
if (typeof closeTimer.unref === 'function') {
|
|
502
|
+
closeTimer.unref();
|
|
503
|
+
}
|
|
504
|
+
socket.once('close', () => clearTimeout(closeTimer));
|
|
505
|
+
}
|
|
456
506
|
}
|
|
457
507
|
catch (_E) {
|
|
458
508
|
// just ignore
|
|
@@ -657,14 +707,9 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
657
707
|
const startTime = Date.now();
|
|
658
708
|
this._setEnvelope(envelope, (err, info) => {
|
|
659
709
|
if (err) {
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
message.pipe(stream);
|
|
664
|
-
}
|
|
665
|
-
else {
|
|
666
|
-
stream.write(message);
|
|
667
|
-
stream.end();
|
|
710
|
+
// the message is not going to be sent, release whatever the stream reads from
|
|
711
|
+
if (typeof message.destroy === 'function') {
|
|
712
|
+
message.destroy();
|
|
668
713
|
}
|
|
669
714
|
return callback(err);
|
|
670
715
|
}
|
|
@@ -718,7 +763,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
718
763
|
*/
|
|
719
764
|
_onConnect() {
|
|
720
765
|
const socket = this._socket;
|
|
721
|
-
|
|
766
|
+
this._clearPhase();
|
|
722
767
|
this.logger.info({
|
|
723
768
|
tnx: 'network',
|
|
724
769
|
localAddress: socket.localAddress,
|
|
@@ -748,16 +793,42 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
748
793
|
socket.once('end', this._onSocketEnd);
|
|
749
794
|
socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT);
|
|
750
795
|
socket.on('timeout', this._onSocketTimeout);
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
796
|
+
// keepalive also covers sockets handed over by a proxy or by the caller
|
|
797
|
+
if (typeof socket.setKeepAlive === 'function') {
|
|
798
|
+
socket.setKeepAlive(true, KEEPALIVE_DELAY);
|
|
799
|
+
}
|
|
800
|
+
// Commands are written in the batches they belong to (see cork() for PIPELINING), Nagle
|
|
801
|
+
// would only hold a write back until the server acknowledged the previous one. Against a
|
|
802
|
+
// server that delays its ACKs that costs 40ms on every message
|
|
803
|
+
if (typeof socket.setNoDelay === 'function') {
|
|
804
|
+
socket.setNoDelay(true);
|
|
805
|
+
}
|
|
806
|
+
// bounded by greetingTimeout and by what is left of connectionTimeout, which covers setting
|
|
807
|
+
// the session up from the DNS lookup to the end of a STARTTLS upgrade
|
|
808
|
+
this._startPhase(this._remainingSetupTime(), timeoutError('Greeting never received', 'GREETING_TIMEOUT'));
|
|
757
809
|
this._responseActions.push(this._actionGreeting);
|
|
758
810
|
// we have a 'data' listener set up so resume socket if it was paused
|
|
759
811
|
socket.resume();
|
|
760
812
|
}
|
|
813
|
+
/**
|
|
814
|
+
* Ends the session after a 421 reply. The replies queued for commands sent along with the
|
|
815
|
+
* answered one are not going to come, so the message in flight is failed here
|
|
816
|
+
*
|
|
817
|
+
* @param str The 421 reply
|
|
818
|
+
* @internal
|
|
819
|
+
*/
|
|
820
|
+
_onServerClosing(str) {
|
|
821
|
+
if (this._destroyed) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const pendingSend = this._pendingSend;
|
|
825
|
+
const envelope = this._envelope;
|
|
826
|
+
this._responseActions = [];
|
|
827
|
+
this.close();
|
|
828
|
+
if (pendingSend) {
|
|
829
|
+
pendingSend.callback((envelope && envelope.mailError) || this._formatError('Server closed the connection', 'ECONNECTION', str, 'CONN'));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
761
832
|
/**
|
|
762
833
|
* 'data' listener for data coming from the server
|
|
763
834
|
*
|
|
@@ -832,8 +903,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
832
903
|
* @internal
|
|
833
904
|
*/
|
|
834
905
|
_onError(err, type, data, command) {
|
|
835
|
-
|
|
836
|
-
clearTimeout(this._greetingTimeout);
|
|
906
|
+
this._clearPhase();
|
|
837
907
|
if (this._destroyed) {
|
|
838
908
|
// just ignore, already closed
|
|
839
909
|
// this might happen when a socket is canceled because of reached timeout
|
|
@@ -841,12 +911,12 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
841
911
|
return;
|
|
842
912
|
}
|
|
843
913
|
err = this._formatError(err, type, data, command);
|
|
844
|
-
|
|
845
|
-
if (
|
|
846
|
-
this.logger.warn(
|
|
914
|
+
// the message carries the server response, it is an argument and not the format string
|
|
915
|
+
if ((0, errors_js_1.isTransientError)(err)) {
|
|
916
|
+
this.logger.warn({ tnx: 'smtp', err }, '%s', err.message);
|
|
847
917
|
}
|
|
848
918
|
else {
|
|
849
|
-
this.logger.error(
|
|
919
|
+
this.logger.error({ tnx: 'smtp', err }, '%s', err.message);
|
|
850
920
|
}
|
|
851
921
|
// close() forgets the send in flight, it is completed with this same error afterwards so
|
|
852
922
|
// a late message stream error has nothing left to report
|
|
@@ -866,7 +936,12 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
866
936
|
else {
|
|
867
937
|
err = new Error(message);
|
|
868
938
|
}
|
|
869
|
-
|
|
939
|
+
// a permission model denial keeps its own code, see ERR_ACCESS_DENIED
|
|
940
|
+
if (type && type !== 'Error' && err.code !== errors_js_1.ERR_ACCESS_DENIED) {
|
|
941
|
+
// the code of a system error, such as ECONNREFUSED, still tells what happened
|
|
942
|
+
if (err.code && err.code !== type && !err.originalCode) {
|
|
943
|
+
err.originalCode = err.code;
|
|
944
|
+
}
|
|
870
945
|
err.code = type;
|
|
871
946
|
}
|
|
872
947
|
if (response) {
|
|
@@ -918,6 +993,19 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
918
993
|
this.close();
|
|
919
994
|
return;
|
|
920
995
|
}
|
|
996
|
+
if (!failureResponse &&
|
|
997
|
+
this.stage === 'connected' &&
|
|
998
|
+
!this._responseActions.length &&
|
|
999
|
+
!this._pendingSend &&
|
|
1000
|
+
!this._destroyed &&
|
|
1001
|
+
!this._closing) {
|
|
1002
|
+
// nothing was waiting for the server, so this is the server ending an idle session
|
|
1003
|
+
// (usually after its idle timeout), not a failure
|
|
1004
|
+
this.logger.info({
|
|
1005
|
+
tnx: 'network'
|
|
1006
|
+
}, 'Server closed the idle connection');
|
|
1007
|
+
return this._destroy();
|
|
1008
|
+
}
|
|
921
1009
|
if (failureResponse || (this._responseActions[0] !== this.close && !this._destroyed)) {
|
|
922
1010
|
return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', failureResponse, 'CONN');
|
|
923
1011
|
}
|
|
@@ -943,7 +1031,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
943
1031
|
* @internal
|
|
944
1032
|
*/
|
|
945
1033
|
_onTimeout() {
|
|
946
|
-
return this._onError(
|
|
1034
|
+
return this._onError(timeoutError('Timeout', 'SOCKET_TIMEOUT'), 'ETIMEDOUT', false, 'CONN');
|
|
947
1035
|
}
|
|
948
1036
|
/**
|
|
949
1037
|
* Destroys the client, emits 'end'
|
|
@@ -958,10 +1046,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
958
1046
|
this.destroyed = true;
|
|
959
1047
|
// a connection the server dropped before the greeting would otherwise keep
|
|
960
1048
|
// the greeting timer, and with it the process, alive until it fires
|
|
961
|
-
|
|
962
|
-
clearTimeout(this._greetingTimeout);
|
|
963
|
-
this._connectionTimeout = false;
|
|
964
|
-
this._greetingTimeout = false;
|
|
1049
|
+
this._clearPhase();
|
|
965
1050
|
this.emit('end');
|
|
966
1051
|
}
|
|
967
1052
|
/**
|
|
@@ -978,6 +1063,15 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
978
1063
|
// inject plaintext bytes after the "220" reply (e.g. a CRLF-free fragment that
|
|
979
1064
|
// would otherwise be prepended to the first post-TLS response and parsed as
|
|
980
1065
|
// part of the secured EHLO capabilities). STARTTLS response injection.
|
|
1066
|
+
const discarded = this._remainder.length + this._responseQueue.reduce((total, response) => total + response.length, 0);
|
|
1067
|
+
if (discarded) {
|
|
1068
|
+
// a server does not send anything here on its own, this is worth knowing about
|
|
1069
|
+
this.logger.warn({
|
|
1070
|
+
tnx: 'smtp',
|
|
1071
|
+
discarded
|
|
1072
|
+
}, 'Discarded %s bytes received in plaintext after the STARTTLS response', discarded);
|
|
1073
|
+
}
|
|
1074
|
+
this._plaintextEhlo = false;
|
|
981
1075
|
this._remainder = '';
|
|
982
1076
|
this._responseQueue = [];
|
|
983
1077
|
this._responsePartial = false;
|
|
@@ -987,6 +1081,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
987
1081
|
const socketPlain = this._socket;
|
|
988
1082
|
socketPlain.removeListener('data', this._onSocketData); // incoming data is going to be gibberish from this point onwards
|
|
989
1083
|
socketPlain.removeListener('timeout', this._onSocketTimeout); // timeout will be re-set for the new socket object
|
|
1084
|
+
socketPlain.setTimeout(0);
|
|
990
1085
|
const opts = Object.assign({
|
|
991
1086
|
socket: socketPlain,
|
|
992
1087
|
host: this.host
|
|
@@ -1007,9 +1102,14 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1007
1102
|
socketPlain.removeListener('error', this._onConnectionSocketError);
|
|
1008
1103
|
};
|
|
1009
1104
|
this.upgrading = true;
|
|
1105
|
+
// the socket timeout only notices a server that sends nothing at all, a handshake that
|
|
1106
|
+
// trickles along would otherwise hold the connection for as long as the server likes
|
|
1107
|
+
// STARTTLS is the last step of setting the session up
|
|
1108
|
+
this._startPhase(this._remainingSetupTime(), timeoutError('TLS handshake timed out', 'UPGRADE_TIMEOUT'));
|
|
1010
1109
|
// tls.connect is not an asynchronous function however it may still throw errors and requires to be wrapped with try/catch
|
|
1011
1110
|
try {
|
|
1012
1111
|
this._socket = node_tls_1.default.connect(opts, () => {
|
|
1112
|
+
this._clearPhase();
|
|
1013
1113
|
this.secure = true;
|
|
1014
1114
|
this.upgrading = false;
|
|
1015
1115
|
this._socket.on('data', this._onSocketData);
|
|
@@ -1018,6 +1118,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1018
1118
|
});
|
|
1019
1119
|
}
|
|
1020
1120
|
catch (err) {
|
|
1121
|
+
this._clearPhase();
|
|
1021
1122
|
removePlainSocketListeners();
|
|
1022
1123
|
return callback(err);
|
|
1023
1124
|
}
|
|
@@ -1058,10 +1159,24 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1058
1159
|
}, str.replace(/\r?\n$/, ''));
|
|
1059
1160
|
}
|
|
1060
1161
|
const action = this._responseActions.shift();
|
|
1162
|
+
// RFC 5321 4.2: 421 means the server is about to close the connection, whatever it answers
|
|
1163
|
+
const closing = /^421[ -]/.test(str);
|
|
1061
1164
|
if (typeof action === 'function') {
|
|
1165
|
+
// the command gets its own error first, the code tells which step failed
|
|
1062
1166
|
action.call(this, str);
|
|
1167
|
+
if (closing) {
|
|
1168
|
+
return this._onServerClosing(str);
|
|
1169
|
+
}
|
|
1063
1170
|
setImmediate(() => this._processResponse());
|
|
1064
1171
|
}
|
|
1172
|
+
else if (closing && !this._pendingSend && this.stage === 'connected') {
|
|
1173
|
+
// RFC 5321 4.2: a server may send 421 at any time when it is about to close the
|
|
1174
|
+
// connection. Nothing was waiting for a reply, so this ends an idle session
|
|
1175
|
+
this.logger.info({
|
|
1176
|
+
tnx: 'smtp'
|
|
1177
|
+
}, 'Server closed the idle connection: %s', str);
|
|
1178
|
+
this.close();
|
|
1179
|
+
}
|
|
1065
1180
|
else {
|
|
1066
1181
|
return this._onError(new Error('Unexpected Response'), 'EPROTOCOL', str, 'CONN');
|
|
1067
1182
|
}
|
|
@@ -1156,6 +1271,9 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1156
1271
|
return callback(this._formatError('Server does not support REQUIRETLS extension (RFC 8689)', 'EREQUIRETLS', false, 'MAIL FROM'));
|
|
1157
1272
|
}
|
|
1158
1273
|
}
|
|
1274
|
+
// RFC 2920: with PIPELINING the whole envelope and DATA go out without waiting for the
|
|
1275
|
+
// replies in between, which saves two round trips for every message
|
|
1276
|
+
this._envelope.pipelined = this._supportedExtensions.includes('PIPELINING');
|
|
1159
1277
|
this._responseActions.push(str => {
|
|
1160
1278
|
this._actionMAIL(str, callback);
|
|
1161
1279
|
});
|
|
@@ -1193,7 +1311,38 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1193
1311
|
if (this._envelope.requireTLSExtensionEnabled) {
|
|
1194
1312
|
args.push('REQUIRETLS');
|
|
1195
1313
|
}
|
|
1196
|
-
|
|
1314
|
+
const mailFrom = 'MAIL FROM:<' + this._envelope.from + '>' + (args.length ? ' ' + args.join(' ') : '');
|
|
1315
|
+
this._recipientQueue = [];
|
|
1316
|
+
if (!this._envelope.pipelined) {
|
|
1317
|
+
this._sendCommand(mailFrom);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
// corked, so the batch leaves in one segment instead of the first command alone
|
|
1321
|
+
const socket = this._socket;
|
|
1322
|
+
socket.cork();
|
|
1323
|
+
this._sendCommand(mailFrom);
|
|
1324
|
+
while (this._envelope.rcptQueue.length) {
|
|
1325
|
+
this._sendRcpt(this._envelope.rcptQueue.shift(), callback);
|
|
1326
|
+
}
|
|
1327
|
+
this._responseActions.push(str => {
|
|
1328
|
+
this._actionDATA(str, callback);
|
|
1329
|
+
});
|
|
1330
|
+
this._sendCommand('DATA');
|
|
1331
|
+
socket.uncork();
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Sends RCPT TO for a recipient and queues the handler for the reply
|
|
1335
|
+
*
|
|
1336
|
+
* @param recipient Recipient address
|
|
1337
|
+
* @param callback Callback to run once the envelope is processed
|
|
1338
|
+
* @internal
|
|
1339
|
+
*/
|
|
1340
|
+
_sendRcpt(recipient, callback) {
|
|
1341
|
+
this._recipientQueue.push(recipient);
|
|
1342
|
+
this._responseActions.push(str => {
|
|
1343
|
+
this._actionRCPT(str, callback);
|
|
1344
|
+
});
|
|
1345
|
+
this._sendCommand('RCPT TO:<' + recipient + '>' + this._getDsnRcptToArgs());
|
|
1197
1346
|
}
|
|
1198
1347
|
/** @internal */
|
|
1199
1348
|
_setDsnEnvelope(params) {
|
|
@@ -1309,7 +1458,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1309
1458
|
* @internal
|
|
1310
1459
|
*/
|
|
1311
1460
|
_actionGreeting(str) {
|
|
1312
|
-
|
|
1461
|
+
this._clearPhase();
|
|
1313
1462
|
if (str.substr(0, 3) !== '220') {
|
|
1314
1463
|
this._onError(new Error('Invalid greeting. response=' + str), 'EPROTOCOL', str, 'CONN');
|
|
1315
1464
|
return;
|
|
@@ -1347,7 +1496,6 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1347
1496
|
* @internal
|
|
1348
1497
|
*/
|
|
1349
1498
|
_actionEHLO(str) {
|
|
1350
|
-
let match;
|
|
1351
1499
|
if (str.substr(0, 3) === '421') {
|
|
1352
1500
|
this._onError(new Error('Server terminates connection. response=' + str), 'ECONNECTION', str, 'EHLO');
|
|
1353
1501
|
return;
|
|
@@ -1362,17 +1510,30 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1362
1510
|
this._sendCommand('HELO ' + this.name);
|
|
1363
1511
|
return;
|
|
1364
1512
|
}
|
|
1365
|
-
this._ehloLines = str
|
|
1366
|
-
.split(/\r?\n/)
|
|
1367
|
-
.map(line => line.replace(/^\d+[ -]/, '').trim())
|
|
1368
|
-
.filter(line => line)
|
|
1369
|
-
.slice(1);
|
|
1370
1513
|
// Detect if the server supports STARTTLS
|
|
1371
1514
|
if (!this.secure && !this.options.ignoreTLS && (/[ -]STARTTLS\b/im.test(str) || this.options.requireTLS)) {
|
|
1515
|
+
// kept for opportunisticTLS, a session that stays in plaintext still has these extensions
|
|
1516
|
+
this._plaintextEhlo = str;
|
|
1372
1517
|
this._sendCommand('STARTTLS');
|
|
1373
1518
|
this._responseActions.push(this._actionSTARTTLS);
|
|
1374
1519
|
return;
|
|
1375
1520
|
}
|
|
1521
|
+
this._parseEhloExtensions(str);
|
|
1522
|
+
this.emit('connect');
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Reads the extensions and the authentication mechanisms out of an EHLO response
|
|
1526
|
+
*
|
|
1527
|
+
* @param str EHLO response from the server
|
|
1528
|
+
* @internal
|
|
1529
|
+
*/
|
|
1530
|
+
_parseEhloExtensions(str) {
|
|
1531
|
+
let match;
|
|
1532
|
+
this._ehloLines = str
|
|
1533
|
+
.split(/\r?\n/)
|
|
1534
|
+
.map(line => line.replace(/^\d+[ -]/, '').trim())
|
|
1535
|
+
.filter(line => line)
|
|
1536
|
+
.slice(1);
|
|
1376
1537
|
// Detect if the server supports SMTPUTF8
|
|
1377
1538
|
if (/[ -]SMTPUTF8\b/im.test(str)) {
|
|
1378
1539
|
this._supportedExtensions.push('SMTPUTF8');
|
|
@@ -1422,7 +1583,6 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1422
1583
|
this._supportedExtensions.push('SIZE');
|
|
1423
1584
|
this._maxAllowedSize = Number(match[1]) || 0;
|
|
1424
1585
|
}
|
|
1425
|
-
this.emit('connect');
|
|
1426
1586
|
}
|
|
1427
1587
|
/**
|
|
1428
1588
|
* Handles server response for HELO command. If it yielded in
|
|
@@ -1454,6 +1614,14 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1454
1614
|
this.logger.info({
|
|
1455
1615
|
tnx: 'smtp'
|
|
1456
1616
|
}, 'Failed STARTTLS upgrade, continuing unencrypted');
|
|
1617
|
+
// the plaintext session goes on with what the server announced for it, except for
|
|
1618
|
+
// AUTH: credentials are not sent over a connection that failed to encrypt
|
|
1619
|
+
if (this._plaintextEhlo) {
|
|
1620
|
+
this._parseEhloExtensions(this._plaintextEhlo);
|
|
1621
|
+
this._plaintextEhlo = false;
|
|
1622
|
+
this.allowsAuth = false;
|
|
1623
|
+
this._supportedAuth = [];
|
|
1624
|
+
}
|
|
1457
1625
|
this.emit('connect');
|
|
1458
1626
|
return;
|
|
1459
1627
|
}
|
|
@@ -1634,21 +1802,9 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1634
1802
|
const message = this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(envelope.from)
|
|
1635
1803
|
? 'Internationalized mailbox name not allowed'
|
|
1636
1804
|
: 'Mail command failed';
|
|
1637
|
-
|
|
1638
|
-
}
|
|
1639
|
-
if (!envelope.rcptQueue.length) {
|
|
1640
|
-
return callback(this._formatError("Can't send mail - no recipients defined", 'EENVELOPE', false, 'API'));
|
|
1805
|
+
envelope.mailError = this._formatError(message, 'EENVELOPE', str, 'MAIL FROM');
|
|
1641
1806
|
}
|
|
1642
|
-
this.
|
|
1643
|
-
const usePipelining = this._supportedExtensions.includes('PIPELINING');
|
|
1644
|
-
do {
|
|
1645
|
-
const curRecipient = envelope.rcptQueue.shift();
|
|
1646
|
-
this._recipientQueue.push(curRecipient);
|
|
1647
|
-
this._responseActions.push(str => {
|
|
1648
|
-
this._actionRCPT(str, callback);
|
|
1649
|
-
});
|
|
1650
|
-
this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
|
|
1651
|
-
} while (usePipelining && envelope.rcptQueue.length);
|
|
1807
|
+
this._advanceEnvelope(str, callback);
|
|
1652
1808
|
}
|
|
1653
1809
|
/**
|
|
1654
1810
|
* Handle response for a RCPT TO: command
|
|
@@ -1675,32 +1831,67 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1675
1831
|
else {
|
|
1676
1832
|
envelope.accepted.push(curRecipient);
|
|
1677
1833
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
return callback(err);
|
|
1694
|
-
}
|
|
1834
|
+
this._advanceEnvelope(str, callback);
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Moves the envelope on after a reply to MAIL FROM or RCPT TO. A pipelined envelope sent every
|
|
1838
|
+
* command at once and is decided by the reply to DATA. Otherwise the commands go one at a
|
|
1839
|
+
* time: the next recipient, or once every reply is in, DATA or the error that ends the message
|
|
1840
|
+
*
|
|
1841
|
+
* @param str The reply that was handled
|
|
1842
|
+
* @param callback Callback to run once the envelope is processed
|
|
1843
|
+
* @internal
|
|
1844
|
+
*/
|
|
1845
|
+
_advanceEnvelope(str, callback) {
|
|
1846
|
+
const envelope = this._envelope;
|
|
1847
|
+
if (envelope.pipelined) {
|
|
1848
|
+
return;
|
|
1695
1849
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1850
|
+
if (envelope.mailError) {
|
|
1851
|
+
return callback(envelope.mailError);
|
|
1852
|
+
}
|
|
1853
|
+
if (envelope.rcptQueue.length) {
|
|
1854
|
+
return this._sendRcpt(envelope.rcptQueue.shift(), callback);
|
|
1855
|
+
}
|
|
1856
|
+
if (this._recipientQueue.length) {
|
|
1857
|
+
// replies still to come
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
const err = this._envelopeError(str);
|
|
1861
|
+
if (err) {
|
|
1862
|
+
return callback(err);
|
|
1703
1863
|
}
|
|
1864
|
+
this._responseActions.push(str => {
|
|
1865
|
+
this._actionDATA(str, callback);
|
|
1866
|
+
});
|
|
1867
|
+
this._sendCommand('DATA');
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Decides how the envelope went once every reply to MAIL FROM and RCPT TO is in. Called after
|
|
1871
|
+
* the last RCPT TO reply, or with PIPELINING on the reply to the DATA command sent along
|
|
1872
|
+
*
|
|
1873
|
+
* @param str The reply being handled
|
|
1874
|
+
* @returns The error to fail the message with, or null when DATA can go ahead
|
|
1875
|
+
* @internal
|
|
1876
|
+
*/
|
|
1877
|
+
_envelopeError(str) {
|
|
1878
|
+
const envelope = this._envelope;
|
|
1879
|
+
if (envelope.mailError) {
|
|
1880
|
+
return envelope.mailError;
|
|
1881
|
+
}
|
|
1882
|
+
if (envelope.accepted.length) {
|
|
1883
|
+
return null;
|
|
1884
|
+
}
|
|
1885
|
+
// report a temporary rejection when there is one, taking the last reply would mark the
|
|
1886
|
+
// whole message as permanently failed although some recipients were only deferred
|
|
1887
|
+
const deferred = envelope.rejectedErrors.find(rejectedErr => rejectedErr.responseCode && rejectedErr.responseCode < 500);
|
|
1888
|
+
const lastRejected = envelope.rejectedErrors[envelope.rejectedErrors.length - 1];
|
|
1889
|
+
const reply = deferred?.response ?? (envelope.pipelined && lastRejected ? lastRejected.response : str);
|
|
1890
|
+
// every recipient was rejected
|
|
1891
|
+
const err = this._formatError("Can't send mail - all recipients were rejected", 'EENVELOPE', reply, 'RCPT TO');
|
|
1892
|
+
err.rejected = envelope.rejected;
|
|
1893
|
+
err.rejectedErrors = envelope.rejectedErrors;
|
|
1894
|
+
return err;
|
|
1704
1895
|
}
|
|
1705
1896
|
/**
|
|
1706
1897
|
* Handle response for a DATA command
|
|
@@ -1711,6 +1902,27 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|
|
1711
1902
|
*/
|
|
1712
1903
|
_actionDATA(str, callback) {
|
|
1713
1904
|
const envelope = this._envelope;
|
|
1905
|
+
if (envelope.pipelined) {
|
|
1906
|
+
const err = this._envelopeError(str);
|
|
1907
|
+
if (err) {
|
|
1908
|
+
if (/^3/.test(str)) {
|
|
1909
|
+
if (envelope.mailError) {
|
|
1910
|
+
// A server that refused the sender has no transaction to end, one that
|
|
1911
|
+
// took DATA anyway can not be trusted with an empty message either.
|
|
1912
|
+
// Drop the connection instead of answering it
|
|
1913
|
+
this.close();
|
|
1914
|
+
return callback(err);
|
|
1915
|
+
}
|
|
1916
|
+
// A server must refuse DATA without an accepted recipient, this one took it
|
|
1917
|
+
// anyway. End the empty message, it has nobody to go to, so the session
|
|
1918
|
+
// stays usable
|
|
1919
|
+
this._responseActions.push(() => callback(err));
|
|
1920
|
+
this._sendCommand('.');
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
return callback(err);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1714
1926
|
// response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
|
|
1715
1927
|
// some servers might use 250 instead, so lets check for 2 or 3 as the first digit
|
|
1716
1928
|
if (!/^[23]/.test(str)) {
|