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.
- package/Laba6/file1.js +23 -0
- package/Laba6/file2.js +23 -0
- package/Laba6/index.html +63 -0
- package/Laba6/m0603.js +29 -0
- package/Laba6/node_modules/.package-lock.json +83 -0
- package/Laba6/node_modules/httpntlm/.jshintrc +4 -0
- package/Laba6/node_modules/httpntlm/LICENSE +20 -0
- package/Laba6/node_modules/httpntlm/README.md +148 -0
- package/Laba6/node_modules/httpntlm/httpntlm.js +104 -0
- package/Laba6/node_modules/httpntlm/ntlm.js +390 -0
- package/Laba6/node_modules/httpntlm/package.json +33 -0
- package/Laba6/node_modules/httpreq/LICENSE +19 -0
- package/Laba6/node_modules/httpreq/README.md +383 -0
- package/Laba6/node_modules/httpreq/contributors.md +26 -0
- package/Laba6/node_modules/httpreq/lib/httpreq.js +681 -0
- package/Laba6/node_modules/httpreq/package.json +102 -0
- package/Laba6/node_modules/m0603sol/m0603.js +29 -0
- package/Laba6/node_modules/m0603sol/package.json +12 -0
- package/Laba6/node_modules/nodemailer/.gitattributes +6 -0
- package/Laba6/node_modules/nodemailer/.prettierrc.js +8 -0
- package/Laba6/node_modules/nodemailer/CHANGELOG.md +725 -0
- package/Laba6/node_modules/nodemailer/CODE_OF_CONDUCT.md +76 -0
- package/Laba6/node_modules/nodemailer/CONTRIBUTING.md +67 -0
- package/Laba6/node_modules/nodemailer/LICENSE +16 -0
- package/Laba6/node_modules/nodemailer/README.md +97 -0
- package/Laba6/node_modules/nodemailer/SECURITY.txt +22 -0
- package/Laba6/node_modules/nodemailer/lib/addressparser/index.js +313 -0
- package/Laba6/node_modules/nodemailer/lib/base64/index.js +142 -0
- package/Laba6/node_modules/nodemailer/lib/dkim/index.js +251 -0
- package/Laba6/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
- package/Laba6/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/Laba6/node_modules/nodemailer/lib/dkim/sign.js +117 -0
- package/Laba6/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
- package/Laba6/node_modules/nodemailer/lib/fetch/index.js +274 -0
- package/Laba6/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/Laba6/node_modules/nodemailer/lib/mail-composer/index.js +558 -0
- package/Laba6/node_modules/nodemailer/lib/mailer/index.js +427 -0
- package/Laba6/node_modules/nodemailer/lib/mailer/mail-message.js +315 -0
- package/Laba6/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
- package/Laba6/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2102 -0
- package/Laba6/node_modules/nodemailer/lib/mime-node/index.js +1290 -0
- package/Laba6/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/Laba6/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
- package/Laba6/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
- package/Laba6/node_modules/nodemailer/lib/nodemailer.js +143 -0
- package/Laba6/node_modules/nodemailer/lib/qp/index.js +219 -0
- package/Laba6/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
- package/Laba6/node_modules/nodemailer/lib/ses-transport/index.js +349 -0
- package/Laba6/node_modules/nodemailer/lib/shared/index.js +638 -0
- package/Laba6/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
- package/Laba6/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
- package/Laba6/node_modules/nodemailer/lib/smtp-connection/index.js +1796 -0
- package/Laba6/node_modules/nodemailer/lib/smtp-pool/index.js +648 -0
- package/Laba6/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +253 -0
- package/Laba6/node_modules/nodemailer/lib/smtp-transport/index.js +416 -0
- package/Laba6/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/Laba6/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/Laba6/node_modules/nodemailer/lib/well-known/services.json +286 -0
- package/Laba6/node_modules/nodemailer/lib/xoauth2/index.js +376 -0
- package/Laba6/node_modules/nodemailer/package.json +46 -0
- package/Laba6/node_modules/nodemailer/postinstall.js +101 -0
- package/Laba6/node_modules/nodemailer-fetch/.eslintrc.js +56 -0
- package/Laba6/node_modules/nodemailer-fetch/.travis.yml +19 -0
- package/Laba6/node_modules/nodemailer-fetch/CHANGELOG.md +30 -0
- package/Laba6/node_modules/nodemailer-fetch/Gruntfile.js +27 -0
- package/Laba6/node_modules/nodemailer-fetch/LICENSE +16 -0
- package/Laba6/node_modules/nodemailer-fetch/README.md +55 -0
- package/Laba6/node_modules/nodemailer-fetch/lib/cookies.js +275 -0
- package/Laba6/node_modules/nodemailer-fetch/lib/fetch.js +224 -0
- package/Laba6/node_modules/nodemailer-fetch/package.json +30 -0
- package/Laba6/node_modules/nodemailer-fetch/test/cookies-test.js +391 -0
- package/Laba6/node_modules/nodemailer-fetch/test/fetch-test.js +486 -0
- package/Laba6/node_modules/nodemailer-shared/.eslintrc.js +59 -0
- package/Laba6/node_modules/nodemailer-shared/.travis.yml +18 -0
- package/Laba6/node_modules/nodemailer-shared/Gruntfile.js +27 -0
- package/Laba6/node_modules/nodemailer-shared/LICENSE +16 -0
- package/Laba6/node_modules/nodemailer-shared/README.md +14 -0
- package/Laba6/node_modules/nodemailer-shared/lib/shared.js +282 -0
- package/Laba6/node_modules/nodemailer-shared/package.json +36 -0
- package/Laba6/node_modules/nodemailer-shared/test/fixtures/message.html +1 -0
- package/Laba6/node_modules/nodemailer-shared/test/shared-test.js +291 -0
- package/Laba6/node_modules/nodemailer-smtp-transport/.eslintrc.js +59 -0
- package/Laba6/node_modules/nodemailer-smtp-transport/Gruntfile.js +27 -0
- package/Laba6/node_modules/nodemailer-smtp-transport/LICENSE +19 -0
- package/Laba6/node_modules/nodemailer-smtp-transport/README.md +7 -0
- package/Laba6/node_modules/nodemailer-smtp-transport/lib/smtp-transport.js +281 -0
- package/Laba6/node_modules/nodemailer-smtp-transport/package.json +37 -0
- package/Laba6/node_modules/nodemailer-wellknown/.travis.yml +17 -0
- package/Laba6/node_modules/nodemailer-wellknown/LICENSE +19 -0
- package/Laba6/node_modules/nodemailer-wellknown/README.md +80 -0
- package/Laba6/node_modules/nodemailer-wellknown/index.js +47 -0
- package/Laba6/node_modules/nodemailer-wellknown/package.json +26 -0
- package/Laba6/node_modules/nodemailer-wellknown/services.json +255 -0
- package/Laba6/node_modules/nodemailer-wellknown/test.js +23 -0
- package/Laba6/node_modules/smtp-connection/.eslintrc.js +56 -0
- package/Laba6/node_modules/smtp-connection/CHANGELOG.md +164 -0
- package/Laba6/node_modules/smtp-connection/Gruntfile.js +27 -0
- package/Laba6/node_modules/smtp-connection/LICENSE +19 -0
- package/Laba6/node_modules/smtp-connection/README.md +200 -0
- package/Laba6/node_modules/smtp-connection/lib/data-stream.js +111 -0
- package/Laba6/node_modules/smtp-connection/lib/smtp-connection.js +1443 -0
- package/Laba6/node_modules/smtp-connection/package.json +41 -0
- package/Laba6/node_modules/underscore/LICENSE +23 -0
- package/Laba6/node_modules/underscore/README.md +22 -0
- package/Laba6/node_modules/underscore/package.json +41 -0
- package/Laba6/node_modules/underscore/underscore-min.js +6 -0
- package/Laba6/node_modules/underscore/underscore.js +1415 -0
- package/Laba6/package-lock.json +94 -0
- package/Laba6/package.json +17 -0
- package/Laba6.txt +31 -0
- package/file1.js +23 -0
- package/file2.js +23 -0
- package/index.html +63 -0
- package/m0603.js +29 -0
- package/m0603Sol/m0603.js +29 -0
- package/m0603Sol/package.json +12 -0
- 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
|
+
};
|