@bobfrankston/mailx 1.0.9 → 1.0.13

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