Haraka 3.0.3 → 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 (238) hide show
  1. package/.eslintrc.yaml +5 -9
  2. package/.prettierrc.yml +1 -0
  3. package/CONTRIBUTORS.md +11 -0
  4. package/Changes.md +1365 -1214
  5. package/Plugins.md +117 -105
  6. package/README.md +4 -13
  7. package/bin/haraka +197 -298
  8. package/config/auth_flat_file.ini +1 -0
  9. package/config/dhparams.pem +8 -0
  10. package/config/mail_from.is_resolvable.ini +4 -2
  11. package/config/me +1 -0
  12. package/config/outbound.ini +0 -2
  13. package/config/plugins +36 -35
  14. package/config/smtp.ini +0 -1
  15. package/config/smtp.json +17 -0
  16. package/config/tls_cert.pem +23 -0
  17. package/config/tls_key.pem +28 -0
  18. package/connection.js +46 -73
  19. package/contrib/bsd-rc.d/haraka +3 -1
  20. package/contrib/plugin2npm.sh +6 -36
  21. package/docs/CoreConfig.md +2 -2
  22. package/docs/Logging.md +7 -21
  23. package/docs/Outbound.md +104 -201
  24. package/docs/Plugins.md +2 -2
  25. package/docs/Transaction.md +59 -82
  26. package/docs/plugins/queue/smtp_proxy.md +5 -10
  27. package/docs/plugins/tls.md +29 -9
  28. package/endpoint.js +16 -13
  29. package/haraka.js +10 -14
  30. package/host_pool.js +5 -5
  31. package/line_socket.js +3 -4
  32. package/logger.js +44 -28
  33. package/outbound/client_pool.js +27 -23
  34. package/outbound/config.js +4 -6
  35. package/outbound/fsync_writestream.js +1 -1
  36. package/outbound/hmail.js +178 -218
  37. package/outbound/index.js +86 -99
  38. package/outbound/qfile.js +1 -1
  39. package/outbound/queue.js +51 -44
  40. package/outbound/timer_queue.js +3 -2
  41. package/outbound/tls.js +19 -7
  42. package/package.json +59 -48
  43. package/plugins/.eslintrc.yaml +0 -6
  44. package/plugins/auth/auth_base.js +4 -2
  45. package/plugins/auth/auth_proxy.js +14 -12
  46. package/plugins/auth/auth_vpopmaild.js +1 -1
  47. package/plugins/block_me.js +1 -1
  48. package/plugins/data.signatures.js +2 -4
  49. package/plugins/early_talker.js +2 -1
  50. package/plugins/mail_from.is_resolvable.js +65 -135
  51. package/plugins/queue/deliver.js +4 -5
  52. package/plugins/queue/lmtp.js +11 -14
  53. package/plugins/queue/qmail-queue.js +2 -2
  54. package/plugins/queue/quarantine.js +2 -2
  55. package/plugins/queue/rabbitmq.js +16 -17
  56. package/plugins/queue/smtp_forward.js +3 -3
  57. package/plugins/queue/smtp_proxy.js +10 -1
  58. package/plugins/queue/test.js +2 -2
  59. package/plugins/rcpt_to.host_list_base.js +5 -5
  60. package/plugins/rcpt_to.in_host_list.js +2 -2
  61. package/plugins/relay.js +6 -7
  62. package/plugins/reseed_rng.js +1 -1
  63. package/plugins/status.js +37 -33
  64. package/plugins/tls.js +2 -2
  65. package/plugins/xclient.js +3 -2
  66. package/plugins.js +50 -54
  67. package/run_tests +3 -30
  68. package/server.js +190 -190
  69. package/smtp_client.js +30 -23
  70. package/{tests → test}/config/plugins +0 -2
  71. package/{tests → test}/config/smtp.ini +1 -1
  72. package/test/config/tls/example.com/_.example.com.key +28 -0
  73. package/test/config/tls/example.com/example.com.crt +25 -0
  74. package/test/connection.js +302 -0
  75. package/test/endpoint.js +94 -0
  76. package/{tests → test}/fixtures/line_socket.js +1 -1
  77. package/{tests → test}/fixtures/util_hmailitem.js +19 -25
  78. package/{tests → test}/host_pool.js +42 -57
  79. package/test/logger.js +258 -0
  80. package/test/outbound/hmail.js +141 -0
  81. package/test/outbound/index.js +220 -0
  82. package/test/outbound/qfile.js +126 -0
  83. package/test/outbound_bounce_net_errors.js +142 -0
  84. package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
  85. package/test/plugins/auth/auth_base.js +484 -0
  86. package/test/plugins/auth/auth_vpopmaild.js +83 -0
  87. package/test/plugins/early_talker.js +104 -0
  88. package/test/plugins/mail_from.is_resolvable.js +35 -0
  89. package/test/plugins/queue/smtp_forward.js +206 -0
  90. package/test/plugins/rcpt_to.host_list_base.js +122 -0
  91. package/test/plugins/rcpt_to.in_host_list.js +193 -0
  92. package/test/plugins/relay.js +303 -0
  93. package/test/plugins/status.js +130 -0
  94. package/test/plugins/tls.js +70 -0
  95. package/test/plugins.js +228 -0
  96. package/test/rfc1869.js +73 -0
  97. package/test/server.js +491 -0
  98. package/test/smtp_client.js +299 -0
  99. package/test/tls_socket.js +273 -0
  100. package/test/transaction.js +270 -0
  101. package/tls_socket.js +202 -252
  102. package/transaction.js +8 -23
  103. package/CONTRIBUTING.md +0 -1
  104. package/bin/dkimverify +0 -40
  105. package/config/access.domains +0 -13
  106. package/config/attachment.ctype.regex +0 -2
  107. package/config/attachment.filename.regex +0 -1
  108. package/config/avg.ini +0 -5
  109. package/config/bounce.ini +0 -15
  110. package/config/data.headers.ini +0 -61
  111. package/config/dkim/dkim_key_gen.sh +0 -78
  112. package/config/dkim_sign.ini +0 -4
  113. package/config/dkim_verify.ini +0 -7
  114. package/config/dnsbl.ini +0 -23
  115. package/config/greylist.ini +0 -43
  116. package/config/helo.checks.ini +0 -52
  117. package/config/messagesniffer.ini +0 -18
  118. package/config/spamassassin.ini +0 -56
  119. package/dkim.js +0 -614
  120. package/docs/plugins/avg.md +0 -35
  121. package/docs/plugins/bounce.md +0 -69
  122. package/docs/plugins/clamd.md +0 -147
  123. package/docs/plugins/esets.md +0 -8
  124. package/docs/plugins/greylist.md +0 -90
  125. package/docs/plugins/helo.checks.md +0 -135
  126. package/docs/plugins/messagesniffer.md +0 -163
  127. package/docs/plugins/spamassassin.md +0 -180
  128. package/outbound/mx_lookup.js +0 -70
  129. package/plugins/auth/auth_ldap.js +0 -3
  130. package/plugins/avg.js +0 -162
  131. package/plugins/backscatterer.js +0 -25
  132. package/plugins/bounce.js +0 -381
  133. package/plugins/clamd.js +0 -382
  134. package/plugins/data.uribl.js +0 -4
  135. package/plugins/dkim_sign.js +0 -395
  136. package/plugins/dkim_verify.js +0 -62
  137. package/plugins/dns_list_base.js +0 -221
  138. package/plugins/dnsbl.js +0 -146
  139. package/plugins/dnswl.js +0 -58
  140. package/plugins/esets.js +0 -71
  141. package/plugins/graph.js +0 -5
  142. package/plugins/greylist.js +0 -645
  143. package/plugins/helo.checks.js +0 -533
  144. package/plugins/messagesniffer.js +0 -381
  145. package/plugins/rcpt_to.ldap.js +0 -3
  146. package/plugins/rcpt_to.max_count.js +0 -24
  147. package/plugins/spamassassin.js +0 -384
  148. package/tests/config/dkim/example.com/dns +0 -29
  149. package/tests/config/dkim/example.com/private +0 -6
  150. package/tests/config/dkim/example.com/public +0 -4
  151. package/tests/config/dkim/example.com/selector +0 -1
  152. package/tests/config/dkim.private.key +0 -6
  153. package/tests/config/dkim_sign.ini +0 -4
  154. package/tests/config/helo.checks.ini +0 -52
  155. package/tests/connection.js +0 -327
  156. package/tests/endpoint.js +0 -128
  157. package/tests/fixtures/vm_harness.js +0 -59
  158. package/tests/logger.js +0 -327
  159. package/tests/outbound/hmail.js +0 -112
  160. package/tests/outbound/index.js +0 -324
  161. package/tests/outbound/qfile.js +0 -67
  162. package/tests/outbound_bounce_net_errors.js +0 -173
  163. package/tests/plugins/auth/auth_base.js +0 -463
  164. package/tests/plugins/auth/auth_vpopmaild.js +0 -91
  165. package/tests/plugins/bounce.js +0 -307
  166. package/tests/plugins/clamd.js +0 -224
  167. package/tests/plugins/deprecated/relay_acl.js +0 -140
  168. package/tests/plugins/deprecated/relay_all.js +0 -59
  169. package/tests/plugins/dkim_sign.js +0 -315
  170. package/tests/plugins/dkim_signer.js +0 -108
  171. package/tests/plugins/dns_list_base.js +0 -259
  172. package/tests/plugins/dnsbl.js +0 -101
  173. package/tests/plugins/early_talker.js +0 -115
  174. package/tests/plugins/greylist.js +0 -58
  175. package/tests/plugins/helo.checks.js +0 -525
  176. package/tests/plugins/mail_from.is_resolvable.js +0 -116
  177. package/tests/plugins/queue/smtp_forward.js +0 -221
  178. package/tests/plugins/rcpt_to.host_list_base.js +0 -132
  179. package/tests/plugins/rcpt_to.in_host_list.js +0 -218
  180. package/tests/plugins/relay.js +0 -339
  181. package/tests/plugins/spamassassin.js +0 -171
  182. package/tests/plugins/status.js +0 -138
  183. package/tests/plugins/tls.js +0 -84
  184. package/tests/plugins.js +0 -247
  185. package/tests/rfc1869.js +0 -61
  186. package/tests/server.js +0 -510
  187. package/tests/smtp_client/auth.js +0 -105
  188. package/tests/smtp_client/basic.js +0 -101
  189. package/tests/smtp_client.js +0 -80
  190. package/tests/tls_socket.js +0 -333
  191. package/tests/transaction.js +0 -284
  192. /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
  193. /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
  194. /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
  195. /package/docs/{plugins → deprecated}/dnswl.md +0 -0
  196. /package/{tests → test}/.eslintrc.yaml +0 -0
  197. /package/{tests → test}/config/auth_flat_file.ini +0 -0
  198. /package/{tests → test}/config/dhparams.pem +0 -0
  199. /package/{tests → test}/config/host_list +0 -0
  200. /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
  201. /package/{tests → test}/config/outbound_tls_key.pem +0 -0
  202. /package/{tests → test}/config/smtp_forward.ini +0 -0
  203. /package/{tests → test}/config/tls/ec.pem +0 -0
  204. /package/{tests → test}/config/tls/haraka.local.pem +0 -0
  205. /package/{tests → test}/config/tls/mismatched.pem +0 -0
  206. /package/{tests → test}/config/tls.ini +0 -0
  207. /package/{tests → test}/config/tls_cert.pem +0 -0
  208. /package/{tests → test}/config/tls_key.pem +0 -0
  209. /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
  210. /package/{tests → test}/installation/config/test-plugin-flat +0 -0
  211. /package/{tests → test}/installation/config/test-plugin.ini +0 -0
  212. /package/{tests → test}/installation/config/tls.ini +0 -0
  213. /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
  214. /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
  215. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
  216. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
  217. /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
  218. /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
  219. /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
  220. /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
  221. /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
  222. /package/{tests → test}/installation/plugins/inherits.js +0 -0
  223. /package/{tests → test}/installation/plugins/load_first.js +0 -0
  224. /package/{tests → test}/installation/plugins/plugin.js +0 -0
  225. /package/{tests → test}/installation/plugins/tls.js +0 -0
  226. /package/{tests → test}/loud/config/dhparams.pem +0 -0
  227. /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
  228. /package/{tests → test}/loud/config/tls.ini +0 -0
  229. /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
  230. /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
  231. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  232. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  233. /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  234. /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  235. /package/{tests → test}/queue/multibyte +0 -0
  236. /package/{tests → test}/queue/plain +0 -0
  237. /package/{tests → test}/queue/zero-length +0 -0
  238. /package/{tests → test}/test-queue/delete-me +0 -0
