Skip to content

Commit

Permalink
Merge pull request #68 from NGTmeaty/master
Browse files Browse the repository at this point in the history
STARTTLS support and some other code cleanup
  • Loading branch information
GreenPioneer authored Oct 3, 2019
2 parents ff8f9b6 + c1f5c39 commit f8f5666
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 35 deletions.
130 changes: 97 additions & 33 deletions sendmail.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const {createConnection} = require('net');
const {connect} = require('tls');
const {resolveMx} = require('dns');
const {DKIMSign} = require('dkim-signer');
const CRLF = '\r\n';
Expand All @@ -21,8 +22,11 @@ module.exports = function (options) {
const dkimKeySelector = (options.dkim || {}).keySelector || 'dkim';
const devPort = options.devPort || -1;
const devHost = options.devHost || 'localhost';
const smtpPort = options.smtpPort || 25
const smtpHost = options.smtpHost || -1
const smtpPort = options.smtpPort || 25;
const smtpHost = options.smtpHost || -1;
const rejectUnauthorized = options.rejectUnauthorized;
const autoEHLO = options.autoEHLO;

/*
* 邮件服务返回代码含义 Mail service return code Meaning
* 500 格式错误,命令不可识别(此错误也包括命令行过长)format error, command unrecognized (This error also includes command line too long)
Expand Down Expand Up @@ -64,7 +68,7 @@ module.exports = function (options) {
host = getHost(recipients[i]);
(groups[host] || (groups[host] = [])).push(recipients[i])
}
return groups
return groups;
}

/**
Expand All @@ -74,21 +78,22 @@ module.exports = function (options) {
if (devPort === -1) { // not in development mode -> search the MX
resolveMx(domain, function (err, data) {
if (err) {
return callback(err)
return callback(err);
}

data.sort(function (a, b) { return a.priority > b.priority });
logger.debug('mx resolved: ', data);

if (!data || data.length === 0) {
return callback(new Error('can not resolve Mx of <' + domain + '>'))
return callback(new Error('can not resolve Mx of <' + domain + '>'));
}
if(smtpHost !== -1)data.push({exchange:smtpHost})
if(smtpHost !== -1) data.push({exchange:smtpHost});

function tryConnect (i) {
if (i >= data.length) return callback(new Error('can not connect to any SMTP server'));

const sock = createConnection(smtpPort, data[i].exchange);

sock.on('error', function (err) {
logger.error('Error on connectMx for: ', data[i], err);
tryConnect(++i)
Expand All @@ -97,7 +102,7 @@ module.exports = function (options) {
sock.on('connect', function () {
logger.debug('MX connection created: ', data[i].exchange);
sock.removeAllListeners('error');
callback(null, sock)
callback(null, sock);
})
}

Expand All @@ -113,7 +118,7 @@ module.exports = function (options) {
sock.on('connect', function () {
logger.debug('MX (development) connection created: '+ devHost +':' + devPort);
sock.removeAllListeners('error');
callback(null, sock)
callback(null, sock);
})
}
}
Expand All @@ -123,12 +128,12 @@ module.exports = function (options) {
connectMx(domain, function (err, sock) {
if (err) {
logger.error('error on connectMx', err.stack);
return callback(err)
return callback(err);
}

function w (s) {
logger.debug('send ' + domain + '>' + s);
sock.write(s + CRLF)
sock.write(s + CRLF);
}

sock.setEncoding('utf8');
Expand All @@ -138,14 +143,14 @@ module.exports = function (options) {
parts = data.split(CRLF);
const parts_length = parts.length - 1;
for (let i = 0, len = parts_length; i < len; i++) {
onLine(parts[i])
onLine(parts[i]);
}
data = parts[parts.length - 1]
data = parts[parts.length - 1];
});

sock.on('error', function (err) {
logger.error('fail to connect ' + domain)
callback(err)
logger.error('fail to connect ' + domain);
callback(err);
});

let data = '';
Expand All @@ -155,6 +160,7 @@ module.exports = function (options) {
const login = [];
let parts;
let cmd;
let upgraded = false;

/*
if(mail.user && mail.pass){
Expand All @@ -167,7 +173,7 @@ module.exports = function (options) {
queue.push('MAIL FROM:<' + from + '>');
const recipients_length = recipients.length;
for (let i = 0; i < recipients_length; i++) {
queue.push('RCPT TO:<' + recipients[i] + '>')
queue.push('RCPT TO:<' + recipients[i] + '>');
}
queue.push('DATA');
queue.push('QUIT');
Expand All @@ -178,22 +184,80 @@ module.exports = function (options) {
case 220:
//* 220 on server ready
//* 220 服务就绪
if (/\besmtp\b/i.test(msg)) {
// TODO: determin AUTH type; auth login, auth crm-md5, auth plain
cmd = 'EHLO'
} else {
cmd = 'HELO'
}
w(cmd + ' ' + srcHost);
break;
if(upgraded === "in-progress"){
sock.removeAllListeners('data');

let original = sock;
original.pause();

let opts = {
socket: sock,
host: sock._host,
rejectUnauthorized,
};

sock = connect(
opts,
() => {
sock.on('data', function (chunk) {
data += chunk;
parts = data.split(CRLF);
const parts_length = parts.length - 1;
for (let i = 0, len = parts_length; i < len; i++) {
onLine(parts[i])
}
data = parts[parts.length - 1]
});

sock.removeAllListeners('close');
sock.removeAllListeners('end');

return;
}
);

sock.on('error', function (err) {
logger.error('Error on connectMx for: ', err);
});

original.resume();
upgraded = true;
w("EHLO " + srcHost);
break;
} else
{
if (/\besmtp\b/i.test(msg) || autoEHLO) {
// TODO: determin AUTH type; auth login, auth crm-md5, auth plain
cmd = 'EHLO';
} else {
upgraded = true;
cmd = 'HELO';
}
w(cmd + ' ' + srcHost);
break;
}

case 221: // bye
sock.end();
callback(null, msg);
break;
case 235: // verify ok
case 250: // operation OK
if(upgraded != true){
if(/\bSTARTTLS\b/i.test(msg)){
w('STARTTLS');
upgraded = "in-progress";
} else {
upgraded = true;
}

break;
}

case 251: // foward
if (step === queue.length - 1) {
logger.info('OK:', code, msg);
callback(null, msg)
callback(null, msg);
}
w(queue[step]);
step++;
Expand Down Expand Up @@ -252,7 +316,7 @@ module.exports = function (options) {
for (let i = 0; i < addresses_length; i++) {
results.push(getAddress(addresses[i]));
}
return results
return results;
}

/**
Expand Down Expand Up @@ -289,15 +353,15 @@ module.exports = function (options) {
let groups;
let srcHost;
if (mail.to) {
recipients = recipients.concat(getAddresses(mail.to))
recipients = recipients.concat(getAddresses(mail.to));
}

if (mail.cc) {
recipients = recipients.concat(getAddresses(mail.cc))
recipients = recipients.concat(getAddresses(mail.cc));
}

if (mail.bcc) {
recipients = recipients.concat(getAddresses(mail.bcc))
recipients = recipients.concat(getAddresses(mail.bcc));
}

groups = groupRecipients(recipients);
Expand All @@ -307,7 +371,7 @@ module.exports = function (options) {

mailMe.build(function (err, message) {
if (err) {
logger.error('Error on creating message : ', err)
logger.error('Error on creating message : ', err);
callback(err, null);
return
}
Expand All @@ -317,12 +381,12 @@ module.exports = function (options) {
keySelector: dkimKeySelector,
domainName: srcHost
});
message = signature + '\r\n' + message
message = signature + '\r\n' + message;
}
for (let domain in groups) {
sendToSMTP(domain, srcHost, from, groups[domain], message, callback)
sendToSMTP(domain, srcHost, from, groups[domain], message, callback);
}
});
}
return sendmail
return sendmail;
};
6 changes: 4 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,10 @@ del@^2.0.2:
pinkie-promise "^2.0.0"
rimraf "^2.2.8"

dkim-signer@^0.2.2:
dkim-signer@0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dkim-signer/-/dkim-signer-0.2.2.tgz#aa81ec071eeed3622781baa922044d7800e5f308"
integrity sha1-qoHsBx7u02IngbqpIgRNeADl8wg=
dependencies:
libmime "^2.0.3"

Expand Down Expand Up @@ -838,9 +839,10 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"

mailcomposer@^3.12.0:
mailcomposer@3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/mailcomposer/-/mailcomposer-3.12.0.tgz#9c5e1188aa8e1c62ec8b86bd43468102b639e8f9"
integrity sha1-nF4RiKqOHGLsi4a9Q0aBArY56Pk=
dependencies:
buildmail "3.10.0"
libmime "2.1.0"
Expand Down

0 comments on commit f8f5666

Please sign in to comment.