Skip to content

Commit f8f5666

Browse files
authored
Merge pull request guileen#68 from NGTmeaty/master
STARTTLS support and some other code cleanup
2 parents ff8f9b6 + c1f5c39 commit f8f5666

File tree

2 files changed

+101
-35
lines changed

2 files changed

+101
-35
lines changed

sendmail.js

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const {createConnection} = require('net');
2+
const {connect} = require('tls');
23
const {resolveMx} = require('dns');
34
const {DKIMSign} = require('dkim-signer');
45
const CRLF = '\r\n';
@@ -21,8 +22,11 @@ module.exports = function (options) {
2122
const dkimKeySelector = (options.dkim || {}).keySelector || 'dkim';
2223
const devPort = options.devPort || -1;
2324
const devHost = options.devHost || 'localhost';
24-
const smtpPort = options.smtpPort || 25
25-
const smtpHost = options.smtpHost || -1
25+
const smtpPort = options.smtpPort || 25;
26+
const smtpHost = options.smtpHost || -1;
27+
const rejectUnauthorized = options.rejectUnauthorized;
28+
const autoEHLO = options.autoEHLO;
29+
2630
/*
2731
* 邮件服务返回代码含义 Mail service return code Meaning
2832
* 500 格式错误,命令不可识别(此错误也包括命令行过长)format error, command unrecognized (This error also includes command line too long)
@@ -64,7 +68,7 @@ module.exports = function (options) {
6468
host = getHost(recipients[i]);
6569
(groups[host] || (groups[host] = [])).push(recipients[i])
6670
}
67-
return groups
71+
return groups;
6872
}
6973

7074
/**
@@ -74,21 +78,22 @@ module.exports = function (options) {
7478
if (devPort === -1) { // not in development mode -> search the MX
7579
resolveMx(domain, function (err, data) {
7680
if (err) {
77-
return callback(err)
81+
return callback(err);
7882
}
7983

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

8387
if (!data || data.length === 0) {
84-
return callback(new Error('can not resolve Mx of <' + domain + '>'))
88+
return callback(new Error('can not resolve Mx of <' + domain + '>'));
8589
}
86-
if(smtpHost !== -1)data.push({exchange:smtpHost})
90+
if(smtpHost !== -1) data.push({exchange:smtpHost});
91+
8792
function tryConnect (i) {
8893
if (i >= data.length) return callback(new Error('can not connect to any SMTP server'));
8994

9095
const sock = createConnection(smtpPort, data[i].exchange);
91-
96+
9297
sock.on('error', function (err) {
9398
logger.error('Error on connectMx for: ', data[i], err);
9499
tryConnect(++i)
@@ -97,7 +102,7 @@ module.exports = function (options) {
97102
sock.on('connect', function () {
98103
logger.debug('MX connection created: ', data[i].exchange);
99104
sock.removeAllListeners('error');
100-
callback(null, sock)
105+
callback(null, sock);
101106
})
102107
}
103108

@@ -113,7 +118,7 @@ module.exports = function (options) {
113118
sock.on('connect', function () {
114119
logger.debug('MX (development) connection created: '+ devHost +':' + devPort);
115120
sock.removeAllListeners('error');
116-
callback(null, sock)
121+
callback(null, sock);
117122
})
118123
}
119124
}
@@ -123,12 +128,12 @@ module.exports = function (options) {
123128
connectMx(domain, function (err, sock) {
124129
if (err) {
125130
logger.error('error on connectMx', err.stack);
126-
return callback(err)
131+
return callback(err);
127132
}
128133

129134
function w (s) {
130135
logger.debug('send ' + domain + '>' + s);
131-
sock.write(s + CRLF)
136+
sock.write(s + CRLF);
132137
}
133138

134139
sock.setEncoding('utf8');
@@ -138,14 +143,14 @@ module.exports = function (options) {
138143
parts = data.split(CRLF);
139144
const parts_length = parts.length - 1;
140145
for (let i = 0, len = parts_length; i < len; i++) {
141-
onLine(parts[i])
146+
onLine(parts[i]);
142147
}
143-
data = parts[parts.length - 1]
148+
data = parts[parts.length - 1];
144149
});
145150

146151
sock.on('error', function (err) {
147-
logger.error('fail to connect ' + domain)
148-
callback(err)
152+
logger.error('fail to connect ' + domain);
153+
callback(err);
149154
});
150155

151156
let data = '';
@@ -155,6 +160,7 @@ module.exports = function (options) {
155160
const login = [];
156161
let parts;
157162
let cmd;
163+
let upgraded = false;
158164

159165
/*
160166
if(mail.user && mail.pass){
@@ -167,7 +173,7 @@ module.exports = function (options) {
167173
queue.push('MAIL FROM:<' + from + '>');
168174
const recipients_length = recipients.length;
169175
for (let i = 0; i < recipients_length; i++) {
170-
queue.push('RCPT TO:<' + recipients[i] + '>')
176+
queue.push('RCPT TO:<' + recipients[i] + '>');
171177
}
172178
queue.push('DATA');
173179
queue.push('QUIT');
@@ -178,22 +184,80 @@ module.exports = function (options) {
178184
case 220:
179185
//* 220 on server ready
180186
//* 220 服务就绪
181-
if (/\besmtp\b/i.test(msg)) {
182-
// TODO: determin AUTH type; auth login, auth crm-md5, auth plain
183-
cmd = 'EHLO'
184-
} else {
185-
cmd = 'HELO'
186-
}
187-
w(cmd + ' ' + srcHost);
188-
break;
187+
if(upgraded === "in-progress"){
188+
sock.removeAllListeners('data');
189+
190+
let original = sock;
191+
original.pause();
192+
193+
let opts = {
194+
socket: sock,
195+
host: sock._host,
196+
rejectUnauthorized,
197+
};
198+
199+
sock = connect(
200+
opts,
201+
() => {
202+
sock.on('data', function (chunk) {
203+
data += chunk;
204+
parts = data.split(CRLF);
205+
const parts_length = parts.length - 1;
206+
for (let i = 0, len = parts_length; i < len; i++) {
207+
onLine(parts[i])
208+
}
209+
data = parts[parts.length - 1]
210+
});
211+
212+
sock.removeAllListeners('close');
213+
sock.removeAllListeners('end');
214+
215+
return;
216+
}
217+
);
218+
219+
sock.on('error', function (err) {
220+
logger.error('Error on connectMx for: ', err);
221+
});
222+
223+
original.resume();
224+
upgraded = true;
225+
w("EHLO " + srcHost);
226+
break;
227+
} else
228+
{
229+
if (/\besmtp\b/i.test(msg) || autoEHLO) {
230+
// TODO: determin AUTH type; auth login, auth crm-md5, auth plain
231+
cmd = 'EHLO';
232+
} else {
233+
upgraded = true;
234+
cmd = 'HELO';
235+
}
236+
w(cmd + ' ' + srcHost);
237+
break;
238+
}
189239

190240
case 221: // bye
241+
sock.end();
242+
callback(null, msg);
243+
break;
191244
case 235: // verify ok
192245
case 250: // operation OK
246+
if(upgraded != true){
247+
if(/\bSTARTTLS\b/i.test(msg)){
248+
w('STARTTLS');
249+
upgraded = "in-progress";
250+
} else {
251+
upgraded = true;
252+
}
253+
254+
break;
255+
}
256+
193257
case 251: // foward
194258
if (step === queue.length - 1) {
195259
logger.info('OK:', code, msg);
196-
callback(null, msg)
260+
callback(null, msg);
197261
}
198262
w(queue[step]);
199263
step++;
@@ -252,7 +316,7 @@ module.exports = function (options) {
252316
for (let i = 0; i < addresses_length; i++) {
253317
results.push(getAddress(addresses[i]));
254318
}
255-
return results
319+
return results;
256320
}
257321

258322
/**
@@ -289,15 +353,15 @@ module.exports = function (options) {
289353
let groups;
290354
let srcHost;
291355
if (mail.to) {
292-
recipients = recipients.concat(getAddresses(mail.to))
356+
recipients = recipients.concat(getAddresses(mail.to));
293357
}
294358

295359
if (mail.cc) {
296-
recipients = recipients.concat(getAddresses(mail.cc))
360+
recipients = recipients.concat(getAddresses(mail.cc));
297361
}
298362

299363
if (mail.bcc) {
300-
recipients = recipients.concat(getAddresses(mail.bcc))
364+
recipients = recipients.concat(getAddresses(mail.bcc));
301365
}
302366

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

308372
mailMe.build(function (err, message) {
309373
if (err) {
310-
logger.error('Error on creating message : ', err)
374+
logger.error('Error on creating message : ', err);
311375
callback(err, null);
312376
return
313377
}
@@ -317,12 +381,12 @@ module.exports = function (options) {
317381
keySelector: dkimKeySelector,
318382
domainName: srcHost
319383
});
320-
message = signature + '\r\n' + message
384+
message = signature + '\r\n' + message;
321385
}
322386
for (let domain in groups) {
323-
sendToSMTP(domain, srcHost, from, groups[domain], message, callback)
387+
sendToSMTP(domain, srcHost, from, groups[domain], message, callback);
324388
}
325389
});
326390
}
327-
return sendmail
391+
return sendmail;
328392
};

yarn.lock

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,10 @@ del@^2.0.2:
266266
pinkie-promise "^2.0.0"
267267
rimraf "^2.2.8"
268268

269-
dkim-signer@^0.2.2:
269+
dkim-signer@0.2.2:
270270
version "0.2.2"
271271
resolved "https://registry.yarnpkg.com/dkim-signer/-/dkim-signer-0.2.2.tgz#aa81ec071eeed3622781baa922044d7800e5f308"
272+
integrity sha1-qoHsBx7u02IngbqpIgRNeADl8wg=
272273
dependencies:
273274
libmime "^2.0.3"
274275

@@ -838,9 +839,10 @@ lru-cache@^4.0.1:
838839
pseudomap "^1.0.2"
839840
yallist "^2.1.2"
840841

841-
mailcomposer@^3.12.0:
842+
mailcomposer@3.12.0:
842843
version "3.12.0"
843844
resolved "https://registry.yarnpkg.com/mailcomposer/-/mailcomposer-3.12.0.tgz#9c5e1188aa8e1c62ec8b86bd43468102b639e8f9"
845+
integrity sha1-nF4RiKqOHGLsi4a9Q0aBArY56Pk=
844846
dependencies:
845847
buildmail "3.10.0"
846848
libmime "2.1.0"

0 commit comments

Comments
 (0)