m06_task3.js 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (204) hide show
  1. package/.idea/.name +1 -0
  2. package/.idea/inspectionProfiles/Project_Default.xml +10 -0
  3. package/.idea/jsLibraryMappings.xml +6 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/node_lab_6.iml +12 -0
  6. package/06_task2.html +73 -0
  7. package/06_task2.js +52 -0
  8. package/06_task3.js +3 -0
  9. package/06_task4.js +3 -0
  10. package/m06_task3.js +28 -0
  11. package/package/index.js +28 -0
  12. package/package/node_modules/.package-lock.json +31 -0
  13. package/package/node_modules/@types/node/LICENSE +21 -0
  14. package/package/node_modules/@types/node/README.md +16 -0
  15. package/package/node_modules/@types/node/assert/strict.d.ts +8 -0
  16. package/package/node_modules/@types/node/assert.d.ts +961 -0
  17. package/package/node_modules/@types/node/async_hooks.d.ts +501 -0
  18. package/package/node_modules/@types/node/buffer.d.ts +2258 -0
  19. package/package/node_modules/@types/node/child_process.d.ts +1369 -0
  20. package/package/node_modules/@types/node/cluster.d.ts +410 -0
  21. package/package/node_modules/@types/node/console.d.ts +412 -0
  22. package/package/node_modules/@types/node/constants.d.ts +18 -0
  23. package/package/node_modules/@types/node/crypto.d.ts +3964 -0
  24. package/package/node_modules/@types/node/dgram.d.ts +545 -0
  25. package/package/node_modules/@types/node/diagnostics_channel.d.ts +153 -0
  26. package/package/node_modules/@types/node/dns/promises.d.ts +370 -0
  27. package/package/node_modules/@types/node/dns.d.ts +659 -0
  28. package/package/node_modules/@types/node/dom-events.d.ts +126 -0
  29. package/package/node_modules/@types/node/domain.d.ts +170 -0
  30. package/package/node_modules/@types/node/events.d.ts +678 -0
  31. package/package/node_modules/@types/node/fs/promises.d.ts +1138 -0
  32. package/package/node_modules/@types/node/fs.d.ts +3872 -0
  33. package/package/node_modules/@types/node/globals.d.ts +300 -0
  34. package/package/node_modules/@types/node/globals.global.d.ts +1 -0
  35. package/package/node_modules/@types/node/http.d.ts +1614 -0
  36. package/package/node_modules/@types/node/http2.d.ts +2134 -0
  37. package/package/node_modules/@types/node/https.d.ts +541 -0
  38. package/package/node_modules/@types/node/index.d.ts +133 -0
  39. package/package/node_modules/@types/node/inspector.d.ts +2741 -0
  40. package/package/node_modules/@types/node/module.d.ts +114 -0
  41. package/package/node_modules/@types/node/net.d.ts +869 -0
  42. package/package/node_modules/@types/node/os.d.ts +466 -0
  43. package/package/node_modules/@types/node/package.json +232 -0
  44. package/package/node_modules/@types/node/path.d.ts +191 -0
  45. package/package/node_modules/@types/node/perf_hooks.d.ts +625 -0
  46. package/package/node_modules/@types/node/process.d.ts +1482 -0
  47. package/package/node_modules/@types/node/punycode.d.ts +117 -0
  48. package/package/node_modules/@types/node/querystring.d.ts +131 -0
  49. package/package/node_modules/@types/node/readline/promises.d.ts +143 -0
  50. package/package/node_modules/@types/node/readline.d.ts +653 -0
  51. package/package/node_modules/@types/node/repl.d.ts +424 -0
  52. package/package/node_modules/@types/node/stream/consumers.d.ts +12 -0
  53. package/package/node_modules/@types/node/stream/promises.d.ts +42 -0
  54. package/package/node_modules/@types/node/stream/web.d.ts +330 -0
  55. package/package/node_modules/@types/node/stream.d.ts +1340 -0
  56. package/package/node_modules/@types/node/string_decoder.d.ts +67 -0
  57. package/package/node_modules/@types/node/test.d.ts +314 -0
  58. package/package/node_modules/@types/node/timers/promises.d.ts +68 -0
  59. package/package/node_modules/@types/node/timers.d.ts +94 -0
  60. package/package/node_modules/@types/node/tls.d.ts +1028 -0
  61. package/package/node_modules/@types/node/trace_events.d.ts +171 -0
  62. package/package/node_modules/@types/node/ts4.8/assert/strict.d.ts +8 -0
  63. package/package/node_modules/@types/node/ts4.8/assert.d.ts +961 -0
  64. package/package/node_modules/@types/node/ts4.8/async_hooks.d.ts +501 -0
  65. package/package/node_modules/@types/node/ts4.8/buffer.d.ts +2259 -0
  66. package/package/node_modules/@types/node/ts4.8/child_process.d.ts +1369 -0
  67. package/package/node_modules/@types/node/ts4.8/cluster.d.ts +410 -0
  68. package/package/node_modules/@types/node/ts4.8/console.d.ts +412 -0
  69. package/package/node_modules/@types/node/ts4.8/constants.d.ts +18 -0
  70. package/package/node_modules/@types/node/ts4.8/crypto.d.ts +3964 -0
  71. package/package/node_modules/@types/node/ts4.8/dgram.d.ts +545 -0
  72. package/package/node_modules/@types/node/ts4.8/diagnostics_channel.d.ts +153 -0
  73. package/package/node_modules/@types/node/ts4.8/dns/promises.d.ts +370 -0
  74. package/package/node_modules/@types/node/ts4.8/dns.d.ts +659 -0
  75. package/package/node_modules/@types/node/ts4.8/dom-events.d.ts +126 -0
  76. package/package/node_modules/@types/node/ts4.8/domain.d.ts +170 -0
  77. package/package/node_modules/@types/node/ts4.8/events.d.ts +678 -0
  78. package/package/node_modules/@types/node/ts4.8/fs/promises.d.ts +1138 -0
  79. package/package/node_modules/@types/node/ts4.8/fs.d.ts +3872 -0
  80. package/package/node_modules/@types/node/ts4.8/globals.d.ts +294 -0
  81. package/package/node_modules/@types/node/ts4.8/globals.global.d.ts +1 -0
  82. package/package/node_modules/@types/node/ts4.8/http.d.ts +1614 -0
  83. package/package/node_modules/@types/node/ts4.8/http2.d.ts +2134 -0
  84. package/package/node_modules/@types/node/ts4.8/https.d.ts +541 -0
  85. package/package/node_modules/@types/node/ts4.8/index.d.ts +88 -0
  86. package/package/node_modules/@types/node/ts4.8/inspector.d.ts +2741 -0
  87. package/package/node_modules/@types/node/ts4.8/module.d.ts +114 -0
  88. package/package/node_modules/@types/node/ts4.8/net.d.ts +869 -0
  89. package/package/node_modules/@types/node/ts4.8/os.d.ts +466 -0
  90. package/package/node_modules/@types/node/ts4.8/path.d.ts +191 -0
  91. package/package/node_modules/@types/node/ts4.8/perf_hooks.d.ts +625 -0
  92. package/package/node_modules/@types/node/ts4.8/process.d.ts +1482 -0
  93. package/package/node_modules/@types/node/ts4.8/punycode.d.ts +117 -0
  94. package/package/node_modules/@types/node/ts4.8/querystring.d.ts +131 -0
  95. package/package/node_modules/@types/node/ts4.8/readline/promises.d.ts +143 -0
  96. package/package/node_modules/@types/node/ts4.8/readline.d.ts +653 -0
  97. package/package/node_modules/@types/node/ts4.8/repl.d.ts +424 -0
  98. package/package/node_modules/@types/node/ts4.8/stream/consumers.d.ts +12 -0
  99. package/package/node_modules/@types/node/ts4.8/stream/promises.d.ts +42 -0
  100. package/package/node_modules/@types/node/ts4.8/stream/web.d.ts +330 -0
  101. package/package/node_modules/@types/node/ts4.8/stream.d.ts +1340 -0
  102. package/package/node_modules/@types/node/ts4.8/string_decoder.d.ts +67 -0
  103. package/package/node_modules/@types/node/ts4.8/test.d.ts +314 -0
  104. package/package/node_modules/@types/node/ts4.8/timers/promises.d.ts +68 -0
  105. package/package/node_modules/@types/node/ts4.8/timers.d.ts +94 -0
  106. package/package/node_modules/@types/node/ts4.8/tls.d.ts +1028 -0
  107. package/package/node_modules/@types/node/ts4.8/trace_events.d.ts +171 -0
  108. package/package/node_modules/@types/node/ts4.8/tty.d.ts +206 -0
  109. package/package/node_modules/@types/node/ts4.8/url.d.ts +897 -0
  110. package/package/node_modules/@types/node/ts4.8/util.d.ts +1850 -0
  111. package/package/node_modules/@types/node/ts4.8/v8.d.ts +396 -0
  112. package/package/node_modules/@types/node/ts4.8/vm.d.ts +509 -0
  113. package/package/node_modules/@types/node/ts4.8/wasi.d.ts +158 -0
  114. package/package/node_modules/@types/node/ts4.8/worker_threads.d.ts +689 -0
  115. package/package/node_modules/@types/node/ts4.8/zlib.d.ts +517 -0
  116. package/package/node_modules/@types/node/tty.d.ts +206 -0
  117. package/package/node_modules/@types/node/url.d.ts +897 -0
  118. package/package/node_modules/@types/node/util.d.ts +1850 -0
  119. package/package/node_modules/@types/node/v8.d.ts +396 -0
  120. package/package/node_modules/@types/node/vm.d.ts +509 -0
  121. package/package/node_modules/@types/node/wasi.d.ts +158 -0
  122. package/package/node_modules/@types/node/worker_threads.d.ts +689 -0
  123. package/package/node_modules/@types/node/zlib.d.ts +517 -0
  124. package/package/node_modules/@types/nodemailer/LICENSE +21 -0
  125. package/package/node_modules/@types/nodemailer/README.md +16 -0
  126. package/package/node_modules/@types/nodemailer/index.d.ts +83 -0
  127. package/package/node_modules/@types/nodemailer/lib/addressparser/index.d.ts +31 -0
  128. package/package/node_modules/@types/nodemailer/lib/base64/index.d.ts +22 -0
  129. package/package/node_modules/@types/nodemailer/lib/dkim/index.d.ts +45 -0
  130. package/package/node_modules/@types/nodemailer/lib/dkim/message-parser.d.ts +75 -0
  131. package/package/node_modules/@types/nodemailer/lib/dkim/relaxed-body.d.ts +75 -0
  132. package/package/node_modules/@types/nodemailer/lib/dkim/sign.d.ts +11 -0
  133. package/package/node_modules/@types/nodemailer/lib/fetch/cookies.d.ts +54 -0
  134. package/package/node_modules/@types/nodemailer/lib/fetch/index.d.ts +38 -0
  135. package/package/node_modules/@types/nodemailer/lib/json-transport/index.d.ts +50 -0
  136. package/package/node_modules/@types/nodemailer/lib/mail-composer/index.d.ts +25 -0
  137. package/package/node_modules/@types/nodemailer/lib/mailer/index.d.ts +230 -0
  138. package/package/node_modules/@types/nodemailer/lib/mailer/mail-message.d.ts +28 -0
  139. package/package/node_modules/@types/nodemailer/lib/mime-funcs/index.d.ts +87 -0
  140. package/package/node_modules/@types/nodemailer/lib/mime-funcs/mime-types.d.ts +2 -0
  141. package/package/node_modules/@types/nodemailer/lib/mime-node/index.d.ts +137 -0
  142. package/package/node_modules/@types/nodemailer/lib/mime-node/last-newline.d.ts +9 -0
  143. package/package/node_modules/@types/nodemailer/lib/qp/index.d.ts +23 -0
  144. package/package/node_modules/@types/nodemailer/lib/sendmail-transport/index.d.ts +53 -0
  145. package/package/node_modules/@types/nodemailer/lib/sendmail-transport/le-unix.d.ts +7 -0
  146. package/package/node_modules/@types/nodemailer/lib/sendmail-transport/le-windows.d.ts +7 -0
  147. package/package/node_modules/@types/nodemailer/lib/ses-transport/index.d.ts +136 -0
  148. package/package/node_modules/@types/nodemailer/lib/shared/index.d.ts +51 -0
  149. package/package/node_modules/@types/nodemailer/lib/smtp-connection/data-stream.d.ts +11 -0
  150. package/package/node_modules/@types/nodemailer/lib/smtp-connection/http-proxy-client.d.ts +11 -0
  151. package/package/node_modules/@types/nodemailer/lib/smtp-connection/index.d.ts +254 -0
  152. package/package/node_modules/@types/nodemailer/lib/smtp-pool/index.d.ts +90 -0
  153. package/package/node_modules/@types/nodemailer/lib/smtp-pool/pool-resource.d.ts +66 -0
  154. package/package/node_modules/@types/nodemailer/lib/smtp-transport/index.d.ts +115 -0
  155. package/package/node_modules/@types/nodemailer/lib/stream-transport/index.d.ts +56 -0
  156. package/package/node_modules/@types/nodemailer/lib/well-known/index.d.ts +6 -0
  157. package/package/node_modules/@types/nodemailer/lib/xoauth2/index.d.ts +110 -0
  158. package/package/node_modules/@types/nodemailer/package.json +37 -0
  159. package/package/node_modules/nodemailer/.gitattributes +6 -0
  160. package/package/node_modules/nodemailer/.prettierrc.js +8 -0
  161. package/package/node_modules/nodemailer/CHANGELOG.md +715 -0
  162. package/package/node_modules/nodemailer/CODE_OF_CONDUCT.md +76 -0
  163. package/package/node_modules/nodemailer/CONTRIBUTING.md +67 -0
  164. package/package/node_modules/nodemailer/LICENSE +16 -0
  165. package/package/node_modules/nodemailer/README.md +93 -0
  166. package/package/node_modules/nodemailer/SECURITY.txt +22 -0
  167. package/package/node_modules/nodemailer/lib/addressparser/index.js +313 -0
  168. package/package/node_modules/nodemailer/lib/base64/index.js +142 -0
  169. package/package/node_modules/nodemailer/lib/dkim/index.js +251 -0
  170. package/package/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
  171. package/package/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
  172. package/package/node_modules/nodemailer/lib/dkim/sign.js +117 -0
  173. package/package/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
  174. package/package/node_modules/nodemailer/lib/fetch/index.js +269 -0
  175. package/package/node_modules/nodemailer/lib/json-transport/index.js +82 -0
  176. package/package/node_modules/nodemailer/lib/mail-composer/index.js +558 -0
  177. package/package/node_modules/nodemailer/lib/mailer/index.js +427 -0
  178. package/package/node_modules/nodemailer/lib/mailer/mail-message.js +315 -0
  179. package/package/node_modules/nodemailer/lib/mime-funcs/index.js +619 -0
  180. package/package/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2102 -0
  181. package/package/node_modules/nodemailer/lib/mime-node/index.js +1290 -0
  182. package/package/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
  183. package/package/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
  184. package/package/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
  185. package/package/node_modules/nodemailer/lib/nodemailer.js +143 -0
  186. package/package/node_modules/nodemailer/lib/qp/index.js +219 -0
  187. package/package/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
  188. package/package/node_modules/nodemailer/lib/ses-transport/index.js +349 -0
  189. package/package/node_modules/nodemailer/lib/shared/index.js +639 -0
  190. package/package/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
  191. package/package/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
  192. package/package/node_modules/nodemailer/lib/smtp-connection/index.js +1786 -0
  193. package/package/node_modules/nodemailer/lib/smtp-pool/index.js +648 -0
  194. package/package/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +253 -0
  195. package/package/node_modules/nodemailer/lib/smtp-transport/index.js +416 -0
  196. package/package/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
  197. package/package/node_modules/nodemailer/lib/well-known/index.js +47 -0
  198. package/package/node_modules/nodemailer/lib/well-known/services.json +286 -0
  199. package/package/node_modules/nodemailer/lib/xoauth2/index.js +376 -0
  200. package/package/node_modules/nodemailer/package.json +46 -0
  201. package/package/node_modules/nodemailer/postinstall.js +101 -0
  202. package/package/package-lock.json +60 -0
  203. package/package/package.json +6 -0
  204. package/package.json +19 -0
