m0603va 1.0.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.
Files changed (117) hide show
  1. package/Laba6/file1.js +23 -0
  2. package/Laba6/file2.js +23 -0
  3. package/Laba6/index.html +63 -0
  4. package/Laba6/m0603.js +29 -0
  5. package/Laba6/node_modules/.package-lock.json +83 -0
  6. package/Laba6/node_modules/httpntlm/.jshintrc +4 -0
  7. package/Laba6/node_modules/httpntlm/LICENSE +20 -0
  8. package/Laba6/node_modules/httpntlm/README.md +148 -0
  9. package/Laba6/node_modules/httpntlm/httpntlm.js +104 -0
  10. package/Laba6/node_modules/httpntlm/ntlm.js +390 -0
  11. package/Laba6/node_modules/httpntlm/package.json +33 -0
  12. package/Laba6/node_modules/httpreq/LICENSE +19 -0
  13. package/Laba6/node_modules/httpreq/README.md +383 -0
  14. package/Laba6/node_modules/httpreq/contributors.md +26 -0
  15. package/Laba6/node_modules/httpreq/lib/httpreq.js +681 -0
  16. package/Laba6/node_modules/httpreq/package.json +102 -0
  17. package/Laba6/node_modules/m0603sol/m0603.js +29 -0
  18. package/Laba6/node_modules/m0603sol/package.json +12 -0
  19. package/Laba6/node_modules/nodemailer/.gitattributes +6 -0
  20. package/Laba6/node_modules/nodemailer/.prettierrc.js +8 -0
  21. package/Laba6/node_modules/nodemailer/CHANGELOG.md +725 -0
  22. package/Laba6/node_modules/nodemailer/CODE_OF_CONDUCT.md +76 -0
  23. package/Laba6/node_modules/nodemailer/CONTRIBUTING.md +67 -0
  24. package/Laba6/node_modules/nodemailer/LICENSE +16 -0
  25. package/Laba6/node_modules/nodemailer/README.md +97 -0
  26. package/Laba6/node_modules/nodemailer/SECURITY.txt +22 -0
  27. package/Laba6/node_modules/nodemailer/lib/addressparser/index.js +313 -0
  28. package/Laba6/node_modules/nodemailer/lib/base64/index.js +142 -0
  29. package/Laba6/node_modules/nodemailer/lib/dkim/index.js +251 -0
  30. package/Laba6/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
  31. package/Laba6/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
  32. package/Laba6/node_modules/nodemailer/lib/dkim/sign.js +117 -0
  33. package/Laba6/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
  34. package/Laba6/node_modules/nodemailer/lib/fetch/index.js +274 -0
  35. package/Laba6/node_modules/nodemailer/lib/json-transport/index.js +82 -0
  36. package/Laba6/node_modules/nodemailer/lib/mail-composer/index.js +558 -0
  37. package/Laba6/node_modules/nodemailer/lib/mailer/index.js +427 -0
  38. package/Laba6/node_modules/nodemailer/lib/mailer/mail-message.js +315 -0
  39. package/Laba6/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
  40. package/Laba6/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2102 -0
  41. package/Laba6/node_modules/nodemailer/lib/mime-node/index.js +1290 -0
  42. package/Laba6/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
  43. package/Laba6/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
  44. package/Laba6/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
  45. package/Laba6/node_modules/nodemailer/lib/nodemailer.js +143 -0
  46. package/Laba6/node_modules/nodemailer/lib/qp/index.js +219 -0
  47. package/Laba6/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
  48. package/Laba6/node_modules/nodemailer/lib/ses-transport/index.js +349 -0
  49. package/Laba6/node_modules/nodemailer/lib/shared/index.js +638 -0
  50. package/Laba6/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
  51. package/Laba6/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
  52. package/Laba6/node_modules/nodemailer/lib/smtp-connection/index.js +1796 -0
  53. package/Laba6/node_modules/nodemailer/lib/smtp-pool/index.js +648 -0
  54. package/Laba6/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +253 -0
  55. package/Laba6/node_modules/nodemailer/lib/smtp-transport/index.js +416 -0
  56. package/Laba6/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
  57. package/Laba6/node_modules/nodemailer/lib/well-known/index.js +47 -0
  58. package/Laba6/node_modules/nodemailer/lib/well-known/services.json +286 -0
  59. package/Laba6/node_modules/nodemailer/lib/xoauth2/index.js +376 -0
  60. package/Laba6/node_modules/nodemailer/package.json +46 -0
  61. package/Laba6/node_modules/nodemailer/postinstall.js +101 -0
  62. package/Laba6/node_modules/nodemailer-fetch/.eslintrc.js +56 -0
  63. package/Laba6/node_modules/nodemailer-fetch/.travis.yml +19 -0
  64. package/Laba6/node_modules/nodemailer-fetch/CHANGELOG.md +30 -0
  65. package/Laba6/node_modules/nodemailer-fetch/Gruntfile.js +27 -0
  66. package/Laba6/node_modules/nodemailer-fetch/LICENSE +16 -0
  67. package/Laba6/node_modules/nodemailer-fetch/README.md +55 -0
  68. package/Laba6/node_modules/nodemailer-fetch/lib/cookies.js +275 -0
  69. package/Laba6/node_modules/nodemailer-fetch/lib/fetch.js +224 -0
  70. package/Laba6/node_modules/nodemailer-fetch/package.json +30 -0
  71. package/Laba6/node_modules/nodemailer-fetch/test/cookies-test.js +391 -0
  72. package/Laba6/node_modules/nodemailer-fetch/test/fetch-test.js +486 -0
  73. package/Laba6/node_modules/nodemailer-shared/.eslintrc.js +59 -0
  74. package/Laba6/node_modules/nodemailer-shared/.travis.yml +18 -0
  75. package/Laba6/node_modules/nodemailer-shared/Gruntfile.js +27 -0
  76. package/Laba6/node_modules/nodemailer-shared/LICENSE +16 -0
  77. package/Laba6/node_modules/nodemailer-shared/README.md +14 -0
  78. package/Laba6/node_modules/nodemailer-shared/lib/shared.js +282 -0
  79. package/Laba6/node_modules/nodemailer-shared/package.json +36 -0
  80. package/Laba6/node_modules/nodemailer-shared/test/fixtures/message.html +1 -0
  81. package/Laba6/node_modules/nodemailer-shared/test/shared-test.js +291 -0
  82. package/Laba6/node_modules/nodemailer-smtp-transport/.eslintrc.js +59 -0
  83. package/Laba6/node_modules/nodemailer-smtp-transport/Gruntfile.js +27 -0
  84. package/Laba6/node_modules/nodemailer-smtp-transport/LICENSE +19 -0
  85. package/Laba6/node_modules/nodemailer-smtp-transport/README.md +7 -0
  86. package/Laba6/node_modules/nodemailer-smtp-transport/lib/smtp-transport.js +281 -0
  87. package/Laba6/node_modules/nodemailer-smtp-transport/package.json +37 -0
  88. package/Laba6/node_modules/nodemailer-wellknown/.travis.yml +17 -0
  89. package/Laba6/node_modules/nodemailer-wellknown/LICENSE +19 -0
  90. package/Laba6/node_modules/nodemailer-wellknown/README.md +80 -0
  91. package/Laba6/node_modules/nodemailer-wellknown/index.js +47 -0
  92. package/Laba6/node_modules/nodemailer-wellknown/package.json +26 -0
  93. package/Laba6/node_modules/nodemailer-wellknown/services.json +255 -0
  94. package/Laba6/node_modules/nodemailer-wellknown/test.js +23 -0
  95. package/Laba6/node_modules/smtp-connection/.eslintrc.js +56 -0
  96. package/Laba6/node_modules/smtp-connection/CHANGELOG.md +164 -0
  97. package/Laba6/node_modules/smtp-connection/Gruntfile.js +27 -0
  98. package/Laba6/node_modules/smtp-connection/LICENSE +19 -0
  99. package/Laba6/node_modules/smtp-connection/README.md +200 -0
  100. package/Laba6/node_modules/smtp-connection/lib/data-stream.js +111 -0
  101. package/Laba6/node_modules/smtp-connection/lib/smtp-connection.js +1443 -0
  102. package/Laba6/node_modules/smtp-connection/package.json +41 -0
  103. package/Laba6/node_modules/underscore/LICENSE +23 -0
  104. package/Laba6/node_modules/underscore/README.md +22 -0
  105. package/Laba6/node_modules/underscore/package.json +41 -0
  106. package/Laba6/node_modules/underscore/underscore-min.js +6 -0
  107. package/Laba6/node_modules/underscore/underscore.js +1415 -0
  108. package/Laba6/package-lock.json +94 -0
  109. package/Laba6/package.json +17 -0
  110. package/Laba6.txt +31 -0
  111. package/file1.js +23 -0
  112. package/file2.js +23 -0
  113. package/index.html +63 -0
  114. package/m0603.js +29 -0
  115. package/m0603Sol/m0603.js +29 -0
  116. package/m0603Sol/package.json +12 -0
  117. package/package.json +17 -0
