Haraka 3.0.2 → 3.0.4

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 (267) hide show
  1. package/.eslintrc.yaml +5 -9
  2. package/.prettierrc.yml +1 -0
  3. package/CONTRIBUTORS.md +11 -0
  4. package/Changes.md +1393 -1211
  5. package/Dockerfile +3 -3
  6. package/Plugins.md +119 -106
  7. package/README.md +7 -16
  8. package/TODO +1 -24
  9. package/bin/haraka +197 -298
  10. package/config/auth_flat_file.ini +2 -0
  11. package/config/auth_vpopmaild.ini +4 -2
  12. package/config/dhparams.pem +8 -0
  13. package/config/mail_from.is_resolvable.ini +4 -2
  14. package/config/me +1 -0
  15. package/config/outbound.ini +0 -2
  16. package/config/plugins +36 -35
  17. package/config/rabbitmq_amqplib.ini +8 -1
  18. package/config/smtp.ini +0 -1
  19. package/config/smtp.json +17 -0
  20. package/config/tls_cert.pem +23 -0
  21. package/config/tls_key.pem +28 -0
  22. package/connection.js +46 -73
  23. package/contrib/bsd-rc.d/haraka +3 -1
  24. package/contrib/plugin2npm.sh +6 -36
  25. package/docs/Connection.md +1 -1
  26. package/docs/CoreConfig.md +2 -2
  27. package/docs/Logging.md +7 -21
  28. package/docs/Outbound.md +104 -210
  29. package/docs/Plugins.md +47 -40
  30. package/docs/Transaction.md +59 -82
  31. package/docs/{plugins → deprecated}/connect.rdns_access.md +1 -1
  32. package/docs/{plugins → deprecated}/mail_from.access.md +1 -1
  33. package/docs/{plugins → deprecated}/rcpt_to.access.md +1 -1
  34. package/docs/plugins/auth/auth_vpopmaild.md +15 -19
  35. package/docs/plugins/auth/flat_file.md +23 -30
  36. package/docs/plugins/queue/rabbitmq_amqplib.md +7 -0
  37. package/docs/plugins/queue/smtp_forward.md +1 -1
  38. package/docs/plugins/queue/smtp_proxy.md +5 -10
  39. package/docs/plugins/relay.md +2 -2
  40. package/docs/plugins/tls.md +29 -9
  41. package/endpoint.js +16 -13
  42. package/haraka.js +10 -14
  43. package/host_pool.js +5 -5
  44. package/line_socket.js +3 -4
  45. package/logger.js +44 -28
  46. package/outbound/client_pool.js +27 -23
  47. package/outbound/config.js +4 -6
  48. package/outbound/fsync_writestream.js +1 -1
  49. package/outbound/hmail.js +180 -220
  50. package/outbound/index.js +86 -99
  51. package/outbound/qfile.js +1 -1
  52. package/outbound/queue.js +55 -43
  53. package/outbound/timer_queue.js +3 -2
  54. package/outbound/tls.js +19 -7
  55. package/package.json +66 -55
  56. package/plugins/.eslintrc.yaml +0 -6
  57. package/plugins/auth/auth_base.js +30 -12
  58. package/plugins/auth/auth_proxy.js +14 -12
  59. package/plugins/auth/auth_vpopmaild.js +30 -20
  60. package/plugins/auth/flat_file.js +17 -12
  61. package/plugins/block_me.js +1 -1
  62. package/plugins/data.signatures.js +2 -4
  63. package/plugins/early_talker.js +2 -1
  64. package/plugins/mail_from.is_resolvable.js +65 -135
  65. package/plugins/queue/deliver.js +4 -5
  66. package/plugins/queue/lmtp.js +11 -14
  67. package/plugins/queue/qmail-queue.js +2 -2
  68. package/plugins/queue/quarantine.js +2 -2
  69. package/plugins/queue/rabbitmq.js +16 -17
  70. package/plugins/queue/rabbitmq_amqplib.js +1 -1
  71. package/plugins/queue/smtp_forward.js +6 -6
  72. package/plugins/queue/smtp_proxy.js +10 -1
  73. package/plugins/queue/test.js +2 -2
  74. package/plugins/rcpt_to.host_list_base.js +5 -5
  75. package/plugins/rcpt_to.in_host_list.js +2 -2
  76. package/plugins/relay.js +6 -7
  77. package/plugins/reseed_rng.js +1 -1
  78. package/plugins/status.js +37 -33
  79. package/plugins/tls.js +2 -2
  80. package/plugins/xclient.js +3 -2
  81. package/plugins.js +51 -54
  82. package/run_tests +3 -30
  83. package/server.js +190 -190
  84. package/smtp_client.js +30 -23
  85. package/{tests → test}/config/plugins +0 -2
  86. package/{tests → test}/config/smtp.ini +1 -1
  87. package/test/config/tls/example.com/_.example.com.key +28 -0
  88. package/test/config/tls/example.com/example.com.crt +25 -0
  89. package/test/connection.js +302 -0
  90. package/test/endpoint.js +94 -0
  91. package/{tests → test}/fixtures/line_socket.js +1 -1
  92. package/{tests → test}/fixtures/util_hmailitem.js +19 -25
  93. package/{tests → test}/host_pool.js +42 -57
  94. package/test/logger.js +258 -0
  95. package/test/outbound/hmail.js +141 -0
  96. package/test/outbound/index.js +220 -0
  97. package/test/outbound/qfile.js +126 -0
  98. package/test/outbound_bounce_net_errors.js +142 -0
  99. package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
  100. package/test/plugins/auth/auth_base.js +484 -0
  101. package/test/plugins/auth/auth_vpopmaild.js +83 -0
  102. package/test/plugins/early_talker.js +104 -0
  103. package/test/plugins/mail_from.is_resolvable.js +35 -0
  104. package/test/plugins/queue/smtp_forward.js +206 -0
  105. package/test/plugins/rcpt_to.host_list_base.js +122 -0
  106. package/test/plugins/rcpt_to.in_host_list.js +193 -0
  107. package/test/plugins/relay.js +303 -0
  108. package/test/plugins/status.js +130 -0
  109. package/test/plugins/tls.js +70 -0
  110. package/test/plugins.js +228 -0
  111. package/{tests → test}/queue/multibyte +0 -0
  112. package/{tests → test}/queue/plain +0 -0
  113. package/test/rfc1869.js +73 -0
  114. package/test/server.js +491 -0
  115. package/test/smtp_client.js +299 -0
  116. package/test/tls_socket.js +273 -0
  117. package/test/transaction.js +270 -0
  118. package/tls_socket.js +202 -252
  119. package/transaction.js +9 -24
  120. package/CONTRIBUTING.md +0 -1
  121. package/bin/dkimverify +0 -40
  122. package/config/access.domains +0 -13
  123. package/config/attachment.ctype.regex +0 -2
  124. package/config/attachment.filename.regex +0 -1
  125. package/config/avg.ini +0 -5
  126. package/config/bounce.ini +0 -15
  127. package/config/data.headers.ini +0 -61
  128. package/config/dkim/dkim_key_gen.sh +0 -78
  129. package/config/dkim_sign.ini +0 -4
  130. package/config/dkim_verify.ini +0 -7
  131. package/config/dnsbl.ini +0 -23
  132. package/config/greylist.ini +0 -43
  133. package/config/helo.checks.ini +0 -52
  134. package/config/lookup_rdns.strict.ini +0 -12
  135. package/config/lookup_rdns.strict.timeout +0 -1
  136. package/config/lookup_rdns.strict.whitelist +0 -1
  137. package/config/lookup_rdns.strict.whitelist_regex +0 -5
  138. package/config/messagesniffer.ini +0 -18
  139. package/config/rcpt_to.blocklist +0 -1
  140. package/config/rdns.allow_regexps +0 -0
  141. package/config/rdns.deny_regexps +0 -0
  142. package/config/spamassassin.ini +0 -56
  143. package/config.js +0 -6
  144. package/dkim.js +0 -614
  145. package/docs/plugins/avg.md +0 -35
  146. package/docs/plugins/bounce.md +0 -69
  147. package/docs/plugins/clamd.md +0 -147
  148. package/docs/plugins/esets.md +0 -8
  149. package/docs/plugins/greylist.md +0 -90
  150. package/docs/plugins/helo.checks.md +0 -135
  151. package/docs/plugins/messagesniffer.md +0 -163
  152. package/docs/plugins/relay_acl.md +0 -29
  153. package/docs/plugins/relay_all.md +0 -15
  154. package/docs/plugins/relay_force_routing.md +0 -33
  155. package/docs/plugins/spamassassin.md +0 -180
  156. package/outbound/mx_lookup.js +0 -70
  157. package/plugins/auth/auth_ldap.js +0 -3
  158. package/plugins/avg.js +0 -162
  159. package/plugins/backscatterer.js +0 -25
  160. package/plugins/bounce.js +0 -381
  161. package/plugins/clamd.js +0 -381
  162. package/plugins/data.headers.js +0 -4
  163. package/plugins/data.uribl.js +0 -4
  164. package/plugins/dkim_sign.js +0 -395
  165. package/plugins/dkim_verify.js +0 -62
  166. package/plugins/dns_list_base.js +0 -221
  167. package/plugins/dnsbl.js +0 -146
  168. package/plugins/dnswl.js +0 -58
  169. package/plugins/esets.js +0 -71
  170. package/plugins/graph.js +0 -5
  171. package/plugins/greylist.js +0 -645
  172. package/plugins/helo.checks.js +0 -533
  173. package/plugins/messagesniffer.js +0 -381
  174. package/plugins/rcpt_to.ldap.js +0 -3
  175. package/plugins/rcpt_to.max_count.js +0 -24
  176. package/plugins/relay_all.js +0 -13
  177. package/plugins/spamassassin.js +0 -384
  178. package/tests/config/dkim/example.com/dns +0 -29
  179. package/tests/config/dkim/example.com/private +0 -6
  180. package/tests/config/dkim/example.com/public +0 -4
  181. package/tests/config/dkim/example.com/selector +0 -1
  182. package/tests/config/dkim.private.key +0 -6
  183. package/tests/config/dkim_sign.ini +0 -4
  184. package/tests/config/helo.checks.ini +0 -52
  185. package/tests/connection.js +0 -327
  186. package/tests/endpoint.js +0 -128
  187. package/tests/fixtures/vm_harness.js +0 -59
  188. package/tests/logger.js +0 -327
  189. package/tests/outbound/hmail.js +0 -112
  190. package/tests/outbound/index.js +0 -324
  191. package/tests/outbound/qfile.js +0 -67
  192. package/tests/outbound_bounce_net_errors.js +0 -173
  193. package/tests/plugins/auth/auth_base.js +0 -463
  194. package/tests/plugins/auth/auth_vpopmaild.js +0 -91
  195. package/tests/plugins/bounce.js +0 -307
  196. package/tests/plugins/clamd.js +0 -224
  197. package/tests/plugins/deprecated/relay_acl.js +0 -140
  198. package/tests/plugins/deprecated/relay_all.js +0 -59
  199. package/tests/plugins/dkim_sign.js +0 -315
  200. package/tests/plugins/dkim_signer.js +0 -108
  201. package/tests/plugins/dns_list_base.js +0 -259
  202. package/tests/plugins/dnsbl.js +0 -101
  203. package/tests/plugins/early_talker.js +0 -115
  204. package/tests/plugins/greylist.js +0 -58
  205. package/tests/plugins/helo.checks.js +0 -525
  206. package/tests/plugins/mail_from.is_resolvable.js +0 -116
  207. package/tests/plugins/queue/smtp_forward.js +0 -221
  208. package/tests/plugins/rcpt_to.host_list_base.js +0 -132
  209. package/tests/plugins/rcpt_to.in_host_list.js +0 -218
  210. package/tests/plugins/relay.js +0 -339
  211. package/tests/plugins/spamassassin.js +0 -171
  212. package/tests/plugins/status.js +0 -138
  213. package/tests/plugins/tls.js +0 -84
  214. package/tests/plugins.js +0 -247
  215. package/tests/rfc1869.js +0 -61
  216. package/tests/server.js +0 -510
  217. package/tests/smtp_client/auth.js +0 -105
  218. package/tests/smtp_client/basic.js +0 -101
  219. package/tests/smtp_client.js +0 -80
  220. package/tests/tls_socket.js +0 -333
  221. package/tests/transaction.js +0 -284
  222. /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
  223. /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
  224. /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
  225. /package/docs/{plugins → deprecated}/dnswl.md +0 -0
  226. /package/docs/{plugins → deprecated}/rcpt_to.routes.md +0 -0
  227. /package/{tests → test}/.eslintrc.yaml +0 -0
  228. /package/{tests → test}/config/auth_flat_file.ini +0 -0
  229. /package/{tests → test}/config/dhparams.pem +0 -0
  230. /package/{tests → test}/config/host_list +0 -0
  231. /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
  232. /package/{tests → test}/config/outbound_tls_key.pem +0 -0
  233. /package/{tests → test}/config/smtp_forward.ini +0 -0
  234. /package/{tests → test}/config/tls/ec.pem +0 -0
  235. /package/{tests → test}/config/tls/haraka.local.pem +0 -0
  236. /package/{tests → test}/config/tls/mismatched.pem +0 -0
  237. /package/{tests → test}/config/tls.ini +0 -0
  238. /package/{tests → test}/config/tls_cert.pem +0 -0
  239. /package/{tests → test}/config/tls_key.pem +0 -0
  240. /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
  241. /package/{tests → test}/installation/config/test-plugin-flat +0 -0
  242. /package/{tests → test}/installation/config/test-plugin.ini +0 -0
  243. /package/{tests → test}/installation/config/tls.ini +0 -0
  244. /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
  245. /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
  246. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
  247. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
  248. /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
  249. /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
  250. /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
  251. /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
  252. /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
  253. /package/{tests → test}/installation/plugins/inherits.js +0 -0
  254. /package/{tests → test}/installation/plugins/load_first.js +0 -0
  255. /package/{tests → test}/installation/plugins/plugin.js +0 -0
  256. /package/{tests → test}/installation/plugins/tls.js +0 -0
  257. /package/{tests → test}/loud/config/dhparams.pem +0 -0
  258. /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
  259. /package/{tests → test}/loud/config/tls.ini +0 -0
  260. /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
  261. /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
  262. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  263. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  264. /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  265. /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  266. /package/{tests → test}/queue/zero-length +0 -0
  267. /package/{tests → test}/test-queue/delete-me +0 -0
