@nocobase/plugin-workflow-mailer 2.3.0-beta.13 → 2.3.0-beta.14

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