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
@@ -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
- }