package/plugins/bounce.js DELETED
@@ -1,381 +0,0 @@
1
- // bounce tests
2
- const tlds = require('haraka-tld');
3
- const { SPF } = require('haraka-plugin-spf');
4
-
5
- const net_utils = require('haraka-net-utils');
6
-
7
- exports.register = function () {
8
- this.load_bounce_ini();
9
- this.load_bounce_bad_rcpt();
10
-
11
- this.register_hook('mail', 'reject_all');
12
- this.register_hook('data', 'single_recipient');
13
- this.register_hook('data', 'bad_rcpt');
14
- this.register_hook('data_post', 'empty_return_path');
15
- this.register_hook('data', 'bounce_spf_enable');
16
- this.register_hook('data_post', 'bounce_spf');
17
- this.register_hook('data_post', 'non_local_msgid');
18
- }
19
-
20
- exports.load_bounce_bad_rcpt = function () {
21
-
22
- const new_list = this.config.get('bounce_bad_rcpt', 'list', () => {
23
- this.load_bounce_bad_rcpt();
24
- });
25
-
26
- const invalids = {};
27
- for (const element of new_list) {
28
- invalids[element] = true;
29
- }
30
-
31
- this.cfg.invalid_addrs = invalids;
32
- }
33
-
34
- exports.load_bounce_ini = function () {
35
- this.cfg = this.config.get('bounce.ini', {
36
- booleans: [
37
- '-check.reject_all',
38
- '+check.single_recipient',
39
- '-check.empty_return_path',
40
- '+check.bad_rcpt',
41
- '+check.bounce_spf',
42
- '+check.non_local_msgid',
43
-
44
- '+reject.single_recipient',
45
- '-reject.empty_return_path',
46
- '-reject.bounce_spf',
47
- '-reject.non_local_msgid',
48
- ],
49
- }, () => {
50
- this.load_bounce_ini();
51
- });
52
-
53
- // Legacy config handling
54
- if (this.cfg.main.reject_invalid) {
55
- this.logerror('bounce.ini is out of date, please update!');
56
- this.cfg.check.single_recipient=true;
57
- this.cfg.reject.single_recipient=true;
58
- }
59
-
60
- if (this.cfg.main.reject_all) {
61
- this.logerror('bounce.ini is out of date, please update!');
62
- this.cfg.check.reject_all=true;
63
- }
64
- }
65
-
66
- exports.reject_all = function (next, connection, params) {
67
- if (!this.cfg.check.reject_all) return next();
68
-
69
- const mail_from = params[0];
70
- // bounce messages are from null senders
71
- if (!this.has_null_sender(connection, mail_from)) return next();
72
-
73
- connection.transaction.results.add(this, {fail: 'bounces_accepted', emit: true });
74
- return next(DENY, 'No bounces accepted here');
75
- }
76
-
77
- exports.single_recipient = function (next, connection) {
78
- if (!this?.cfg?.check?.single_recipient) return next();
79
- if (!this?.has_null_sender(connection)) return next();
80
- const { transaction, relaying, remote } = connection;
81
-
82
- // Valid bounces have a single recipient
83
- if (transaction.rcpt_to.length === 1) {
84
- transaction.results.add(this, {pass: 'single_recipient', emit: true });
85
- return next();
86
- }
87
-
88
- // Skip this check for relays or private_ips
89
- // This is because Microsoft Exchange will send mail
90
- // to distribution groups using the null-sender if
91
- // the option 'Do not send delivery reports' is
92
- // checked (not sure if this is default or not)
93
- if (relaying) {
94
- transaction.results.add(this, {skip: 'single_recipient(relay)', emit: true });
95
- return next();
96
- }
97
- if (remote.is_private) {
98
- transaction.results.add(this, {skip: 'single_recipient(private_ip)', emit: true });
99
- return next();
100
- }
101
-
102
- connection.loginfo(this, `bounce with too many recipients to: ${transaction.rcpt_to.join(',')}`);
103
-
104
- transaction.results.add(this, {fail: 'single_recipient', emit: true });
105
-
106
- if (!this.cfg.reject.single_recipient) return next();
107
-
108
- return next(DENY, 'this bounce message does not have 1 recipient');
109
- }
110
-
111
- exports.empty_return_path = function (next, connection) {
112
- if (!this.cfg.check.empty_return_path) return next();
113
- if (!this.has_null_sender(connection)) return next();
114
-
115
- const { transaction } = connection;
116
- // Bounce messages generally do not have a Return-Path set. This checks
117
- // for that. But whether it should is worth questioning...
118
-
119
- // On Jan 20, 2014, Matt Simerson examined the most recent 50,000 mail
120
- // connections for the presence of Return-Path in bounce messages. I
121
- // found 14 hits, 12 of which were from Google, in response to
122
- // undeliverable DMARC reports (IE, automated messages that Google
123
- // shouldn't have replied to). Another appears to be a valid bounce from
124
- // a poorly configured mailer, and the 14th was a confirmed spam kill.
125
- // Unless new data demonstrate otherwise, this should remain disabled.
126
-
127
- // Return-Path, aka Reverse-PATH, Envelope FROM, RFC5321.MailFrom
128
- // validate that the Return-Path header is empty, RFC 3834
129
-
130
- const rp = transaction.header.get('Return-Path');
131
- if (!rp) {
132
- transaction.results.add(this, {pass: 'empty_return_path' });
133
- return next();
134
- }
135
-
136
- if (rp === '<>') {
137
- transaction.results.add(this, {pass: 'empty_return_path' });
138
- return next();
139
- }
140
-
141
- transaction.results.add(this, {fail: 'empty_return_path', emit: true });
142
- return next(DENY, 'bounce with non-empty Return-Path (RFC 3834)');
143
- }
144
-
145
- exports.bad_rcpt = function (next, connection) {
146
- if (!this.cfg.check.bad_rcpt) return next();
147
- if (!this.has_null_sender(connection)) return next();
148
- if (!this.cfg.invalid_addrs) return next();
149
-
150
- const { transaction } = connection;
151
- for (const element of transaction.rcpt_to) {
152
- const rcpt = element.address();
153
- if (!this.cfg.invalid_addrs[rcpt]) continue;
154
- transaction.results.add(this, {fail: 'bad_rcpt', emit: true });
155
- return next(DENY, 'That recipient does not accept bounces');
156
- }
157
- transaction.results.add(this, {pass: 'bad_rcpt'});
158
-
159
- return next();
160
- }
161
-
162
- exports.has_null_sender = function (connection, mail_from) {
163
- // ok ?
164
- const transaction = connection?.transaction;
165
- if (!transaction) return false;
166
-
167
- if (!mail_from) mail_from = transaction.mail_from;
168
-
169
- // bounces have a null sender.
170
- // null sender could also be tested with mail_from.user
171
- // Why would isNull() exist if it wasn't the right way to test this?
172
- if (mail_from.isNull()) {
173
- transaction.results.add(this, {isa: 'yes'});
174
- return true;
175
- }
176
-
177
- transaction.results.add(this, {isa: 'no'});
178
- return false;
179
- }
180
-
181
- const message_id_re = /^Message-ID:\s*(<?[^>]+>?)/mig;
182
-
183
- function find_message_id_headers (headers, body, connection, self) {
184
- if (!body) return;
185
-
186
- let match;
187
- while ((match = message_id_re.exec(body.bodytext))) {
188
- const mid = match[1];
189
- headers[mid] = true;
190
- }
191
-
192
- for (let i=0,l=body.children.length; i < l; i++) {
193
- // Recure to any MIME children
194
- find_message_id_headers(headers, body.children[i], connection, self);
195
- }
196
- }
197
-
198
- exports.non_local_msgid = function (next, connection) {
199
- if (!this.cfg.check.non_local_msgid) return next();
200
- if (!this.has_null_sender(connection)) return next();
201
-
202
- const transaction = connection?.transaction;
203
- if (!transaction) return next();
204
- // Bounce messages usually contain the headers of the original message
205
- // in the body. This parses the body, searching for the Message-ID header.
206
- // It then inspects the contents of that header, extracting the domain part,
207
- // and then checks to see if that domain is local to this server.
208
-
209
- // NOTE: this only works reliably if *every* message sent has a local
210
- // domain in the Message-ID. In practice, that means outbound MXes MUST
211
- // check Message-ID on outbound and modify non-conforming Message-IDs.
212
- //
213
- // NOTE 2: Searching the bodytext of a bounce is too simple. The bounce
214
- // message should exist as a MIME Encoded part. See here for ideas
215
- // http://lamsonproject.org/blog/2009-07-09.html
216
- // http://lamsonproject.org/docs/bounce_detection.html
217
-
218
- let matches = {}
219
- find_message_id_headers(matches, transaction.body, connection, this);
220
- matches = Object.keys(matches);
221
- connection.logdebug(this, `found Message-IDs: ${matches.join(', ')}`);
222
-
223
- if (!matches.length) {
224
- connection.loginfo(this, 'no Message-ID matches');
225
- transaction.results.add(this, { fail: 'Message-ID' });
226
- if (!this.cfg.reject.non_local_msgid) return next();
227
- return next(DENY, `bounce without Message-ID in headers, unable to verify that I sent it`);
228
- }
229
-
230
- const domains=[];
231
- for (const match of matches) {
232
- const res = match.match(/@([^>]*)>?/i);
233
- if (!res) continue;
234
- domains.push(res[1]);
235
- }
236
-
237
- if (domains.length === 0) {
238
- connection.loginfo(this, 'no domain(s) parsed from Message-ID headers');
239
- transaction.results.add(this, { fail: 'Message-ID parseable' });
240
- if (!this.cfg.reject.non_local_msgid) return next();
241
- return next(DENY, `bounce with invalid Message-ID, I didn't send it.`);
242
- }
243
-
244
- connection.logdebug(this, domains);
245
-
246
- const valid_domains=[];
247
- for (const domain of domains) {
248
- const org_dom = tlds.get_organizational_domain(domain);
249
- if (!org_dom) { continue; }
250
- valid_domains.push(org_dom);
251
- }
252
-
253
- if (valid_domains.length === 0) {
254
- transaction.results.add(this, { fail: 'Message-ID valid domain' });
255
- if (!this.cfg.reject.non_local_msgid) return next();
256
- return next(DENY, `bounce Message-ID without valid domain, I didn't send it.`);
257
- }
258
-
259
- return next();
260
-
261
- /* The code below needs some kind of test to say the domain isn't local.
262
- this would be hard to do without knowing how you have Haraka configured.
263
- e.g. it could be config/host_list, or it could be some other way.
264
- - hence I added the return next() above or this test can never be correct.
265
- */
266
- // we wouldn't have accepted the bounce if the recipient wasn't local
267
- // transaction.results.add(plugin,
268
- // {fail: 'Message-ID not local', emit: true });
269
- // if (!plugin.cfg.reject.non_local_msgid) return next();
270
- // return next(DENY, "bounce with non-local Message-ID (RFC 3834)");
271
- }
272
-
273
- // Lazy regexp to get IPs from Received: headers in bounces
274
- const received_re = net_utils.get_ipany_re('^Received:[\\s\\S]*?[\\[\\(](?:IPv6:)?', '[\\]\\)]');
275
-
276
- function find_received_headers (ips, body, connection, self) {
277
- if (!body) return;
278
- let match;
279
- while ((match = received_re.exec(body.bodytext))) {
280
- const ip = match[1];
281
- if (net_utils.is_private_ip(ip)) continue;
282
- ips[ip] = true;
283
- }
284
- for (let i=0,l=body.children.length; i < l; i++) {
285
- // Recurse in any MIME children
286
- find_received_headers(ips, body.children[i], connection, self);
287
- }
288
- }
289
-
290
- exports.bounce_spf_enable = function (next, connection) {
291
- if (!connection.transaction) return next();
292
- if (this.cfg.check.bounce_spf) {
293
- connection.transaction.parse_body = true;
294
- }
295
- return next();
296
- }
297
-
298
- exports.bounce_spf = function (next, connection) {
299
- if (!this.cfg.check.bounce_spf) return next();
300
- if (!this.has_null_sender(connection)) return next();
301
-
302
- const txn = connection?.transaction;
303
- if (!txn) return next();
304
-
305
- // Recurse through all textual parts and store all parsed IPs
306
- // in an object to remove any duplicates which might appear.
307
- let ips = {};
308
- find_received_headers(ips, txn.body, connection, this);
309
- ips = Object.keys(ips);
310
- if (!ips.length) {
311
- connection.loginfo(this, 'No received headers found in message');
312
- return next();
313
- }
314
-
315
- connection.logdebug(this, `found IPs to check: ${ips.join(', ')}`);
316
-
317
- let pending = 0;
318
- let aborted = false;
319
- let called_cb = false;
320
- let timer;
321
-
322
- function run_cb (abort, retval, msg) {
323
- if (aborted) return;
324
- if (abort) aborted = true;
325
- if (!aborted && pending > 0) return;
326
- if (called_cb) return;
327
- clearTimeout(timer);
328
- called_cb = true;
329
- return next(retval, msg);
330
- }
331
-
332
- timer = setTimeout(() => {
333
- connection.logerror(this, 'Timed out');
334
- txn.results.add(this, { skip: 'bounce_spf(timeout)' });
335
- return run_cb(true);
336
- }, (this.timeout - 1) * 1000);
337
-
338
- ips.forEach(ip => {
339
- if (aborted) return;
340
- const spf = new SPF();
341
- pending++;
342
- spf.check_host(ip, txn.rcpt_to[0].host, txn.rcpt_to[0].address(),
343
- (err, result) => {
344
- if (aborted) return;
345
- pending--;
346
- if (err) {
347
- connection.logerror(this, err.message);
348
- return run_cb();
349
- }
350
- connection.logdebug(this, `ip=${ip} spf_result=${spf.result(result)}`);
351
- switch (result) {
352
- case (spf.SPF_NONE):
353
- // falls through, domain doesn't publish an SPF record
354
- case (spf.SPF_TEMPERROR):
355
- case (spf.SPF_PERMERROR):
356
- // Abort as all subsequent lookups will return this
357
- connection.logdebug(this, `Aborted: SPF returned ${spf.result(result)}`);
358
- txn.results.add(this, { skip: 'bounce_spf' });
359
- return run_cb(true);
360
- case (spf.SPF_PASS):
361
- // Presume this is a valid bounce
362
- // TODO: this could be spoofed; could weight each IP to combat
363
- connection.loginfo(this, `Valid bounce originated from ${ip}`);
364
- txn.results.add(this, { pass: 'bounce_spf' });
365
- return run_cb(true);
366
- }
367
- if (pending === 0 && !aborted) {
368
- // We've checked all the IPs and none of them returned Pass
369
- txn.results.add(this, {fail: 'bounce_spf', emit: true });
370
- if (!this.cfg.reject.bounce_spf) return run_cb();
371
- return run_cb(false, DENY, 'Invalid bounce (spoofed sender)');
372
- }
373
- }
374
- );
375
- if (pending === 0 && !aborted) {
376
- // No lookups run for some reason
377
- return run_cb();
378
- }
379
- });
380
- }
381
-