haraka 0.0.33 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/.githooks/pre-commit +41 -0
  3. package/.prettierignore +6 -0
  4. package/.qlty/.gitignore +7 -0
  5. package/.qlty/configs/.shellcheckrc +1 -0
  6. package/.qlty/qlty.toml +15 -0
  7. package/CHANGELOG.md +1894 -0
  8. package/CLAUDE.md +40 -0
  9. package/CONTRIBUTORS.md +34 -0
  10. package/Dockerfile +50 -0
  11. package/GEMINI.md +38 -0
  12. package/LICENSE +22 -0
  13. package/Plugins.md +227 -0
  14. package/README.md +119 -4
  15. package/SECURITY.md +178 -0
  16. package/TODO +22 -0
  17. package/address.js +53 -0
  18. package/bin/haraka +593 -0
  19. package/bin/haraka_grep +32 -0
  20. package/config/aliases +2 -0
  21. package/config/auth_flat_file.ini +7 -0
  22. package/config/auth_vpopmaild.ini +9 -0
  23. package/config/connection.ini +79 -0
  24. package/config/delay_deny.ini +7 -0
  25. package/config/dhparams.pem +8 -0
  26. package/config/host_list +3 -0
  27. package/config/host_list_regex +6 -0
  28. package/config/http.ini +11 -0
  29. package/config/lmtp.ini +7 -0
  30. package/config/log.ini +11 -0
  31. package/config/me +1 -0
  32. package/config/outbound.bounce_message +18 -0
  33. package/config/outbound.bounce_message_html +36 -0
  34. package/config/outbound.bounce_message_image +106 -0
  35. package/config/outbound.ini +24 -0
  36. package/config/plugins +67 -0
  37. package/config/smtp.ini +37 -0
  38. package/config/smtp_bridge.ini +4 -0
  39. package/config/smtp_forward.ini +31 -0
  40. package/config/smtp_proxy.ini +27 -0
  41. package/config/tarpit.timeout +1 -0
  42. package/config/tls.ini +83 -0
  43. package/config/tls_cert.pem +23 -0
  44. package/config/tls_key.pem +28 -0
  45. package/config/watch.ini +12 -0
  46. package/config/xclient.hosts +2 -0
  47. package/connection.js +1863 -0
  48. package/contrib/Haraka.cf +6 -0
  49. package/contrib/Haraka.pm +35 -0
  50. package/contrib/bad_smtp_server.pl +25 -0
  51. package/contrib/bsd-rc.d/haraka +61 -0
  52. package/contrib/debian-init.d/haraka +87 -0
  53. package/contrib/haraka.init +96 -0
  54. package/contrib/haraka.service +23 -0
  55. package/contrib/plugin2npm.sh +81 -0
  56. package/contrib/ubuntu-upstart/haraka.conf +27 -0
  57. package/coverage/coverage-final.json +2 -0
  58. package/coverage/coverage-summary.json +33 -0
  59. package/coverage/tmp/coverage-79131-1779241025146-0.json +1 -0
  60. package/coverage/tmp/coverage-79132-1779240999690-0.json +1 -0
  61. package/coverage/tmp/coverage-79172-1779241000095-0.json +1 -0
  62. package/coverage/tmp/coverage-79210-1779241000156-0.json +1 -0
  63. package/coverage/tmp/coverage-79211-1779241000209-0.json +1 -0
  64. package/coverage/tmp/coverage-79212-1779241000266-0.json +1 -0
  65. package/coverage/tmp/coverage-79213-1779241000441-0.json +1 -0
  66. package/coverage/tmp/coverage-79214-1779241000626-0.json +1 -0
  67. package/coverage/tmp/coverage-79215-1779241000795-0.json +1 -0
  68. package/coverage/tmp/coverage-79216-1779241000965-0.json +1 -0
  69. package/coverage/tmp/coverage-79218-1779241001013-0.json +1 -0
  70. package/coverage/tmp/coverage-79219-1779241001179-0.json +1 -0
  71. package/coverage/tmp/coverage-79220-1779241006249-0.json +1 -0
  72. package/coverage/tmp/coverage-79227-1779241011453-0.json +1 -0
  73. package/coverage/tmp/coverage-79229-1779241011537-0.json +1 -0
  74. package/coverage/tmp/coverage-79230-1779241011647-0.json +1 -0
  75. package/coverage/tmp/coverage-79231-1779241011765-0.json +1 -0
  76. package/coverage/tmp/coverage-79232-1779241011841-0.json +1 -0
  77. package/coverage/tmp/coverage-79233-1779241011909-0.json +1 -0
  78. package/coverage/tmp/coverage-79234-1779241011984-0.json +1 -0
  79. package/coverage/tmp/coverage-79235-1779241012055-0.json +1 -0
  80. package/coverage/tmp/coverage-79236-1779241012230-0.json +1 -0
  81. package/coverage/tmp/coverage-79237-1779241012300-0.json +1 -0
  82. package/coverage/tmp/coverage-79238-1779241012368-0.json +1 -0
  83. package/coverage/tmp/coverage-79239-1779241012438-0.json +1 -0
  84. package/coverage/tmp/coverage-79240-1779241012511-0.json +1 -0
  85. package/coverage/tmp/coverage-79241-1779241012582-0.json +1 -0
  86. package/coverage/tmp/coverage-79242-1779241012652-0.json +1 -0
  87. package/coverage/tmp/coverage-79243-1779241012814-0.json +1 -0
  88. package/coverage/tmp/coverage-79244-1779241012931-0.json +1 -0
  89. package/coverage/tmp/coverage-79245-1779241013007-0.json +1 -0
  90. package/coverage/tmp/coverage-79246-1779241013106-0.json +1 -0
  91. package/coverage/tmp/coverage-79247-1779241013178-0.json +1 -0
  92. package/coverage/tmp/coverage-79248-1779241013244-0.json +1 -0
  93. package/coverage/tmp/coverage-79249-1779241013409-0.json +1 -0
  94. package/coverage/tmp/coverage-79250-1779241013697-0.json +1 -0
  95. package/coverage/tmp/coverage-79251-1779241013847-0.json +1 -0
  96. package/coverage/tmp/coverage-79252-1779241014288-0.json +1 -0
  97. package/coverage/tmp/coverage-79253-1779241014378-0.json +1 -0
  98. package/coverage/tmp/coverage-79254-1779241014428-0.json +1 -0
  99. package/coverage/tmp/coverage-79255-1779241021774-0.json +1 -0
  100. package/coverage/tmp/coverage-80382-1779241021949-0.json +1 -0
  101. package/coverage/tmp/coverage-80383-1779241025019-0.json +1 -0
  102. package/coverage/tmp/coverage-80384-1779241025133-0.json +1 -0
  103. package/docs/Body.md +1 -0
  104. package/docs/Config.md +1 -0
  105. package/docs/Connection.md +153 -0
  106. package/docs/CoreConfig.md +96 -0
  107. package/docs/CustomReturnCodes.md +3 -0
  108. package/docs/HAProxy.md +62 -0
  109. package/docs/Header.md +1 -0
  110. package/docs/Logging.md +129 -0
  111. package/docs/Outbound.md +210 -0
  112. package/docs/Plugins.md +372 -0
  113. package/docs/Results.md +7 -0
  114. package/docs/Transaction.md +135 -0
  115. package/docs/Tutorial.md +183 -0
  116. package/docs/deprecated/access.md +3 -0
  117. package/docs/deprecated/backscatterer.md +9 -0
  118. package/docs/deprecated/connect.rdns_access.md +53 -0
  119. package/docs/deprecated/data.headers.md +3 -0
  120. package/docs/deprecated/data.nomsgid.md +7 -0
  121. package/docs/deprecated/data.noreceived.md +11 -0
  122. package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
  123. package/docs/deprecated/dkim_sign.md +97 -0
  124. package/docs/deprecated/dkim_verify.md +28 -0
  125. package/docs/deprecated/dnsbl.md +80 -0
  126. package/docs/deprecated/dnswl.md +73 -0
  127. package/docs/deprecated/lookup_rdns.strict.md +67 -0
  128. package/docs/deprecated/mail_from.access.md +52 -0
  129. package/docs/deprecated/mail_from.blocklist.md +18 -0
  130. package/docs/deprecated/mail_from.nobounces.md +8 -0
  131. package/docs/deprecated/rcpt_to.access.md +53 -0
  132. package/docs/deprecated/rcpt_to.blocklist.md +18 -0
  133. package/docs/deprecated/rcpt_to.routes.md +3 -0
  134. package/docs/deprecated/rdns.regexp.md +30 -0
  135. package/docs/plugins/aliases.md +3 -0
  136. package/docs/plugins/auth/auth_bridge.md +34 -0
  137. package/docs/plugins/auth/auth_ldap.md +4 -0
  138. package/docs/plugins/auth/auth_proxy.md +36 -0
  139. package/docs/plugins/auth/auth_vpopmaild.md +33 -0
  140. package/docs/plugins/auth/flat_file.md +40 -0
  141. package/docs/plugins/block_me.md +18 -0
  142. package/docs/plugins/data.signatures.md +11 -0
  143. package/docs/plugins/delay_deny.md +23 -0
  144. package/docs/plugins/max_unrecognized_commands.md +6 -0
  145. package/docs/plugins/prevent_credential_leaks.md +22 -0
  146. package/docs/plugins/process_title.md +42 -0
  147. package/docs/plugins/queue/deliver.md +3 -0
  148. package/docs/plugins/queue/discard.md +32 -0
  149. package/docs/plugins/queue/lmtp.md +24 -0
  150. package/docs/plugins/queue/qmail-queue.md +16 -0
  151. package/docs/plugins/queue/quarantine.md +87 -0
  152. package/docs/plugins/queue/smtp_bridge.md +32 -0
  153. package/docs/plugins/queue/smtp_forward.md +127 -0
  154. package/docs/plugins/queue/smtp_proxy.md +68 -0
  155. package/docs/plugins/queue/test.md +7 -0
  156. package/docs/plugins/rcpt_to.in_host_list.md +34 -0
  157. package/docs/plugins/rcpt_to.max_count.md +3 -0
  158. package/docs/plugins/record_envelope_addresses.md +20 -0
  159. package/docs/plugins/relay.md +3 -0
  160. package/docs/plugins/reseed_rng.md +16 -0
  161. package/docs/plugins/status.md +41 -0
  162. package/docs/plugins/tarpit.md +50 -0
  163. package/docs/plugins/tls.md +235 -0
  164. package/docs/plugins/toobusy.md +27 -0
  165. package/docs/plugins/xclient.md +10 -0
  166. package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
  167. package/docs/tutorials/SettingUpOutbound.md +62 -0
  168. package/eslint.config.mjs +2 -0
  169. package/haraka.js +74 -0
  170. package/haraka.sh +2 -0
  171. package/http/html/404.html +58 -0
  172. package/http/html/index.html +47 -0
  173. package/http/package.json +21 -0
  174. package/line_socket.js +24 -0
  175. package/logger.js +322 -0
  176. package/outbound/client_pool.js +59 -0
  177. package/outbound/config.js +134 -0
  178. package/outbound/hmail.js +1504 -0
  179. package/outbound/index.js +349 -0
  180. package/outbound/qfile.js +93 -0
  181. package/outbound/queue.js +399 -0
  182. package/outbound/tls.js +85 -0
  183. package/outbound/todo.js +17 -0
  184. package/package.json +99 -4
  185. package/plugins/.eslintrc.yaml +3 -0
  186. package/plugins/auth/auth_base.js +261 -0
  187. package/plugins/auth/auth_bridge.js +20 -0
  188. package/plugins/auth/auth_proxy.js +227 -0
  189. package/plugins/auth/auth_vpopmaild.js +162 -0
  190. package/plugins/auth/flat_file.js +44 -0
  191. package/plugins/block_me.js +88 -0
  192. package/plugins/data.signatures.js +30 -0
  193. package/plugins/delay_deny.js +153 -0
  194. package/plugins/prevent_credential_leaks.js +61 -0
  195. package/plugins/process_title.js +197 -0
  196. package/plugins/profile.js +11 -0
  197. package/plugins/queue/deliver.js +12 -0
  198. package/plugins/queue/discard.js +27 -0
  199. package/plugins/queue/lmtp.js +45 -0
  200. package/plugins/queue/qmail-queue.js +93 -0
  201. package/plugins/queue/quarantine.js +133 -0
  202. package/plugins/queue/smtp_bridge.js +45 -0
  203. package/plugins/queue/smtp_forward.js +371 -0
  204. package/plugins/queue/smtp_proxy.js +142 -0
  205. package/plugins/queue/test.js +15 -0
  206. package/plugins/rcpt_to.host_list_base.js +65 -0
  207. package/plugins/rcpt_to.in_host_list.js +56 -0
  208. package/plugins/record_envelope_addresses.js +17 -0
  209. package/plugins/reseed_rng.js +7 -0
  210. package/plugins/status.js +274 -0
  211. package/plugins/tarpit.js +45 -0
  212. package/plugins/tls.js +164 -0
  213. package/plugins/toobusy.js +47 -0
  214. package/plugins/xclient.js +124 -0
  215. package/plugins.js +604 -0
  216. package/queue/1772642154987_1775581346001_4_82235_TGwgfd_2_mattbook-m3.home.simerson.net +0 -0
  217. package/run_tests +11 -0
  218. package/server.js +827 -0
  219. package/smtp_client.js +504 -0
  220. package/test/.eslintrc.yaml +11 -0
  221. package/test/config/auth_flat_file.ini +5 -0
  222. package/test/config/block_me.recipient +1 -0
  223. package/test/config/block_me.senders +1 -0
  224. package/test/config/dhparams.pem +8 -0
  225. package/test/config/host_list +2 -0
  226. package/test/config/outbound_tls_cert.pem +1 -0
  227. package/test/config/outbound_tls_key.pem +1 -0
  228. package/test/config/plugins +7 -0
  229. package/test/config/smtp.ini +11 -0
  230. package/test/config/smtp_forward.ini +30 -0
  231. package/test/config/tls/example.com/_.example.com.key +28 -0
  232. package/test/config/tls/example.com/example.com.crt +25 -0
  233. package/test/config/tls/haraka.local.pem +51 -0
  234. package/test/config/tls.ini +45 -0
  235. package/test/config/tls_cert.pem +21 -0
  236. package/test/config/tls_key.pem +28 -0
  237. package/test/connection.js +817 -0
  238. package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
  239. package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
  240. package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
  241. package/test/fixtures/line_socket.js +21 -0
  242. package/test/fixtures/todo_qfile.txt +0 -0
  243. package/test/fixtures/util_hmailitem.js +156 -0
  244. package/test/installation/config/test-plugin-flat +1 -0
  245. package/test/installation/config/test-plugin.ini +10 -0
  246. package/test/installation/config/tls.ini +1 -0
  247. package/test/installation/node_modules/load_first/index.js +5 -0
  248. package/test/installation/node_modules/load_first/package.json +11 -0
  249. package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
  250. package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
  251. package/test/installation/node_modules/test-plugin/package.json +5 -0
  252. package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
  253. package/test/installation/plugins/base_plugin.js +3 -0
  254. package/test/installation/plugins/folder_plugin/index.js +3 -0
  255. package/test/installation/plugins/folder_plugin/package.json +11 -0
  256. package/test/installation/plugins/inherits.js +7 -0
  257. package/test/installation/plugins/load_first.js +3 -0
  258. package/test/installation/plugins/plugin.js +1 -0
  259. package/test/installation/plugins/tls.js +3 -0
  260. package/test/logger.js +217 -0
  261. package/test/loud/config/dhparams.pem +0 -0
  262. package/test/loud/config/tls/goobered.pem +45 -0
  263. package/test/loud/config/tls.ini +43 -0
  264. package/test/mail_specimen/base64-root-part.txt +23 -0
  265. package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
  266. package/test/outbound/bounce_net_errors.js +133 -0
  267. package/test/outbound/bounce_rfc3464.js +226 -0
  268. package/test/outbound/hmail.js +210 -0
  269. package/test/outbound/index.js +385 -0
  270. package/test/outbound/qfile.js +124 -0
  271. package/test/outbound/queue.js +325 -0
  272. package/test/plugins/auth/auth_base.js +620 -0
  273. package/test/plugins/auth/auth_bridge.js +80 -0
  274. package/test/plugins/auth/auth_vpopmaild.js +81 -0
  275. package/test/plugins/auth/flat_file.js +123 -0
  276. package/test/plugins/block_me.js +141 -0
  277. package/test/plugins/data.signatures.js +111 -0
  278. package/test/plugins/delay_deny.js +262 -0
  279. package/test/plugins/prevent_credential_leaks.js +174 -0
  280. package/test/plugins/process_title.js +141 -0
  281. package/test/plugins/queue/deliver.js +98 -0
  282. package/test/plugins/queue/discard.js +78 -0
  283. package/test/plugins/queue/lmtp.js +137 -0
  284. package/test/plugins/queue/qmail-queue.js +98 -0
  285. package/test/plugins/queue/quarantine.js +80 -0
  286. package/test/plugins/queue/smtp_bridge.js +152 -0
  287. package/test/plugins/queue/smtp_forward.js +1023 -0
  288. package/test/plugins/queue/smtp_proxy.js +138 -0
  289. package/test/plugins/rcpt_to.host_list_base.js +102 -0
  290. package/test/plugins/rcpt_to.in_host_list.js +186 -0
  291. package/test/plugins/record_envelope_addresses.js +66 -0
  292. package/test/plugins/reseed_rng.js +34 -0
  293. package/test/plugins/status.js +207 -0
  294. package/test/plugins/tarpit.js +90 -0
  295. package/test/plugins/tls.js +86 -0
  296. package/test/plugins/toobusy.js +21 -0
  297. package/test/plugins/xclient.js +119 -0
  298. package/test/plugins.js +230 -0
  299. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  300. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  301. package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  302. package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  303. package/test/queue/zero-length +0 -0
  304. package/test/server.js +1012 -0
  305. package/test/smtp_client.js +1303 -0
  306. package/test/tls_socket.js +321 -0
  307. package/test/transaction.js +554 -0
  308. package/tls_socket.js +771 -0
  309. package/transaction.js +267 -0
