Skip to content

Commit 9657f92

Browse files
committed
fix: fixed case when ENHANCEDSTATUSCODES enabled and it was sending a status code in initial connection, HELO, EHLO, or LHLO commands (which was against RFC spec and caused mail client issues, e.g. Apple Mail when advertising capabilities in EHLO)
1 parent 8a1d41b commit 9657f92

File tree

1 file changed

+30
-20
lines changed

1 file changed

+30
-20
lines changed

lib/smtp-connection.js

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ const ENHANCED_STATUS_CODES = {
5151
556: '5.1.10' // RCPT TO syntax error
5252
};
5353

54+
// Skip enhanced status codes for initial greeting and HELO/EHLO responses
55+
const SKIPPED_COMMANDS_FOR_ENHANCED_STATUS_CODES = new Set([
56+
'HELO',
57+
'EHLO',
58+
'LHLO'
59+
]);
60+
5461
// Context-specific enhanced status code mappings
5562
const CONTEXTUAL_STATUS_CODES = {
5663
// Mail transaction specific codes
@@ -192,7 +199,7 @@ class SMTPConnection extends EventEmitter {
192199
this._setListeners(() => {
193200
// Check that connection limit is not exceeded
194201
if (this._server.options.maxClients && this._server.connections.size > this._server.options.maxClients) {
195-
return this.send(421, this.name + ' Too many connected clients, try again in a moment');
202+
return this.send(421, this.name + ' Too many connected clients, try again in a moment', false);
196203
}
197204

198205
// Keep a small delay for detecting early talkers
@@ -255,7 +262,7 @@ class SMTPConnection extends EventEmitter {
255262
);
256263

257264
if (err) {
258-
this.send(err.responseCode || 554, err.message);
265+
this.send(err.responseCode || 554, err.message, false);
259266
return this.close();
260267
}
261268

@@ -271,7 +278,8 @@ class SMTPConnection extends EventEmitter {
271278
this.name +
272279
' ' +
273280
(this._server.options.lmtp ? 'LMTP' : 'ESMTP') +
274-
(this._server.options.banner ? ' ' + this._server.options.banner : '')
281+
(this._server.options.banner ? ' ' + this._server.options.banner : ''),
282+
false
275283
);
276284

277285
if (typeof next === 'function') {
@@ -330,7 +338,7 @@ class SMTPConnection extends EventEmitter {
330338
*
331339
* @param {Number} code Response code
332340
* @param {String|Array} data If data is Array, send a multi-line response
333-
* @param {String} context Optional context for enhanced status codes
341+
* @param {String|Boolean} context Optional context for enhanced status codes
334342
*/
335343
send(code, data, context) {
336344
let payload;
@@ -531,17 +539,17 @@ class SMTPConnection extends EventEmitter {
531539

532540
// If server already closing then ignore commands
533541
if (this._server._closeTimeout) {
534-
return this.send(421, 'Server shutting down');
542+
return this.send(421, 'Server shutting down', commandName);
535543
}
536544

537545
if (!this._ready) {
538546
// block spammers that send payloads before server greeting
539-
return this.send(421, this.name + ' You talk too soon');
547+
return this.send(421, this.name + ' You talk too soon', commandName);
540548
}
541549

542550
// block malicious web pages that try to make SMTP calls from an AJAX request
543551
if (/^(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) \/.* HTTP\/\d\.\d$/i.test(command)) {
544-
return this.send(421, 'HTTP requests not allowed');
552+
return this.send(421, 'HTTP requests not allowed', commandName);
545553
}
546554

547555
if (this._upgrading) {
@@ -566,7 +574,7 @@ class SMTPConnection extends EventEmitter {
566574
switch (commandName) {
567575
case 'HELO':
568576
case 'EHLO':
569-
this.send(500, 'Error: ' + commandName + ' not allowed in LMTP server');
577+
this.send(500, 'Error: ' + commandName + ' not allowed in LMTP server', false);
570578
return setImmediate(callback);
571579
case 'LHLO':
572580
commandName = 'EHLO';
@@ -582,10 +590,10 @@ class SMTPConnection extends EventEmitter {
582590
// if the user makes more
583591
this._unrecognizedCommands++;
584592
if (this._unrecognizedCommands >= 10) {
585-
return this.send(421, 'Error: too many unrecognized commands');
593+
return this.send(421, 'Error: too many unrecognized commands', commandName);
586594
}
587595

588-
this.send(500, 'Error: command not recognized');
596+
this.send(500, 'Error: command not recognized', commandName);
589597
return setImmediate(callback);
590598
}
591599

@@ -599,7 +607,7 @@ class SMTPConnection extends EventEmitter {
599607
) {
600608
this._unauthenticatedCommands++;
601609
if (this._unauthenticatedCommands >= this._maxAllowedUnauthenticatedCommands) {
602-
return this.send(421, 'Error: too many unauthenticated commands');
610+
return this.send(421, 'Error: too many unauthenticated commands', commandName);
603611
}
604612
}
605613

@@ -642,22 +650,24 @@ class SMTPConnection extends EventEmitter {
642650
/**
643651
* Gets the appropriate enhanced status code for a given SMTP response code and context
644652
* @param {Number} code SMTP response code
645-
* @param {String} context Optional context for more specific status codes
653+
* @param {String|Boolean} context Optional context for more specific status codes
646654
* @returns {String} Enhanced status code or empty string if not applicable
647655
*/
648656
_getEnhancedStatusCode(code, context) {
649-
if (!this._useEnhancedStatusCodes()) {
657+
if (context === false || !this._useEnhancedStatusCodes()) {
650658
return '';
651659
}
652660

653-
// Skip enhanced status codes for initial greeting and HELO/EHLO responses
654-
// This is handled by checking the context in the calling code
655-
656661
// Skip 3xx responses as per RFC 2034
657662
if (code >= 300 && code < 400) {
658663
return '';
659664
}
660665

666+
// Skip enhanced status codes for initial greeting and HELO/EHLO responses
667+
if (context && SKIPPED_COMMANDS_FOR_ENHANCED_STATUS_CODES.has(context)) {
668+
return '';
669+
}
670+
661671
// Use contextual codes if available
662672
if (context && CONTEXTUAL_STATUS_CODES[context]) {
663673
return CONTEXTUAL_STATUS_CODES[context];
@@ -831,7 +841,7 @@ class SMTPConnection extends EventEmitter {
831841
let hostname = parts[1] || '';
832842

833843
if (parts.length !== 2) {
834-
this.send(501, 'Error: syntax: ' + (this._server.options.lmtp ? 'LHLO' : 'EHLO') + ' hostname');
844+
this.send(501, 'Error: syntax: ' + (this._server.options.lmtp ? 'LHLO' : 'EHLO') + ' hostname', false);
835845
return callback();
836846
}
837847

@@ -862,7 +872,7 @@ class SMTPConnection extends EventEmitter {
862872
}
863873

864874
this._resetSession(); // EHLO is effectively the same as RSET
865-
this.send(250, [this.name + ' Nice to meet you, ' + this.clientHostname].concat(features || []));
875+
this.send(250, [this.name + ' Nice to meet you, ' + this.clientHostname].concat(features || []), false);
866876

867877
callback();
868878
}
@@ -875,14 +885,14 @@ class SMTPConnection extends EventEmitter {
875885
let hostname = parts[1] || '';
876886

877887
if (parts.length !== 2) {
878-
this.send(501, 'Error: Syntax: HELO hostname');
888+
this.send(501, 'Error: Syntax: HELO hostname', false);
879889
return callback();
880890
}
881891

882892
this.hostNameAppearsAs = hostname.toLowerCase();
883893

884894
this._resetSession(); // HELO is effectively the same as RSET
885-
this.send(250, this.name + ' Nice to meet you, ' + this.clientHostname);
895+
this.send(250, this.name + ' Nice to meet you, ' + this.clientHostname, false);
886896

887897
callback();
888898
}

0 commit comments

Comments
 (0)