@@ -1,395 +0,0 @@
1
- // dkim_signer
2
- // Implements DKIM core as per www.dkimcore.org
3
-
4
- const addrparser = require('address-rfc2822');
5
- const async = require('async');
6
- const crypto = require('crypto');
7
- const fs = require('fs');
8
- const path = require('path');
9
- const { Stream } = require('stream');
10
-
11
- const utils = require('haraka-utils');
12
-
13
- class DKIMSignStream extends Stream {
14
- constructor (props, header, done) {
15
- super();
16
-
17
- this.selector = props.selector;
18
-
19
- // fix issue #2668 renaming reserved kw/property of 'domain' to 'domain_name'
20
- this.domain_name = props.domain;
21
- this.private_key = props.private_key;
22
- this.headers_to_sign = props.headers;
23
- this.header = header;
24
- this.end_callback = done;
25
- this.writable = true;
26
- this.found_eoh = false;
27
- this.buffer = { ar: [], len: 0 };
28
- this.hash = crypto.createHash('SHA256');
29
- this.line_buffer = { ar: [], len: 0 };
30
- this.signer = crypto.createSign('RSA-SHA256');
31
- this.body_found = false;
32
- }
33
-
34
- write (buf) {
35
- /*
36
- ** BODY (simple canonicalization)
37
- */
38
-
39
- // Merge in any partial data from last iteration
40
- if (this.buffer.ar.length) {
41
- this.buffer.ar.push(buf);
42
- this.buffer.len += buf.length;
43
- const nb = Buffer.concat(this.buffer.ar, this.buffer.len);
44
- buf = nb;
45
- this.buffer = { ar: [], len: 0 };
46
- }
47
- // Process input buffer into lines
48
- let offset = 0;
49
- while ((offset = utils.indexOfLF(buf)) !== -1) {
50
- const line = buf.slice(0, offset+1);
51
- if (buf.length > offset) {
52
- buf = buf.slice(offset+1);
53
- }
54
- // Look for CRLF
55
- if (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a) {
56
- // Look for end of headers marker
57
- if (!this.found_eoh) {
58
- this.found_eoh = true;
59
- }
60
- else {
61
- // Store any empty lines so that we can discard
62
- // any trailing CRLFs at the end of the message
63
- this.line_buffer.ar.push(line);
64
- this.line_buffer.len += line.length;
65
- }
66
- }
67
- else {
68
- if (!this.found_eoh) continue; // Skip headers
69
- if (this.line_buffer.ar.length) {
70
- // We need to process the buffered CRLFs
71
- const lb = Buffer.concat(this.line_buffer.ar, this.line_buffer.len);
72
- this.line_buffer = { ar: [], len: 0 };
73
- this.hash.update(lb);
74
- }
75
- this.hash.update(line);
76
- this.body_found = true;
77
- }
78
- }
79
- if (buf.length) {
80
- // We have partial data...
81
- this.buffer.ar.push(buf);
82
- this.buffer.len += buf.length;
83
- }
84
- }
85
-
86
- end (buf) {
87
- this.writable = false;
88
-
89
- // Add trailing CRLF if we have data left over
90
- if (this.buffer.ar.length) {
91
- this.buffer.ar.push(Buffer.from("\r\n"));
92
- this.buffer.len += 2;
93
- const le = Buffer.concat(this.buffer.ar, this.buffer.len);
94
- this.hash.update(le);
95
- this.buffer = { ar: [], len: 0 };
96
- }
97
-
98
- if (!this.body_found) {
99
- this.hash.update(Buffer.from("\r\n"));
100
- }
101
-
102
- const bodyhash = this.hash.digest('base64');
103
-
104
- /*
105
- ** HEADERS (relaxed canonicaliztion)
106
- */
107
-
108
- const headers = [];
109
- for (const element of this.headers_to_sign) {
110
- let head = this.header.get(element);
111
- if (head) {
112
- head = head.replace(/\r?\n/gm, '');
113
- head = head.replace(/\s+/gm, ' ');
114
- head = head.replace(/\s+$/gm, '');
115
- this.signer.update(`${element}:${head}\r\n`);
116
- headers.push(element);
117
- }
118
- }
119
-
120
- // Create DKIM header
121
- let dkim_header = `v=1; a=rsa-sha256; c=relaxed/simple; d=${this.domain_name}; s=${this.selector}; h=${headers.join(':')}; bh=${bodyhash}; b=`;
122
- this.signer.update(`dkim-signature:${dkim_header}`);
123
- const signature = this.signer.sign(this.private_key, 'base64');
124
- dkim_header = `v=1; a=rsa-sha256; c=relaxed/simple;\r\n\td=${this.domain_name}; s=${this.selector};\r\n\th=${headers.join(':')};\r\n\tbh=${bodyhash};\r\n\tb=`;
125
- dkim_header += signature.substring(0,74);
126
- for (let i=74; i<signature.length; i+=76) {
127
- dkim_header += `\r\n\t${signature.substring(i, i+76)}`;
128
- }
129
-
130
- if (this.end_callback) this.end_callback(null, dkim_header);
131
- this.end_callback = null;
132
- }
133
-
134
- destroy () {
135
- this.writable = false;
136
- // Stream destroyed before the callback ran
137
- if (this.end_callback) {
138
- this.end_callback(new Error('Stream destroyed'));
139
- }
140
- }
141
- }
142
-
143
- exports.DKIMSignStream = DKIMSignStream;
144
-
145
- exports.register = function () {
146
- this.load_dkim_sign_ini();
147
- this.load_dkim_default_key();
148
- }
149
-
150
- exports.load_dkim_sign_ini = function () {
151
- this.cfg = this.config.get('dkim_sign.ini', {
152
- booleans: [
153
- '-disabled',
154
- ]
155
- },
156
- () => { this.load_dkim_sign_ini(); }
157
- );
158
-
159
- this.cfg.headers_to_sign = this.get_headers_to_sign();
160
- }
161
-
162
- exports.load_dkim_default_key = function () {
163
- this.private_key = this.config.get('dkim.private.key', 'data', () => {
164
- this.load_dkim_default_key();
165
- }).join('\n');
166
- }
167
-
168
- exports.load_key = function (file) {
169
- return this.config.get(file, 'data').join('\n');
170
- }
171
-
172
- exports.hook_queue_outbound = exports.hook_pre_send_trans_email = function (next, connection) {
173
- if (this.cfg.main.disabled) return next();
174
- if (!connection?.transaction) return next();
175
-
176
- if (connection.transaction.notes?.dkim_signed) {
177
- connection.logdebug(this, 'already signed');
178
- return next();
179
- }
180
-
181
- exports.get_sign_properties(connection, (err, props) => {
182
- if (!connection?.transaction) return next();
183
- // props: selector, domain, & private_key
184
- if (err) connection.logerror(this, `${err.message}`);
185
-
186
- if (!this.has_key_data(connection, props)) return next();
187
-
188
- connection.logdebug(this, `domain: ${props.domain}`);
189
-
190
- const txn = connection.transaction;
191
- props.headers = this.cfg.headers_to_sign;
192
-
193
- txn.message_stream.pipe(
194
- new DKIMSignStream(props, txn.header, (err2, dkim_header) => {
195
- if (err2) {
196
- txn.results.add(this, {err: err2.message});
197
- return next(err2);
198
- }
199
-
200
- connection.loginfo(this, `signed for ${props.domain}`);
201
- txn.results.add(this, {pass: dkim_header});
202
- txn.add_header('DKIM-Signature', dkim_header);
203
-
204
- connection.transaction.notes.dkim_signed = true;
205
- next();
206
- })
207
- );
208
- });
209
- }
210
-
211
- exports.get_sign_properties = function (connection, done) {
212
- if (!connection.transaction) return;
213
-
214
- const domain = this.get_sender_domain(connection);
215
-
216
- if (!domain) {
217
- connection.transaction.results.add(this, {msg: 'sending domain not detected', emit: true });
218
- }
219
-
220
- const props = { domain }
221
-
222
- this.get_key_dir(connection, props, (err, keydir) => {
223
- if (err) {
224
- console.error(`err: ${err}`);
225
- connection.logerror(this, err);
226
- return done(new Error(`Error getting DKIM key_dir for ${domain}: ${err}`), props)
227
- }
228
-
229
- if (!connection.transaction) return done(null, props);
230
-
231
- // a directory for ${domain} exists
232
- if (keydir) {
233
- props.domain = path.basename(keydir); // keydir might be apex (vs sub)domain
234
- props.private_key = this.load_key(path.join('dkim', props.domain, 'private'));
235
- props.selector = this.load_key(path.join('dkim', props.domain, 'selector')).trim();
236
-
237
- if (!props.selector) {
238
- connection.transaction.results.add(this, {err: `missing selector for domain ${domain}`});
239
- }
240
- if (!props.private_key) {
241
- connection.transaction.results.add(this, {err: `missing dkim private_key for domain ${domain}`});
242
- }
243
-
244
- if (props.selector && props.private_key ) { // AND has correct files
245
- return done(null, props);
246
- }
247
- }
248
-
249
- // try [default / single domain] configuration
250
- if (this.cfg.main.domain && this.cfg.main.selector && this.private_key) {
251
-
252
- connection.transaction.results.add(this, {msg: 'using default key', emit: true });
253
-
254
- props.domain = this.cfg.main.domain;
255
- props.private_key = this.private_key;
256
- props.selector = this.cfg.main.selector;
257
-
258
- return done(null, props)
259
- }
260
-
261
- console.error(`no valid DKIM properties found`)
262
- done(null, props);
263
- })
264
- }
265
-
266
- exports.get_key_dir = function (connection, props, done) {
267
-
268
- if (!props.domain) return done();
269
-
270
- // split the domain name into labels
271
- const labels = props.domain.split('.');
272
- const haraka_dir = process.env.HARAKA || '';
273
-
274
- // list possible matches (ex: mail.example.com, example.com, com)
275
- const dom_hier = [];
276
- for (let i=0; i<labels.length; i++) {
277
- const dom = labels.slice(i).join('.');
278
- dom_hier[i] = path.resolve(haraka_dir, 'config', 'dkim', dom);
279
- }
280
-
281
- async.detectSeries(dom_hier, (filePath, iterDone) => {
282
- fs.stat(filePath, (err, stats) => {
283
- if (err) return iterDone(null, false);
284
- iterDone(null, stats.isDirectory());
285
- });
286
- },
287
- (err, results) => {
288
- connection.logdebug(this, results);
289
- done(err, results);
290
- });
291
- }
292
-
293
- exports.has_key_data = function (conn, props) {
294
-
295
- let missing = undefined;
296
-
297
- // Make sure we have all the relevant configuration
298
- if (!props.private_key) {
299
- missing = 'private key';
300
- }
301
- else if (!props.selector) {
302
- missing = 'selector';
303
- }
304
- else if (!props.domain) {
305
- missing = 'domain';
306
- }
307
-
308
- if (missing) {
309
- if (props.domain) {
310
- conn.lognotice(this, `skipped: no ${missing} for ${props.domain}`);
311
- }
312
- else {
313
- conn.lognotice(this, `skipped: no ${missing}`);
314
- }
315
- return false;
316
- }
317
-
318
- conn.logprotocol(this, `using selector: ${props.selector} at domain ${props.domain}`);
319
- return true;
320
- }
321
-
322
- exports.get_headers_to_sign = function (cfg) {
323
-
324
- if (!cfg) cfg = this.cfg;
325
- if (!cfg.main.headers_to_sign) return [ 'from' ];
326
-
327
- const headers = cfg.main.headers_to_sign
328
- .toLowerCase()
329
- .replace(/\s+/g,'')
330
- .split(/[,;:]/);
331
-
332
- // From MUST be present
333
- if (!headers.includes('from')) headers.push('from');
334
-
335
- return headers;
336
- }
337
-
338
- exports.get_sender_domain = function (connection) {
339
-
340
- const txn = connection?.transaction;
341
- if (!txn) return;
342
-
343
- // fallback: use Envelope FROM when header parsing fails
344
- let domain;
345
- if (txn.mail_from.host) {
346
- try { domain = txn.mail_from.host.toLowerCase(); }
347
- catch (e) {
348
- connection.logerror(this, e);
349
- }
350
- }
351
-
352
- // In case of forwarding, only use the Envelope
353
- if (txn.notes.forward) return domain;
354
- if (!txn.header) return domain;
355
-
356
- // the DKIM signing key should be aligned with the domain in the From
357
- // header (see DMARC). Try to parse the domain from there.
358
- const from_hdr = txn.header.get_decoded('From');
359
- if (!from_hdr) return domain;
360
-
361
- // The From header can contain multiple addresses and should be
362
- // parsed as described in RFC 2822 3.6.2.
363
- let addrs;
364
- try {
365
- addrs = addrparser.parse(from_hdr);
366
- }
367
- catch (e) {
368
- connection.logerror(this, `address-rfc2822 failed to parse From header: ${from_hdr}`)
369
- return domain;
370
- }
371
- if (!addrs || ! addrs.length) return domain;
372
-
373
- // If From has a single address, we're done
374
- if (addrs.length === 1 && addrs[0].host) {
375
- let fromHost = addrs[0].host();
376
- if (fromHost) {
377
- // don't attempt to lower a null or undefined value #1575
378
- fromHost = fromHost.toLowerCase();
379
- }
380
- return fromHost;
381
- }
382
-
383
- // If From has multiple-addresses, we must parse and
384
- // use the domain in the Sender header.
385
- const sender = txn.header.get_decoded('Sender');
386
- if (sender) {
387
- try {
388
- domain = (addrparser.parse(sender))[0].host().toLowerCase();
389
- }
390
- catch (e) {
391
- connection.logerror(this, e);
392
- }
393
- }
394
- return domain;
395
- }
@@ -1,62 +0,0 @@
1
-
2
- const dkim = require('./dkim');
3
-
4
- const { DKIMVerifyStream } = dkim;
5
-
6
- const plugin = exports;
7
-
8
- dkim.DKIMObject.prototype.debug = str => {
9
- plugin.logdebug(str);
10
- }
11
-
12
- DKIMVerifyStream.prototype.debug = str => {
13
- plugin.logdebug(str);
14
- }
15
-
16
- exports.register = function () {
17
- this.load_config()
18
- }
19
-
20
- exports.load_config = function () {
21
- const cfg = this.config.get('dkim_verify.ini', {}, () => this.load_config())
22
-
23
- this.cfg = Object.assign({}, cfg.main, {
24
- timeout: plugin.timeout ? plugin.timeout - 1 : 0
25
- })
26
- }
27
-
28
- exports.hook_data_post = function (next, connection) {
29
- const txn = connection?.transaction;
30
- if (!txn) return next();
31
-
32
- const verifier = new DKIMVerifyStream(this.cfg, (err, result, results) => {
33
- if (err) {
34
- txn.results.add(this, { err });
35
- return next();
36
- }
37
- if (!results || results.length === 0) {
38
- txn.results.add(this, { skip: 'no/bad dkim signature' });
39
- return next(CONT, 'no/bad signature')
40
- }
41
- results.forEach((res) => {
42
- let res_err = '';
43
- if (res.error) res_err = ` (${res.error})`;
44
- connection.auth_results(`dkim=${res.result}${res_err} header.i=${res.identity} header.d=${res.domain} header.s=${res.selector}`);
45
- connection.loginfo(this, `identity="${res.identity}" domain="${res.domain}" selector="${res.selector}" result=${res.result} ${res_err}`);
46
-
47
- // save to ResultStore
48
- const rs_obj = JSON.parse(JSON.stringify(res));
49
- if (res.result === 'pass') { rs_obj.pass = res.domain; }
50
- else if (res.result === 'fail') { rs_obj.fail = res.domain + res_err; }
51
- else { rs_obj.err = res.domain + res_err; }
52
- txn.results.add(this, rs_obj);
53
- });
54
-
55
- connection.logdebug(this, JSON.stringify(results));
56
- // Store results for other plugins
57
- txn.notes.dkim_results = results;
58
- next();
59
- })
60
-
61
- txn.message_stream.pipe(verifier, { line_endings: '\r\n' });
62
- }
@@ -1,221 +0,0 @@
1
- // DNS list module
2
- const dns = require('dns');
3
- const net = require('net');
4
- const net_utils = require('haraka-net-utils');
5
- const async = require('async');
6
-
7
- exports.enable_stats = false;
8
- exports.disable_allowed = false;
9
- exports.redis_host = '127.0.0.1:6379';
10
- let redis_client;
11
-
12
- exports.lookup = function (lookup, zone, cb) {
13
-
14
- if (!lookup || !zone) {
15
- return setImmediate(() => cb(new Error('missing data')));
16
- }
17
-
18
- if (this.enable_stats) { this.init_redis(); }
19
-
20
- // Reverse lookup if IPv4 address
21
- if (net.isIPv4(lookup)) {
22
- lookup = lookup.split('.').reverse().join('.');
23
- }
24
- else if (net.isIPv6(lookup)) {
25
- lookup = net_utils.ipv6_reverse(lookup);
26
- }
27
-
28
- let start;
29
- if (this.enable_stats) {
30
- start = new Date().getTime();
31
- }
32
-
33
- // Build the query, adding the root dot if missing
34
- let query = [lookup, zone].join('.');
35
- if (!query.endsWith('.')) {
36
- query += '.';
37
- }
38
- this.logdebug(`looking up: ${query}`);
39
- // IS: IPv6 compatible (maybe; only if BL return IPv4 answers)
40
- dns.resolve(query, 'A', (err, a) => {
41
- this.stats_incr_zone(err, zone, start); // Statistics
42
-
43
- // Check for a result of 127.0.0.1 or outside 127/8
44
- // This should *never* happen on a proper DNS list
45
- if (a && ((!this.lookback_is_rejected && a.includes('127.0.0.1')) ||
46
- a.find((rec) => { return rec.split('.')[0] !== '127' }))
47
- ) {
48
- this.disable_zone(zone, a);
49
- return cb(err, null); // Return a null A record
50
- }
51
-
52
- // <https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now>
53
- if (a?.includes('127.255.255.')) {
54
- this.disable_zone(zone, a);
55
- return cb(err, null); // Return a null A record
56
- }
57
-
58
- if (err) {
59
- if (err.code === dns.TIMEOUT) { // list timed out
60
- this.disable_zone(zone, err.code); // disable it
61
- }
62
- if (err.code === dns.NOTFOUND) { // unlisted
63
- return cb(null, a); // not an error for a DNSBL
64
- }
65
- }
66
- return cb(err, a);
67
- });
68
- }
69
-
70
- exports.stats_incr_zone = function (err, zone, start) {
71
- if (!this.enable_stats) return;
72
-
73
- const rkey = `dns-list-stat:${zone}`;
74
- const elapsed = new Date().getTime() - start;
75
- redis_client.hIncrBy(rkey, 'TOTAL', 1);
76
- const foo = (err) ? err.code : 'LISTED';
77
- redis_client.hIncrBy(rkey, foo, 1);
78
- redis_client.hGet(rkey, 'AVG_RT').then(rt => {
79
- const avg = parseInt(rt) ? (parseInt(elapsed) + parseInt(rt))/2
80
- : parseInt(elapsed);
81
- redis_client.hSet(rkey, 'AVG_RT', avg);
82
- });
83
- }
84
-
85
- exports.init_redis = function () {
86
- if (redis_client) { return; }
87
-
88
- const redis = require('redis');
89
- const host_port = this.redis_host.split(':');
90
- const host = host_port[0] || '127.0.0.1';
91
- const port = parseInt(host_port[1], 10) || 6379;
92
-
93
- redis_client = redis.createClient(port, host);
94
- redis_client.connect().then(() => {
95
- redis_client.on('error', err => {
96
- this.logerror(`Redis error: ${err}`);
97
- redis_client.quit();
98
- redis_client = null; // should force a reconnect
99
- // not sure if that's the right thing but better than nothing...
100
- })
101
- })
102
- }
103
-
104
- exports.multi = function (lookup, zones, cb) {
105
- if (!lookup) return cb();
106
- if (!zones ) return cb();
107
- if (typeof zones === 'string') zones = [ `${zones}` ];
108
- const self = this;
109
- const listed = [];
110
-
111
- function redis_incr (zone) {
112
- if (!self.enable_stats) return;
113
-
114
- // Statistics: check hit overlap
115
- for (const element of listed) {
116
- const foo = (element === zone) ? 'TOTAL' : element;
117
- redis_client.hIncrBy(`dns-list-overlap:${zone}`, foo, 1);
118
- }
119
- }
120
-
121
- function zoneIter (zone, done) {
122
- self.lookup(lookup, zone, (err, a) => {
123
- if (a) {
124
- listed.push(zone);
125
- redis_incr(zone);
126
- }
127
- cb(err, zone, a, true);
128
- done();
129
- })
130
- }
131
- function zonesDone (err) {
132
- cb(err, null, null, false);
133
- }
134
- async.each(zones, zoneIter, zonesDone);
135
- }
136
-
137
- // Return first positive or last result.
138
- exports.first = function (lookup, zones, cb, cb_each) {
139
- if (!lookup || !zones) return cb();
140
- if (typeof zones === 'string') zones = [ `${zones}` ];
141
- let ran_cb = false;
142
- this.multi(lookup, zones, (err, zone, a, pending) => {
143
- if (zone && cb_each && typeof cb_each === 'function') {
144
- cb_each(err, zone, a);
145
- }
146
- if (ran_cb) return;
147
- if (pending && (err || !a)) return;
148
-
149
- // has pending queries OR this one is a positive result
150
- ran_cb = true;
151
- cb(err, zone, a);
152
- })
153
- }
154
-
155
- exports.check_zones = function (interval) {
156
- this.disable_allowed = true;
157
- if (interval) interval = parseInt(interval);
158
- if ((this.zones?.length) ||
159
- (this.disabled_zones?.length)) {
160
- let zones = [];
161
- if (this.zones?.length) zones = zones.concat(this.zones);
162
- if (this.disabled_zones?.length) {
163
- zones = zones.concat(this.disabled_zones);
164
- }
165
-
166
- // A DNS list should never return positive or an error for this lookup
167
- // If it does, move it to the disabled list
168
- this.multi('127.0.0.1', zones, (err, zone, a, pending) => {
169
- if (!zone) return;
170
-
171
- if ((!this.lookback_is_rejected && a) || (err && err.code === 'ETIMEOUT')) {
172
- return this.disable_zone(zone, ((a) ? a : err.code));
173
- }
174
-
175
- // Try the test point
176
- this.lookup('127.0.0.2', zone, (err2, a2) => {
177
- if (!a2) {
178
- this.logwarn(`zone '${zone}' did not respond to test point (${err2})`);
179
- return this.disable_zone(zone, a2);
180
- }
181
- // Was this zone previously disabled?
182
- if (!this.zones.includes(zone)) {
183
- this.loginfo(`re-enabling zone ${zone}`);
184
- this.zones.push(zone);
185
- }
186
- });
187
- });
188
- }
189
- // Set a timer to re-test
190
- if (interval && interval >= 5 && !this._interval) {
191
- this.logdebug(`will re-test list zones every ${interval} minutes`);
192
- this._interval = setInterval(() => {
193
- this.check_zones();
194
- }, (interval * 60) * 1000);
195
- }
196
- }
197
-
198
- exports.shutdown = function () {
199
- clearInterval(this._interval);
200
- if (redis_client) redis_client.quit();
201
- }
202
-
203
- exports.disable_zone = function (zone, result) {
204
- if (!zone) return false;
205
- if (!this.zones) return false;
206
- if (!this.zones.length) return false;
207
- if (!this.disable_allowed) return false;
208
-
209
- const idx = this.zones.indexOf(zone);
210
- if (idx === -1) return false; // not enabled
211
-
212
- this.zones.splice(idx, 1);
213
- if (!(this.disabled_zones?.length)) {
214
- this.disabled_zones = [];
215
- }
216
- if (!this.disabled_zones.includes(zone)) {
217
- this.disabled_zones.push(zone);
218
- }
219
- this.logwarn(`disabling zone '${zone}'${result ? `: ${result}` : ''}`);
220
- return true;
221
- }