nodemailer 6.6.2 → 6.7.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 6.7.0 2021-10-11
4
+
5
+ - Updated DNS resolving logic. If there are multiple responses for a A/AAAA record, then loop these randomly instead of only caching the first one
6
+
7
+ ## 6.6.5 2021-09-23
8
+
9
+ - Replaced Object.values() and Array.flat() with polyfills to allow using Nodemailer in Node v6+
10
+
11
+ ## 6.6.4 2021-09-22
12
+
13
+ - Better compatibility with IPv6-only SMTP hosts (oxzi)
14
+ - Fix ses verify for sdk v3 (hannesvdvreken)
15
+ - Added SECURITY.txt for contact info
16
+
17
+ ## 6.6.3 2021-07-14
18
+
19
+ - Do not show passwords in SMTP transaction logs. All passwords used in logging are replaced by `"/* secret */"`
20
+
3
21
  ## 6.6.1 2021-05-23
4
22
 
5
23
  - Fixed address formatting issue where newlines in an email address, if provided via address object, were not properly removed. Reported by tmazeika (#1289)
package/README.md CHANGED
@@ -10,6 +10,8 @@ See [nodemailer.com](https://nodemailer.com/) for documentation and terms.
10
10
 
11
11
  ## Having an issue?
12
12
 
13
+ > Nodemailer supports all Node.js versions starting from Node.js@v6.0.0. Existing test suite does not support such old Node.js versions so all features are not actually tested. From time to time some regression bugs might occur because of this.
14
+
13
15
  #### First review the docs
14
16
 
15
17
  Documentation for Nodemailer can be found at [nodemailer.com](https://nodemailer.com/about/).
package/SECURITY.txt ADDED
@@ -0,0 +1,22 @@
1
+ -----BEGIN PGP SIGNED MESSAGE-----
2
+ Hash: SHA256
3
+
4
+ Contact: mailto:andris@reinman.eu
5
+ Encryption: https://keys.openpgp.org/vks/v1/by-fingerprint/5D952A46E1D8C931F6364E01DC6C83F4D584D364
6
+ Preferred-Languages: en, et
7
+ -----BEGIN PGP SIGNATURE-----
8
+
9
+ iQIzBAEBCAAdFiEEXZUqRuHYyTH2Nk4B3GyD9NWE02QFAmFDnUgACgkQ3GyD9NWE
10
+ 02RqUA/+MM3afmRYq874C7wp+uN6dTMCvUX5g5zqBZ2yKpFr46L+PYvM7o8TMm5h
11
+ hmLT2I1zZmi+xezOL3zHFizaw0tKkZIz9cWl3Jrgs0FLp0zOsSz1xucp9Q2tYM/Q
12
+ vbiP6ys0gbim4tkDGRmZOEiO23s0BuRnmHt7vZg210O+D105Yd8/Ohzbj6PSLBO5
13
+ W1tA7Xw5t0FQ14NNH5+MKyDIKoCX12n0FmrC6qLTXeojf291UgKhCUPda3LIGTmx
14
+ mTXz0y68149Mw+JikRCYP8HfGRY9eA4XZrYXF7Bl2T9OJpKD3JAH+69P3xBw19Gn
15
+ Csaw3twu8P1bxoVGjY4KRrBOp68W8TwZYjWVWbqY6oV8hb/JfrMxa+kaSxRuloFs
16
+ oL6+phrDSPTWdOj2LlEDBJbPOMeDFzIlsBBcJ/JHCEHTvlHl7LoWr3YuWce9PUwl
17
+ 4r3JUovvaeuJxLgC0vu3WCB3Jeocsl3SreqNkrVc1IjvkSomn3YGm5nCNAd/2F0V
18
+ exCGRk/8wbkSjAY38GwQ8K/VuFsefWN3L9sVwIMAMu88KFCAN+GzVFiwvyIXehF5
19
+ eogP9mIXzdQ5YReQjUjApOzGz54XnDyv9RJ3sdvMHosLP+IOg+0q5t9agWv6aqSR
20
+ 2HzCpiQnH/gmM5NS0AU4Koq/L7IBeLu1B8+61/+BiHgZJJmPdgU=
21
+ =BUZr
22
+ -----END PGP SIGNATURE-----
@@ -321,7 +321,7 @@ class SESTransport extends EventEmitter {
321
321
  Destinations: ['invalid@invalid']
322
322
  };
323
323
  const cb = err => {
324
- if (err && err.code !== 'InvalidParameterValue') {
324
+ if (err && (err.code || err.Code) !== 'InvalidParameterValue') {
325
325
  return callback(err);
326
326
  }
327
327
  return callback(null, true);
@@ -335,6 +335,7 @@ class SESTransport extends EventEmitter {
335
335
 
336
336
  if (typeof ses.send === 'function' && aws.SendRawEmailCommand) {
337
337
  // v3 API
338
+ sesMessage.RawMessage.Data = Buffer.from(sesMessage.RawMessage.Data);
338
339
  ses.send(new aws.SendRawEmailCommand(sesMessage), cb);
339
340
  } else {
340
341
  // v2 API
@@ -8,10 +8,26 @@ const fs = require('fs');
8
8
  const fetch = require('../fetch');
9
9
  const dns = require('dns');
10
10
  const net = require('net');
11
+ const os = require('os');
11
12
 
12
13
  const DNS_TTL = 5 * 60 * 1000;
13
14
 
15
+ const networkInterfaces = (module.exports.networkInterfaces = os.networkInterfaces());
16
+
14
17
  const resolver = (family, hostname, callback) => {
18
+ const familySupported =
19
+ // crux that replaces Object.values(networkInterfaces) as Object.values is not supported in nodejs v6
20
+ Object.keys(networkInterfaces)
21
+ .map(key => networkInterfaces[key])
22
+ // crux that replaces .flat() as it is not supported in older Node versions (v10 and older)
23
+ .reduce((acc, val) => acc.concat(val), [])
24
+ .filter(i => !i.internal)
25
+ .filter(i => i.family === 'IPv' + family).length > 0;
26
+
27
+ if (!familySupported) {
28
+ return callback(null, []);
29
+ }
30
+
15
31
  dns['resolve' + family](hostname, (err, addresses) => {
16
32
  if (err) {
17
33
  switch (err.code) {
@@ -30,16 +46,45 @@ const resolver = (family, hostname, callback) => {
30
46
  };
31
47
 
32
48
  const dnsCache = (module.exports.dnsCache = new Map());
49
+
50
+ const formatDNSValue = (value, extra) => {
51
+ if (!value) {
52
+ return Object.assign({}, extra || {});
53
+ }
54
+
55
+ return Object.assign(
56
+ {
57
+ servername: value.servername,
58
+ host:
59
+ !value.addresses || !value.addresses.length
60
+ ? null
61
+ : value.addresses.length === 1
62
+ ? value.addresses[0]
63
+ : value.addresses[Math.floor(Math.random() * value.addresses.length)]
64
+ },
65
+ extra || {}
66
+ );
67
+ };
68
+
33
69
  module.exports.resolveHostname = (options, callback) => {
34
70
  options = options || {};
35
71
 
72
+ if (!options.host && options.servername) {
73
+ options.host = options.servername;
74
+ }
75
+
36
76
  if (!options.host || net.isIP(options.host)) {
37
77
  // nothing to do here
38
78
  let value = {
39
- host: options.host,
79
+ addresses: [options.host],
40
80
  servername: options.servername || false
41
81
  };
42
- return callback(null, value);
82
+ return callback(
83
+ null,
84
+ formatDNSValue(value, {
85
+ cached: false
86
+ })
87
+ );
43
88
  }
44
89
 
45
90
  let cached;
@@ -47,11 +92,12 @@ module.exports.resolveHostname = (options, callback) => {
47
92
  if (dnsCache.has(options.host)) {
48
93
  cached = dnsCache.get(options.host);
49
94
  if (!cached.expires || cached.expires >= Date.now()) {
50
- return callback(null, {
51
- host: cached.value.host,
52
- servername: cached.value.servername,
53
- _cached: true
54
- });
95
+ return callback(
96
+ null,
97
+ formatDNSValue(cached.value, {
98
+ cached: true
99
+ })
100
+ );
55
101
  }
56
102
  }
57
103
 
@@ -59,40 +105,68 @@ module.exports.resolveHostname = (options, callback) => {
59
105
  if (err) {
60
106
  if (cached) {
61
107
  // ignore error, use expired value
62
- return callback(null, cached.value);
108
+ return callback(
109
+ null,
110
+ formatDNSValue(cached.value, {
111
+ cached: true,
112
+ error: err
113
+ })
114
+ );
63
115
  }
64
116
  return callback(err);
65
117
  }
118
+
66
119
  if (addresses && addresses.length) {
67
120
  let value = {
68
- host: addresses[0] || options.host,
121
+ addresses,
69
122
  servername: options.servername || options.host
70
123
  };
124
+
71
125
  dnsCache.set(options.host, {
72
126
  value,
73
127
  expires: Date.now() + DNS_TTL
74
128
  });
75
- return callback(null, value);
129
+
130
+ return callback(
131
+ null,
132
+ formatDNSValue(value, {
133
+ cached: false
134
+ })
135
+ );
76
136
  }
77
137
 
78
138
  resolver(6, options.host, (err, addresses) => {
79
139
  if (err) {
80
140
  if (cached) {
81
141
  // ignore error, use expired value
82
- return callback(null, cached.value);
142
+ return callback(
143
+ null,
144
+ formatDNSValue(cached.value, {
145
+ cached: true,
146
+ error: err
147
+ })
148
+ );
83
149
  }
84
150
  return callback(err);
85
151
  }
152
+
86
153
  if (addresses && addresses.length) {
87
154
  let value = {
88
- host: addresses[0] || options.host,
155
+ addresses,
89
156
  servername: options.servername || options.host
90
157
  };
158
+
91
159
  dnsCache.set(options.host, {
92
160
  value,
93
161
  expires: Date.now() + DNS_TTL
94
162
  });
95
- return callback(null, value);
163
+
164
+ return callback(
165
+ null,
166
+ formatDNSValue(value, {
167
+ cached: false
168
+ })
169
+ );
96
170
  }
97
171
 
98
172
  try {
@@ -100,30 +174,54 @@ module.exports.resolveHostname = (options, callback) => {
100
174
  if (err) {
101
175
  if (cached) {
102
176
  // ignore error, use expired value
103
- return callback(null, cached.value);
177
+ return callback(
178
+ null,
179
+ formatDNSValue(cached.value, {
180
+ cached: true,
181
+ error: err
182
+ })
183
+ );
104
184
  }
105
185
  return callback(err);
106
186
  }
107
187
 
108
188
  if (!address && cached) {
109
189
  // nothing was found, fallback to cached value
110
- return callback(null, cached.value);
190
+ return callback(
191
+ null,
192
+ formatDNSValue(cached.value, {
193
+ cached: true
194
+ })
195
+ );
111
196
  }
112
197
 
113
198
  let value = {
114
- host: address || options.host,
199
+ addresses: address ? [address] : [options.host],
115
200
  servername: options.servername || options.host
116
201
  };
202
+
117
203
  dnsCache.set(options.host, {
118
204
  value,
119
205
  expires: Date.now() + DNS_TTL
120
206
  });
121
- return callback(null, value);
207
+
208
+ return callback(
209
+ null,
210
+ formatDNSValue(value, {
211
+ cached: false
212
+ })
213
+ );
122
214
  });
123
215
  } catch (err) {
124
216
  if (cached) {
125
217
  // ignore error, use expired value
126
- return callback(null, cached.value);
218
+ return callback(
219
+ null,
220
+ formatDNSValue(cached.value, {
221
+ cached: true,
222
+ error: err
223
+ })
224
+ );
127
225
  }
128
226
  return callback(err);
129
227
  }
@@ -261,12 +261,12 @@ class SMTPConnection extends EventEmitter {
261
261
  tnx: 'dns',
262
262
  source: opts.host,
263
263
  resolved: resolved.host,
264
- cached: !!resolved._cached
264
+ cached: !!resolved.cached
265
265
  },
266
266
  'Resolved %s as %s [cache %s]',
267
267
  opts.host,
268
268
  resolved.host,
269
- resolved._cached ? 'hit' : 'miss'
269
+ resolved.cached ? 'hit' : 'miss'
270
270
  );
271
271
  Object.keys(resolved).forEach(key => {
272
272
  if (key.charAt(0) !== '_' && resolved[key]) {
@@ -299,12 +299,12 @@ class SMTPConnection extends EventEmitter {
299
299
  tnx: 'dns',
300
300
  source: opts.host,
301
301
  resolved: resolved.host,
302
- cached: !!resolved._cached
302
+ cached: !!resolved.cached
303
303
  },
304
304
  'Resolved %s as %s [cache %s]',
305
305
  opts.host,
306
306
  resolved.host,
307
- resolved._cached ? 'hit' : 'miss'
307
+ resolved.cached ? 'hit' : 'miss'
308
308
  );
309
309
  Object.keys(resolved).forEach(key => {
310
310
  if (key.charAt(0) !== '_' && resolved[key]) {
@@ -332,12 +332,12 @@ class SMTPConnection extends EventEmitter {
332
332
  tnx: 'dns',
333
333
  source: opts.host,
334
334
  resolved: resolved.host,
335
- cached: !!resolved._cached
335
+ cached: !!resolved.cached
336
336
  },
337
337
  'Resolved %s as %s [cache %s]',
338
338
  opts.host,
339
339
  resolved.host,
340
- resolved._cached ? 'hit' : 'miss'
340
+ resolved.cached ? 'hit' : 'miss'
341
341
  );
342
342
  Object.keys(resolved).forEach(key => {
343
343
  if (key.charAt(0) !== '_' && resolved[key]) {
@@ -548,6 +548,16 @@ class SMTPConnection extends EventEmitter {
548
548
  '\u0000' +
549
549
  this._auth.credentials.pass,
550
550
  'utf-8'
551
+ ).toString('base64'),
552
+ // log entry without passwords
553
+ 'AUTH PLAIN ' +
554
+ Buffer.from(
555
+ //this._auth.user+'\u0000'+
556
+ '\u0000' + // skip authorization identity as it causes problems with some servers
557
+ this._auth.credentials.user +
558
+ '\u0000' +
559
+ '/* secret */',
560
+ 'utf-8'
551
561
  ).toString('base64')
552
562
  );
553
563
  return;
@@ -945,8 +955,9 @@ class SMTPConnection extends EventEmitter {
945
955
  * Send a command to the server, append \r\n
946
956
  *
947
957
  * @param {String} str String to be sent to the server
958
+ * @param {String} logStr Optional string to be used for logging instead of the actual string
948
959
  */
949
- _sendCommand(str) {
960
+ _sendCommand(str, logStr) {
950
961
  if (this._destroyed) {
951
962
  // Connection already closed, can't send any more data
952
963
  return;
@@ -961,7 +972,7 @@ class SMTPConnection extends EventEmitter {
961
972
  {
962
973
  tnx: 'client'
963
974
  },
964
- (str || '').toString().replace(/\r?\n$/, '')
975
+ (logStr || str || '').toString().replace(/\r?\n$/, '')
965
976
  );
966
977
  }
967
978
 
@@ -1420,18 +1431,21 @@ class SMTPConnection extends EventEmitter {
1420
1431
 
1421
1432
  // Decode from base64
1422
1433
  let base64decoded = Buffer.from(challengeString, 'base64').toString('ascii'),
1423
- hmac_md5 = crypto.createHmac('md5', this._auth.credentials.pass);
1434
+ hmacMD5 = crypto.createHmac('md5', this._auth.credentials.pass);
1424
1435
 
1425
- hmac_md5.update(base64decoded);
1436
+ hmacMD5.update(base64decoded);
1426
1437
 
1427
- let hex_hmac = hmac_md5.digest('hex');
1428
- let prepended = this._auth.credentials.user + ' ' + hex_hmac;
1438
+ let prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
1429
1439
 
1430
1440
  this._responseActions.push(str => {
1431
1441
  this._actionAUTH_CRAM_MD5_PASS(str, callback);
1432
1442
  });
1433
1443
 
1434
- this._sendCommand(Buffer.from(prepended).toString('base64'));
1444
+ this._sendCommand(
1445
+ Buffer.from(prepended).toString('base64'),
1446
+ // hidden hash for logs
1447
+ Buffer.from(this._auth.credentials.user + ' /* secret */').toString('base64')
1448
+ );
1435
1449
  }
1436
1450
 
1437
1451
  /**
@@ -1476,7 +1490,11 @@ class SMTPConnection extends EventEmitter {
1476
1490
  this._actionAUTHComplete(str, callback);
1477
1491
  });
1478
1492
 
1479
- this._sendCommand(Buffer.from(this._auth.credentials.pass + '', 'utf-8').toString('base64'));
1493
+ this._sendCommand(
1494
+ Buffer.from((this._auth.credentials.pass || '').toString(), 'utf-8').toString('base64'),
1495
+ // Hidden pass for logs
1496
+ Buffer.from('/* secret */', 'utf-8').toString('base64')
1497
+ );
1480
1498
  }
1481
1499
 
1482
1500
  /**
@@ -1706,7 +1724,11 @@ class SMTPConnection extends EventEmitter {
1706
1724
  this._responseActions.push(str => {
1707
1725
  this._actionAUTHComplete(str, isRetry, callback);
1708
1726
  });
1709
- this._sendCommand('AUTH XOAUTH2 ' + this._auth.oauth2.buildXOAuth2Token(accessToken));
1727
+ this._sendCommand(
1728
+ 'AUTH XOAUTH2 ' + this._auth.oauth2.buildXOAuth2Token(accessToken),
1729
+ // Hidden for logs
1730
+ 'AUTH XOAUTH2 ' + this._auth.oauth2.buildXOAuth2Token('/* secret */')
1731
+ );
1710
1732
  });
1711
1733
  }
1712
1734
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodemailer",
3
- "version": "6.6.2",
3
+ "version": "6.7.0",
4
4
  "description": "Easy as cake e-mail sending from your Node.js applications",
5
5
  "main": "lib/nodemailer.js",
6
6
  "scripts": {
@@ -20,6 +20,8 @@
20
20
  },
21
21
  "homepage": "https://nodemailer.com/",
22
22
  "devDependencies": {
23
+ "@aws-sdk/client-ses": "3.36.0",
24
+ "aws-sdk": "2.1004.0",
23
25
  "bunyan": "1.8.15",
24
26
  "chai": "4.3.4",
25
27
  "eslint-config-nodemailer": "1.2.0",
@@ -31,11 +33,11 @@
31
33
  "libbase64": "1.2.1",
32
34
  "libmime": "5.0.0",
33
35
  "libqp": "1.1.0",
34
- "mocha": "9.0.0",
36
+ "mocha": "9.1.2",
35
37
  "nodemailer-ntlm-auth": "1.0.1",
36
38
  "proxy": "1.0.2",
37
39
  "proxy-test-server": "1.0.0",
38
- "sinon": "11.1.1",
40
+ "sinon": "11.1.2",
39
41
  "smtp-server": "3.9.0"
40
42
  },
41
43
  "engines": {