@@ -0,0 +1,1786 @@
1
+ 'use strict';
2
+
3
+ const packageInfo = require('../../package.json');
4
+ const EventEmitter = require('events').EventEmitter;
5
+ const net = require('net');
6
+ const tls = require('tls');
7
+ const os = require('os');
8
+ const crypto = require('crypto');
9
+ const DataStream = require('./data-stream');
10
+ const PassThrough = require('stream').PassThrough;
11
+ const shared = require('../shared');
12
+
13
+ // default timeout values in ms
14
+ const CONNECTION_TIMEOUT = 2 * 60 * 1000; // how much to wait for the connection to be established
15
+ const SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity before disconnecting the client
16
+ const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved
17
+ const DNS_TIMEOUT = 30 * 1000; // how much to wait for resolveHostname
18
+
19
+ /**
20
+ * Generates a SMTP connection object
21
+ *
22
+ * Optional options object takes the following possible properties:
23
+ *
24
+ * * **port** - is the port to connect to (defaults to 587 or 465)
25
+ * * **host** - is the hostname or IP address to connect to (defaults to 'localhost')
26
+ * * **secure** - use SSL
27
+ * * **ignoreTLS** - ignore server support for STARTTLS
28
+ * * **requireTLS** - forces the client to use STARTTLS
29
+ * * **name** - the name of the client server
30
+ * * **localAddress** - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)
31
+ * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 10000)
32
+ * * **connectionTimeout** - how many milliseconds to wait for the connection to establish
33
+ * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 1 hour)
34
+ * * **dnsTimeout** - Time to wait in ms for the DNS requests to be resolved (defaults to 30 seconds)
35
+ * * **lmtp** - if true, uses LMTP instead of SMTP protocol
36
+ * * **logger** - bunyan compatible logger interface
37
+ * * **debug** - if true pass SMTP traffic to the logger
38
+ * * **tls** - options for createCredentials
39
+ * * **socket** - existing socket to use instead of creating a new one (see: http://nodejs.org/api/net.html#net_class_net_socket)
40
+ * * **secured** - boolean indicates that the provided socket has already been upgraded to tls
41
+ *
42
+ * @constructor
43
+ * @namespace SMTP Client module
44
+ * @param {Object} [options] Option properties
45
+ */
46
+ class SMTPConnection extends EventEmitter {
47
+ constructor(options) {
48
+ super(options);
49
+
50
+ this.id = crypto.randomBytes(8).toString('base64').replace(/\W/g, '');
51
+ this.stage = 'init';
52
+
53
+ this.options = options || {};
54
+
55
+ this.secureConnection = !!this.options.secure;
56
+ this.alreadySecured = !!this.options.secured;
57
+
58
+ this.port = Number(this.options.port) || (this.secureConnection ? 465 : 587);
59
+ this.host = this.options.host || 'localhost';
60
+
61
+ this.allowInternalNetworkInterfaces = this.options.allowInternalNetworkInterfaces || false;
62
+
63
+ if (typeof this.options.secure === 'undefined' && this.port === 465) {
64
+ // if secure option is not set but port is 465, then default to secure
65
+ this.secureConnection = true;
66
+ }
67
+
68
+ this.name = this.options.name || this._getHostname();
69
+
70
+ this.logger = shared.getLogger(this.options, {
71
+ component: this.options.component || 'smtp-connection',
72
+ sid: this.id
73
+ });
74
+
75
+ this.customAuth = new Map();
76
+ Object.keys(this.options.customAuth || {}).forEach(key => {
77
+ let mapKey = (key || '').toString().trim().toUpperCase();
78
+ if (!mapKey) {
79
+ return;
80
+ }
81
+ this.customAuth.set(mapKey, this.options.customAuth[key]);
82
+ });
83
+
84
+ /**
85
+ * Expose version nr, just for the reference
86
+ * @type {String}
87
+ */
88
+ this.version = packageInfo.version;
89
+
90
+ /**
91
+ * If true, then the user is authenticated
92
+ * @type {Boolean}
93
+ */
94
+ this.authenticated = false;
95
+
96
+ /**
97
+ * If set to true, this instance is no longer active
98
+ * @private
99
+ */
100
+ this.destroyed = false;
101
+
102
+ /**
103
+ * Defines if the current connection is secure or not. If not,
104
+ * STARTTLS can be used if available
105
+ * @private
106
+ */
107
+ this.secure = !!this.secureConnection;
108
+
109
+ /**
110
+ * Store incomplete messages coming from the server
111
+ * @private
112
+ */
113
+ this._remainder = '';
114
+
115
+ /**
116
+ * Unprocessed responses from the server
117
+ * @type {Array}
118
+ */
119
+ this._responseQueue = [];
120
+
121
+ this.lastServerResponse = false;
122
+
123
+ /**
124
+ * The socket connecting to the server
125
+ * @publick
126
+ */
127
+ this._socket = false;
128
+
129
+ /**
130
+ * Lists supported auth mechanisms
131
+ * @private
132
+ */
133
+ this._supportedAuth = [];
134
+
135
+ /**
136
+ * Set to true, if EHLO response includes "AUTH".
137
+ * If false then authentication is not tried
138
+ */
139
+ this.allowsAuth = false;
140
+
141
+ /**
142
+ * Includes current envelope (from, to)
143
+ * @private
144
+ */
145
+ this._envelope = false;
146
+
147
+ /**
148
+ * Lists supported extensions
149
+ * @private
150
+ */
151
+ this._supportedExtensions = [];
152
+
153
+ /**
154
+ * Defines the maximum allowed size for a single message
155
+ * @private
156
+ */
157
+ this._maxAllowedSize = 0;
158
+
159
+ /**
160
+ * Function queue to run if a data chunk comes from the server
161
+ * @private
162
+ */
163
+ this._responseActions = [];
164
+ this._recipientQueue = [];
165
+
166
+ /**
167
+ * Timeout variable for waiting the greeting
168
+ * @private
169
+ */
170
+ this._greetingTimeout = false;
171
+
172
+ /**
173
+ * Timeout variable for waiting the connection to start
174
+ * @private
175
+ */
176
+ this._connectionTimeout = false;
177
+
178
+ /**
179
+ * If the socket is deemed already closed
180
+ * @private
181
+ */
182
+ this._destroyed = false;
183
+
184
+ /**
185
+ * If the socket is already being closed
186
+ * @private
187
+ */
188
+ this._closing = false;
189
+
190
+ /**
191
+ * Callbacks for socket's listeners
192
+ */
193
+ this._onSocketData = chunk => this._onData(chunk);
194
+ this._onSocketError = error => this._onError(error, 'ESOCKET', false, 'CONN');
195
+ this._onSocketClose = () => this._onClose();
196
+ this._onSocketEnd = () => this._onEnd();
197
+ this._onSocketTimeout = () => this._onTimeout();
198
+ }
199
+
200
+ /**
201
+ * Creates a connection to a SMTP server and sets up connection
202
+ * listener
203
+ */
204
+ connect(connectCallback) {
205
+ if (typeof connectCallback === 'function') {
206
+ this.once('connect', () => {
207
+ this.logger.debug(
208
+ {
209
+ tnx: 'smtp'
210
+ },
211
+ 'SMTP handshake finished'
212
+ );
213
+ connectCallback();
214
+ });
215
+
216
+ const isDestroyedMessage = this._isDestroyedMessage('connect');
217
+ if (isDestroyedMessage) {
218
+ return connectCallback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'CONN'));
219
+ }
220
+ }
221
+
222
+ let opts = {
223
+ port: this.port,
224
+ host: this.host,
225
+ allowInternalNetworkInterfaces: this.allowInternalNetworkInterfaces,
226
+ timeout: this.options.dnsTimeout || DNS_TIMEOUT
227
+ };
228
+
229
+ if (this.options.localAddress) {
230
+ opts.localAddress = this.options.localAddress;
231
+ }
232
+
233
+ let setupConnectionHandlers = () => {
234
+ this._connectionTimeout = setTimeout(() => {
235
+ this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
236
+ }, this.options.connectionTimeout || CONNECTION_TIMEOUT);
237
+
238
+ this._socket.on('error', this._onSocketError);
239
+ };
240
+
241
+ if (this.options.connection) {
242
+ // connection is already opened
243
+ this._socket = this.options.connection;
244
+ if (this.secureConnection && !this.alreadySecured) {
245
+ setImmediate(() =>
246
+ this._upgradeConnection(err => {
247
+ if (err) {
248
+ this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
249
+ return;
250
+ }
251
+ this._onConnect();
252
+ })
253
+ );
254
+ } else {
255
+ setImmediate(() => this._onConnect());
256
+ }
257
+ return;
258
+ } else if (this.options.socket) {
259
+ // socket object is set up but not yet connected
260
+ this._socket = this.options.socket;
261
+ return shared.resolveHostname(opts, (err, resolved) => {
262
+ if (err) {
263
+ return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
264
+ }
265
+ this.logger.debug(
266
+ {
267
+ tnx: 'dns',
268
+ source: opts.host,
269
+ resolved: resolved.host,
270
+ cached: !!resolved.cached
271
+ },
272
+ 'Resolved %s as %s [cache %s]',
273
+ opts.host,
274
+ resolved.host,
275
+ resolved.cached ? 'hit' : 'miss'
276
+ );
277
+ Object.keys(resolved).forEach(key => {
278
+ if (key.charAt(0) !== '_' && resolved[key]) {
279
+ opts[key] = resolved[key];
280
+ }
281
+ });
282
+ try {
283
+ this._socket.connect(this.port, this.host, () => {
284
+ this._socket.setKeepAlive(true);
285
+ this._onConnect();
286
+ });
287
+ setupConnectionHandlers();
288
+ } catch (E) {
289
+ return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
290
+ }
291
+ });
292
+ } else if (this.secureConnection) {
293
+ // connect using tls
294
+ if (this.options.tls) {
295
+ Object.keys(this.options.tls).forEach(key => {
296
+ opts[key] = this.options.tls[key];
297
+ });
298
+ }
299
+ return shared.resolveHostname(opts, (err, resolved) => {
300
+ if (err) {
301
+ return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
302
+ }
303
+ this.logger.debug(
304
+ {
305
+ tnx: 'dns',
306
+ source: opts.host,
307
+ resolved: resolved.host,
308
+ cached: !!resolved.cached
309
+ },
310
+ 'Resolved %s as %s [cache %s]',
311
+ opts.host,
312
+ resolved.host,
313
+ resolved.cached ? 'hit' : 'miss'
314
+ );
315
+ Object.keys(resolved).forEach(key => {
316
+ if (key.charAt(0) !== '_' && resolved[key]) {
317
+ opts[key] = resolved[key];
318
+ }
319
+ });
320
+ try {
321
+ this._socket = tls.connect(opts, () => {
322
+ this._socket.setKeepAlive(true);
323
+ this._onConnect();
324
+ });
325
+ setupConnectionHandlers();
326
+ } catch (E) {
327
+ return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
328
+ }
329
+ });
330
+ } else {
331
+ // connect using plaintext
332
+ return shared.resolveHostname(opts, (err, resolved) => {
333
+ if (err) {
334
+ return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
335
+ }
336
+ this.logger.debug(
337
+ {
338
+ tnx: 'dns',
339
+ source: opts.host,
340
+ resolved: resolved.host,
341
+ cached: !!resolved.cached
342
+ },
343
+ 'Resolved %s as %s [cache %s]',
344
+ opts.host,
345
+ resolved.host,
346
+ resolved.cached ? 'hit' : 'miss'
347
+ );
348
+ Object.keys(resolved).forEach(key => {
349
+ if (key.charAt(0) !== '_' && resolved[key]) {
350
+ opts[key] = resolved[key];
351
+ }
352
+ });
353
+ try {
354
+ this._socket = net.connect(opts, () => {
355
+ this._socket.setKeepAlive(true);
356
+ this._onConnect();
357
+ });
358
+ setupConnectionHandlers();
359
+ } catch (E) {
360
+ return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
361
+ }
362
+ });
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Sends QUIT
368
+ */
369
+ quit() {
370
+ this._sendCommand('QUIT');
371
+ this._responseActions.push(this.close);
372
+ }
373
+
374
+ /**
375
+ * Closes the connection to the server
376
+ */
377
+ close() {
378
+ clearTimeout(this._connectionTimeout);
379
+ clearTimeout(this._greetingTimeout);
380
+ this._responseActions = [];
381
+
382
+ // allow to run this function only once
383
+ if (this._closing) {
384
+ return;
385
+ }
386
+ this._closing = true;
387
+
388
+ let closeMethod = 'end';
389
+
390
+ if (this.stage === 'init') {
391
+ // Close the socket immediately when connection timed out
392
+ closeMethod = 'destroy';
393
+ }
394
+
395
+ this.logger.debug(
396
+ {
397
+ tnx: 'smtp'
398
+ },
399
+ 'Closing connection to the server using "%s"',
400
+ closeMethod
401
+ );
402
+
403
+ let socket = (this._socket && this._socket.socket) || this._socket;
404
+
405
+ if (socket && !socket.destroyed) {
406
+ try {
407
+ this._socket[closeMethod]();
408
+ } catch (E) {
409
+ // just ignore
410
+ }
411
+ }
412
+
413
+ this._destroy();
414
+ }
415
+
416
+ /**
417
+ * Authenticate user
418
+ */
419
+ login(authData, callback) {
420
+ const isDestroyedMessage = this._isDestroyedMessage('login');
421
+ if (isDestroyedMessage) {
422
+ return callback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
423
+ }
424
+
425
+ this._auth = authData || {};
426
+ // Select SASL authentication method
427
+ this._authMethod = (this._auth.method || '').toString().trim().toUpperCase() || false;
428
+
429
+ if (!this._authMethod && this._auth.oauth2 && !this._auth.credentials) {
430
+ this._authMethod = 'XOAUTH2';
431
+ } else if (!this._authMethod || (this._authMethod === 'XOAUTH2' && !this._auth.oauth2)) {
432
+ // use first supported
433
+ this._authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
434
+ }
435
+
436
+ if (this._authMethod !== 'XOAUTH2' && (!this._auth.credentials || !this._auth.credentials.user || !this._auth.credentials.pass)) {
437
+ if (this._auth.user && this._auth.pass) {
438
+ this._auth.credentials = {
439
+ user: this._auth.user,
440
+ pass: this._auth.pass,
441
+ options: this._auth.options
442
+ };
443
+ } else {
444
+ return callback(this._formatError('Missing credentials for "' + this._authMethod + '"', 'EAUTH', false, 'API'));
445
+ }
446
+ }
447
+
448
+ if (this.customAuth.has(this._authMethod)) {
449
+ let handler = this.customAuth.get(this._authMethod);
450
+ let lastResponse;
451
+ let returned = false;
452
+
453
+ let resolve = () => {
454
+ if (returned) {
455
+ return;
456
+ }
457
+ returned = true;
458
+ this.logger.info(
459
+ {
460
+ tnx: 'smtp',
461
+ username: this._auth.user,
462
+ action: 'authenticated',
463
+ method: this._authMethod
464
+ },
465
+ 'User %s authenticated',
466
+ JSON.stringify(this._auth.user)
467
+ );
468
+ this.authenticated = true;
469
+ callback(null, true);
470
+ };
471
+
472
+ let reject = err => {
473
+ if (returned) {
474
+ return;
475
+ }
476
+ returned = true;
477
+ callback(this._formatError(err, 'EAUTH', lastResponse, 'AUTH ' + this._authMethod));
478
+ };
479
+
480
+ let handlerResponse = handler({
481
+ auth: this._auth,
482
+ method: this._authMethod,
483
+
484
+ extensions: [].concat(this._supportedExtensions),
485
+ authMethods: [].concat(this._supportedAuth),
486
+ maxAllowedSize: this._maxAllowedSize || false,
487
+
488
+ sendCommand: (cmd, done) => {
489
+ let promise;
490
+
491
+ if (!done) {
492
+ promise = new Promise((resolve, reject) => {
493
+ done = shared.callbackPromise(resolve, reject);
494
+ });
495
+ }
496
+
497
+ this._responseActions.push(str => {
498
+ lastResponse = str;
499
+
500
+ let codes = str.match(/^(\d+)(?:\s(\d+\.\d+\.\d+))?\s/);
501
+ let data = {
502
+ command: cmd,
503
+ response: str
504
+ };
505
+ if (codes) {
506
+ data.status = Number(codes[1]) || 0;
507
+ if (codes[2]) {
508
+ data.code = codes[2];
509
+ }
510
+ data.text = str.substr(codes[0].length);
511
+ } else {
512
+ data.text = str;
513
+ data.status = 0; // just in case we need to perform numeric comparisons
514
+ }
515
+ done(null, data);
516
+ });
517
+ setImmediate(() => this._sendCommand(cmd));
518
+
519
+ return promise;
520
+ },
521
+
522
+ resolve,
523
+ reject
524
+ });
525
+
526
+ if (handlerResponse && typeof handlerResponse.catch === 'function') {
527
+ // a promise was returned
528
+ handlerResponse.then(resolve).catch(reject);
529
+ }
530
+
531
+ return;
532
+ }
533
+
534
+ switch (this._authMethod) {
535
+ case 'XOAUTH2':
536
+ this._handleXOauth2Token(false, callback);
537
+ return;
538
+ case 'LOGIN':
539
+ this._responseActions.push(str => {
540
+ this._actionAUTH_LOGIN_USER(str, callback);
541
+ });
542
+ this._sendCommand('AUTH LOGIN');
543
+ return;
544
+ case 'PLAIN':
545
+ this._responseActions.push(str => {
546
+ this._actionAUTHComplete(str, callback);
547
+ });
548
+ this._sendCommand(
549
+ 'AUTH PLAIN ' +
550
+ Buffer.from(
551
+ //this._auth.user+'\u0000'+
552
+ '\u0000' + // skip authorization identity as it causes problems with some servers
553
+ this._auth.credentials.user +
554
+ '\u0000' +
555
+ this._auth.credentials.pass,
556
+ 'utf-8'
557
+ ).toString('base64'),
558
+ // log entry without passwords
559
+ 'AUTH PLAIN ' +
560
+ Buffer.from(
561
+ //this._auth.user+'\u0000'+
562
+ '\u0000' + // skip authorization identity as it causes problems with some servers
563
+ this._auth.credentials.user +
564
+ '\u0000' +
565
+ '/* secret */',
566
+ 'utf-8'
567
+ ).toString('base64')
568
+ );
569
+ return;
570
+ case 'CRAM-MD5':
571
+ this._responseActions.push(str => {
572
+ this._actionAUTH_CRAM_MD5(str, callback);
573
+ });
574
+ this._sendCommand('AUTH CRAM-MD5');
575
+ return;
576
+ }
577
+
578
+ return callback(this._formatError('Unknown authentication method "' + this._authMethod + '"', 'EAUTH', false, 'API'));
579
+ }
580
+
581
+ /**
582
+ * Sends a message
583
+ *
584
+ * @param {Object} envelope Envelope object, {from: addr, to: [addr]}
585
+ * @param {Object} message String, Buffer or a Stream
586
+ * @param {Function} callback Callback to return once sending is completed
587
+ */
588
+ send(envelope, message, done) {
589
+ if (!message) {
590
+ return done(this._formatError('Empty message', 'EMESSAGE', false, 'API'));
591
+ }
592
+
593
+ const isDestroyedMessage = this._isDestroyedMessage('send message');
594
+ if (isDestroyedMessage) {
595
+ return done(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
596
+ }
597
+
598
+ // reject larger messages than allowed
599
+ if (this._maxAllowedSize && envelope.size > this._maxAllowedSize) {
600
+ return setImmediate(() => {
601
+ done(this._formatError('Message size larger than allowed ' + this._maxAllowedSize, 'EMESSAGE', false, 'MAIL FROM'));
602
+ });
603
+ }
604
+
605
+ // ensure that callback is only called once
606
+ let returned = false;
607
+ let callback = function () {
608
+ if (returned) {
609
+ return;
610
+ }
611
+ returned = true;
612
+
613
+ done(...arguments);
614
+ };
615
+
616
+ if (typeof message.on === 'function') {
617
+ message.on('error', err => callback(this._formatError(err, 'ESTREAM', false, 'API')));
618
+ }
619
+
620
+ let startTime = Date.now();
621
+ this._setEnvelope(envelope, (err, info) => {
622
+ if (err) {
623
+ return callback(err);
624
+ }
625
+ let envelopeTime = Date.now();
626
+ let stream = this._createSendStream((err, str) => {
627
+ if (err) {
628
+ return callback(err);
629
+ }
630
+
631
+ info.envelopeTime = envelopeTime - startTime;
632
+ info.messageTime = Date.now() - envelopeTime;
633
+ info.messageSize = stream.outByteCount;
634
+ info.response = str;
635
+
636
+ return callback(null, info);
637
+ });
638
+ if (typeof message.pipe === 'function') {
639
+ message.pipe(stream);
640
+ } else {
641
+ stream.write(message);
642
+ stream.end();
643
+ }
644
+ });
645
+ }
646
+
647
+ /**
648
+ * Resets connection state
649
+ *
650
+ * @param {Function} callback Callback to return once connection is reset
651
+ */
652
+ reset(callback) {
653
+ this._sendCommand('RSET');
654
+ this._responseActions.push(str => {
655
+ if (str.charAt(0) !== '2') {
656
+ return callback(this._formatError('Could not reset session state. response=' + str, 'EPROTOCOL', str, 'RSET'));
657
+ }
658
+ this._envelope = false;
659
+ return callback(null, true);
660
+ });
661
+ }
662
+
663
+ /**
664
+ * Connection listener that is run when the connection to
665
+ * the server is opened
666
+ *
667
+ * @event
668
+ */
669
+ _onConnect() {
670
+ clearTimeout(this._connectionTimeout);
671
+
672
+ this.logger.info(
673
+ {
674
+ tnx: 'network',
675
+ localAddress: this._socket.localAddress,
676
+ localPort: this._socket.localPort,
677
+ remoteAddress: this._socket.remoteAddress,
678
+ remotePort: this._socket.remotePort
679
+ },
680
+ '%s established to %s:%s',
681
+ this.secure ? 'Secure connection' : 'Connection',
682
+ this._socket.remoteAddress,
683
+ this._socket.remotePort
684
+ );
685
+
686
+ if (this._destroyed) {
687
+ // Connection was established after we already had canceled it
688
+ this.close();
689
+ return;
690
+ }
691
+
692
+ this.stage = 'connected';
693
+
694
+ // clear existing listeners for the socket
695
+ this._socket.removeListener('data', this._onSocketData);
696
+ this._socket.removeListener('timeout', this._onSocketTimeout);
697
+ this._socket.removeListener('close', this._onSocketClose);
698
+ this._socket.removeListener('end', this._onSocketEnd);
699
+
700
+ this._socket.on('data', this._onSocketData);
701
+ this._socket.once('close', this._onSocketClose);
702
+ this._socket.once('end', this._onSocketEnd);
703
+
704
+ this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT);
705
+ this._socket.on('timeout', this._onSocketTimeout);
706
+
707
+ this._greetingTimeout = setTimeout(() => {
708
+ // if still waiting for greeting, give up
709
+ if (this._socket && !this._destroyed && this._responseActions[0] === this._actionGreeting) {
710
+ this._onError('Greeting never received', 'ETIMEDOUT', false, 'CONN');
711
+ }
712
+ }, this.options.greetingTimeout || GREETING_TIMEOUT);
713
+
714
+ this._responseActions.push(this._actionGreeting);
715
+
716
+ // we have a 'data' listener set up so resume socket if it was paused
717
+ this._socket.resume();
718
+ }
719
+
720
+ /**
721
+ * 'data' listener for data coming from the server
722
+ *
723
+ * @event
724
+ * @param {Buffer} chunk Data chunk coming from the server
725
+ */
726
+ _onData(chunk) {
727
+ if (this._destroyed || !chunk || !chunk.length) {
728
+ return;
729
+ }
730
+
731
+ let data = (chunk || '').toString('binary');
732
+ let lines = (this._remainder + data).split(/\r?\n/);
733
+ let lastline;
734
+
735
+ this._remainder = lines.pop();
736
+
737
+ for (let i = 0, len = lines.length; i < len; i++) {
738
+ if (this._responseQueue.length) {
739
+ lastline = this._responseQueue[this._responseQueue.length - 1];
740
+ if (/^\d+-/.test(lastline.split('\n').pop())) {
741
+ this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
742
+ continue;
743
+ }
744
+ }
745
+ this._responseQueue.push(lines[i]);
746
+ }
747
+
748
+ if (this._responseQueue.length) {
749
+ lastline = this._responseQueue[this._responseQueue.length - 1];
750
+ if (/^\d+-/.test(lastline.split('\n').pop())) {
751
+ return;
752
+ }
753
+ }
754
+
755
+ this._processResponse();
756
+ }
757
+
758
+ /**
759
+ * 'error' listener for the socket
760
+ *
761
+ * @event
762
+ * @param {Error} err Error object
763
+ * @param {String} type Error name
764
+ */
765
+ _onError(err, type, data, command) {
766
+ clearTimeout(this._connectionTimeout);
767
+ clearTimeout(this._greetingTimeout);
768
+
769
+ if (this._destroyed) {
770
+ // just ignore, already closed
771
+ // this might happen when a socket is canceled because of reached timeout
772
+ // but the socket timeout error itself receives only after
773
+ return;
774
+ }
775
+
776
+ err = this._formatError(err, type, data, command);
777
+
778
+ this.logger.error(data, err.message);
779
+
780
+ this.emit('error', err);
781
+ this.close();
782
+ }
783
+
784
+ _formatError(message, type, response, command) {
785
+ let err;
786
+
787
+ if (/Error\]$/i.test(Object.prototype.toString.call(message))) {
788
+ err = message;
789
+ } else {
790
+ err = new Error(message);
791
+ }
792
+
793
+ if (type && type !== 'Error') {
794
+ err.code = type;
795
+ }
796
+
797
+ if (response) {
798
+ err.response = response;
799
+ err.message += ': ' + response;
800
+ }
801
+
802
+ let responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
803
+ if (responseCode) {
804
+ err.responseCode = responseCode;
805
+ }
806
+
807
+ if (command) {
808
+ err.command = command;
809
+ }
810
+
811
+ return err;
812
+ }
813
+
814
+ /**
815
+ * 'close' listener for the socket
816
+ *
817
+ * @event
818
+ */
819
+ _onClose() {
820
+ this.logger.info(
821
+ {
822
+ tnx: 'network'
823
+ },
824
+ 'Connection closed'
825
+ );
826
+
827
+ if (this.upgrading && !this._destroyed) {
828
+ return this._onError(new Error('Connection closed unexpectedly'), 'ETLS', false, 'CONN');
829
+ } else if (![this._actionGreeting, this.close].includes(this._responseActions[0]) && !this._destroyed) {
830
+ return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', false, 'CONN');
831
+ }
832
+
833
+ this._destroy();
834
+ }
835
+
836
+ /**
837
+ * 'end' listener for the socket
838
+ *
839
+ * @event
840
+ */
841
+ _onEnd() {
842
+ if (this._socket && !this._socket.destroyed) {
843
+ this._socket.destroy();
844
+ }
845
+ }
846
+
847
+ /**
848
+ * 'timeout' listener for the socket
849
+ *
850
+ * @event
851
+ */
852
+ _onTimeout() {
853
+ return this._onError(new Error('Timeout'), 'ETIMEDOUT', false, 'CONN');
854
+ }
855
+
856
+ /**
857
+ * Destroys the client, emits 'end'
858
+ */
859
+ _destroy() {
860
+ if (this._destroyed) {
861
+ return;
862
+ }
863
+ this._destroyed = true;
864
+ this.emit('end');
865
+ }
866
+
867
+ /**
868
+ * Upgrades the connection to TLS
869
+ *
870
+ * @param {Function} callback Callback function to run when the connection
871
+ * has been secured
872
+ */
873
+ _upgradeConnection(callback) {
874
+ // do not remove all listeners or it breaks node v0.10 as there's
875
+ // apparently a 'finish' event set that would be cleared as well
876
+
877
+ // we can safely keep 'error', 'end', 'close' etc. events
878
+ this._socket.removeListener('data', this._onSocketData); // incoming data is going to be gibberish from this point onwards
879
+ this._socket.removeListener('timeout', this._onSocketTimeout); // timeout will be re-set for the new socket object
880
+
881
+ let socketPlain = this._socket;
882
+ let opts = {
883
+ socket: this._socket,
884
+ host: this.host
885
+ };
886
+
887
+ Object.keys(this.options.tls || {}).forEach(key => {
888
+ opts[key] = this.options.tls[key];
889
+ });
890
+
891
+ this.upgrading = true;
892
+ // tls.connect is not an asynchronous function however it may still throw errors and requires to be wrapped with try/catch
893
+ try {
894
+ this._socket = tls.connect(opts, () => {
895
+ this.secure = true;
896
+ this.upgrading = false;
897
+ this._socket.on('data', this._onSocketData);
898
+
899
+ socketPlain.removeListener('close', this._onSocketClose);
900
+ socketPlain.removeListener('end', this._onSocketEnd);
901
+
902
+ return callback(null, true);
903
+ });
904
+ } catch (err) {
905
+ return callback(err);
906
+ }
907
+
908
+ this._socket.on('error', this._onSocketError);
909
+ this._socket.once('close', this._onSocketClose);
910
+ this._socket.once('end', this._onSocketEnd);
911
+
912
+ this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT); // 10 min.
913
+ this._socket.on('timeout', this._onSocketTimeout);
914
+
915
+ // resume in case the socket was paused
916
+ socketPlain.resume();
917
+ }
918
+
919
+ /**
920
+ * Processes queued responses from the server
921
+ *
922
+ * @param {Boolean} force If true, ignores _processing flag
923
+ */
924
+ _processResponse() {
925
+ if (!this._responseQueue.length) {
926
+ return false;
927
+ }
928
+
929
+ let str = (this.lastServerResponse = (this._responseQueue.shift() || '').toString());
930
+
931
+ if (/^\d+-/.test(str.split('\n').pop())) {
932
+ // keep waiting for the final part of multiline response
933
+ return;
934
+ }
935
+
936
+ if (this.options.debug || this.options.transactionLog) {
937
+ this.logger.debug(
938
+ {
939
+ tnx: 'server'
940
+ },
941
+ str.replace(/\r?\n$/, '')
942
+ );
943
+ }
944
+
945
+ if (!str.trim()) {
946
+ // skip unexpected empty lines
947
+ setImmediate(() => this._processResponse(true));
948
+ }
949
+
950
+ let action = this._responseActions.shift();
951
+
952
+ if (typeof action === 'function') {
953
+ action.call(this, str);
954
+ setImmediate(() => this._processResponse(true));
955
+ } else {
956
+ return this._onError(new Error('Unexpected Response'), 'EPROTOCOL', str, 'CONN');
957
+ }
958
+ }
959
+
960
+ /**
961
+ * Send a command to the server, append \r\n
962
+ *
963
+ * @param {String} str String to be sent to the server
964
+ * @param {String} logStr Optional string to be used for logging instead of the actual string
965
+ */
966
+ _sendCommand(str, logStr) {
967
+ if (this._destroyed) {
968
+ // Connection already closed, can't send any more data
969
+ return;
970
+ }
971
+
972
+ if (this._socket.destroyed) {
973
+ return this.close();
974
+ }
975
+
976
+ if (this.options.debug || this.options.transactionLog) {
977
+ this.logger.debug(
978
+ {
979
+ tnx: 'client'
980
+ },
981
+ (logStr || str || '').toString().replace(/\r?\n$/, '')
982
+ );
983
+ }
984
+
985
+ this._socket.write(Buffer.from(str + '\r\n', 'utf-8'));
986
+ }
987
+
988
+ /**
989
+ * Initiates a new message by submitting envelope data, starting with
990
+ * MAIL FROM: command
991
+ *
992
+ * @param {Object} envelope Envelope object in the form of
993
+ * {from:'...', to:['...']}
994
+ * or
995
+ * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
996
+ */
997
+ _setEnvelope(envelope, callback) {
998
+ let args = [];
999
+ let useSmtpUtf8 = false;
1000
+
1001
+ this._envelope = envelope || {};
1002
+ this._envelope.from = ((this._envelope.from && this._envelope.from.address) || this._envelope.from || '').toString().trim();
1003
+
1004
+ this._envelope.to = [].concat(this._envelope.to || []).map(to => ((to && to.address) || to || '').toString().trim());
1005
+
1006
+ if (!this._envelope.to.length) {
1007
+ return callback(this._formatError('No recipients defined', 'EENVELOPE', false, 'API'));
1008
+ }
1009
+
1010
+ if (this._envelope.from && /[\r\n<>]/.test(this._envelope.from)) {
1011
+ return callback(this._formatError('Invalid sender ' + JSON.stringify(this._envelope.from), 'EENVELOPE', false, 'API'));
1012
+ }
1013
+
1014
+ // check if the sender address uses only ASCII characters,
1015
+ // otherwise require usage of SMTPUTF8 extension
1016
+ if (/[\x80-\uFFFF]/.test(this._envelope.from)) {
1017
+ useSmtpUtf8 = true;
1018
+ }
1019
+
1020
+ for (let i = 0, len = this._envelope.to.length; i < len; i++) {
1021
+ if (!this._envelope.to[i] || /[\r\n<>]/.test(this._envelope.to[i])) {
1022
+ return callback(this._formatError('Invalid recipient ' + JSON.stringify(this._envelope.to[i]), 'EENVELOPE', false, 'API'));
1023
+ }
1024
+
1025
+ // check if the recipients addresses use only ASCII characters,
1026
+ // otherwise require usage of SMTPUTF8 extension
1027
+ if (/[\x80-\uFFFF]/.test(this._envelope.to[i])) {
1028
+ useSmtpUtf8 = true;
1029
+ }
1030
+ }
1031
+
1032
+ // clone the recipients array for latter manipulation
1033
+ this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
1034
+ this._envelope.rejected = [];
1035
+ this._envelope.rejectedErrors = [];
1036
+ this._envelope.accepted = [];
1037
+
1038
+ if (this._envelope.dsn) {
1039
+ try {
1040
+ this._envelope.dsn = this._setDsnEnvelope(this._envelope.dsn);
1041
+ } catch (err) {
1042
+ return callback(this._formatError('Invalid DSN ' + err.message, 'EENVELOPE', false, 'API'));
1043
+ }
1044
+ }
1045
+
1046
+ this._responseActions.push(str => {
1047
+ this._actionMAIL(str, callback);
1048
+ });
1049
+
1050
+ // If the server supports SMTPUTF8 and the envelope includes an internationalized
1051
+ // email address then append SMTPUTF8 keyword to the MAIL FROM command
1052
+ if (useSmtpUtf8 && this._supportedExtensions.includes('SMTPUTF8')) {
1053
+ args.push('SMTPUTF8');
1054
+ this._usingSmtpUtf8 = true;
1055
+ }
1056
+
1057
+ // If the server supports 8BITMIME and the message might contain non-ascii bytes
1058
+ // then append the 8BITMIME keyword to the MAIL FROM command
1059
+ if (this._envelope.use8BitMime && this._supportedExtensions.includes('8BITMIME')) {
1060
+ args.push('BODY=8BITMIME');
1061
+ this._using8BitMime = true;
1062
+ }
1063
+
1064
+ if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
1065
+ args.push('SIZE=' + this._envelope.size);
1066
+ }
1067
+
1068
+ // If the server supports DSN and the envelope includes an DSN prop
1069
+ // then append DSN params to the MAIL FROM command
1070
+ if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
1071
+ if (this._envelope.dsn.ret) {
1072
+ args.push('RET=' + shared.encodeXText(this._envelope.dsn.ret));
1073
+ }
1074
+ if (this._envelope.dsn.envid) {
1075
+ args.push('ENVID=' + shared.encodeXText(this._envelope.dsn.envid));
1076
+ }
1077
+ }
1078
+
1079
+ this._sendCommand('MAIL FROM:<' + this._envelope.from + '>' + (args.length ? ' ' + args.join(' ') : ''));
1080
+ }
1081
+
1082
+ _setDsnEnvelope(params) {
1083
+ let ret = (params.ret || params.return || '').toString().toUpperCase() || null;
1084
+ if (ret) {
1085
+ switch (ret) {
1086
+ case 'HDRS':
1087
+ case 'HEADERS':
1088
+ ret = 'HDRS';
1089
+ break;
1090
+ case 'FULL':
1091
+ case 'BODY':
1092
+ ret = 'FULL';
1093
+ break;
1094
+ }
1095
+ }
1096
+
1097
+ if (ret && !['FULL', 'HDRS'].includes(ret)) {
1098
+ throw new Error('ret: ' + JSON.stringify(ret));
1099
+ }
1100
+
1101
+ let envid = (params.envid || params.id || '').toString() || null;
1102
+
1103
+ let notify = params.notify || null;
1104
+ if (notify) {
1105
+ if (typeof notify === 'string') {
1106
+ notify = notify.split(',');
1107
+ }
1108
+ notify = notify.map(n => n.trim().toUpperCase());
1109
+ let validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
1110
+ let invaliNotify = notify.filter(n => !validNotify.includes(n));
1111
+ if (invaliNotify.length || (notify.length > 1 && notify.includes('NEVER'))) {
1112
+ throw new Error('notify: ' + JSON.stringify(notify.join(',')));
1113
+ }
1114
+ notify = notify.join(',');
1115
+ }
1116
+
1117
+ let orcpt = (params.recipient || params.orcpt || '').toString() || null;
1118
+ if (orcpt && orcpt.indexOf(';') < 0) {
1119
+ orcpt = 'rfc822;' + orcpt;
1120
+ }
1121
+
1122
+ return {
1123
+ ret,
1124
+ envid,
1125
+ notify,
1126
+ orcpt
1127
+ };
1128
+ }
1129
+
1130
+ _getDsnRcptToArgs() {
1131
+ let args = [];
1132
+ // If the server supports DSN and the envelope includes an DSN prop
1133
+ // then append DSN params to the RCPT TO command
1134
+ if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
1135
+ if (this._envelope.dsn.notify) {
1136
+ args.push('NOTIFY=' + shared.encodeXText(this._envelope.dsn.notify));
1137
+ }
1138
+ if (this._envelope.dsn.orcpt) {
1139
+ args.push('ORCPT=' + shared.encodeXText(this._envelope.dsn.orcpt));
1140
+ }
1141
+ }
1142
+ return args.length ? ' ' + args.join(' ') : '';
1143
+ }
1144
+
1145
+ _createSendStream(callback) {
1146
+ let dataStream = new DataStream();
1147
+ let logStream;
1148
+
1149
+ if (this.options.lmtp) {
1150
+ this._envelope.accepted.forEach((recipient, i) => {
1151
+ let final = i === this._envelope.accepted.length - 1;
1152
+ this._responseActions.push(str => {
1153
+ this._actionLMTPStream(recipient, final, str, callback);
1154
+ });
1155
+ });
1156
+ } else {
1157
+ this._responseActions.push(str => {
1158
+ this._actionSMTPStream(str, callback);
1159
+ });
1160
+ }
1161
+
1162
+ dataStream.pipe(this._socket, {
1163
+ end: false
1164
+ });
1165
+
1166
+ if (this.options.debug) {
1167
+ logStream = new PassThrough();
1168
+ logStream.on('readable', () => {
1169
+ let chunk;
1170
+ while ((chunk = logStream.read())) {
1171
+ this.logger.debug(
1172
+ {
1173
+ tnx: 'message'
1174
+ },
1175
+ chunk.toString('binary').replace(/\r?\n$/, '')
1176
+ );
1177
+ }
1178
+ });
1179
+ dataStream.pipe(logStream);
1180
+ }
1181
+
1182
+ dataStream.once('end', () => {
1183
+ this.logger.info(
1184
+ {
1185
+ tnx: 'message',
1186
+ inByteCount: dataStream.inByteCount,
1187
+ outByteCount: dataStream.outByteCount
1188
+ },
1189
+ '<%s bytes encoded mime message (source size %s bytes)>',
1190
+ dataStream.outByteCount,
1191
+ dataStream.inByteCount
1192
+ );
1193
+ });
1194
+
1195
+ return dataStream;
1196
+ }
1197
+
1198
+ /** ACTIONS **/
1199
+
1200
+ /**
1201
+ * Will be run after the connection is created and the server sends
1202
+ * a greeting. If the incoming message starts with 220 initiate
1203
+ * SMTP session by sending EHLO command
1204
+ *
1205
+ * @param {String} str Message from the server
1206
+ */
1207
+ _actionGreeting(str) {
1208
+ clearTimeout(this._greetingTimeout);
1209
+
1210
+ if (str.substr(0, 3) !== '220') {
1211
+ this._onError(new Error('Invalid greeting. response=' + str), 'EPROTOCOL', str, 'CONN');
1212
+ return;
1213
+ }
1214
+
1215
+ if (this.options.lmtp) {
1216
+ this._responseActions.push(this._actionLHLO);
1217
+ this._sendCommand('LHLO ' + this.name);
1218
+ } else {
1219
+ this._responseActions.push(this._actionEHLO);
1220
+ this._sendCommand('EHLO ' + this.name);
1221
+ }
1222
+ }
1223
+
1224
+ /**
1225
+ * Handles server response for LHLO command. If it yielded in
1226
+ * error, emit 'error', otherwise treat this as an EHLO response
1227
+ *
1228
+ * @param {String} str Message from the server
1229
+ */
1230
+ _actionLHLO(str) {
1231
+ if (str.charAt(0) !== '2') {
1232
+ this._onError(new Error('Invalid LHLO. response=' + str), 'EPROTOCOL', str, 'LHLO');
1233
+ return;
1234
+ }
1235
+
1236
+ this._actionEHLO(str);
1237
+ }
1238
+
1239
+ /**
1240
+ * Handles server response for EHLO command. If it yielded in
1241
+ * error, try HELO instead, otherwise initiate TLS negotiation
1242
+ * if STARTTLS is supported by the server or move into the
1243
+ * authentication phase.
1244
+ *
1245
+ * @param {String} str Message from the server
1246
+ */
1247
+ _actionEHLO(str) {
1248
+ let match;
1249
+
1250
+ if (str.substr(0, 3) === '421') {
1251
+ this._onError(new Error('Server terminates connection. response=' + str), 'ECONNECTION', str, 'EHLO');
1252
+ return;
1253
+ }
1254
+
1255
+ if (str.charAt(0) !== '2') {
1256
+ if (this.options.requireTLS) {
1257
+ this._onError(new Error('EHLO failed but HELO does not support required STARTTLS. response=' + str), 'ECONNECTION', str, 'EHLO');
1258
+ return;
1259
+ }
1260
+
1261
+ // Try HELO instead
1262
+ this._responseActions.push(this._actionHELO);
1263
+ this._sendCommand('HELO ' + this.name);
1264
+ return;
1265
+ }
1266
+
1267
+ // Detect if the server supports STARTTLS
1268
+ if (!this.secure && !this.options.ignoreTLS && (/[ -]STARTTLS\b/im.test(str) || this.options.requireTLS)) {
1269
+ this._sendCommand('STARTTLS');
1270
+ this._responseActions.push(this._actionSTARTTLS);
1271
+ return;
1272
+ }
1273
+
1274
+ // Detect if the server supports SMTPUTF8
1275
+ if (/[ -]SMTPUTF8\b/im.test(str)) {
1276
+ this._supportedExtensions.push('SMTPUTF8');
1277
+ }
1278
+
1279
+ // Detect if the server supports DSN
1280
+ if (/[ -]DSN\b/im.test(str)) {
1281
+ this._supportedExtensions.push('DSN');
1282
+ }
1283
+
1284
+ // Detect if the server supports 8BITMIME
1285
+ if (/[ -]8BITMIME\b/im.test(str)) {
1286
+ this._supportedExtensions.push('8BITMIME');
1287
+ }
1288
+
1289
+ // Detect if the server supports PIPELINING
1290
+ if (/[ -]PIPELINING\b/im.test(str)) {
1291
+ this._supportedExtensions.push('PIPELINING');
1292
+ }
1293
+
1294
+ // Detect if the server supports AUTH
1295
+ if (/[ -]AUTH\b/i.test(str)) {
1296
+ this.allowsAuth = true;
1297
+ }
1298
+
1299
+ // Detect if the server supports PLAIN auth
1300
+ if (/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i.test(str)) {
1301
+ this._supportedAuth.push('PLAIN');
1302
+ }
1303
+
1304
+ // Detect if the server supports LOGIN auth
1305
+ if (/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i.test(str)) {
1306
+ this._supportedAuth.push('LOGIN');
1307
+ }
1308
+
1309
+ // Detect if the server supports CRAM-MD5 auth
1310
+ if (/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i.test(str)) {
1311
+ this._supportedAuth.push('CRAM-MD5');
1312
+ }
1313
+
1314
+ // Detect if the server supports XOAUTH2 auth
1315
+ if (/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i.test(str)) {
1316
+ this._supportedAuth.push('XOAUTH2');
1317
+ }
1318
+
1319
+ // Detect if the server supports SIZE extensions (and the max allowed size)
1320
+ if ((match = str.match(/[ -]SIZE(?:[ \t]+(\d+))?/im))) {
1321
+ this._supportedExtensions.push('SIZE');
1322
+ this._maxAllowedSize = Number(match[1]) || 0;
1323
+ }
1324
+
1325
+ this.emit('connect');
1326
+ }
1327
+
1328
+ /**
1329
+ * Handles server response for HELO command. If it yielded in
1330
+ * error, emit 'error', otherwise move into the authentication phase.
1331
+ *
1332
+ * @param {String} str Message from the server
1333
+ */
1334
+ _actionHELO(str) {
1335
+ if (str.charAt(0) !== '2') {
1336
+ this._onError(new Error('Invalid HELO. response=' + str), 'EPROTOCOL', str, 'HELO');
1337
+ return;
1338
+ }
1339
+
1340
+ // assume that authentication is enabled (most probably is not though)
1341
+ this.allowsAuth = true;
1342
+
1343
+ this.emit('connect');
1344
+ }
1345
+
1346
+ /**
1347
+ * Handles server response for STARTTLS command. If there's an error
1348
+ * try HELO instead, otherwise initiate TLS upgrade. If the upgrade
1349
+ * succeedes restart the EHLO
1350
+ *
1351
+ * @param {String} str Message from the server
1352
+ */
1353
+ _actionSTARTTLS(str) {
1354
+ if (str.charAt(0) !== '2') {
1355
+ if (this.options.opportunisticTLS) {
1356
+ this.logger.info(
1357
+ {
1358
+ tnx: 'smtp'
1359
+ },
1360
+ 'Failed STARTTLS upgrade, continuing unencrypted'
1361
+ );
1362
+ return this.emit('connect');
1363
+ }
1364
+ this._onError(new Error('Error upgrading connection with STARTTLS'), 'ETLS', str, 'STARTTLS');
1365
+ return;
1366
+ }
1367
+
1368
+ this._upgradeConnection((err, secured) => {
1369
+ if (err) {
1370
+ this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'STARTTLS');
1371
+ return;
1372
+ }
1373
+
1374
+ this.logger.info(
1375
+ {
1376
+ tnx: 'smtp'
1377
+ },
1378
+ 'Connection upgraded with STARTTLS'
1379
+ );
1380
+
1381
+ if (secured) {
1382
+ // restart session
1383
+ if (this.options.lmtp) {
1384
+ this._responseActions.push(this._actionLHLO);
1385
+ this._sendCommand('LHLO ' + this.name);
1386
+ } else {
1387
+ this._responseActions.push(this._actionEHLO);
1388
+ this._sendCommand('EHLO ' + this.name);
1389
+ }
1390
+ } else {
1391
+ this.emit('connect');
1392
+ }
1393
+ });
1394
+ }
1395
+
1396
+ /**
1397
+ * Handle the response for AUTH LOGIN command. We are expecting
1398
+ * '334 VXNlcm5hbWU6' (base64 for 'Username:'). Data to be sent as
1399
+ * response needs to be base64 encoded username. We do not need
1400
+ * exact match but settle with 334 response in general as some
1401
+ * hosts invalidly use a longer message than VXNlcm5hbWU6
1402
+ *
1403
+ * @param {String} str Message from the server
1404
+ */
1405
+ _actionAUTH_LOGIN_USER(str, callback) {
1406
+ if (!/^334[ -]/.test(str)) {
1407
+ // expecting '334 VXNlcm5hbWU6'
1408
+ callback(this._formatError('Invalid login sequence while waiting for "334 VXNlcm5hbWU6"', 'EAUTH', str, 'AUTH LOGIN'));
1409
+ return;
1410
+ }
1411
+
1412
+ this._responseActions.push(str => {
1413
+ this._actionAUTH_LOGIN_PASS(str, callback);
1414
+ });
1415
+
1416
+ this._sendCommand(Buffer.from(this._auth.credentials.user + '', 'utf-8').toString('base64'));
1417
+ }
1418
+
1419
+ /**
1420
+ * Handle the response for AUTH CRAM-MD5 command. We are expecting
1421
+ * '334 <challenge string>'. Data to be sent as response needs to be
1422
+ * base64 decoded challenge string, MD5 hashed using the password as
1423
+ * a HMAC key, prefixed by the username and a space, and finally all
1424
+ * base64 encoded again.
1425
+ *
1426
+ * @param {String} str Message from the server
1427
+ */
1428
+ _actionAUTH_CRAM_MD5(str, callback) {
1429
+ let challengeMatch = str.match(/^334\s+(.+)$/);
1430
+ let challengeString = '';
1431
+
1432
+ if (!challengeMatch) {
1433
+ return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5'));
1434
+ } else {
1435
+ challengeString = challengeMatch[1];
1436
+ }
1437
+
1438
+ // Decode from base64
1439
+ let base64decoded = Buffer.from(challengeString, 'base64').toString('ascii'),
1440
+ hmacMD5 = crypto.createHmac('md5', this._auth.credentials.pass);
1441
+
1442
+ hmacMD5.update(base64decoded);
1443
+
1444
+ let prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
1445
+
1446
+ this._responseActions.push(str => {
1447
+ this._actionAUTH_CRAM_MD5_PASS(str, callback);
1448
+ });
1449
+
1450
+ this._sendCommand(
1451
+ Buffer.from(prepended).toString('base64'),
1452
+ // hidden hash for logs
1453
+ Buffer.from(this._auth.credentials.user + ' /* secret */').toString('base64')
1454
+ );
1455
+ }
1456
+
1457
+ /**
1458
+ * Handles the response to CRAM-MD5 authentication, if there's no error,
1459
+ * the user can be considered logged in. Start waiting for a message to send
1460
+ *
1461
+ * @param {String} str Message from the server
1462
+ */
1463
+ _actionAUTH_CRAM_MD5_PASS(str, callback) {
1464
+ if (!str.match(/^235\s+/)) {
1465
+ return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH CRAM-MD5'));
1466
+ }
1467
+
1468
+ this.logger.info(
1469
+ {
1470
+ tnx: 'smtp',
1471
+ username: this._auth.user,
1472
+ action: 'authenticated',
1473
+ method: this._authMethod
1474
+ },
1475
+ 'User %s authenticated',
1476
+ JSON.stringify(this._auth.user)
1477
+ );
1478
+ this.authenticated = true;
1479
+ callback(null, true);
1480
+ }
1481
+
1482
+ /**
1483
+ * Handle the response for AUTH LOGIN command. We are expecting
1484
+ * '334 UGFzc3dvcmQ6' (base64 for 'Password:'). Data to be sent as
1485
+ * response needs to be base64 encoded password.
1486
+ *
1487
+ * @param {String} str Message from the server
1488
+ */
1489
+ _actionAUTH_LOGIN_PASS(str, callback) {
1490
+ if (!/^334[ -]/.test(str)) {
1491
+ // expecting '334 UGFzc3dvcmQ6'
1492
+ return callback(this._formatError('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6"', 'EAUTH', str, 'AUTH LOGIN'));
1493
+ }
1494
+
1495
+ this._responseActions.push(str => {
1496
+ this._actionAUTHComplete(str, callback);
1497
+ });
1498
+
1499
+ this._sendCommand(
1500
+ Buffer.from((this._auth.credentials.pass || '').toString(), 'utf-8').toString('base64'),
1501
+ // Hidden pass for logs
1502
+ Buffer.from('/* secret */', 'utf-8').toString('base64')
1503
+ );
1504
+ }
1505
+
1506
+ /**
1507
+ * Handles the response for authentication, if there's no error,
1508
+ * the user can be considered logged in. Start waiting for a message to send
1509
+ *
1510
+ * @param {String} str Message from the server
1511
+ */
1512
+ _actionAUTHComplete(str, isRetry, callback) {
1513
+ if (!callback && typeof isRetry === 'function') {
1514
+ callback = isRetry;
1515
+ isRetry = false;
1516
+ }
1517
+
1518
+ if (str.substr(0, 3) === '334') {
1519
+ this._responseActions.push(str => {
1520
+ if (isRetry || this._authMethod !== 'XOAUTH2') {
1521
+ this._actionAUTHComplete(str, true, callback);
1522
+ } else {
1523
+ // fetch a new OAuth2 access token
1524
+ setImmediate(() => this._handleXOauth2Token(true, callback));
1525
+ }
1526
+ });
1527
+ this._sendCommand('');
1528
+ return;
1529
+ }
1530
+
1531
+ if (str.charAt(0) !== '2') {
1532
+ this.logger.info(
1533
+ {
1534
+ tnx: 'smtp',
1535
+ username: this._auth.user,
1536
+ action: 'authfail',
1537
+ method: this._authMethod
1538
+ },
1539
+ 'User %s failed to authenticate',
1540
+ JSON.stringify(this._auth.user)
1541
+ );
1542
+ return callback(this._formatError('Invalid login', 'EAUTH', str, 'AUTH ' + this._authMethod));
1543
+ }
1544
+
1545
+ this.logger.info(
1546
+ {
1547
+ tnx: 'smtp',
1548
+ username: this._auth.user,
1549
+ action: 'authenticated',
1550
+ method: this._authMethod
1551
+ },
1552
+ 'User %s authenticated',
1553
+ JSON.stringify(this._auth.user)
1554
+ );
1555
+ this.authenticated = true;
1556
+ callback(null, true);
1557
+ }
1558
+
1559
+ /**
1560
+ * Handle response for a MAIL FROM: command
1561
+ *
1562
+ * @param {String} str Message from the server
1563
+ */
1564
+ _actionMAIL(str, callback) {
1565
+ let message, curRecipient;
1566
+ if (Number(str.charAt(0)) !== 2) {
1567
+ if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
1568
+ message = 'Internationalized mailbox name not allowed';
1569
+ } else {
1570
+ message = 'Mail command failed';
1571
+ }
1572
+ return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
1573
+ }
1574
+
1575
+ if (!this._envelope.rcptQueue.length) {
1576
+ return callback(this._formatError('Can\x27t send mail - no recipients defined', 'EENVELOPE', false, 'API'));
1577
+ } else {
1578
+ this._recipientQueue = [];
1579
+
1580
+ if (this._supportedExtensions.includes('PIPELINING')) {
1581
+ while (this._envelope.rcptQueue.length) {
1582
+ curRecipient = this._envelope.rcptQueue.shift();
1583
+ this._recipientQueue.push(curRecipient);
1584
+ this._responseActions.push(str => {
1585
+ this._actionRCPT(str, callback);
1586
+ });
1587
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1588
+ }
1589
+ } else {
1590
+ curRecipient = this._envelope.rcptQueue.shift();
1591
+ this._recipientQueue.push(curRecipient);
1592
+ this._responseActions.push(str => {
1593
+ this._actionRCPT(str, callback);
1594
+ });
1595
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1596
+ }
1597
+ }
1598
+ }
1599
+
1600
+ /**
1601
+ * Handle response for a RCPT TO: command
1602
+ *
1603
+ * @param {String} str Message from the server
1604
+ */
1605
+ _actionRCPT(str, callback) {
1606
+ let message,
1607
+ err,
1608
+ curRecipient = this._recipientQueue.shift();
1609
+ if (Number(str.charAt(0)) !== 2) {
1610
+ // this is a soft error
1611
+ if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)) {
1612
+ message = 'Internationalized mailbox name not allowed';
1613
+ } else {
1614
+ message = 'Recipient command failed';
1615
+ }
1616
+ this._envelope.rejected.push(curRecipient);
1617
+ // store error for the failed recipient
1618
+ err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
1619
+ err.recipient = curRecipient;
1620
+ this._envelope.rejectedErrors.push(err);
1621
+ } else {
1622
+ this._envelope.accepted.push(curRecipient);
1623
+ }
1624
+
1625
+ if (!this._envelope.rcptQueue.length && !this._recipientQueue.length) {
1626
+ if (this._envelope.rejected.length < this._envelope.to.length) {
1627
+ this._responseActions.push(str => {
1628
+ this._actionDATA(str, callback);
1629
+ });
1630
+ this._sendCommand('DATA');
1631
+ } else {
1632
+ err = this._formatError('Can\x27t send mail - all recipients were rejected', 'EENVELOPE', str, 'RCPT TO');
1633
+ err.rejected = this._envelope.rejected;
1634
+ err.rejectedErrors = this._envelope.rejectedErrors;
1635
+ return callback(err);
1636
+ }
1637
+ } else if (this._envelope.rcptQueue.length) {
1638
+ curRecipient = this._envelope.rcptQueue.shift();
1639
+ this._recipientQueue.push(curRecipient);
1640
+ this._responseActions.push(str => {
1641
+ this._actionRCPT(str, callback);
1642
+ });
1643
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1644
+ }
1645
+ }
1646
+
1647
+ /**
1648
+ * Handle response for a DATA command
1649
+ *
1650
+ * @param {String} str Message from the server
1651
+ */
1652
+ _actionDATA(str, callback) {
1653
+ // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
1654
+ // some servers might use 250 instead, so lets check for 2 or 3 as the first digit
1655
+ if (!/^[23]/.test(str)) {
1656
+ return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
1657
+ }
1658
+
1659
+ let response = {
1660
+ accepted: this._envelope.accepted,
1661
+ rejected: this._envelope.rejected
1662
+ };
1663
+
1664
+ if (this._envelope.rejectedErrors.length) {
1665
+ response.rejectedErrors = this._envelope.rejectedErrors;
1666
+ }
1667
+
1668
+ callback(null, response);
1669
+ }
1670
+
1671
+ /**
1672
+ * Handle response for a DATA stream when using SMTP
1673
+ * We expect a single response that defines if the sending succeeded or failed
1674
+ *
1675
+ * @param {String} str Message from the server
1676
+ */
1677
+ _actionSMTPStream(str, callback) {
1678
+ if (Number(str.charAt(0)) !== 2) {
1679
+ // Message failed
1680
+ return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
1681
+ } else {
1682
+ // Message sent succesfully
1683
+ return callback(null, str);
1684
+ }
1685
+ }
1686
+
1687
+ /**
1688
+ * Handle response for a DATA stream
1689
+ * We expect a separate response for every recipient. All recipients can either
1690
+ * succeed or fail separately
1691
+ *
1692
+ * @param {String} recipient The recipient this response applies to
1693
+ * @param {Boolean} final Is this the final recipient?
1694
+ * @param {String} str Message from the server
1695
+ */
1696
+ _actionLMTPStream(recipient, final, str, callback) {
1697
+ let err;
1698
+ if (Number(str.charAt(0)) !== 2) {
1699
+ // Message failed
1700
+ err = this._formatError('Message failed for recipient ' + recipient, 'EMESSAGE', str, 'DATA');
1701
+ err.recipient = recipient;
1702
+ this._envelope.rejected.push(recipient);
1703
+ this._envelope.rejectedErrors.push(err);
1704
+ for (let i = 0, len = this._envelope.accepted.length; i < len; i++) {
1705
+ if (this._envelope.accepted[i] === recipient) {
1706
+ this._envelope.accepted.splice(i, 1);
1707
+ }
1708
+ }
1709
+ }
1710
+ if (final) {
1711
+ return callback(null, str);
1712
+ }
1713
+ }
1714
+
1715
+ _handleXOauth2Token(isRetry, callback) {
1716
+ this._auth.oauth2.getToken(isRetry, (err, accessToken) => {
1717
+ if (err) {
1718
+ this.logger.info(
1719
+ {
1720
+ tnx: 'smtp',
1721
+ username: this._auth.user,
1722
+ action: 'authfail',
1723
+ method: this._authMethod
1724
+ },
1725
+ 'User %s failed to authenticate',
1726
+ JSON.stringify(this._auth.user)
1727
+ );
1728
+ return callback(this._formatError(err, 'EAUTH', false, 'AUTH XOAUTH2'));
1729
+ }
1730
+ this._responseActions.push(str => {
1731
+ this._actionAUTHComplete(str, isRetry, callback);
1732
+ });
1733
+ this._sendCommand(
1734
+ 'AUTH XOAUTH2 ' + this._auth.oauth2.buildXOAuth2Token(accessToken),
1735
+ // Hidden for logs
1736
+ 'AUTH XOAUTH2 ' + this._auth.oauth2.buildXOAuth2Token('/* secret */')
1737
+ );
1738
+ });
1739
+ }
1740
+
1741
+ /**
1742
+ *
1743
+ * @param {string} command
1744
+ * @private
1745
+ */
1746
+ _isDestroyedMessage(command) {
1747
+ if (this._destroyed) {
1748
+ return 'Cannot ' + command + ' - smtp connection is already destroyed.';
1749
+ }
1750
+
1751
+ if (this._socket) {
1752
+ if (this._socket.destroyed) {
1753
+ return 'Cannot ' + command + ' - smtp connection socket is already destroyed.';
1754
+ }
1755
+
1756
+ if (!this._socket.writable) {
1757
+ return 'Cannot ' + command + ' - smtp connection socket is already half-closed.';
1758
+ }
1759
+ }
1760
+ }
1761
+
1762
+ _getHostname() {
1763
+ // defaul hostname is machine hostname or [IP]
1764
+ let defaultHostname;
1765
+ try {
1766
+ defaultHostname = os.hostname() || '';
1767
+ } catch (err) {
1768
+ // fails on windows 7
1769
+ defaultHostname = 'localhost';
1770
+ }
1771
+
1772
+ // ignore if not FQDN
1773
+ if (!defaultHostname || defaultHostname.indexOf('.') < 0) {
1774
+ defaultHostname = '[127.0.0.1]';
1775
+ }
1776
+
1777
+ // IP should be enclosed in []
1778
+ if (defaultHostname.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
1779
+ defaultHostname = '[' + defaultHostname + ']';
1780
+ }
1781
+
1782
+ return defaultHostname;
1783
+ }
1784
+ }
1785
+
1786
+ module.exports = SMTPConnection;