diff --git a/lib/pop3-connection.js b/lib/pop3-connection.js index 361ce7af..be9facfd 100644 --- a/lib/pop3-connection.js +++ b/lib/pop3-connection.js @@ -171,15 +171,44 @@ class POP3Connection extends EventEmitter { return; } let line = this.queue.shift().trim(); + + if (typeof this._nextHandler === 'function') { + let handler = this._nextHandler; + this._nextHandler = null; + this._server.logger.debug({ + tnx: 'receive', + cid: this._id, + user: this.session.user && this.session.user.username + }, 'C: <%s bytes of continue data>', Buffer.byteLength(line)); + return handler(line, err => { + if (err) { + this._server.logger.info({ + err, + tnx: '+', + cid: this._id, + host: this.remoteAddress + }, 'Error processing continue data. %s', err.message); + this.send('-ERR ' + err.message); + this.close(); + } else { + this.processQueue(); + } + }); + } + let parts = line.split(' '); let command = parts.shift().toUpperCase(); let args = parts.join(' '); + let logLine = (line || '').toString(); + if (/^(PASS|AUTH PLAIN)\s+[^\s]+/i.test(line)) { + logLine = logLine.replace(/[^\s]+$/, '*hidden*'); + } this._server.logger.debug({ tnx: 'receive', cid: this._id, user: this.session.user && this.session.user.username - }, 'C:', (line || '').toString()); + }, 'C:', logLine); if (typeof this['command_' + command] === 'function') { this['command_' + command](args, err => { @@ -310,70 +339,26 @@ class POP3Connection extends EventEmitter { let params = args.split(/\s+/); let mechanism = params.shift().toUpperCase(); - let plain = params.shift(); if (mechanism !== 'PLAIN') { this.send('-ERR unsupported SASL mechanism'); return next(); } - if (params.length || !/^[a-zA-Z0-9+\/]+=+?$/.test(plain)) { + if (!params.length) { + this.send('+'); + this._nextHandler = (args, next) => this.authPlain(args, next); + return next(); + } + + let plain = params.shift(); + + if (params.length) { this.send('-ERR malformed command'); return next(); } - let credentials = Buffer.from(plain, 'base64').toString().split('\x00'); - if (credentials.length !== 3) { - this.send('-ERR malformed command'); - return next(); - } - - let username = credentials[1] || credentials[0] || ''; - let password = credentials[2] || ''; - - this._server.onAuth({ - method: 'PLAIN', - username, - password - }, this.session, (err, response) => { - - if (err) { - this._server.logger.info({ - err, - tnx: 'autherror', - cid: this._id, - method: 'PLAIN', - user: username - }, 'Authentication error for %s using %s. %s', username, 'PLAIN', err.message); - return next(err); - } - - if (!response.user) { - this._server.logger.info({ - tnx: 'authfail', - cid: this._id, - method: 'PLAIN', - user: username - }, 'Authentication failed for %s using %s', username, 'PLAIN'); - this.send('-ERR [AUTH] ' + (response.message || 'Username and password not accepted')); - return next(); - } - - this._server.logger.info({ - tnx: 'auth', - cid: this._id, - method: 'PLAIN', - user: username - }, '%s authenticated using %s', username, 'PLAIN'); - this.session.user = response.user; - - this.openMailbox(err => { - if (err) { - return next(err); - } - next(); - }); - }); + this.authPlain(plain, next); } // https://tools.ietf.org/html/rfc1939#page-9 @@ -662,7 +647,7 @@ class POP3Connection extends EventEmitter { } else { data = ''; } - this.write(Buffer.from(headers.replace(/^\./gm,'..'), 'binary')); + this.write(Buffer.from(headers.replace(/^\./gm, '..'), 'binary')); } } if (headers) { @@ -682,7 +667,7 @@ class POP3Connection extends EventEmitter { } else { data = ''; } - this.write(Buffer.from(line.replace(/^\./gm,'..'), 'binary')); + this.write(Buffer.from(line.replace(/^\./gm, '..'), 'binary')); if (linesSent >= lines) { finished = true; if (typeof stream.abort === 'function') { @@ -705,6 +690,66 @@ class POP3Connection extends EventEmitter { }); } + authPlain(plain, next) { + if (!/^[a-zA-Z0-9+\/]+=+?$/.test(plain)) { + this.send('-ERR malformed command'); + return next(); + } + + let credentials = Buffer.from(plain, 'base64').toString().split('\x00'); + if (credentials.length !== 3) { + this.send('-ERR malformed command'); + return next(); + } + + let username = credentials[1] || credentials[0] || ''; + let password = credentials[2] || ''; + + this._server.onAuth({ + method: 'PLAIN', + username, + password + }, this.session, (err, response) => { + + if (err) { + this._server.logger.info({ + err, + tnx: 'autherror', + cid: this._id, + method: 'PLAIN', + user: username + }, 'Authentication error for %s using %s. %s', username, 'PLAIN', err.message); + return next(err); + } + + if (!response.user) { + this._server.logger.info({ + tnx: 'authfail', + cid: this._id, + method: 'PLAIN', + user: username + }, 'Authentication failed for %s using %s', username, 'PLAIN'); + this.send('-ERR [AUTH] ' + (response.message || 'Username and password not accepted')); + return next(); + } + + this._server.logger.info({ + tnx: 'auth', + cid: this._id, + method: 'PLAIN', + user: username + }, '%s authenticated using %s', username, 'PLAIN'); + this.session.user = response.user; + + this.openMailbox(err => { + if (err) { + return next(err); + } + next(); + }); + }); + } + openMailbox(next) { this._server.onListMessages(this.session, (err, listing) => { if (err) {