@@ -0,0 +1,1443 @@
1
+ 'use strict';
2
+
3
+ var packageInfo = require('../package.json');
4
+ var EventEmitter = require('events').EventEmitter;
5
+ var util = require('util');
6
+ var net = require('net');
7
+ var tls = require('tls');
8
+ var os = require('os');
9
+ var crypto = require('crypto');
10
+ var DataStream = require('./data-stream');
11
+ var PassThrough = require('stream').PassThrough;
12
+ var shared = require('nodemailer-shared');
13
+ var ntlm = require('httpntlm/ntlm');
14
+
15
+ // default timeout values in ms
16
+ var CONNECTION_TIMEOUT = 2 * 60 * 1000; // how much to wait for the connection to be established
17
+ var SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity before disconnecting the client
18
+ var GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved
19
+
20
+ module.exports = SMTPConnection;
21
+
22
+ /**
23
+ * Generates a SMTP connection object
24
+ *
25
+ * Optional options object takes the following possible properties:
26
+ *
27
+ * * **port** - is the port to connect to (defaults to 25 or 465)
28
+ * * **host** - is the hostname or IP address to connect to (defaults to 'localhost')
29
+ * * **secure** - use SSL
30
+ * * **ignoreTLS** - ignore server support for STARTTLS
31
+ * * **requireTLS** - forces the client to use STARTTLS
32
+ * * **name** - the name of the client server
33
+ * * **localAddress** - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)
34
+ * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 10000)
35
+ * * **connectionTimeout** - how many milliseconds to wait for the connection to establish
36
+ * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 1 hour)
37
+ * * **lmtp** - if true, uses LMTP instead of SMTP protocol
38
+ * * **logger** - bunyan compatible logger interface
39
+ * * **debug** - if true pass SMTP traffic to the logger
40
+ * * **tls** - options for createCredentials
41
+ * * **socket** - existing socket to use instead of creating a new one (see: http://nodejs.org/api/net.html#net_class_net_socket)
42
+ * * **secured** - boolean indicates that the provided socket has already been upgraded to tls
43
+ *
44
+ * @constructor
45
+ * @namespace SMTP Client module
46
+ * @param {Object} [options] Option properties
47
+ */
48
+ function SMTPConnection(options) {
49
+ EventEmitter.call(this);
50
+
51
+ this.id = crypto.randomBytes(8).toString('base64').replace(/\W/g, '');
52
+ this.stage = 'init';
53
+
54
+ this.options = options || {};
55
+
56
+ this.secureConnection = !!this.options.secure;
57
+ this.alreadySecured = !!this.options.secured;
58
+
59
+ this.port = this.options.port || (this.secureConnection ? 465 : 25);
60
+ this.host = this.options.host || 'localhost';
61
+
62
+ if (typeof this.options.secure === 'undefined' && this.port === 465) {
63
+ // if secure option is not set but port is 465, then default to secure
64
+ this.secureConnection = true;
65
+ }
66
+
67
+ this.name = this.options.name || this._getHostname();
68
+
69
+ this.logger = shared.getLogger(this.options);
70
+
71
+ /**
72
+ * Expose version nr, just for the reference
73
+ * @type {String}
74
+ */
75
+ this.version = packageInfo.version;
76
+
77
+ /**
78
+ * If true, then the user is authenticated
79
+ * @type {Boolean}
80
+ */
81
+ this.authenticated = false;
82
+
83
+ /**
84
+ * If set to true, this instance is no longer active
85
+ * @private
86
+ */
87
+ this.destroyed = false;
88
+
89
+ /**
90
+ * Defines if the current connection is secure or not. If not,
91
+ * STARTTLS can be used if available
92
+ * @private
93
+ */
94
+ this.secure = !!this.secureConnection;
95
+
96
+ /**
97
+ * Store incomplete messages coming from the server
98
+ * @private
99
+ */
100
+ this._remainder = '';
101
+
102
+ /**
103
+ * Unprocessed responses from the server
104
+ * @type {Array}
105
+ */
106
+ this._responseQueue = [];
107
+
108
+ /**
109
+ * The socket connecting to the server
110
+ * @publick
111
+ */
112
+ this._socket = false;
113
+
114
+ /**
115
+ * Lists supported auth mechanisms
116
+ * @private
117
+ */
118
+ this._supportedAuth = [];
119
+
120
+ /**
121
+ * Includes current envelope (from, to)
122
+ * @private
123
+ */
124
+ this._envelope = false;
125
+
126
+ /**
127
+ * Lists supported extensions
128
+ * @private
129
+ */
130
+ this._supportedExtensions = [];
131
+
132
+ /**
133
+ * Defines the maximum allowed size for a single message
134
+ * @private
135
+ */
136
+ this._maxAllowedSize = 0;
137
+
138
+ /**
139
+ * Function queue to run if a data chunk comes from the server
140
+ * @private
141
+ */
142
+ this._responseActions = [];
143
+ this._recipientQueue = [];
144
+
145
+ /**
146
+ * Timeout variable for waiting the greeting
147
+ * @private
148
+ */
149
+ this._greetingTimeout = false;
150
+
151
+ /**
152
+ * Timeout variable for waiting the connection to start
153
+ * @private
154
+ */
155
+ this._connectionTimeout = false;
156
+
157
+ /**
158
+ * If the socket is deemed already closed
159
+ * @private
160
+ */
161
+ this._destroyed = false;
162
+
163
+ /**
164
+ * If the socket is already being closed
165
+ * @private
166
+ */
167
+ this._closing = false;
168
+ }
169
+ util.inherits(SMTPConnection, EventEmitter);
170
+
171
+ /**
172
+ * Creates a connection to a SMTP server and sets up connection
173
+ * listener
174
+ */
175
+ SMTPConnection.prototype.connect = function (connectCallback) {
176
+ if (typeof connectCallback === 'function') {
177
+ this.once('connect', function () {
178
+ this.logger.debug('[%s] SMTP handshake finished', this.id);
179
+ connectCallback();
180
+ }.bind(this));
181
+ }
182
+
183
+ var opts = {
184
+ port: this.port,
185
+ host: this.host
186
+ };
187
+
188
+ if (this.options.localAddress) {
189
+ opts.localAddress = this.options.localAddress;
190
+ }
191
+
192
+ if (this.options.connection) {
193
+ // connection is already opened
194
+ this._socket = this.options.connection;
195
+ if (this.secureConnection && !this.alreadySecured) {
196
+ setImmediate(this._upgradeConnection.bind(this, function (err) {
197
+ if (err) {
198
+ this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
199
+ return;
200
+ }
201
+ this._onConnect();
202
+ }.bind(this)));
203
+ } else {
204
+ setImmediate(this._onConnect.bind(this));
205
+ }
206
+ } else if (this.options.socket) {
207
+ // socket object is set up but not yet connected
208
+ this._socket = this.options.socket;
209
+ try {
210
+ this._socket.connect(this.port, this.host, this._onConnect.bind(this));
211
+ } catch (E) {
212
+ return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
213
+ }
214
+ } else if (this.secureConnection) {
215
+ // connect using tls
216
+ if (this.options.tls) {
217
+ Object.keys(this.options.tls).forEach(function (key) {
218
+ opts[key] = this.options.tls[key];
219
+ }.bind(this));
220
+ }
221
+ try {
222
+ this._socket = tls.connect(this.port, this.host, opts, this._onConnect.bind(this));
223
+ } catch (E) {
224
+ return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
225
+ }
226
+ } else {
227
+ // connect using plaintext
228
+ try {
229
+ this._socket = net.connect(opts, this._onConnect.bind(this));
230
+ } catch (E) {
231
+ return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
232
+ }
233
+ }
234
+
235
+ this._connectionTimeout = setTimeout(function () {
236
+ this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
237
+ }.bind(this), this.options.connectionTimeout || CONNECTION_TIMEOUT);
238
+
239
+ this._socket.on('error', function (err) {
240
+ this._onError(err, 'ECONNECTION', false, 'CONN');
241
+ }.bind(this));
242
+ };
243
+
244
+ /**
245
+ * Sends QUIT
246
+ */
247
+ SMTPConnection.prototype.quit = function () {
248
+ this._sendCommand('QUIT');
249
+ this._responseActions.push(this.close);
250
+ };
251
+
252
+ /**
253
+ * Closes the connection to the server
254
+ */
255
+ SMTPConnection.prototype.close = function () {
256
+ clearTimeout(this._connectionTimeout);
257
+ clearTimeout(this._greetingTimeout);
258
+ this._responseActions = [];
259
+
260
+ // allow to run this function only once
261
+ if (this._closing) {
262
+ return;
263
+ }
264
+ this._closing = true;
265
+
266
+ var closeMethod = 'end';
267
+
268
+ if (this.stage === 'init') {
269
+ // Close the socket immediately when connection timed out
270
+ closeMethod = 'destroy';
271
+ }
272
+
273
+ this.logger.debug('[%s] Closing connection to the server using "%s"', this.id, closeMethod);
274
+
275
+ var socket = this._socket && this._socket.socket || this._socket;
276
+
277
+ if (socket && !socket.destroyed) {
278
+ try {
279
+ this._socket[closeMethod]();
280
+ } catch (E) {
281
+ // just ignore
282
+ }
283
+ }
284
+
285
+ this._destroy();
286
+ };
287
+
288
+ /**
289
+ * Authenticate user
290
+ */
291
+ SMTPConnection.prototype.login = function (authData, callback) {
292
+ this._auth = authData || {};
293
+ this._user = this._auth.xoauth2 && this._auth.xoauth2.options && this._auth.xoauth2.options.user || this._auth.user || '';
294
+
295
+ this._authMethod = false;
296
+ if (this.options.authMethod) {
297
+ this._authMethod = this.options.authMethod.toUpperCase().trim();
298
+ } else if (this._auth.xoauth2 && this._supportedAuth.indexOf('XOAUTH2') >= 0) {
299
+ this._authMethod = 'XOAUTH2';
300
+ } else if (this._auth.domain && this._supportedAuth.indexOf('NTLM') >= 0) {
301
+ this._authMethod = 'NTLM';
302
+ } else {
303
+ // use first supported
304
+ this._authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
305
+ }
306
+
307
+ switch (this._authMethod) {
308
+ case 'XOAUTH2':
309
+ this._handleXOauth2Token(false, callback);
310
+ return;
311
+ case 'LOGIN':
312
+ this._responseActions.push(function (str) {
313
+ this._actionAUTH_LOGIN_USER(str, callback);
314
+ }.bind(this));
315
+ this._sendCommand('AUTH LOGIN');
316
+ return;
317
+ case 'PLAIN':
318
+ this._responseActions.push(function (str) {
319
+ this._actionAUTHComplete(str, callback);
320
+ }.bind(this));
321
+ this._sendCommand('AUTH PLAIN ' + new Buffer(
322
+ //this._auth.user+'\u0000'+
323
+ '\u0000' + // skip authorization identity as it causes problems with some servers
324
+ this._auth.user + '\u0000' +
325
+ this._auth.pass, 'utf-8').toString('base64'));
326
+ return;
327
+ case 'CRAM-MD5':
328
+ this._responseActions.push(function (str) {
329
+ this._actionAUTH_CRAM_MD5(str, callback);
330
+ }.bind(this));
331
+ this._sendCommand('AUTH CRAM-MD5');
332
+ return;
333
+ case 'NTLM':
334
+ this._responseActions.push(function (str) {
335
+ this._actionAUTH_NTLM_TYPE1(str, callback);
336
+ }.bind(this));
337
+ this._sendCommand('AUTH ' + ntlm.createType1Message({
338
+ domain: this._auth.domain || '',
339
+ workstation: this._auth.workstation || ''
340
+ }));
341
+ return;
342
+ }
343
+
344
+ return callback(this._formatError('Unknown authentication method "' + this._authMethod + '"', 'EAUTH', false, 'API'));
345
+ };
346
+
347
+ /**
348
+ * Sends a message
349
+ *
350
+ * @param {Object} envelope Envelope object, {from: addr, to: [addr]}
351
+ * @param {Object} message String, Buffer or a Stream
352
+ * @param {Function} callback Callback to return once sending is completed
353
+ */
354
+ SMTPConnection.prototype.send = function (envelope, message, done) {
355
+ if (!message) {
356
+ return done(this._formatError('Empty message', 'EMESSAGE', false, 'API'));
357
+ }
358
+
359
+ // reject larger messages than allowed
360
+ if (this._maxAllowedSize && envelope.size > this._maxAllowedSize) {
361
+ return setImmediate(function () {
362
+ done(this._formatError('Message size larger than allowed ' + this._maxAllowedSize, 'EMESSAGE', false, 'MAIL FROM'));
363
+ }.bind(this));
364
+ }
365
+
366
+ // ensure that callback is only called once
367
+ var returned = false;
368
+ var callback = function () {
369
+ if (returned) {
370
+ return;
371
+ }
372
+ returned = true;
373
+
374
+ done.apply(null, Array.prototype.slice.call(arguments));
375
+ };
376
+
377
+ if (typeof message.on === 'function') {
378
+ message.on('error', function (err) {
379
+ return callback(this._formatError(err, 'ESTREAM', false, 'API'));
380
+ }.bind(this));
381
+ }
382
+
383
+ this._setEnvelope(envelope, function (err, info) {
384
+ if (err) {
385
+ return callback(err);
386
+ }
387
+ var stream = this._createSendStream(function (err, str) {
388
+ if (err) {
389
+ return callback(err);
390
+ }
391
+ info.response = str;
392
+ return callback(null, info);
393
+ });
394
+ if (typeof message.pipe === 'function') {
395
+ message.pipe(stream);
396
+ } else {
397
+ stream.write(message);
398
+ stream.end();
399
+ }
400
+
401
+ }.bind(this));
402
+ };
403
+
404
+ /**
405
+ * Resets connection state
406
+ *
407
+ * @param {Function} callback Callback to return once connection is reset
408
+ */
409
+ SMTPConnection.prototype.reset = function (callback) {
410
+ this._sendCommand('RSET');
411
+ this._responseActions.push(function (str) {
412
+ if (str.charAt(0) !== '2') {
413
+ return callback(this._formatError('Could not reset session state:\n' + str, 'EPROTOCOL', str, 'RSET'));
414
+ }
415
+ this._envelope = false;
416
+ return callback(null, true);
417
+ }.bind(this));
418
+ };
419
+
420
+ /**
421
+ * Connection listener that is run when the connection to
422
+ * the server is opened
423
+ *
424
+ * @event
425
+ */
426
+ SMTPConnection.prototype._onConnect = function () {
427
+ clearTimeout(this._connectionTimeout);
428
+
429
+ this.logger.info('[%s] %s established to %s:%s', this.id, this.secure ? 'Secure connection' : 'Connection', this._socket.remoteAddress, this._socket.remotePort);
430
+
431
+ if (this._destroyed) {
432
+ // Connection was established after we already had canceled it
433
+ this.close();
434
+ return;
435
+ }
436
+
437
+ this.stage = 'connected';
438
+
439
+ // clear existing listeners for the socket
440
+ this._socket.removeAllListeners('data');
441
+ this._socket.removeAllListeners('timeout');
442
+ this._socket.removeAllListeners('close');
443
+ this._socket.removeAllListeners('end');
444
+
445
+ this._socket.on('data', this._onData.bind(this));
446
+ this._socket.once('close', this._onClose.bind(this));
447
+ this._socket.once('end', this._onEnd.bind(this));
448
+
449
+ this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT);
450
+ this._socket.on('timeout', this._onTimeout.bind(this));
451
+
452
+ this._greetingTimeout = setTimeout(function () {
453
+ // if still waiting for greeting, give up
454
+ if (this._socket && !this._destroyed && this._responseActions[0] === this._actionGreeting) {
455
+ this._onError('Greeting never received', 'ETIMEDOUT', false, 'CONN');
456
+ }
457
+ }.bind(this), this.options.greetingTimeout || GREETING_TIMEOUT);
458
+
459
+ this._responseActions.push(this._actionGreeting);
460
+
461
+ // we have a 'data' listener set up so resume socket if it was paused
462
+ this._socket.resume();
463
+ };
464
+
465
+ /**
466
+ * 'data' listener for data coming from the server
467
+ *
468
+ * @event
469
+ * @param {Buffer} chunk Data chunk coming from the server
470
+ */
471
+ SMTPConnection.prototype._onData = function (chunk) {
472
+ if (this._destroyed || !chunk || !chunk.length) {
473
+ return;
474
+ }
475
+
476
+ var data = (chunk || '').toString('binary');
477
+ var lines = (this._remainder + data).split(/\r?\n/);
478
+ var lastline;
479
+
480
+ this._remainder = lines.pop();
481
+
482
+ for (var i = 0, len = lines.length; i < len; i++) {
483
+ if (this._responseQueue.length) {
484
+ lastline = this._responseQueue[this._responseQueue.length - 1];
485
+ if (/^\d+\-/.test(lastline.split('\n').pop())) {
486
+ this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
487
+ continue;
488
+ }
489
+ }
490
+ this._responseQueue.push(lines[i]);
491
+ }
492
+
493
+ this._processResponse();
494
+ };
495
+
496
+ /**
497
+ * 'error' listener for the socket
498
+ *
499
+ * @event
500
+ * @param {Error} err Error object
501
+ * @param {String} type Error name
502
+ */
503
+ SMTPConnection.prototype._onError = function (err, type, data, command) {
504
+ clearTimeout(this._connectionTimeout);
505
+ clearTimeout(this._greetingTimeout);
506
+
507
+ if (this._destroyed) {
508
+ // just ignore, already closed
509
+ // this might happen when a socket is canceled because of reached timeout
510
+ // but the socket timeout error itself receives only after
511
+ return;
512
+ }
513
+
514
+ err = this._formatError(err, type, data, command);
515
+
516
+ this.logger.error('[%s] %s', this.id, err.message);
517
+
518
+ this.emit('error', err);
519
+ this.close();
520
+ };
521
+
522
+ SMTPConnection.prototype._formatError = function (message, type, response, command) {
523
+ var err;
524
+
525
+ if (/Error\]$/i.test(Object.prototype.toString.call(message))) {
526
+ err = message;
527
+ } else {
528
+ err = new Error(message);
529
+ }
530
+
531
+ if (type && type !== 'Error') {
532
+ err.code = type;
533
+ }
534
+
535
+ if (response) {
536
+ err.response = response;
537
+ err.message += ': ' + response;
538
+ }
539
+
540
+ var responseCode = typeof response === 'string' && Number((response.match(/^\d+/) || [])[0]) || false;
541
+ if (responseCode) {
542
+ err.responseCode = responseCode;
543
+ }
544
+
545
+ if (command) {
546
+ err.command = command;
547
+ }
548
+
549
+ return err;
550
+ };
551
+
552
+ /**
553
+ * 'close' listener for the socket
554
+ *
555
+ * @event
556
+ */
557
+ SMTPConnection.prototype._onClose = function () {
558
+ this.logger.info('[%s] Connection closed', this.id);
559
+
560
+ if ([this._actionGreeting, this.close].indexOf(this._responseActions[0]) < 0 && !this._destroyed) {
561
+ return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', false, 'CONN');
562
+ }
563
+
564
+ this._destroy();
565
+ };
566
+
567
+ /**
568
+ * 'end' listener for the socket
569
+ *
570
+ * @event
571
+ */
572
+ SMTPConnection.prototype._onEnd = function () {
573
+ this._destroy();
574
+ };
575
+
576
+ /**
577
+ * 'timeout' listener for the socket
578
+ *
579
+ * @event
580
+ */
581
+ SMTPConnection.prototype._onTimeout = function () {
582
+ return this._onError(new Error('Timeout'), 'ETIMEDOUT', false, 'CONN');
583
+ };
584
+
585
+ /**
586
+ * Destroys the client, emits 'end'
587
+ */
588
+ SMTPConnection.prototype._destroy = function () {
589
+ if (this._destroyed) {
590
+ return;
591
+ }
592
+ this._destroyed = true;
593
+ this.emit('end');
594
+ };
595
+
596
+ /**
597
+ * Upgrades the connection to TLS
598
+ *
599
+ * @param {Function} callback Callback function to run when the connection
600
+ * has been secured
601
+ */
602
+ SMTPConnection.prototype._upgradeConnection = function (callback) {
603
+ // do not remove all listeners or it breaks node v0.10 as there's
604
+ // apparently a 'finish' event set that would be cleared as well
605
+
606
+ // we can safely keep 'error', 'end', 'close' etc. events
607
+ this._socket.removeAllListeners('data'); // incoming data is going to be gibberish from this point onwards
608
+ this._socket.removeAllListeners('timeout'); // timeout will be re-set for the new socket object
609
+
610
+ var socketPlain = this._socket;
611
+ var opts = {
612
+ socket: this._socket,
613
+ host: this.host
614
+ };
615
+
616
+ Object.keys(this.options.tls || {}).forEach(function (key) {
617
+ opts[key] = this.options.tls[key];
618
+ }.bind(this));
619
+
620
+ this._socket = tls.connect(opts, function () {
621
+ this.secure = true;
622
+ this._socket.on('data', this._onData.bind(this));
623
+
624
+ socketPlain.removeAllListeners('close');
625
+ socketPlain.removeAllListeners('end');
626
+
627
+ return callback(null, true);
628
+ }.bind(this));
629
+
630
+ this._socket.on('error', this._onError.bind(this));
631
+ this._socket.once('close', this._onClose.bind(this));
632
+ this._socket.once('end', this._onEnd.bind(this));
633
+
634
+ this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT); // 10 min.
635
+ this._socket.on('timeout', this._onTimeout.bind(this));
636
+
637
+ // resume in case the socket was paused
638
+ socketPlain.resume();
639
+ };
640
+
641
+ /**
642
+ * Processes queued responses from the server
643
+ *
644
+ * @param {Boolean} force If true, ignores _processing flag
645
+ */
646
+ SMTPConnection.prototype._processResponse = function () {
647
+ if (!this._responseQueue.length) {
648
+ return false;
649
+ }
650
+
651
+ var str = (this._responseQueue.shift() || '').toString();
652
+
653
+ if (/^\d+\-/.test(str.split('\n').pop())) {
654
+ // keep waiting for the final part of multiline response
655
+ return;
656
+ }
657
+
658
+ if (this.options.debug) {
659
+ this.logger.debug('[%s] S: %s', this.id, str.replace(/\r?\n$/, ''));
660
+ }
661
+
662
+ if (!str.trim()) { // skip unexpected empty lines
663
+ setImmediate(this._processResponse.bind(this, true));
664
+ }
665
+
666
+ var action = this._responseActions.shift();
667
+
668
+ if (typeof action === 'function') {
669
+ action.call(this, str);
670
+ setImmediate(this._processResponse.bind(this, true));
671
+ } else {
672
+ return this._onError(new Error('Unexpected Response'), 'EPROTOCOL', str, 'CONN');
673
+ }
674
+ };
675
+
676
+ /**
677
+ * Send a command to the server, append \r\n
678
+ *
679
+ * @param {String} str String to be sent to the server
680
+ */
681
+ SMTPConnection.prototype._sendCommand = function (str) {
682
+ if (this._destroyed) {
683
+ // Connection already closed, can't send any more data
684
+ return;
685
+ }
686
+
687
+ if (this._socket.destroyed) {
688
+ return this.close();
689
+ }
690
+
691
+ if (this.options.debug) {
692
+ this.logger.debug('[%s] C: %s', this.id, (str || '').toString().replace(/\r?\n$/, ''));
693
+ }
694
+
695
+ this._socket.write(new Buffer(str + '\r\n', 'utf-8'));
696
+ };
697
+
698
+ /**
699
+ * Initiates a new message by submitting envelope data, starting with
700
+ * MAIL FROM: command
701
+ *
702
+ * @param {Object} envelope Envelope object in the form of
703
+ * {from:'...', to:['...']}
704
+ * or
705
+ * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
706
+ */
707
+ SMTPConnection.prototype._setEnvelope = function (envelope, callback) {
708
+ var args = [];
709
+ var useSmtpUtf8 = false;
710
+
711
+ this._envelope = envelope || {};
712
+ this._envelope.from = (this._envelope.from && this._envelope.from.address || this._envelope.from || '').toString().trim();
713
+
714
+ this._envelope.to = [].concat(this._envelope.to || []).map(function (to) {
715
+ return (to && to.address || to || '').toString().trim();
716
+ });
717
+
718
+ if (!this._envelope.to.length) {
719
+ return callback(this._formatError('No recipients defined', 'EENVELOPE', false, 'API'));
720
+ }
721
+
722
+ if (this._envelope.from && /[\r\n<>]/.test(this._envelope.from)) {
723
+ return callback(this._formatError('Invalid sender ' + JSON.stringify(this._envelope.from), 'EENVELOPE', false, 'API'));
724
+ }
725
+
726
+ // check if the sender address uses only ASCII characters,
727
+ // otherwise require usage of SMTPUTF8 extension
728
+ if (/[\x80-\uFFFF]/.test(this._envelope.from)) {
729
+ useSmtpUtf8 = true;
730
+ }
731
+
732
+ for (var i = 0, len = this._envelope.to.length; i < len; i++) {
733
+ if (!this._envelope.to[i] || /[\r\n<>]/.test(this._envelope.to[i])) {
734
+ return callback(this._formatError('Invalid recipient ' + JSON.stringify(this._envelope.to[i]), 'EENVELOPE', false, 'API'));
735
+ }
736
+
737
+ // check if the recipients addresses use only ASCII characters,
738
+ // otherwise require usage of SMTPUTF8 extension
739
+ if (/[\x80-\uFFFF]/.test(this._envelope.to[i])) {
740
+ useSmtpUtf8 = true;
741
+ }
742
+ }
743
+
744
+ // clone the recipients array for latter manipulation
745
+ this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
746
+ this._envelope.rejected = [];
747
+ this._envelope.rejectedErrors = [];
748
+ this._envelope.accepted = [];
749
+
750
+ if (this._envelope.dsn) {
751
+ try {
752
+ this._envelope.dsn = this._setDsnEnvelope(this._envelope.dsn);
753
+ } catch (err) {
754
+ return callback(this._formatError('Invalid dsn ' + err.message, 'EENVELOPE', false, 'API'));
755
+ }
756
+ }
757
+
758
+ this._responseActions.push(function (str) {
759
+ this._actionMAIL(str, callback);
760
+ }.bind(this));
761
+
762
+ // If the server supports SMTPUTF8 and the envelope includes an internationalized
763
+ // email address then append SMTPUTF8 keyword to the MAIL FROM command
764
+ if (useSmtpUtf8 && this._supportedExtensions.indexOf('SMTPUTF8') >= 0) {
765
+ args.push('SMTPUTF8');
766
+ this._usingSmtpUtf8 = true;
767
+ }
768
+
769
+ // If the server supports 8BITMIME and the message might contain non-ascii bytes
770
+ // then append the 8BITMIME keyword to the MAIL FROM command
771
+ if (this._envelope.use8BitMime && this._supportedExtensions.indexOf('8BITMIME') >= 0) {
772
+ args.push('BODY=8BITMIME');
773
+ this._using8BitMime = true;
774
+ }
775
+
776
+ if (this._envelope.size && this._supportedExtensions.indexOf('SIZE') >= 0) {
777
+ args.push('SIZE=' + this._envelope.size);
778
+ }
779
+
780
+ // If the server supports DSN and the envelope includes an DSN prop
781
+ // then append DSN params to the MAIL FROM command
782
+ if (this._envelope.dsn && this._supportedExtensions.indexOf('DSN') >= 0) {
783
+ if (this._envelope.dsn.ret) {
784
+ args.push('RET=' + this._envelope.dsn.ret);
785
+ }
786
+ if (this._envelope.dsn.envid) {
787
+ args.push('ENVID=' + this._envelope.dsn.envid);
788
+ }
789
+ }
790
+
791
+ this._sendCommand('MAIL FROM:<' + (this._envelope.from) + '>' + (args.length ? ' ' + args.join(' ') : ''));
792
+ };
793
+
794
+ SMTPConnection.prototype._setDsnEnvelope = function (params) {
795
+ var ret = params.ret ? params.ret.toString().toUpperCase() : null;
796
+ if (ret && ['FULL', 'HDRS'].indexOf(ret) < 0) {
797
+ throw new Error('ret: ' + JSON.stringify(ret));
798
+ }
799
+ var envid = params.envid ? params.envid.toString() : null;
800
+ var notify = params.notify ? params.notify : null;
801
+ if (notify) {
802
+ if (typeof notify === 'string') {
803
+ notify = notify.split(',');
804
+ }
805
+ notify = notify.map(function (n) {
806
+ return n.trim().toUpperCase();
807
+ });
808
+ var validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
809
+ var invaliNotify = notify.filter(function (n) {
810
+ return validNotify.indexOf(n) === -1;
811
+ });
812
+ if (invaliNotify.length || (notify.length > 1 && notify.indexOf('NEVER') >= 0)) {
813
+ throw new Error('notify: ' + JSON.stringify(notify.join(',')));
814
+ }
815
+ notify = notify.join(',');
816
+ }
817
+ var orcpt = params.orcpt ? params.orcpt.toString() : null;
818
+ return {
819
+ ret: ret,
820
+ envid: envid,
821
+ notify: notify,
822
+ orcpt: orcpt
823
+ };
824
+ };
825
+
826
+ SMTPConnection.prototype._getDsnRcptToArgs = function () {
827
+ var args = [];
828
+ // If the server supports DSN and the envelope includes an DSN prop
829
+ // then append DSN params to the RCPT TO command
830
+ if (this._envelope.dsn && this._supportedExtensions.indexOf('DSN') >= 0) {
831
+ if (this._envelope.dsn.notify) {
832
+ args.push('NOTIFY=' + this._envelope.dsn.notify);
833
+ }
834
+ if (this._envelope.dsn.orcpt) {
835
+ args.push('ORCPT=' + this._envelope.dsn.orcpt);
836
+ }
837
+ }
838
+ return (args.length ? ' ' + args.join(' ') : '');
839
+ };
840
+
841
+ SMTPConnection.prototype._createSendStream = function (callback) {
842
+ var dataStream = new DataStream();
843
+ var logStream;
844
+
845
+ if (this.options.lmtp) {
846
+ this._envelope.accepted.forEach(function (recipient, i) {
847
+ var final = i === this._envelope.accepted.length - 1;
848
+ this._responseActions.push(function (str) {
849
+ this._actionLMTPStream(recipient, final, str, callback);
850
+ }.bind(this));
851
+ }.bind(this));
852
+ } else {
853
+ this._responseActions.push(function (str) {
854
+ this._actionSMTPStream(str, callback);
855
+ }.bind(this));
856
+ }
857
+
858
+ dataStream.pipe(this._socket, {
859
+ end: false
860
+ });
861
+
862
+ if (this.options.debug) {
863
+ logStream = new PassThrough();
864
+ logStream.on('readable', function () {
865
+ var chunk;
866
+ while ((chunk = logStream.read())) {
867
+ this.logger.debug('[%s] C: %s', this.id, chunk.toString('binary').replace(/\r?\n$/, ''));
868
+ }
869
+ }.bind(this));
870
+ dataStream.pipe(logStream);
871
+ }
872
+
873
+ dataStream.once('end', function () {
874
+ this.logger.info('[%s] C: <%s bytes encoded mime message (source size %s bytes)>', this.id, dataStream.outByteCount, dataStream.inByteCount);
875
+ }.bind(this));
876
+
877
+ return dataStream;
878
+ };
879
+
880
+ /** ACTIONS **/
881
+
882
+ /**
883
+ * Will be run after the connection is created and the server sends
884
+ * a greeting. If the incoming message starts with 220 initiate
885
+ * SMTP session by sending EHLO command
886
+ *
887
+ * @param {String} str Message from the server
888
+ */
889
+ SMTPConnection.prototype._actionGreeting = function (str) {
890
+ clearTimeout(this._greetingTimeout);
891
+
892
+ if (str.substr(0, 3) !== '220') {
893
+ this._onError(new Error('Invalid greeting from server:\n' + str), 'EPROTOCOL', str, 'CONN');
894
+ return;
895
+ }
896
+
897
+ if (this.options.lmtp) {
898
+ this._responseActions.push(this._actionLHLO);
899
+ this._sendCommand('LHLO ' + this.name);
900
+ } else {
901
+ this._responseActions.push(this._actionEHLO);
902
+ this._sendCommand('EHLO ' + this.name);
903
+ }
904
+ };
905
+
906
+ /**
907
+ * Handles server response for LHLO command. If it yielded in
908
+ * error, emit 'error', otherwise treat this as an EHLO response
909
+ *
910
+ * @param {String} str Message from the server
911
+ */
912
+ SMTPConnection.prototype._actionLHLO = function (str) {
913
+ if (str.charAt(0) !== '2') {
914
+ this._onError(new Error('Invalid response for LHLO:\n' + str), 'EPROTOCOL', str, 'LHLO');
915
+ return;
916
+ }
917
+
918
+ this._actionEHLO(str);
919
+ };
920
+
921
+ /**
922
+ * Handles server response for EHLO command. If it yielded in
923
+ * error, try HELO instead, otherwise initiate TLS negotiation
924
+ * if STARTTLS is supported by the server or move into the
925
+ * authentication phase.
926
+ *
927
+ * @param {String} str Message from the server
928
+ */
929
+ SMTPConnection.prototype._actionEHLO = function (str) {
930
+ var match;
931
+
932
+ if (str.substr(0, 3) === '421') {
933
+ this._onError(new Error('Server terminates connection:\n' + str), 'ECONNECTION', str, 'EHLO');
934
+ return;
935
+ }
936
+
937
+ if (str.charAt(0) !== '2') {
938
+ if (this.options.requireTLS) {
939
+ this._onError(new Error('EHLO failed but HELO does not support required STARTTLS:\n' + str), 'ECONNECTION', str, 'EHLO');
940
+ return;
941
+ }
942
+
943
+ // Try HELO instead
944
+ this._responseActions.push(this._actionHELO);
945
+ this._sendCommand('HELO ' + this.name);
946
+ return;
947
+ }
948
+
949
+ // Detect if the server supports STARTTLS
950
+ if (!this.secure && !this.options.ignoreTLS && (/[ \-]STARTTLS\b/mi.test(str) || this.options.requireTLS)) {
951
+ this._sendCommand('STARTTLS');
952
+ this._responseActions.push(this._actionSTARTTLS);
953
+ return;
954
+ }
955
+
956
+ // Detect if the server supports SMTPUTF8
957
+ if (/[ \-]SMTPUTF8\b/mi.test(str)) {
958
+ this._supportedExtensions.push('SMTPUTF8');
959
+ }
960
+
961
+ // Detect if the server supports DSN
962
+ if (/[ \-]DSN\b/mi.test(str)) {
963
+ this._supportedExtensions.push('DSN');
964
+ }
965
+
966
+ // Detect if the server supports 8BITMIME
967
+ if (/[ \-]8BITMIME\b/mi.test(str)) {
968
+ this._supportedExtensions.push('8BITMIME');
969
+ }
970
+
971
+ // Detect if the server supports PIPELINING
972
+ if (/[ \-]PIPELINING\b/mi.test(str)) {
973
+ this._supportedExtensions.push('PIPELINING');
974
+ }
975
+
976
+ // Detect if the server supports PLAIN auth
977
+ if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i.test(str)) {
978
+ this._supportedAuth.push('PLAIN');
979
+ }
980
+
981
+ // Detect if the server supports LOGIN auth
982
+ if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i.test(str)) {
983
+ this._supportedAuth.push('LOGIN');
984
+ }
985
+
986
+ // Detect if the server supports CRAM-MD5 auth
987
+ if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i.test(str)) {
988
+ this._supportedAuth.push('CRAM-MD5');
989
+ }
990
+
991
+ // Detect if the server supports XOAUTH2 auth
992
+ if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i.test(str)) {
993
+ this._supportedAuth.push('XOAUTH2');
994
+ }
995
+
996
+ // Detect if the server supports SIZE extensions (and the max allowed size)
997
+ if ((match = str.match(/[ \-]SIZE(?:\s+(\d+))?/mi))) {
998
+ this._supportedExtensions.push('SIZE');
999
+ this._maxAllowedSize = Number(match[1]) || 0;
1000
+ }
1001
+
1002
+ this.emit('connect');
1003
+ };
1004
+
1005
+ /**
1006
+ * Handles server response for HELO command. If it yielded in
1007
+ * error, emit 'error', otherwise move into the authentication phase.
1008
+ *
1009
+ * @param {String} str Message from the server
1010
+ */
1011
+ SMTPConnection.prototype._actionHELO = function (str) {
1012
+ if (str.charAt(0) !== '2') {
1013
+ this._onError(new Error('Invalid response for EHLO/HELO:\n' + str), 'EPROTOCOL', str, 'HELO');
1014
+ return;
1015
+ }
1016
+
1017
+ this.emit('connect');
1018
+ };
1019
+
1020
+ /**
1021
+ * Handles server response for STARTTLS command. If there's an error
1022
+ * try HELO instead, otherwise initiate TLS upgrade. If the upgrade
1023
+ * succeedes restart the EHLO
1024
+ *
1025
+ * @param {String} str Message from the server
1026
+ */
1027
+ SMTPConnection.prototype._actionSTARTTLS = function (str) {
1028
+ if (str.charAt(0) !== '2') {
1029
+ if (this.options.opportunisticTLS) {
1030
+ this.logger.info('[%s] Failed STARTTLS upgrade, continuing unencrypted', this.id);
1031
+ return this.emit('connect');
1032
+ }
1033
+ this._onError(new Error('Error upgrading connection with STARTTLS'), 'ETLS', str, 'STARTTLS');
1034
+ return;
1035
+ }
1036
+
1037
+ this._upgradeConnection(function (err, secured) {
1038
+ if (err) {
1039
+ this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'STARTTLS');
1040
+ return;
1041
+ }
1042
+
1043
+ this.logger.info('[%s] Connection upgraded with STARTTLS', this.id);
1044
+
1045
+ if (secured) {
1046
+ // restart session
1047
+ this._responseActions.push(this._actionEHLO);
1048
+ this._sendCommand('EHLO ' + this.name);
1049
+ } else {
1050
+ this.emit('connect');
1051
+ }
1052
+ }.bind(this));
1053
+ };
1054
+
1055
+ /**
1056
+ * Handle the response for AUTH LOGIN command. We are expecting
1057
+ * '334 VXNlcm5hbWU6' (base64 for 'Username:'). Data to be sent as
1058
+ * response needs to be base64 encoded username.
1059
+ *
1060
+ * @param {String} str Message from the server
1061
+ */
1062
+ SMTPConnection.prototype._actionAUTH_LOGIN_USER = function (str, callback) {
1063
+ if (str !== '334 VXNlcm5hbWU6') {
1064
+ callback(this._formatError('Invalid login sequence while waiting for "334 VXNlcm5hbWU6"', 'EAUTH', str, 'AUTH LOGIN'));
1065
+ return;
1066
+ }
1067
+
1068
+ this._responseActions.push(function (str) {
1069
+ this._actionAUTH_LOGIN_PASS(str, callback);
1070
+ }.bind(this));
1071
+
1072
+ this._sendCommand(new Buffer(this._auth.user + '', 'utf-8').toString('base64'));
1073
+ };
1074
+
1075
+ /**
1076
+ * Handle the response for AUTH NTLM, which should be a
1077
+ * '334 <challenge string>'. See http://davenport.sourceforge.net/ntlm.html
1078
+ * We already sent the Type1 message, the challenge is a Type2 message, we
1079
+ * need to respond with a Type3 message.
1080
+ *
1081
+ * @param {String} str Message from the server
1082
+ */
1083
+ SMTPConnection.prototype._actionAUTH_NTLM_TYPE1 = function (str, callback) {
1084
+ var challengeMatch = str.match(/^334\s+(.+)$/);
1085
+ var challengeString = '';
1086
+
1087
+ if (!challengeMatch) {
1088
+ return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH NTLM'));
1089
+ } else {
1090
+ challengeString = challengeMatch[1];
1091
+ }
1092
+
1093
+ if (!/^NTLM/i.test(challengeString)) {
1094
+ challengeString = 'NTLM ' + challengeString;
1095
+ }
1096
+
1097
+ var type2Message = ntlm.parseType2Message(challengeString, callback);
1098
+ if (!type2Message) {
1099
+ return;
1100
+ }
1101
+
1102
+ var type3Message = ntlm.createType3Message(type2Message, {
1103
+ domain: this._auth.domain || '',
1104
+ workstation: this._auth.workstation || '',
1105
+ username: this._auth.user,
1106
+ password: this._auth.pass
1107
+ });
1108
+
1109
+ type3Message = type3Message.substring(5); // remove the "NTLM " prefix
1110
+
1111
+ this._responseActions.push(function (str) {
1112
+ this._actionAUTH_NTLM_TYPE3(str, callback);
1113
+ }.bind(this));
1114
+
1115
+ this._sendCommand(type3Message);
1116
+ };
1117
+
1118
+ /**
1119
+ * Handle the response for AUTH CRAM-MD5 command. We are expecting
1120
+ * '334 <challenge string>'. Data to be sent as response needs to be
1121
+ * base64 decoded challenge string, MD5 hashed using the password as
1122
+ * a HMAC key, prefixed by the username and a space, and finally all
1123
+ * base64 encoded again.
1124
+ *
1125
+ * @param {String} str Message from the server
1126
+ */
1127
+ SMTPConnection.prototype._actionAUTH_CRAM_MD5 = function (str, callback) {
1128
+ var challengeMatch = str.match(/^334\s+(.+)$/);
1129
+ var challengeString = '';
1130
+
1131
+ if (!challengeMatch) {
1132
+ return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5'));
1133
+ } else {
1134
+ challengeString = challengeMatch[1];
1135
+ }
1136
+
1137
+ // Decode from base64
1138
+ var base64decoded = new Buffer(challengeString, 'base64').toString('ascii'),
1139
+ hmac_md5 = crypto.createHmac('md5', this._auth.pass);
1140
+
1141
+ hmac_md5.update(base64decoded);
1142
+
1143
+ var hex_hmac = hmac_md5.digest('hex'),
1144
+ prepended = this._auth.user + ' ' + hex_hmac;
1145
+
1146
+ this._responseActions.push(function (str) {
1147
+ this._actionAUTH_CRAM_MD5_PASS(str, callback);
1148
+ }.bind(this));
1149
+
1150
+
1151
+ this._sendCommand(new Buffer(prepended).toString('base64'));
1152
+ };
1153
+
1154
+ /**
1155
+ * Handles the response to CRAM-MD5 authentication, if there's no error,
1156
+ * the user can be considered logged in. Start waiting for a message to send
1157
+ *
1158
+ * @param {String} str Message from the server
1159
+ */
1160
+ SMTPConnection.prototype._actionAUTH_CRAM_MD5_PASS = function (str, callback) {
1161
+ if (!str.match(/^235\s+/)) {
1162
+ return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH CRAM-MD5'));
1163
+ }
1164
+
1165
+ this.logger.info('[%s] User %s authenticated', this.id, JSON.stringify(this._user));
1166
+ this.authenticated = true;
1167
+ callback(null, true);
1168
+ };
1169
+
1170
+ /**
1171
+ * Handles the TYPE3 response for NTLM authentication, if there's no error,
1172
+ * the user can be considered logged in. Start waiting for a message to send
1173
+ *
1174
+ * @param {String} str Message from the server
1175
+ */
1176
+ SMTPConnection.prototype._actionAUTH_NTLM_TYPE3 = function (str, callback) {
1177
+ if (!str.match(/^235\s+/)) {
1178
+ return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH NTLM'));
1179
+ }
1180
+
1181
+ this.logger.info('[%s] User %s authenticated', this.id, JSON.stringify(this._user));
1182
+ this.authenticated = true;
1183
+ callback(null, true);
1184
+ };
1185
+
1186
+ /**
1187
+ * Handle the response for AUTH LOGIN command. We are expecting
1188
+ * '334 UGFzc3dvcmQ6' (base64 for 'Password:'). Data to be sent as
1189
+ * response needs to be base64 encoded password.
1190
+ *
1191
+ * @param {String} str Message from the server
1192
+ */
1193
+ SMTPConnection.prototype._actionAUTH_LOGIN_PASS = function (str, callback) {
1194
+ if (str !== '334 UGFzc3dvcmQ6') {
1195
+ return callback(this._formatError('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6"', 'EAUTH', str, 'AUTH LOGIN'));
1196
+ }
1197
+
1198
+ this._responseActions.push(function (str) {
1199
+ this._actionAUTHComplete(str, callback);
1200
+ }.bind(this));
1201
+
1202
+ this._sendCommand(new Buffer(this._auth.pass + '', 'utf-8').toString('base64'));
1203
+ };
1204
+
1205
+ /**
1206
+ * Handles the response for authentication, if there's no error,
1207
+ * the user can be considered logged in. Start waiting for a message to send
1208
+ *
1209
+ * @param {String} str Message from the server
1210
+ */
1211
+ SMTPConnection.prototype._actionAUTHComplete = function (str, isRetry, callback) {
1212
+ if (!callback && typeof isRetry === 'function') {
1213
+ callback = isRetry;
1214
+ isRetry = undefined;
1215
+ }
1216
+
1217
+ if (str.substr(0, 3) === '334') {
1218
+ this._responseActions.push(function (str) {
1219
+ if (isRetry || !this._auth.xoauth2 || typeof this._auth.xoauth2 !== 'object') {
1220
+ this._actionAUTHComplete(str, true, callback);
1221
+ } else {
1222
+ setTimeout(this._handleXOauth2Token.bind(this, true, callback), Math.random() * 4000 + 1000);
1223
+ }
1224
+ }.bind(this));
1225
+ this._sendCommand('');
1226
+ return;
1227
+ }
1228
+
1229
+ if (str.charAt(0) !== '2') {
1230
+ this.logger.info('[%s] User %s failed to authenticate', this.id, JSON.stringify(this._user));
1231
+ return callback(this._formatError('Invalid login', 'EAUTH', str, 'AUTH ' + this._authMethod));
1232
+ }
1233
+
1234
+ this.logger.info('[%s] User %s authenticated', this.id, JSON.stringify(this._user));
1235
+ this.authenticated = true;
1236
+ callback(null, true);
1237
+ };
1238
+
1239
+ /**
1240
+ * Handle response for a MAIL FROM: command
1241
+ *
1242
+ * @param {String} str Message from the server
1243
+ */
1244
+ SMTPConnection.prototype._actionMAIL = function (str, callback) {
1245
+ var message, curRecipient;
1246
+ if (Number(str.charAt(0)) !== 2) {
1247
+ if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
1248
+ message = 'Internationalized mailbox name not allowed';
1249
+ } else {
1250
+ message = 'Mail command failed';
1251
+ }
1252
+ return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
1253
+ }
1254
+
1255
+ if (!this._envelope.rcptQueue.length) {
1256
+ return callback(this._formatError('Can\'t send mail - no recipients defined', 'EENVELOPE', false, 'API'));
1257
+ } else {
1258
+ this._recipientQueue = [];
1259
+
1260
+ if (this._supportedExtensions.indexOf('PIPELINING') >= 0) {
1261
+ while (this._envelope.rcptQueue.length) {
1262
+ curRecipient = this._envelope.rcptQueue.shift();
1263
+ this._recipientQueue.push(curRecipient);
1264
+ this._responseActions.push(function (str) {
1265
+ this._actionRCPT(str, callback);
1266
+ }.bind(this));
1267
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1268
+ }
1269
+ } else {
1270
+ curRecipient = this._envelope.rcptQueue.shift();
1271
+ this._recipientQueue.push(curRecipient);
1272
+ this._responseActions.push(function (str) {
1273
+ this._actionRCPT(str, callback);
1274
+ }.bind(this));
1275
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1276
+ }
1277
+ }
1278
+ };
1279
+
1280
+ /**
1281
+ * Handle response for a RCPT TO: command
1282
+ *
1283
+ * @param {String} str Message from the server
1284
+ */
1285
+ SMTPConnection.prototype._actionRCPT = function (str, callback) {
1286
+ var message, err, curRecipient = this._recipientQueue.shift();
1287
+ if (Number(str.charAt(0)) !== 2) {
1288
+ // this is a soft error
1289
+ if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)) {
1290
+ message = 'Internationalized mailbox name not allowed';
1291
+ } else {
1292
+ message = 'Recipient command failed';
1293
+ }
1294
+ this._envelope.rejected.push(curRecipient);
1295
+ // store error for the failed recipient
1296
+ err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
1297
+ err.recipient = curRecipient;
1298
+ this._envelope.rejectedErrors.push(err);
1299
+ } else {
1300
+ this._envelope.accepted.push(curRecipient);
1301
+ }
1302
+
1303
+ if (!this._envelope.rcptQueue.length && !this._recipientQueue.length) {
1304
+ if (this._envelope.rejected.length < this._envelope.to.length) {
1305
+ this._responseActions.push(function (str) {
1306
+ this._actionDATA(str, callback);
1307
+ }.bind(this));
1308
+ this._sendCommand('DATA');
1309
+ } else {
1310
+ err = this._formatError('Can\'t send mail - all recipients were rejected', 'EENVELOPE', str, 'RCPT TO');
1311
+ err.rejected = this._envelope.rejected;
1312
+ err.rejectedErrors = this._envelope.rejectedErrors;
1313
+ return callback(err);
1314
+ }
1315
+ } else if (this._envelope.rcptQueue.length) {
1316
+ curRecipient = this._envelope.rcptQueue.shift();
1317
+ this._recipientQueue.push(curRecipient);
1318
+ this._responseActions.push(function (str) {
1319
+ this._actionRCPT(str, callback);
1320
+ }.bind(this));
1321
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1322
+ }
1323
+ };
1324
+
1325
+ /**
1326
+ * Handle response for a DATA command
1327
+ *
1328
+ * @param {String} str Message from the server
1329
+ */
1330
+ SMTPConnection.prototype._actionDATA = function (str, callback) {
1331
+ // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
1332
+ // some servers might use 250 instead, so lets check for 2 or 3 as the first digit
1333
+ if ([2, 3].indexOf(Number(str.charAt(0))) < 0) {
1334
+ return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
1335
+ }
1336
+
1337
+ var response = {
1338
+ accepted: this._envelope.accepted,
1339
+ rejected: this._envelope.rejected
1340
+ };
1341
+
1342
+ if (this._envelope.rejectedErrors.length) {
1343
+ response.rejectedErrors = this._envelope.rejectedErrors;
1344
+ }
1345
+
1346
+ callback(null, response);
1347
+ };
1348
+
1349
+ /**
1350
+ * Handle response for a DATA stream when using SMTP
1351
+ * We expect a single response that defines if the sending succeeded or failed
1352
+ *
1353
+ * @param {String} str Message from the server
1354
+ */
1355
+ SMTPConnection.prototype._actionSMTPStream = function (str, callback) {
1356
+ if (Number(str.charAt(0)) !== 2) {
1357
+ // Message failed
1358
+ return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
1359
+ } else {
1360
+ // Message sent succesfully
1361
+ return callback(null, str);
1362
+ }
1363
+ };
1364
+
1365
+ /**
1366
+ * Handle response for a DATA stream
1367
+ * We expect a separate response for every recipient. All recipients can either
1368
+ * succeed or fail separately
1369
+ *
1370
+ * @param {String} recipient The recipient this response applies to
1371
+ * @param {Boolean} final Is this the final recipient?
1372
+ * @param {String} str Message from the server
1373
+ */
1374
+ SMTPConnection.prototype._actionLMTPStream = function (recipient, final, str, callback) {
1375
+ var err;
1376
+ if (Number(str.charAt(0)) !== 2) {
1377
+ // Message failed
1378
+ err = this._formatError('Message failed for recipient ' + recipient, 'EMESSAGE', str, 'DATA');
1379
+ err.recipient = recipient;
1380
+ this._envelope.rejected.push(recipient);
1381
+ this._envelope.rejectedErrors.push(err);
1382
+ for (var i = 0, len = this._envelope.accepted.length; i < len; i++) {
1383
+ if (this._envelope.accepted[i] === recipient) {
1384
+ this._envelope.accepted.splice(i, 1);
1385
+ }
1386
+ }
1387
+ }
1388
+ if (final) {
1389
+ return callback(null, str);
1390
+ }
1391
+ };
1392
+
1393
+ SMTPConnection.prototype._handleXOauth2Token = function (isRetry, callback) {
1394
+ this._responseActions.push(function (str) {
1395
+ this._actionAUTHComplete(str, isRetry, callback);
1396
+ }.bind(this));
1397
+
1398
+ if (this._auth.xoauth2 && typeof this._auth.xoauth2 === 'object') {
1399
+ this._auth.xoauth2[isRetry ? 'generateToken' : 'getToken'](function (err, token) {
1400
+ if (err) {
1401
+ this.logger.info('[%s] User %s failed to authenticate', this.id, JSON.stringify(this._user));
1402
+ return callback(this._formatError(err, 'EAUTH', false, 'AUTH XOAUTH2'));
1403
+ }
1404
+ this._sendCommand('AUTH XOAUTH2 ' + token);
1405
+ }.bind(this));
1406
+ } else {
1407
+ this._sendCommand('AUTH XOAUTH2 ' + this._buildXOAuth2Token(this._auth.user, this._auth.xoauth2));
1408
+ }
1409
+ };
1410
+
1411
+ /**
1412
+ * Builds a login token for XOAUTH2 authentication command
1413
+ *
1414
+ * @param {String} user E-mail address of the user
1415
+ * @param {String} token Valid access token for the user
1416
+ * @return {String} Base64 formatted login token
1417
+ */
1418
+ SMTPConnection.prototype._buildXOAuth2Token = function (user, token) {
1419
+ var authData = [
1420
+ 'user=' + (user || ''),
1421
+ 'auth=Bearer ' + token,
1422
+ '',
1423
+ ''
1424
+ ];
1425
+ return new Buffer(authData.join('\x01')).toString('base64');
1426
+ };
1427
+
1428
+ SMTPConnection.prototype._getHostname = function () {
1429
+ // defaul hostname is machine hostname or [IP]
1430
+ var defaultHostname = os.hostname() || '';
1431
+
1432
+ // ignore if not FQDN
1433
+ if (defaultHostname.indexOf('.') < 0) {
1434
+ defaultHostname = '[127.0.0.1]';
1435
+ }
1436
+
1437
+ // IP should be enclosed in []
1438
+ if (defaultHostname.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
1439
+ defaultHostname = '[' + defaultHostname + ']';
1440
+ }
1441
+
1442
+ return defaultHostname;
1443
+ };