@@ -0,0 +1,283 @@
1
+ MIME-Version: 1.0
2
+ Content-Type: multipart/mixed; boundary="goal_line"
3
+
4
+ --goal_line
5
+ Content-Type: image/gif;
6
+ Content-Transfer-Encoding: base64
7
+ Content-Disposition: attachment; filename="known-good.gif"
8
+
9
+ R0lGODdhMgAoAPcAANK6tfdkZCwtJ5GRjL6+uZiZluXm4ts7NqOknupua6RcWL/Cvtzf3ePj3urq
10
+ 5LkzLkFCPOGCftQ5NKmqpWtsaNLHws2/vszOy9jb2eTl38jIw9/g3NbZ18XIxcnKxScoItLSzc7O
11
+ yfhPTtfa2dPTzufn4a6uqtXY1q+XkkZHQvRGQ7a3sbq6tbKzrtbW0dzc19vb1t3e2NXY2MDDwsfH
12
+ wvdOTcPGw7W1sNvc1tjZ09XRzNXLxtTMxtTTztfX0tI2MvdNTM/Ev9/g29lUUdTUz+I/OcrLycPD
13
+ vrJNSc44M0tMRs/QypxhXKNzbrdDPqSkn+jo4tfY0s6yrcnJxIiLh8LEw+d3c5trZtPX1etdWuDg
14
+ 26amofhiYPhQTuHh3OxiYPdWVfddXPheXfhdW/dUU/RKSMiZlcHCvcaemvNRT/daWLajoe1bWNK3
15
+ sdPV1PhXUru7ttPV08/GwuplY/hhXvhkY/hlZPdQUPRFQr6Pir2SjfJNS/daWvhmZfhjYiorJtXL
16
+ xfhYVepDPsbGwfZNSdHU09Y3M7JXU/dXV9yQi7/Bvz0+ONnc2/dgX+U/OuTl4NLJw5yCffVJRvZZ
17
+ WJh7d5xeWeRBO8UzLvhVVN4/ONra1PhaWZCOivheXbM2MWFiXNDRy7u+u/ZLS9nZ1Mo2Ma8zLfho
18
+ Z8zKxe5IRN5+eqZjXp+gm/dfXfJJRtLV1NHRzLe4s0lKRN7e2cvHwqKjns/S0c3NyNw3M8LDvp2e
19
+ mepCPJeYlOE9N/hfW8LFxPhkYfhSTvdLSPVEQelBPcu5uLm7uqChnUhJQ8zMx7CxrLytqulDPvRB
20
+ PfdIRPhOS/hWUfdoZ/hiX/hSTPdKRvREP/A/OeA/O+Lj3cvLxqSfm9pEQOs7NvFAPPhbV/hdWZ+f
21
+ mfdQSvVJQ/FCPes/OeU6NNA/OpaMicHBvEpLRkdIQ8dgW+A2Muc7Nu9DP/RJRfdRTPhgXPdPSvJI
22
+ Q+1CPOY+N+A6Ndc3MbpPS6ysp8vMx0pLRcrKxcXFwK+SjdA1MCZFySH5BAEAAP8ALAAAAAAyACgA
23
+ AAj/AD8IHEiwoMGDCBMqXMiwocOHECMKmEixosWLGDNi/EPwD4VduXIVCDlSJEmTI1OiXKmypMuQ
24
+ uygIEAhhAYNHUHLqzFmihAGfJR4FffTIQAOiQZP+XGrAAJSeORuMCJVC4LFTRjgI7cmVa4YMDRp4
25
+ ueal7NizXsJ+zVCCbdee1050oBHLKigXRmR4adv2q1gtgGXJEiIksJbBhQOXJct47DVZWKosWYLO
26
+ 6qsYF3xx2HBNrVgvh2XBgDFq9OgXo0uPeiEYtNnOoN3MGPSCsl1ZOGjMOCEENmhZMTT50LFjBwgi
27
+ Lnz4cEEExI4KOlyMEixLy2JZcRTBgSHL9odjoIBr/xq0AEuMDYdf4IgCKZGVBAlSSTkVYoktKRHg
28
+ J0gkxwcM1oERQQABq8kSQmXfLREDDusR4AsWjAAHQxQAfAFGGHzwcYcKZihyDhppTMIHG2TswYYU
29
+ LvwnywtxzNBCii+8EEJdCcawoCYusKBIIaT5IMccz7DiRwB+8JFGO3nkgQofAQTQSBhkiJJFEC5o
30
+ AkMtxeDTgyaoyUjjMbYwqIkmURABRyi1uEBCBHuo0Ugf0ATAChitDDFEGl80GUAYiNwhTCoXuICl
31
+ CSCU9p+XVoWJw5hRuABKC77UosEee7wRRiNNssIHGO2gwgqmTvJRIh6o0HDBDE+E4MMoqiH63SmL
32
+ av+SQ6NEXIBPMRYs04wzYHjTSCNZYMgGk01yMWcakjAzDDHFrKKBC1GwKq0tX54i65izNpePCcos
33
+ w00zhDyDSBhhfKrnF4148wY44TBTzQFr5EIDCUREEa202NCYwik55EAmrSAsMcg32mxTDTVlgPPG
34
+ JL1g2ogfrKjxzB7yzMMOOeWYQ8AS9LrgQg6rjnKKvvvMai+0RJAACjZwqHMLOdvMI08aaXQzCbmT
35
+ TJIGOO2MQ88t9iAxATZLgECCxx4rd0o6VnnQ6MnMgQBKPkf044896xShCzWEvPNMGm+kIQ0hgliS
36
+ iT3+XHIFLLa8chxySLswBdMfpEBDo8z1QAIJr7z/Ygs/ExxyiT231OOIzIQkHo48ghRxANqXIEEF
37
+ AUQf1wMRmCc3hb6DuHB5D6D3IPUU5/DTjxOXXH1LEct0yvgyvBwgASmXOEFJC3BoEILbe5MAOhE0
38
+ HCNQCoOEDjoJUodwRj4XENBEKan/cIs1jugyDD2ZSJAEKQ88cEU2syBzBjJL9G18D8EPz0/oUoNi
39
+ 3xG2SO1BMld4csklPxyQSRFFnJ3EJQ8ohSoGAIcQgOIURzhF0dwGAtANQnh1OwII2gcKUCBjECFQ
40
+ 2RIuADgmQA9/huBF9v4XQAVwogUamIwOQjAI8oGCgvyoSgQr6D778AMZe3NfCPJRhSdU4oM/MMTs
41
+ /wBYChOagB8KLFoP/maLJdAQFEeQIQSOUMMQnIIfswAdCF6hw32cIRs/vAQpfvADIiqACvhI4GSK
42
+ 1kAEJtGJURzeGZy4BGQgcYKg6BsoQmALW3jgHLQIIym4V4pKoPEckOjjEyt4RQXWJ451O0d9bDGI
43
+ fVCQhksIQQiwsY8qbCGMlyjkCRP4xsk8cR9IrM85pHiGENhxClys4BrX2EdknEIRW/BgIQfQAiTS
44
+ p4+zfKIGUrnK4VFuEFOo4SwzqUlNIgMZ2OBHCyJBiQKwgAbPtEUz+1ifNbpvFi0kgBTPQQMNKDOT
45
+ 2tTkLPn4TGyEogWKmIIRkMHHetZHneccBA3ESX4TOAwinZrsoy0UqE1b2NKgCEWGBzyAjVNks4+n
46
+ EKhE7UkDOMhwESvIBzaQIVGDOtSjHH2mLbMp0oRG1KGnQOlAV2oLFixiIJ8oAAJogYCa2vSmODWG
47
+ TmmRDVrstKbZQMAqEBBUBHzDpkXNxSciwtSmOvWpUI2qVKdK1aoqJCAAOw==
48
+
49
+ --goal_line
50
+ Content-Type: image/gif;
51
+ Content-Transfer-Encoding: base64
52
+ Content-Disposition: attachment; filename="75-char-fold.gif"
53
+
54
+ R0lGODdhMgAoAPcAANK6tfdkZCwtJ5GRjL6+uZiZluXm4ts7NqOknupua6RcWL/Cvtzf3ePj3ur
55
+ q5LkzLkFCPOGCftQ5NKmqpWtsaNLHws2/vszOy9jb2eTl38jIw9/g3NbZ18XIxcnKxScoItLSzc
56
+ 7OyfhPTtfa2dPTzufn4a6uqtXY1q+XkkZHQvRGQ7a3sbq6tbKzrtbW0dzc19vb1t3e2NXY2MDDw
57
+ sfHwvdOTcPGw7W1sNvc1tjZ09XRzNXLxtTMxtTTztfX0tI2MvdNTM/Ev9/g29lUUdTUz+I/OcrL
58
+ ycPDvrJNSc44M0tMRs/QypxhXKNzbrdDPqSkn+jo4tfY0s6yrcnJxIiLh8LEw+d3c5trZtPX1et
59
+ dWuDg26amofhiYPhQTuHh3OxiYPdWVfddXPheXfhdW/dUU/RKSMiZlcHCvcaemvNRT/daWLajoe
60
+ 1bWNK3sdPV1PhXUru7ttPV08/GwuplY/hhXvhkY/hlZPdQUPRFQr6Pir2SjfJNS/daWvhmZfhjY
61
+ iorJtXLxfhYVepDPsbGwfZNSdHU09Y3M7JXU/dXV9yQi7/Bvz0+ONnc2/dgX+U/OuTl4NLJw5yC
62
+ ffVJRvZZWJh7d5xeWeRBO8UzLvhVVN4/ONra1PhaWZCOivheXbM2MWFiXNDRy7u+u/ZLS9nZ1Mo
63
+ 2Ma8zLfhoZ8zKxe5IRN5+eqZjXp+gm/dfXfJJRtLV1NHRzLe4s0lKRN7e2cvHwqKjns/S0c3NyN
64
+ w3M8LDvp2emepCPJeYlOE9N/hfW8LFxPhkYfhSTvdLSPVEQelBPcu5uLm7uqChnUhJQ8zMx7Cxr
65
+ LytqulDPvRBPfdIRPhOS/hWUfdoZ/hiX/hSTPdKRvREP/A/OeA/O+Lj3cvLxqSfm9pEQOs7NvFA
66
+ PPhbV/hdWZ+fmfdQSvVJQ/FCPes/OeU6NNA/OpaMicHBvEpLRkdIQ8dgW+A2Muc7Nu9DP/RJRfd
67
+ RTPhgXPdPSvJIQ+1CPOY+N+A6Ndc3MbpPS6ysp8vMx0pLRcrKxcXFwK+SjdA1MCZFySH5BAEAAP
68
+ 8ALAAAAAAyACgAAAj/AD8IHEiwoMGDCBMqXMiwocOHECMKmEixosWLGDNi/EPwD4VduXIVCDlSJ
69
+ EmTI1OiXKmypMuQuygIEAhhAYNHUHLqzFmihAGfJR4FffTIQAOiQZP+XGrAAJSeORuMCJVC4LFT
70
+ RjgI7cmVa4YMDRp4ueal7NizXsJ+zVCCbdee1050oBHLKigXRmR4adv2q1gtgGXJEiIksJbBhQO
71
+ XJct47DVZWKosWYLO6qsYF3xx2HBNrVgvh2XBgDFq9OgXo0uPeiEYtNnOoN3MGPSCsl1ZOGjMOC
72
+ EENmhZMTT50LFjBwgiLnz4cEEExI4KOlyMEixLy2JZcRTBgSHL9odjoIBr/xq0AEuMDYdf4IgCK
73
+ ZGVBAlSSTkVYoktKRHgJ0gkxwcM1oERQQABq8kSQmXfLREDDusR4AsWjAAHQxQAfAFGGHzwcYcK
74
+ ZihyDhppTMIHG2TswYYULvwnywtxzNBCii+8EEJdCcawoCYusKBIIaT5IMccz7DiRwB+8JFGO3n
75
+ kgQofAQTQSBhkiJJFEC5oAkMtxeDTgyaoyUjjMbYwqIkmURABRyi1uEBCBHuo0Ugf0ATAChitDD
76
+ FEGl80GUAYiNwhTCoXuIClCSCU9p+XVoWJw5hRuABKC77UosEee7wRRiNNssIHGO2gwgqmTvJRI
77
+ h6o0HDBDE+E4MMoqiH63SmLav+SQ6NEXIBPMRYs04wzYHjTSCNZYMgGk01yMWcakjAzDDHFrKKB
78
+ C1GwKq0tX54i65izNpePCcosw00zhDyDSBhhfKrnF4148wY44TBTzQFr5EIDCUREEa202NCYwik
79
+ 55EAmrSAsMcg32mxTDTVlgPPGJL1g2ogfrKjxzB7yzMMOOeWYQ8AS9LrgQg6rjnKKvvvMai+0RJ
80
+ AACjZwqHMLOdvMI08aaXQzCbmTTJIGOO2MQ88t9iAxATZLgECCxx4rd0o6VnnQ6MnMgQBKPkf04
81
+ 4896xShCzWEvPNMGm+kIQ0hgliSiT3+XHIFLLa8chxySLswBdMfpEBDo8z1QAIJr7z/Ygs/Exxy
82
+ iT231OOIzIQkHo48ghRxANqXIEEFAUQf1wMRmCc3hb6DuHB5D6D3IPUU5/DTjxOXXH1LEct0yvg
83
+ yvBwgASmXOEFJC3BoEILbe5MAOhE0HCNQCoOEDjoJUodwRj4XENBEKan/cIs1jugyDD2ZSJAEKQ
84
+ 88cEU2syBzBjJL9G18D8EPz0/oUoNi3xG2SO1BMld4csklPxyQSRFFnJ3EJQ8ohSoGAIcQgOIUR
85
+ zhF0dwGAtANQnh1OwII2gcKUCBjECFQ2RIuADgmQA9/huBF9v4XQAVwogUamIwOQjAI8oGCgvyo
86
+ SgQr6D778AMZe3NfCPJRhSdU4oM/MMTs/wBYChOagB8KLFoP/maLJdAQFEeQIQSOUMMQnIIfswA
87
+ dCF6hw32cIRs/vAQpfvADIiqACvhI4GSK1kAEJtGJURzeGZy4BGQgcYKg6BsoQmALW3jgHLQIIy
88
+ m4V4pKoPEckOjjEyt4RQXWJ451O0d9bDGIfVCQhksIQQiwsY8qbCGMlyjkCRP4xsk8cR9IrM85p
89
+ HiGENhxClys4BrX2EdknEIRW/BgIQfQAiTSp4+zfKIGUrnK4VFuEFOo4SwzqUlNIgMZ2OBHCyJB
90
+ iQKwgAbPtEUz+1ifNbpvFi0kgBTPQQMNKDOT2tTkLPn4TGyEogWKmIIRkMHHetZHneccBA3ESX4
91
+ TOAwinZrsoy0UqE1b2NKgCEWGBzyAjVNks4+nEKhE7UkDOMhwESvIBzaQIVGDOtSjHH2mLbMp0o
92
+ RG1KGnQOlAV2oLFixiIJ8oAAJogYCa2vSmODWGTmmRDVrstKbZQMAqEBBUBHzDpkXNxSciwtSmO
93
+ vWpUI2qVKdK1aoqJCAAOw==
94
+
95
+ --goal_line
96
+ Content-Type: image/gif;
97
+ Content-Transfer-Encoding: base64
98
+ Content-Disposition: attachment; filename="74-char-fold.gif"
99
+
100
+ R0lGODdhMgAoAPcAANK6tfdkZCwtJ5GRjL6+uZiZluXm4ts7NqOknupua6RcWL/Cvtzf3ePj3u
101
+ rq5LkzLkFCPOGCftQ5NKmqpWtsaNLHws2/vszOy9jb2eTl38jIw9/g3NbZ18XIxcnKxScoItLS
102
+ zc7OyfhPTtfa2dPTzufn4a6uqtXY1q+XkkZHQvRGQ7a3sbq6tbKzrtbW0dzc19vb1t3e2NXY2M
103
+ DDwsfHwvdOTcPGw7W1sNvc1tjZ09XRzNXLxtTMxtTTztfX0tI2MvdNTM/Ev9/g29lUUdTUz+I/
104
+ OcrLycPDvrJNSc44M0tMRs/QypxhXKNzbrdDPqSkn+jo4tfY0s6yrcnJxIiLh8LEw+d3c5trZt
105
+ PX1etdWuDg26amofhiYPhQTuHh3OxiYPdWVfddXPheXfhdW/dUU/RKSMiZlcHCvcaemvNRT/da
106
+ WLajoe1bWNK3sdPV1PhXUru7ttPV08/GwuplY/hhXvhkY/hlZPdQUPRFQr6Pir2SjfJNS/daWv
107
+ hmZfhjYiorJtXLxfhYVepDPsbGwfZNSdHU09Y3M7JXU/dXV9yQi7/Bvz0+ONnc2/dgX+U/OuTl
108
+ 4NLJw5yCffVJRvZZWJh7d5xeWeRBO8UzLvhVVN4/ONra1PhaWZCOivheXbM2MWFiXNDRy7u+u/
109
+ ZLS9nZ1Mo2Ma8zLfhoZ8zKxe5IRN5+eqZjXp+gm/dfXfJJRtLV1NHRzLe4s0lKRN7e2cvHwqKj
110
+ ns/S0c3NyNw3M8LDvp2emepCPJeYlOE9N/hfW8LFxPhkYfhSTvdLSPVEQelBPcu5uLm7uqChnU
111
+ hJQ8zMx7CxrLytqulDPvRBPfdIRPhOS/hWUfdoZ/hiX/hSTPdKRvREP/A/OeA/O+Lj3cvLxqSf
112
+ m9pEQOs7NvFAPPhbV/hdWZ+fmfdQSvVJQ/FCPes/OeU6NNA/OpaMicHBvEpLRkdIQ8dgW+A2Mu
113
+ c7Nu9DP/RJRfdRTPhgXPdPSvJIQ+1CPOY+N+A6Ndc3MbpPS6ysp8vMx0pLRcrKxcXFwK+SjdA1
114
+ MCZFySH5BAEAAP8ALAAAAAAyACgAAAj/AD8IHEiwoMGDCBMqXMiwocOHECMKmEixosWLGDNi/E
115
+ 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
140
+
141
+ --goal_line
142
+ Content-Type: image/gif;
143
+ Content-Transfer-Encoding: base64
144
+ Content-Disposition: attachment; filename="73-char-fold.gif"
145
+
146
+ R0lGODdhMgAoAPcAANK6tfdkZCwtJ5GRjL6+uZiZluXm4ts7NqOknupua6RcWL/Cvtzf3ePj3
147
+ urq5LkzLkFCPOGCftQ5NKmqpWtsaNLHws2/vszOy9jb2eTl38jIw9/g3NbZ18XIxcnKxScoIt
148
+ LSzc7OyfhPTtfa2dPTzufn4a6uqtXY1q+XkkZHQvRGQ7a3sbq6tbKzrtbW0dzc19vb1t3e2NX
149
+ Y2MDDwsfHwvdOTcPGw7W1sNvc1tjZ09XRzNXLxtTMxtTTztfX0tI2MvdNTM/Ev9/g29lUUdTU
150
+ z+I/OcrLycPDvrJNSc44M0tMRs/QypxhXKNzbrdDPqSkn+jo4tfY0s6yrcnJxIiLh8LEw+d3c
151
+ 5trZtPX1etdWuDg26amofhiYPhQTuHh3OxiYPdWVfddXPheXfhdW/dUU/RKSMiZlcHCvcaemv
152
+ NRT/daWLajoe1bWNK3sdPV1PhXUru7ttPV08/GwuplY/hhXvhkY/hlZPdQUPRFQr6Pir2SjfJ
153
+ NS/daWvhmZfhjYiorJtXLxfhYVepDPsbGwfZNSdHU09Y3M7JXU/dXV9yQi7/Bvz0+ONnc2/dg
154
+ X+U/OuTl4NLJw5yCffVJRvZZWJh7d5xeWeRBO8UzLvhVVN4/ONra1PhaWZCOivheXbM2MWFiX
155
+ NDRy7u+u/ZLS9nZ1Mo2Ma8zLfhoZ8zKxe5IRN5+eqZjXp+gm/dfXfJJRtLV1NHRzLe4s0lKRN
156
+ 7e2cvHwqKjns/S0c3NyNw3M8LDvp2emepCPJeYlOE9N/hfW8LFxPhkYfhSTvdLSPVEQelBPcu
157
+ 5uLm7uqChnUhJQ8zMx7CxrLytqulDPvRBPfdIRPhOS/hWUfdoZ/hiX/hSTPdKRvREP/A/OeA/
158
+ O+Lj3cvLxqSfm9pEQOs7NvFAPPhbV/hdWZ+fmfdQSvVJQ/FCPes/OeU6NNA/OpaMicHBvEpLR
159
+ kdIQ8dgW+A2Muc7Nu9DP/RJRfdRTPhgXPdPSvJIQ+1CPOY+N+A6Ndc3MbpPS6ysp8vMx0pLRc
160
+ rKxcXFwK+SjdA1MCZFySH5BAEAAP8ALAAAAAAyACgAAAj/AD8IHEiwoMGDCBMqXMiwocOHECM
161
+ KmEixosWLGDNi/EPwD4VduXIVCDlSJEmTI1OiXKmypMuQuygIEAhhAYNHUHLqzFmihAGfJR4F
162
+ ffTIQAOiQZP+XGrAAJSeORuMCJVC4LFTRjgI7cmVa4YMDRp4ueal7NizXsJ+zVCCbdee1050o
163
+ BHLKigXRmR4adv2q1gtgGXJEiIksJbBhQOXJct47DVZWKosWYLO6qsYF3xx2HBNrVgvh2XBgD
164
+ Fq9OgXo0uPeiEYtNnOoN3MGPSCsl1ZOGjMOCEENmhZMTT50LFjBwgiLnz4cEEExI4KOlyMEix
165
+ Ly2JZcRTBgSHL9odjoIBr/xq0AEuMDYdf4IgCKZGVBAlSSTkVYoktKRHgJ0gkxwcM1oERQQAB
166
+ q8kSQmXfLREDDusR4AsWjAAHQxQAfAFGGHzwcYcKZihyDhppTMIHG2TswYYULvwnywtxzNBCi
167
+ i+8EEJdCcawoCYusKBIIaT5IMccz7DiRwB+8JFGO3nkgQofAQTQSBhkiJJFEC5oAkMtxeDTgy
168
+ aoyUjjMbYwqIkmURABRyi1uEBCBHuo0Ugf0ATAChitDDFEGl80GUAYiNwhTCoXuIClCSCU9p+
169
+ XVoWJw5hRuABKC77UosEee7wRRiNNssIHGO2gwgqmTvJRIh6o0HDBDE+E4MMoqiH63SmLav+S
170
+ Q6NEXIBPMRYs04wzYHjTSCNZYMgGk01yMWcakjAzDDHFrKKBC1GwKq0tX54i65izNpePCcosw
171
+ 00zhDyDSBhhfKrnF4148wY44TBTzQFr5EIDCUREEa202NCYwik55EAmrSAsMcg32mxTDTVlgP
172
+ PGJL1g2ogfrKjxzB7yzMMOOeWYQ8AS9LrgQg6rjnKKvvvMai+0RJAACjZwqHMLOdvMI08aaXQ
173
+ zCbmTTJIGOO2MQ88t9iAxATZLgECCxx4rd0o6VnnQ6MnMgQBKPkf044896xShCzWEvPNMGm+k
174
+ IQ0hgliSiT3+XHIFLLa8chxySLswBdMfpEBDo8z1QAIJr7z/Ygs/ExxyiT231OOIzIQkHo48g
175
+ hRxANqXIEEFAUQf1wMRmCc3hb6DuHB5D6D3IPUU5/DTjxOXXH1LEct0yvgyvBwgASmXOEFJC3
176
+ BoEILbe5MAOhE0HCNQCoOEDjoJUodwRj4XENBEKan/cIs1jugyDD2ZSJAEKQ88cEU2syBzBjJ
177
+ L9G18D8EPz0/oUoNi3xG2SO1BMld4csklPxyQSRFFnJ3EJQ8ohSoGAIcQgOIURzhF0dwGAtAN
178
+ Qnh1OwII2gcKUCBjECFQ2RIuADgmQA9/huBF9v4XQAVwogUamIwOQjAI8oGCgvyoSgQr6D778
179
+ AMZe3NfCPJRhSdU4oM/MMTs/wBYChOagB8KLFoP/maLJdAQFEeQIQSOUMMQnIIfswAdCF6hw3
180
+ 2cIRs/vAQpfvADIiqACvhI4GSK1kAEJtGJURzeGZy4BGQgcYKg6BsoQmALW3jgHLQIIym4V4p
181
+ KoPEckOjjEyt4RQXWJ451O0d9bDGIfVCQhksIQQiwsY8qbCGMlyjkCRP4xsk8cR9IrM85pHiG
182
+ ENhxClys4BrX2EdknEIRW/BgIQfQAiTSp4+zfKIGUrnK4VFuEFOo4SwzqUlNIgMZ2OBHCyJBi
183
+ QKwgAbPtEUz+1ifNbpvFi0kgBTPQQMNKDOT2tTkLPn4TGyEogWKmIIRkMHHetZHneccBA3ESX
184
+ 4TOAwinZrsoy0UqE1b2NKgCEWGBzyAjVNks4+nEKhE7UkDOMhwESvIBzaQIVGDOtSjHH2mLbM
185
+ p0oRG1KGnQOlAV2oLFixiIJ8oAAJogYCa2vSmODWGTmmRDVrstKbZQMAqEBBUBHzDpkXNxSci
186
+ wtSmOvWpUI2qVKdK1aoqJCAAOw==
187
+
188
+ --goal_line
189
+ Content-Type: image/gif;
190
+ Content-Transfer-Encoding: base64
191
+ Content-Disposition: attachment; filename="72-char-fold-missing-padding.gif"
192
+
193
+ R0lGODdhMgAoAPcAANK6tfdkZCwtJ5GRjL6+uZiZluXm4ts7NqOknupua6RcWL/Cvtzf3ePj
194
+ 3urq5LkzLkFCPOGCftQ5NKmqpWtsaNLHws2/vszOy9jb2eTl38jIw9/g3NbZ18XIxcnKxSco
195
+ ItLSzc7OyfhPTtfa2dPTzufn4a6uqtXY1q+XkkZHQvRGQ7a3sbq6tbKzrtbW0dzc19vb1t3e
196
+ 2NXY2MDDwsfHwvdOTcPGw7W1sNvc1tjZ09XRzNXLxtTMxtTTztfX0tI2MvdNTM/Ev9/g29lU
197
+ UdTUz+I/OcrLycPDvrJNSc44M0tMRs/QypxhXKNzbrdDPqSkn+jo4tfY0s6yrcnJxIiLh8LE
198
+ w+d3c5trZtPX1etdWuDg26amofhiYPhQTuHh3OxiYPdWVfddXPheXfhdW/dUU/RKSMiZlcHC
199
+ vcaemvNRT/daWLajoe1bWNK3sdPV1PhXUru7ttPV08/GwuplY/hhXvhkY/hlZPdQUPRFQr6P
200
+ ir2SjfJNS/daWvhmZfhjYiorJtXLxfhYVepDPsbGwfZNSdHU09Y3M7JXU/dXV9yQi7/Bvz0+
201
+ ONnc2/dgX+U/OuTl4NLJw5yCffVJRvZZWJh7d5xeWeRBO8UzLvhVVN4/ONra1PhaWZCOivhe
202
+ XbM2MWFiXNDRy7u+u/ZLS9nZ1Mo2Ma8zLfhoZ8zKxe5IRN5+eqZjXp+gm/dfXfJJRtLV1NHR
203
+ zLe4s0lKRN7e2cvHwqKjns/S0c3NyNw3M8LDvp2emepCPJeYlOE9N/hfW8LFxPhkYfhSTvdL
204
+ SPVEQelBPcu5uLm7uqChnUhJQ8zMx7CxrLytqulDPvRBPfdIRPhOS/hWUfdoZ/hiX/hSTPdK
205
+ RvREP/A/OeA/O+Lj3cvLxqSfm9pEQOs7NvFAPPhbV/hdWZ+fmfdQSvVJQ/FCPes/OeU6NNA/
206
+ OpaMicHBvEpLRkdIQ8dgW+A2Muc7Nu9DP/RJRfdRTPhgXPdPSvJIQ+1CPOY+N+A6Ndc3MbpP
207
+ S6ysp8vMx0pLRcrKxcXFwK+SjdA1MCZFySH5BAEAAP8ALAAAAAAyACgAAAj/AD8IHEiwoMGD
208
+ CBMqXMiwocOHECMKmEixosWLGDNi/EPwD4VduXIVCDlSJEmTI1OiXKmypMuQuygIEAhhAYNH
209
+ UHLqzFmihAGfJR4FffTIQAOiQZP+XGrAAJSeORuMCJVC4LFTRjgI7cmVa4YMDRp4ueal7Niz
210
+ XsJ+zVCCbdee1050oBHLKigXRmR4adv2q1gtgGXJEiIksJbBhQOXJct47DVZWKosWYLO6qsY
211
+ F3xx2HBNrVgvh2XBgDFq9OgXo0uPeiEYtNnOoN3MGPSCsl1ZOGjMOCEENmhZMTT50LFjBwgi
212
+ Lnz4cEEExI4KOlyMEixLy2JZcRTBgSHL9odjoIBr/xq0AEuMDYdf4IgCKZGVBAlSSTkVYokt
213
+ KRHgJ0gkxwcM1oERQQABq8kSQmXfLREDDusR4AsWjAAHQxQAfAFGGHzwcYcKZihyDhppTMIH
214
+ G2TswYYULvwnywtxzNBCii+8EEJdCcawoCYusKBIIaT5IMccz7DiRwB+8JFGO3nkgQofAQTQ
215
+ SBhkiJJFEC5oAkMtxeDTgyaoyUjjMbYwqIkmURABRyi1uEBCBHuo0Ugf0ATAChitDDFEGl80
216
+ GUAYiNwhTCoXuIClCSCU9p+XVoWJw5hRuABKC77UosEee7wRRiNNssIHGO2gwgqmTvJRIh6o
217
+ 0HDBDE+E4MMoqiH63SmLav+SQ6NEXIBPMRYs04wzYHjTSCNZYMgGk01yMWcakjAzDDHFrKKB
218
+ C1GwKq0tX54i65izNpePCcosw00zhDyDSBhhfKrnF4148wY44TBTzQFr5EIDCUREEa202NCY
219
+ wik55EAmrSAsMcg32mxTDTVlgPPGJL1g2ogfrKjxzB7yzMMOOeWYQ8AS9LrgQg6rjnKKvvvM
220
+ ai+0RJAACjZwqHMLOdvMI08aaXQzCbmTTJIGOO2MQ88t9iAxATZLgECCxx4rd0o6VnnQ6MnM
221
+ gQBKPkf044896xShCzWEvPNMGm+kIQ0hgliSiT3+XHIFLLa8chxySLswBdMfpEBDo8z1QAIJ
222
+ r7z/Ygs/ExxyiT231OOIzIQkHo48ghRxANqXIEEFAUQf1wMRmCc3hb6DuHB5D6D3IPUU5/DT
223
+ jxOXXH1LEct0yvgyvBwgASmXOEFJC3BoEILbe5MAOhE0HCNQCoOEDjoJUodwRj4XENBEKan/
224
+ cIs1jugyDD2ZSJAEKQ88cEU2syBzBjJL9G18D8EPz0/oUoNi3xG2SO1BMld4csklPxyQSRFF
225
+ nJ3EJQ8ohSoGAIcQgOIURzhF0dwGAtANQnh1OwII2gcKUCBjECFQ2RIuADgmQA9/huBF9v4X
226
+ QAVwogUamIwOQjAI8oGCgvyoSgQr6D778AMZe3NfCPJRhSdU4oM/MMTs/wBYChOagB8KLFoP
227
+ /maLJdAQFEeQIQSOUMMQnIIfswAdCF6hw32cIRs/vAQpfvADIiqACvhI4GSK1kAEJtGJURze
228
+ GZy4BGQgcYKg6BsoQmALW3jgHLQIIym4V4pKoPEckOjjEyt4RQXWJ451O0d9bDGIfVCQhksI
229
+ QQiwsY8qbCGMlyjkCRP4xsk8cR9IrM85pHiGENhxClys4BrX2EdknEIRW/BgIQfQAiTSp4+z
230
+ fKIGUrnK4VFuEFOo4SwzqUlNIgMZ2OBHCyJBiQKwgAbPtEUz+1ifNbpvFi0kgBTPQQMNKDOT
231
+ 2tTkLPn4TGyEogWKmIIRkMHHetZHneccBA3ESX4TOAwinZrsoy0UqE1b2NKgCEWGBzyAjVNk
232
+ s4+nEKhE7UkDOMhwESvIBzaQIVGDOtSjHH2mLbMp0oRG1KGnQOlAV2oLFixiIJ8oAAJogYCa
233
+ 2vSmODWGTmmRDVrstKbZQMAqEBBUBHzDpkXNxSciwtSmOvWpUI2qVKdK1aoqJCAAOw
234
+
235
+ --goal_line
236
+ Content-Type: image/gif;
237
+ Content-Transfer-Encoding: base64
238
+ Content-Disposition: attachment; filename="varied-fold-missing-padding.gif"
239
+
240
+ R0lGODdhMgAoAPcAANK6tfdkZCwtJ5GRjL6+uZiZluXm4ts7NqOknupua6RcWL/Cvtzf3ePj3
241
+ urq5LkzLkFCPOGCftQ5NKmqpWtsaNLHws2/vszOy9jb2eTl38jIw9/g3NbZ18XIxcnKxSco
242
+ ItLSzc7OyfhPTtfa2dPTzufn4a6uqtXY1q+XkkZHQvRGQ7a3sbq6tbKzrtbW0dzc19vb1t
243
+ 3e2NXY2MDDwsfHwvdOTcPGw7W1sNvc1tjZ09XRzNXLxtTMxtTTztfX0tI2MvdNTM/Ev9
244
+ /g29lUUdTUz+I/OcrLycPDvrJNSc44M0tMRs/QypxhXKNzbrdDPqSkn+jo4tfY0s6yrcnJxIiLh8LE
245
+ w+d3c5trZtPX1etdWuDg26amofhiYPhQTuHh3OxiYPdWVfddXPheXfhdW/dUU/RKSMiZlcHC
246
+ vcaemvNRT/daWLajoe1bWNK3sdPV1PhXUru7ttPV08/GwuplY/hhXvhkY/hlZPdQUPRFQr6P
247
+ ir2SjfJNS/daWvhmZfhjYiorJtXLxfhYVepDPsbGwfZNSdHU09Y3M7JXU/dXV9yQi7/Bvz0+
248
+ ONnc2/dgX+U/OuTl4NLJw5yCffVJRvZZWJh7d5xeWeRBO8UzLvhVVN4/ONra1PhaWZCOivhe
249
+ XbM2MWFiXNDRy7u+u/ZLS9nZ1Mo2Ma8zLfhoZ8zKxe5IRN5+eqZjXp+gm/dfXfJJRtLV1NHR
250
+ zLe4s0lKRN7e2cvHwqKjns/S0c3NyNw3M8LDvp2emepCPJeYlOE9N/hfW8LFxPhkYfhSTvdL
251
+ SPVEQelBPcu5uLm7uqChnUhJQ8zMx7CxrLytqulDPvRBPfdIRPhOS/hWUfdoZ/hiX/hSTPdK
252
+ RvREP/A/OeA/O+Lj3cvLxqSfm9pEQOs7NvFAPPhbV/hdWZ+fmfdQSvVJQ/FCPes/OeU6NNA/
253
+ OpaMicHBvEpLRkdIQ8dgW+A2Muc7Nu9DP/RJRfdRTPhgXPdPSvJIQ+1CPOY+N+A6Ndc3MbpP
254
+ S6ysp8vMx0pLRcrKxcXFwK+SjdA1MCZFySH5BAEAAP8ALAAAAAAyACgAAAj/AD8IHEiwoMGD
255
+ CBMqXMiwocOHECMKmEixosWLGDNi/EPwD4VduXIVCDlSJEmTI1OiXKmypMuQuygIEAhhAYNH
256
+ UHLqzFmihAGfJR4FffTIQAOiQZP+XGrAAJSeORuMCJVC4LFTRjgI7cmVa4YMDRp4ueal7Niz
257
+ XsJ+zVCCbdee1050oBHLKigXRmR4adv2q1gtgGXJEiIksJbBhQOXJct47DVZWKosWYLO6qsY
258
+ F3xx2HBNrVgvh2XBgDFq9OgXo0uPeiEYtNnOoN3MGPSCsl1ZOGjMOCEENmhZMTT50LFjBwgi
259
+ Lnz4cEEExI4KOlyMEixLy2JZcRTBgSHL9odjoIBr/xq0AEuMDYdf4IgCKZGVBAlSSTkVYokt
260
+ KRHgJ0gkxwcM1oERQQABq8kSQmXfLREDDusR4AsWjAAHQxQAfAFGGHzwcYcKZihyDhppTMIH
261
+ G2TswYYULvwnywtxzNBCii+8EEJdCcawoCYusKBIIaT5IMccz7DiRwB+8JFGO3nkgQofAQTQ
262
+ SBhkiJJFEC5oAkMtxeDTgyaoyUjjMbYwqIkmURABRyi1uEBCBHuo0Ugf0ATAChitDDFEGl80
263
+ GUAYiNwhTCoXuIClCSCU9p+XVoWJw5hRuABKC77UosEee7wRRiNNssIHGO2gwgqmTvJRIh6o
264
+ 0HDBDE+E4MMoqiH63SmLav+SQ6NEXIBPMRYs04wzYHjTSCNZYMgGk01yMWcakjAzDDHFrKKB
265
+ C1GwKq0tX54i65izNpePCcosw00zhDyDSBhhfKrnF4148wY44TBTzQFr5EIDCUREEa202NCY
266
+ wik55EAmrSAsMcg32mxTDTVlgPPGJL1g2ogfrKjxzB7yzMMOOeWYQ8AS9LrgQg6rjnKKvvvM
267
+ ai+0RJAACjZwqHMLOdvMI08aaXQzCbmTTJIGOO2MQ88t9iAxATZLgECCxx4rd0o6VnnQ6MnM
268
+ gQBKPkf044896xShCzWEvPNMGm+kIQ0hgliSiT3+XHIFLLa8chxySLswBdMfpEBDo8z1QAIJ
269
+ r7z/Ygs/ExxyiT231OOIzIQkHo48ghRxANqXIEEFAUQf1wMRmCc3hb6DuHB5D6D3IPUU5/DT
270
+ jxOXXH1LEct0yvgyvBwgASmXOEFJC3BoEILbe5MAOhE0HCNQCoOEDjoJUodwRj4XENBEKan/
271
+ cIs1jugyDD2ZSJAEKQ88cEU2syBzBjJL9G18D8EPz0/oUoNi3xG2SO1BMld4csklPxyQSRFF
272
+ nJ3EJQ8ohSoGAIcQgOIURzhF0dwGAtANQnh1OwII2gcKUCBjECFQ2RIuADgmQA9/huBF9v4X
273
+ QAVwogUamIwOQjAI8oGCgvyoSgQr6D778AMZe3NfCPJRhSdU4oM/MMTs/wBYChOagB8KLFoP
274
+ /maLJdAQFEeQIQSOUMMQnIIfswAdCF6hw32cIRs/vAQpfvADIiqACvhI4GSK1kAEJtGJURze
275
+ GZy4BGQgcYKg6BsoQmALW3jgHLQIIym4V4pKoPEckOjjEyt4RQXWJ451O0d9bDGIfVCQhksI
276
+ QQiwsY8qbCGMlyjkCRP4xsk8cR9IrM85pHiGENhxClys4BrX2EdknEIRW/BgIQfQAiTSp4+z
277
+ fKIGUrnK4VFuEFOo4SwzqUlNIgMZ2OBHCyJBiQKwgAbPtEUz+1ifNbpvFi0kgBTPQQMNKDOT
278
+ 2tTkLPn4TGyEogWKmIIRkMHHetZHneccBA3ESX4TOAwinZrsoy0UqE1b2NKgCEWGBzyAjVNk
279
+ s4+nEKhE7UkDOMhwESvIBzaQIVGDOtSjHH2mLbMp0oRG1KGnQOlAV2oLFixiIJ8oAAJogYCa
280
+ 2vSmODWGTmmRDVrstKbZQMAqEBBUBHzDpkXNxSciwtSmOvWpUI2q
281
+ VKdK1aoqJCAAOw
282
+
283
+ --goal_line--
@@ -0,0 +1,133 @@
1
+ 'use strict'
2
+
3
+ // Testing bounce email contents related to errors occurring during SMTP dialog.
4
+ // Strategy: create an HMailItem via fixtures, invoke an outbound method, then
5
+ // verify that the correct bounce/temp_fail handler is called.
6
+
7
+ const { describe, it, beforeEach, afterEach } = require('node:test')
8
+ const assert = require('node:assert')
9
+ const dns = require('node:dns')
10
+ const fs = require('node:fs')
11
+ const path = require('node:path')
12
+
13
+ const constants = require('haraka-constants')
14
+
15
+ // Load outbound/index FIRST to avoid the circular-dependency boot-order issue:
16
+ // hmail.js → require('./index') while index.js is still loading causes queue.js
17
+ // to capture a stale (empty) module.exports for hmail.js.
18
+ const outbound = require('../../outbound')
19
+ const HMailItem = outbound.HMailItem
20
+ const TODOItem = require('../../outbound/todo')
21
+
22
+ const util_hmailitem = require('../fixtures/util_hmailitem')
23
+
24
+ const outbound_context = { TODOItem, exports: outbound }
25
+ const queue_dir = path.resolve(__dirname, '../test-queue')
26
+
27
+ // ── Helpers ───────────────────────────────────────────────────────────────────
28
+
29
+ const ensureQueueDir = () => fs.promises.mkdir(queue_dir, { recursive: true })
30
+
31
+ const cleanQueueDir = async () => {
32
+ if (!fs.existsSync(queue_dir)) return
33
+ for (const file of fs.readdirSync(queue_dir)) {
34
+ const full = path.resolve(queue_dir, file)
35
+ if (fs.lstatSync(full).isDirectory()) throw new Error(`unexpected subdirectory: ${full}`)
36
+ fs.unlinkSync(full)
37
+ }
38
+ }
39
+
40
+ /** Creates a mock HMailItem, resolving with it or rejecting on error. */
41
+ const mockHMailItem = (ctx, opts = {}) =>
42
+ new Promise((resolve, reject) => {
43
+ util_hmailitem.newMockHMailItem(ctx, reject, opts, resolve)
44
+ })
45
+
46
+ /**
47
+ * Intercepts `HMailItem.prototype[method]`, calls `assertion(this)` when invoked,
48
+ * then triggers the action under test via `trigger()`.
49
+ */
50
+ const interceptAndAssert = (method, assertion, trigger) =>
51
+ new Promise((resolve, reject) => {
52
+ const orig = HMailItem.prototype[method]
53
+ HMailItem.prototype[method] = function () {
54
+ try {
55
+ assertion(this)
56
+ resolve()
57
+ } catch (e) {
58
+ reject(e)
59
+ } finally {
60
+ HMailItem.prototype[method] = orig
61
+ }
62
+ }
63
+ trigger()
64
+ })
65
+
66
+ // ── Tests ─────────────────────────────────────────────────────────────────────
67
+
68
+ // [method, dsn_status, optional setup fn, trigger fn, test name]
69
+ const testCases = [
70
+ {
71
+ name: 'get_mx=DENY triggers bounce with dsn_status 5.1.2',
72
+ method: 'bounce',
73
+ status: '5.1.2',
74
+ setup: (h) => {
75
+ h.domain = h.todo.domain
76
+ },
77
+ trigger: (h) => HMailItem.prototype.get_mx_respond.apply(h, [constants.deny, {}]),
78
+ },
79
+ {
80
+ name: 'get_mx=DENYSOFT triggers temp_fail with dsn_status 4.1.2',
81
+ method: 'temp_fail',
82
+ status: '4.1.2',
83
+ setup: (h) => {
84
+ h.domain = h.todo.domain
85
+ },
86
+ trigger: (h) => HMailItem.prototype.get_mx_respond.apply(h, [constants.denysoft, {}]),
87
+ },
88
+ {
89
+ name: 'get_mx_error({code:NXDOMAIN}) triggers bounce with dsn_status 5.1.2',
90
+ method: 'bounce',
91
+ status: '5.1.2',
92
+ trigger: (h) => HMailItem.prototype.get_mx_error.apply(h, [{ code: dns.NXDOMAIN }]),
93
+ },
94
+ {
95
+ name: "get_mx_error({code:'SOME-OTHER-ERR'}) triggers temp_fail with dsn_status 4.1.0",
96
+ method: 'temp_fail',
97
+ status: '4.1.0',
98
+ trigger: (h) => HMailItem.prototype.get_mx_error.apply(h, [{ code: 'SOME-OTHER-ERR' }, {}]),
99
+ },
100
+ {
101
+ // RFC 7505 NULL MX → DSN.addr_null_mx → 5.1.10 (per haraka-dsn).
102
+ name: 'found_mx with NULL MX (RFC 7505) triggers bounce with dsn_status 5.1.10',
103
+ method: 'bounce',
104
+ status: '5.1.10',
105
+ trigger: (h) => HMailItem.prototype.found_mx.apply(h, [[{ priority: 0, exchange: '' }]]),
106
+ },
107
+ {
108
+ name: 'try_deliver with empty mxlist triggers temp_fail with dsn_status 5.1.2',
109
+ method: 'temp_fail',
110
+ status: '5.1.2',
111
+ setup: (h) => {
112
+ h.mxlist = []
113
+ },
114
+ trigger: (h) => HMailItem.prototype.try_deliver.apply(h, []),
115
+ },
116
+ ]
117
+
118
+ describe('outbound_bounce_net_errors', () => {
119
+ beforeEach(ensureQueueDir)
120
+ afterEach(cleanQueueDir)
121
+
122
+ for (const { name, method, status, setup, trigger } of testCases) {
123
+ it(name, async () => {
124
+ const mock_hmail = await mockHMailItem(outbound_context)
125
+ if (setup) setup(mock_hmail)
126
+ await interceptAndAssert(
127
+ method,
128
+ (h) => assert.equal(h.todo.rcpt_to[0].dsn_status, status, 'dsn_status'),
129
+ () => trigger(mock_hmail),
130
+ )
131
+ })
132
+ }
133
+ })
@@ -0,0 +1,226 @@
1
+ 'use strict'
2
+
3
+ // Testing bounce email contents related to errors occurring during SMTP dialog.
4
+ // These tests simulate a remote SMTP server responding with various error codes
5
+ // and verify that Haraka generates correctly formatted RFC 3464 DSN bounce messages.
6
+
7
+ const { describe, it, beforeEach, afterEach } = require('node:test')
8
+ const assert = require('node:assert')
9
+ const fs = require('node:fs')
10
+ const path = require('node:path')
11
+
12
+ // Load outbound/index FIRST to avoid the circular-dependency boot-order issue.
13
+ const outbound = require('../../outbound')
14
+ const HMailItem = outbound.HMailItem
15
+ const TODOItem = require('../../outbound/todo')
16
+ const obc = require('../../outbound/config')
17
+
18
+ const util_hmailitem = require('../fixtures/util_hmailitem')
19
+ const mock_sock = require('../fixtures/line_socket')
20
+
21
+ obc.cfg.pool_concurrency_max = 0
22
+
23
+ const outbound_context = { TODOItem, exports: outbound }
24
+ const queue_dir = path.resolve(__dirname, '../test-queue')
25
+
26
+ // ── Helpers ───────────────────────────────────────────────────────────────────
27
+
28
+ const ensureQueueDir = () => fs.promises.mkdir(queue_dir, { recursive: true })
29
+
30
+ const cleanQueueDir = async () => {
31
+ if (!fs.existsSync(queue_dir)) return
32
+ for (const file of fs.readdirSync(queue_dir)) {
33
+ const full = path.resolve(queue_dir, file)
34
+ if (fs.lstatSync(full).isDirectory()) throw new Error(`unexpected subdirectory: ${full}`)
35
+ fs.unlinkSync(full)
36
+ }
37
+ }
38
+
39
+ const mockHMailItem = (ctx, opts = {}) =>
40
+ new Promise((resolve, reject) => {
41
+ util_hmailitem.newMockHMailItem(ctx, reject, opts, resolve)
42
+ })
43
+
44
+ /** Spies on HMailItem.prototype.temp_fail and resolves when called. */
45
+ const interceptTempFail = (mock_hmail, mock_socket, assertion, conversation) =>
46
+ new Promise((resolve, reject) => {
47
+ const orig = HMailItem.prototype.temp_fail
48
+ HMailItem.prototype.temp_fail = function () {
49
+ try {
50
+ assertion(this)
51
+ resolve()
52
+ } catch (e) {
53
+ reject(e)
54
+ } finally {
55
+ HMailItem.prototype.temp_fail = orig
56
+ }
57
+ }
58
+ util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, reject, conversation, () => {})
59
+ })
60
+
61
+ /** Spies on outbound.send_email and resolves when called. */
62
+ const interceptSendEmail = (mock_hmail, mock_socket, assertion, conversation) =>
63
+ new Promise((resolve, reject) => {
64
+ const orig = outbound_context.exports.send_email
65
+ outbound_context.exports.send_email = (from, to, contents) => {
66
+ try {
67
+ assertion(contents)
68
+ resolve()
69
+ } catch (e) {
70
+ reject(e)
71
+ } finally {
72
+ outbound_context.exports.send_email = orig
73
+ }
74
+ }
75
+ util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, reject, conversation, () => {})
76
+ })
77
+
78
+ // ── Shared conversation building blocks ───────────────────────────────────────
79
+
80
+ const EHLO_PREAMBLE = [
81
+ { from: 'remote', line: '220 testing-smtp' },
82
+ { from: 'haraka', test: (l) => l.match(/^EHLO /), description: 'EHLO' },
83
+ { from: 'remote', line: '220-testing-smtp' },
84
+ { from: 'remote', line: '220 8BITMIME' },
85
+ ]
86
+ const MAIL_OK = [
87
+ { from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
88
+ { from: 'remote', line: '250 2.1.0 Ok' },
89
+ ]
90
+ const RCPT_OK = [
91
+ { from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
92
+ { from: 'remote', line: '250 2.1.5 Ok' },
93
+ ]
94
+ const QUIT = { from: 'haraka', test: 'QUIT', end_test: true }
95
+
96
+ // ── Tests ─────────────────────────────────────────────────────────────────────
97
+
98
+ // Permanent bounce tests: spy on send_email, check DSN bounce contents
99
+ const bounceCases = [
100
+ {
101
+ name: 'MAIL FROM 500 triggers RFC3464 bounce with status 5.0.0',
102
+ statusRe: /^Status: 5\.0\.0/m,
103
+ messageRe: /Absolutely not acceptable\. Basic Test Only\./,
104
+ conversation: [
105
+ ...EHLO_PREAMBLE,
106
+ { from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
107
+ { from: 'remote', line: '500 5.0.0 Absolutely not acceptable. Basic Test Only.' },
108
+ QUIT,
109
+ ],
110
+ },
111
+ {
112
+ name: 'RCPT-TO 5XX triggers RFC3464 bounce with status 5.1.1',
113
+ statusRe: /^Status: 5\.1\.1/m,
114
+ messageRe: /Not available and will not come back/,
115
+ conversation: [
116
+ ...EHLO_PREAMBLE,
117
+ ...MAIL_OK,
118
+ { from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
119
+ { from: 'remote', line: '550 5.1.1 Not available and will not come back' },
120
+ QUIT,
121
+ ],
122
+ },
123
+ {
124
+ name: 'DATA 5XX triggers RFC3464 bounce with status 5.6.0',
125
+ statusRe: /^Status: 5\.6\.0/m,
126
+ messageRe: /I never did and will like ascii art cats/,
127
+ conversation: [
128
+ ...EHLO_PREAMBLE,
129
+ ...MAIL_OK,
130
+ ...RCPT_OK,
131
+ { from: 'haraka', test: 'DATA' },
132
+ { from: 'remote', line: '550 5.6.0 I never did and will like ascii art cats.' },
133
+ QUIT,
134
+ ],
135
+ },
136
+ ]
137
+
138
+ // Temporary failure tests: spy on temp_fail, check DSN rcpt fields
139
+ const tempFailCases = [
140
+ {
141
+ name: 'early 3XX response triggers temp_fail with status 3.0.0',
142
+ dsn_status: '3.0.0',
143
+ dsn_action: 'delayed',
144
+ smtpRe: /No time for you right now/,
145
+ conversation: [
146
+ ...EHLO_PREAMBLE,
147
+ { from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
148
+ { from: 'remote', line: '300 3.0.0 No time for you right now' },
149
+ QUIT,
150
+ ],
151
+ },
152
+ {
153
+ name: 'RCPT-TO 4XX triggers temp_fail with status 4.0.0',
154
+ dsn_status: '4.0.0',
155
+ dsn_action: 'delayed',
156
+ smtpRe: /Currently not available\. Try again later\./,
157
+ conversation: [
158
+ ...EHLO_PREAMBLE,
159
+ ...MAIL_OK,
160
+ { from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
161
+ { from: 'remote', line: '400 4.0.0 Currently not available. Try again later.' },
162
+ QUIT,
163
+ ],
164
+ },
165
+ {
166
+ name: 'DATA 4XX triggers temp_fail with status 4.6.0',
167
+ dsn_status: '4.6.0',
168
+ dsn_action: 'delayed',
169
+ smtpRe: /Currently I do not like ascii art cats\./,
170
+ conversation: [
171
+ ...EHLO_PREAMBLE,
172
+ ...MAIL_OK,
173
+ ...RCPT_OK,
174
+ { from: 'haraka', test: 'DATA' },
175
+ { from: 'remote', line: '450 4.6.0 Currently I do not like ascii art cats.' },
176
+ QUIT,
177
+ ],
178
+ },
179
+ ]
180
+
181
+ describe('outbound_bounce_rfc3464', () => {
182
+ beforeEach(ensureQueueDir)
183
+ afterEach(cleanQueueDir)
184
+
185
+ describe('permanent bounce (send_email)', () => {
186
+ for (const { name, statusRe, messageRe, conversation } of bounceCases) {
187
+ it(name, async () => {
188
+ const mock_hmail = await mockHMailItem(outbound_context)
189
+ const mock_socket = mock_sock.connect('testhost', 'testport')
190
+ mock_socket.writable = true
191
+ await interceptSendEmail(
192
+ mock_hmail,
193
+ mock_socket,
194
+ (contents) => {
195
+ assert.match(contents, /^Content-type: message\/delivery-status/m)
196
+ assert.match(contents, /^Final-Recipient: rfc822;recipient@domain/m)
197
+ assert.match(contents, /^Action: failed/m)
198
+ assert.match(contents, statusRe)
199
+ assert.match(contents, messageRe)
200
+ },
201
+ conversation,
202
+ )
203
+ })
204
+ }
205
+ })
206
+
207
+ describe('temporary failure (temp_fail)', () => {
208
+ for (const { name, dsn_status, dsn_action, smtpRe, conversation } of tempFailCases) {
209
+ it(name, async () => {
210
+ const mock_hmail = await mockHMailItem(outbound_context)
211
+ const mock_socket = mock_sock.connect('testhost', 'testport')
212
+ mock_socket.writable = true
213
+ await interceptTempFail(
214
+ mock_hmail,
215
+ mock_socket,
216
+ (h) => {
217
+ assert.equal(h.todo.rcpt_to[0].dsn_status, dsn_status)
218
+ assert.equal(h.todo.rcpt_to[0].dsn_action, dsn_action)
219
+ assert.match(h.todo.rcpt_to[0].dsn_smtp_response, smtpRe)
220
+ },
221
+ conversation,
222
+ )
223
+ })
224
+ }
225
+ })
226
+ })