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/dkim.js DELETED
@@ -1,614 +0,0 @@
1
- 'use strict';
2
-
3
- const crypto = require('crypto');
4
- const dns = require('dns');
5
- const { Stream } = require('stream');
6
- const utils = require('haraka-utils');
7
-
8
- //////////////////////
9
- // Common functions //
10
- //////////////////////
11
-
12
- function md5 (str) {
13
- if (!str) str = '';
14
- const h = crypto.createHash('md5');
15
- return h.update(str).digest('hex');
16
- }
17
-
18
- class Buf {
19
- constructor () {
20
- this.bar = [];
21
- this.blen = 0;
22
- }
23
-
24
- pop (buf) {
25
- if (!this.bar.length) {
26
- if (!buf) buf = Buffer.from('');
27
- return buf;
28
- }
29
- if (buf?.length) {
30
- this.bar.push(buf);
31
- this.blen += buf.length;
32
- }
33
- const nb = Buffer.concat(this.bar, this.blen);
34
- this.bar = [];
35
- this.blen = 0;
36
- return nb;
37
- }
38
-
39
- push (buf) {
40
- if (buf.length) {
41
- this.bar.push(buf);
42
- this.blen += buf.length;
43
- }
44
- }
45
- }
46
-
47
- ////////////////
48
- // DKIMObject //
49
- ////////////////
50
-
51
- // There is one DKIMObject created for each signature found
52
-
53
- class DKIMObject {
54
- constructor (header, header_idx, cb, opts) {
55
- this.cb = cb;
56
- this.sig = header;
57
- this.sig_md5 = md5(header);
58
- this.run_cb = false;
59
- this.header_idx = JSON.parse(JSON.stringify(header_idx));
60
- this.timeout = opts.timeout || 30
61
- this.allowed_time_skew = opts.allowed_time_skew
62
- this.fields = {};
63
- this.headercanon = this.bodycanon = 'simple';
64
- this.signed_headers = [];
65
- this.identity = 'unknown';
66
- this.line_buffer = []
67
- this.dns_fields = {
68
- 'v': 'DKIM1',
69
- 'k': 'rsa',
70
- 'g': '*',
71
- };
72
-
73
- const [ , , dkim_signature] = /^([^:]+):\s*((?:.|[\r\n])*)$/.exec(header);
74
- const sig = dkim_signature.trim().replace(/\s+/g,'');
75
- const keys = sig.split(';');
76
- for (const keyElement of keys) {
77
- const key = keyElement.trim();
78
- if (!key) continue; // skip empty keys
79
- const [ , key_name, key_value] = /^([^= ]+)=((?:.|[\r\n])+)$/.exec(key) || [];
80
- if (key_name) {
81
- this.fields[key_name] = key_value;
82
- }
83
- else {
84
- return this.result('header parse error', 'invalid');
85
- }
86
- }
87
- /////////////////////
88
- // Validate fields //
89
- /////////////////////
90
-
91
- if (this.fields.v) {
92
- if (this.fields.v !== '1') {
93
- return this.result('incompatible version', 'invalid');
94
- }
95
- }
96
- else {
97
- return this.result('missing version', 'invalid');
98
- }
99
-
100
- if (this.fields.l) {
101
- return this.result('length tag is unsupported', 'none');
102
- }
103
-
104
- if (this.fields.a) {
105
- switch (this.fields.a) {
106
- case 'rsa-sha1':
107
- this.bh = crypto.createHash('SHA1');
108
- this.verifier = crypto.createVerify('RSA-SHA1');
109
- break;
110
- case 'rsa-sha256':
111
- this.bh = crypto.createHash('SHA256');
112
- this.verifier = crypto.createVerify('RSA-SHA256');
113
- break;
114
- default:
115
- this.debug(`Invalid algorithm: ${this.fields.a}`);
116
- return this.result('invalid algorithm', 'invalid');
117
- }
118
- }
119
- else {
120
- return this.result('missing algorithm', 'invalid');
121
- }
122
-
123
- if (!this.fields.b) return this.result('signature missing', 'invalid');
124
- if (!this.fields.bh) return this.result('body hash missing', 'invalid');
125
-
126
- if (this.fields.c) {
127
- const c = this.fields.c.split('/');
128
- if (c[0]) this.headercanon = c[0];
129
- if (c[1]) this.bodycanon = c[1];
130
- }
131
-
132
- if (!this.fields.d) return this.result('domain missing', 'invalid');
133
-
134
- if (this.fields.h) {
135
- const headers = this.fields.h.split(':');
136
- for (const h of headers) {
137
- this.signed_headers.push(h.trim().toLowerCase());
138
- }
139
- if (!this.signed_headers.includes('from')) {
140
- return this.result('from field not signed', 'invalid');
141
- }
142
- }
143
- else {
144
- return this.result('signed headers missing', 'invalid');
145
- }
146
-
147
- if (this.fields.i) {
148
- // Make sure that this is a sub-domain of the 'd' field
149
- const dom = this.fields.i.substr(this.fields.i.length - this.fields.d.length);
150
- if (dom.toLowerCase() !== this.fields.d.toLowerCase()) {
151
- return this.result('i/d selector domain mismatch', 'invalid')
152
- }
153
- }
154
- else {
155
- this.fields.i = `@${this.fields.d}`;
156
- }
157
- this.identity = this.fields.i;
158
-
159
- if (this.fields.q && this.fields.q !== 'dns/txt') {
160
- return this.result('unknown query method', 'invalid');
161
- }
162
-
163
- const now = new Date().getTime()/1000;
164
- if (this.fields.t) {
165
- if (this.fields.t > (this.allowed_time_skew ? (now + parseInt(this.allowed_time_skew)) : now)) {
166
- return this.result('creation date is invalid or in the future', 'invalid')
167
- }
168
- }
169
-
170
- if (this.fields.x) {
171
- if (this.fields.t && parseInt(this.fields.x) < parseInt(this.fields.t)) {
172
- return this.result('invalid expiration date', 'invalid');
173
- }
174
- if ((this.allowed_time_skew ? (now - parseInt(this.allowed_time_skew)) : now) > parseInt(this.fields.x)) {
175
- return this.result(`signature expired`, 'invalid');
176
- }
177
- }
178
-
179
- this.debug(`${this.identity}: DKIM fields validated OK`);
180
- this.debug(`${this.identity}: a=${this.fields.a} c=${this.headercanon}/${this.bodycanon} h=${this.signed_headers}`);
181
- }
182
-
183
- debug (str) {
184
- console.debug(str)
185
- }
186
-
187
- header_canon_relaxed (header) {
188
- // `|| []` prevents errors thrown when no match
189
- // `\s*` eats all FWS after the colon
190
- // eslint-disable-next-line prefer-const
191
- let [, header_name, header_value] = /^([^:]+):\s*([^]*)$/.exec(header) || []
192
-
193
- if (!header_name) return header;
194
- if (header_value.length === 0) header_value = "\r\n"
195
-
196
- let hc = `${header_name.toLowerCase()}:${header_value}`;
197
- hc = hc.replace(/\r\n([\t ]+)/g, "$1");
198
- hc = hc.replace(/[\t ]+/g, ' ');
199
- hc = hc.replace(/[\t ]+(\r?\n)$/, "$1");
200
- return hc;
201
- }
202
-
203
- add_body_line (line) {
204
- if (this.run_cb) return;
205
-
206
- if (this.bodycanon === 'relaxed') {
207
- line = DKIMObject.canonicalize(line)
208
- }
209
-
210
- // Buffer any lines
211
- const isCRLF = line.length === 2 && line[0] === 0x0d && line[1] === 0x0a;
212
- const isLF = line.length === 1 && line[0] === 0x0a;
213
- if (isCRLF || isLF) {
214
- // Store any empty lines as both canonicalization algorithms
215
- // ignore all empty lines at the end of the message body.
216
- this.line_buffer.push(line)
217
- }
218
- else {
219
- if (this.line_buffer.length > 0) {
220
- this.line_buffer.forEach(v => this.bh.update(v))
221
- this.line_buffer = []
222
- }
223
- this.bh.update(line)
224
- }
225
- }
226
-
227
- result (error, result) {
228
- this.run_cb = true;
229
- return this.cb(
230
- ((error) ? new Error(error) : null),
231
- {
232
- identity: this.identity,
233
- selector: this.fields.s,
234
- domain: this.fields.d,
235
- result
236
- }
237
- );
238
- }
239
-
240
- end () {
241
- if (this.run_cb) return;
242
-
243
- const bh = this.bh.digest('base64');
244
- this.debug(`${this.identity}: bodyhash=${this.fields.bh} computed=${bh}`);
245
- if (bh !== this.fields.bh) {
246
- return this.result('body hash did not verify', 'fail');
247
- }
248
-
249
- // Now we canonicalize the specified headers
250
- for (const header of this.signed_headers) {
251
- this.debug(`${this.identity}: canonicalize header: ${header}`);
252
- if (this.header_idx[header]) {
253
- // RFC 6376 section 5.4.2, read headers from bottom to top
254
- const this_header = this.header_idx[header].pop();
255
- if (this_header) {
256
- // Skip this signature if dkim-signature is specified
257
- if (header === 'dkim-signature') {
258
- const h_md5 = md5(this_header);
259
- if (h_md5 === this.sig_md5) {
260
- this.debug(`${this.identity}: skipped our own DKIM-Signature`);
261
- continue;
262
- }
263
- }
264
- if (this.headercanon === 'simple') {
265
- this.verifier.update(this_header);
266
- }
267
- else if (this.headercanon === 'relaxed') {
268
- const hc = this.header_canon_relaxed(this_header);
269
- this.verifier.update(hc);
270
- }
271
- }
272
- }
273
- }
274
-
275
- // Now add in our original DKIM-Signature header without the b= and trailing CRLF
276
- let our_sig = this.sig.replace(/([:;\s\t]|^)b=([^;]+)/, '$1b=');
277
- if (this.headercanon === 'relaxed') {
278
- our_sig = this.header_canon_relaxed(our_sig);
279
- }
280
- our_sig = our_sig.replace(/\r\n$/,'');
281
- this.verifier.update(our_sig);
282
-
283
- let timeout = false;
284
- const timer = setTimeout(() => {
285
- timeout = true;
286
- return this.result('DNS timeout', 'tempfail');
287
- }, this.timeout * 1000);
288
- const lookup = `${this.fields.s}._domainkey.${this.fields.d}`;
289
- this.debug(`${this.identity}: DNS lookup ${lookup} (timeout= ${this.timeout}s)`);
290
- dns.resolveTxt(lookup, (err, res) => {
291
- if (timeout) return;
292
- clearTimeout(timer);
293
- if (err) {
294
- switch (err.code) {
295
- case dns.NOTFOUND:
296
- case dns.NODATA:
297
- case dns.NXDOMAIN:
298
- return this.result('no key for signature', 'invalid');
299
- default:
300
- this.debug(`${this.identity}: DNS lookup error: ${err.code}`);
301
- return this.result('key unavailable', 'tempfail');
302
- }
303
- }
304
- if (!res) return this.result('no key for signature', 'invalid');
305
- for (const recordSegments of res) {
306
- const record = recordSegments.join('');
307
- if (!record.includes('p=')) {
308
- this.debug(`${this.identity}: ignoring TXT record: ${record}`);
309
- continue;
310
- }
311
- this.debug(`${this.identity}: got DNS record: ${record}`);
312
- const rec = record.replace(/\r?\n/g, '').replace(/\s+/g,'');
313
- const split = rec.split(';');
314
- for (const element of split) {
315
- const split2 = element.split('=');
316
- if (split2[0]) this.dns_fields[split2[0]] = split2[1];
317
- }
318
-
319
- // Validate
320
- if (!this.dns_fields.v || this.dns_fields.v !== 'DKIM1') {
321
- return this.result('invalid version', 'invalid');
322
- }
323
- if (this.dns_fields.g) {
324
- if (this.dns_fields.g !== '*') {
325
- let s = this.dns_fields.g;
326
- // Escape any special regexp characters
327
- s = s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
328
- // Make * a non-greedy match against anything except @
329
- s = s.replace('\\*','[^@]*?');
330
- const reg = new RegExp(`^${s}@`);
331
- this.debug(`${this.identity}: matching ${this.dns_fields.g} against i=${this.fields.i} regexp=${reg.toString()}`);
332
- if (!reg.test(this.fields.i)) {
333
- return this.result('inapplicable key', 'invalid');
334
- }
335
- }
336
- }
337
- else {
338
- return this.result('inapplicable key', 'invalid');
339
- }
340
- if (this.dns_fields.h) {
341
- const hashes = this.dns_fields.h.split(':');
342
- for (const hashElement of hashes) {
343
- const hash = hashElement.trim();
344
- if (!this.fields.a.includes(hash)) {
345
- return this.result('inappropriate hash algorithm', 'invalid');
346
- }
347
- }
348
- }
349
- if (this.dns_fields.k) {
350
- if (!this.fields.a.includes(this.dns_fields.k)) {
351
- return this.result('inappropriate key type', 'invalid');
352
- }
353
- }
354
- if (this.dns_fields.t) {
355
- const flags = this.dns_fields.t.split(':');
356
- for (const flagElement of flags) {
357
- const flag = flagElement.trim();
358
- if (flag === 'y') {
359
- // Test mode
360
- this.test_mode = true;
361
- }
362
- else if (flag === 's') {
363
- // 'i' and 'd' domain much match exactly
364
- let { i } = this.fields
365
- i = i.substr(i.indexOf('@')+1, i.length)
366
- if (i.toLowerCase() !== this.fields.d.toLowerCase()) {
367
- return this.result('i/d selector domain mismatch (t=s)', 'invalid')
368
- }
369
- }
370
- }
371
- }
372
- if (!this.dns_fields.p) return this.result('key revoked', 'invalid');
373
-
374
- // crypto.verifier requires the key in PEM format
375
- this.public_key = `-----BEGIN PUBLIC KEY-----\r\n${
376
-
377
- this.dns_fields.p.replace(/(.{1,76})/g, '$1\r\n')
378
- }-----END PUBLIC KEY-----\r\n`;
379
-
380
- let verified;
381
- try {
382
- verified = this.verifier.verify(this.public_key, this.fields.b, 'base64');
383
- this.debug(`${this.identity}: verified=${verified}`);
384
- }
385
- catch (e) {
386
- this.debug(`${this.identity}: verification error: ${e.message}`);
387
- return this.result('verification error', 'invalid');
388
- }
389
- return this.result(null, ((verified) ? 'pass' : 'fail'));
390
- }
391
- // We didn't find a valid DKIM record for this signature
392
- this.result('no key for signature', 'invalid');
393
- });
394
- }
395
-
396
- static canonicalize (bufin) {
397
- const tmp = []
398
- const len = bufin.length
399
- let last_chunk_idx = 0
400
- let idx_wsp = 0
401
- let in_wsp = false
402
-
403
- for (let idx = 0; idx < len; idx++) {
404
- const char = bufin[idx]
405
- if (char === 9 || char === 32) { // inside WSP
406
- if (!in_wsp) { // WSP started
407
- in_wsp = true
408
- idx_wsp = idx
409
- }
410
- }
411
- else if (char === 13 || char === 10) { // CR?LF
412
- if (in_wsp) { // just after WSP
413
- tmp.push(bufin.slice(last_chunk_idx, idx_wsp))
414
- }
415
- else { // just after regular char
416
- tmp.push(bufin.slice(last_chunk_idx, idx))
417
- }
418
- break
419
- }
420
- else if (in_wsp) { // regular char after WSP
421
- in_wsp = false
422
- tmp.push(bufin.slice(last_chunk_idx, idx_wsp))
423
- tmp.push(Buffer.from(' '))
424
- last_chunk_idx = idx
425
- }
426
- }
427
-
428
- tmp.push(Buffer.from([13, 10]))
429
-
430
- return Buffer.concat(tmp)
431
- }
432
- }
433
-
434
- exports.DKIMObject = DKIMObject;
435
-
436
- //////////////////////
437
- // DKIMVerifyStream //
438
- //////////////////////
439
-
440
- class DKIMVerifyStream extends Stream {
441
- constructor (opts, cb) {
442
- super();
443
- this.run_cb = false;
444
- this.cb = (err, result, results) => {
445
- if (!this.run_cb) {
446
- this.run_cb = true;
447
- return cb(err, result, results);
448
- }
449
- };
450
- this._in_body = false;
451
- this._no_signatures_found = false;
452
- this.buffer = new Buf();
453
- this.headers = [];
454
- this.header_idx = {};
455
- this.dkim_objects = [];
456
- this.results = [];
457
- this.result = 'none';
458
- this.pending = 0;
459
- this.writable = true;
460
- this.opts = opts
461
- }
462
-
463
- debug (str) {
464
- console.debug(str)
465
- }
466
-
467
- handle_buf (buf) {
468
- const self = this;
469
- // Abort any further processing if the headers
470
- // did not contain any DKIM-Signature fields.
471
- if (this._in_body && this._no_signatures_found) {
472
- return true;
473
- }
474
- let once = false;
475
- if (buf === null) {
476
- once = true;
477
- buf = this.buffer.pop();
478
- if (!!buf && buf[buf.length - 2] === 0x0d && buf[buf.length - 1] === 0x0a) {
479
- return true;
480
- }
481
- buf = Buffer.concat([buf, Buffer.from('\r\n\r\n')])
482
- }
483
- else {
484
- buf = this.buffer.pop(buf);
485
- }
486
-
487
- function callback (err, result) {
488
- self.pending--;
489
- if (result) {
490
- const results = {
491
- identity: result.identity,
492
- domain: result.domain,
493
- selector: result.selector,
494
- result: result.result,
495
-
496
- }
497
- if (err) {
498
- results.error = err.message
499
- if (self.opts.sigerror_log_level) results.emit_log_level = self.opts.sigerror_log_level
500
- }
501
- self.results.push(results)
502
-
503
- // Set the overall result based on this precedence order
504
- const rr = ['pass','tempfail','fail','invalid','none'];
505
- for (const element of rr) {
506
- if (!self.result || (self.result && self.result !== element && result.result === element)) {
507
- self.result = element;
508
- }
509
- }
510
- }
511
-
512
- self.debug(JSON.stringify(result));
513
-
514
- if (self.pending === 0 && self.cb) {
515
- return process.nextTick(() => {
516
- self.cb(null, self.result, self.results);
517
- });
518
- }
519
- }
520
-
521
- // Process input buffer into lines
522
- let offset = 0;
523
- while ((offset = utils.indexOfLF(buf)) !== -1) {
524
- let line = buf.slice(0, offset+1);
525
- if (buf.length > offset) {
526
- buf = buf.slice(offset+1);
527
- }
528
-
529
- // Check for LF line endings and convert to CRLF if necessary
530
- if (line[line.length-2] !== 0x0d) {
531
- line = Buffer.concat([ line.slice(0, line.length-1), Buffer.from("\r\n") ], line.length+1);
532
- }
533
-
534
- // Look for CRLF
535
- if (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a) {
536
- // Look for end of headers marker
537
- if (!this._in_body) {
538
- this._in_body = true;
539
- // Parse the headers
540
- for (const header of this.headers) {
541
- const match = /^([^: ]+):\s*((:?.|[\r\n])*)/.exec(header);
542
- if (!match) continue;
543
- const header_name = match[1];
544
- if (!header_name) continue;
545
- const hn = header_name.toLowerCase();
546
- if (!this.header_idx[hn]) this.header_idx[hn] = [];
547
- this.header_idx[hn].push(header);
548
- }
549
- if (!this.header_idx['dkim-signature']) {
550
- this._no_signatures_found = true;
551
- return process.nextTick(() => {
552
- self.cb(null, self.result, self.results);
553
- });
554
- }
555
- else {
556
- // Create new DKIM objects for each header
557
- const dkim_headers = this.header_idx['dkim-signature'];
558
- this.debug(`Found ${dkim_headers.length} DKIM signatures`);
559
- this.pending = dkim_headers.length;
560
- for (const dkimHeader of dkim_headers) {
561
- this.dkim_objects.push(new DKIMObject(dkimHeader, this.header_idx, callback, this.opts));
562
- }
563
- if (this.pending === 0) {
564
- process.nextTick(() => {
565
- if (self.cb) self.cb(new Error('no signatures found'));
566
- });
567
- }
568
- }
569
- continue; // while()
570
- }
571
- }
572
-
573
- if (!this._in_body) {
574
- // Parse headers
575
- if (line[0] === 0x20 || line[0] === 0x09) {
576
- // Header continuation
577
- this.headers[this.headers.length-1] += line.toString('utf-8');
578
- }
579
- else {
580
- this.headers.push(line.toString('utf-8'));
581
- }
582
- }
583
- else {
584
- for (const dkimObject of this.dkim_objects) {
585
- dkimObject.add_body_line(line);
586
- }
587
- }
588
- if (once) {
589
- break;
590
- }
591
- }
592
-
593
- this.buffer.push(buf);
594
- return true;
595
- }
596
-
597
- write (buf) {
598
- return this.handle_buf(buf);
599
- }
600
-
601
- end (buf) {
602
- this.handle_buf(((buf) ? buf : null));
603
- for (const dkimObject of this.dkim_objects) {
604
- dkimObject.end();
605
- }
606
- if (this.pending === 0 && this._no_signatures_found === false) {
607
- process.nextTick(() => {
608
- this.cb(null, this.result, this.results);
609
- });
610
- }
611
- }
612
- }
613
-
614
- exports.DKIMVerifyStream = DKIMVerifyStream;
@@ -1,35 +0,0 @@
1
- # avg - Anti-Virus scanner
2
-
3
- Implement virus scanning with AVG's TCPD daemon, available for Linux/FreeBSD. AVG linux is [free for personal or commercial use](http://www.avg.com/gb-en/faq.pnuid-faq_v3_linux) and can be downloaded from [free.avg.com](http://free.avg.com/gb-en/download.prd-alf).
4
-
5
- Messages that AVG detects as infected are rejected. Errors will cause the plugin to return temporary failures unless the defer options are changed (see below).
6
-
7
- ## Configuration
8
-
9
- The following options can be set in avg.ini:
10
-
11
- * port (default: 54322)
12
-
13
- TCP port to communicate with the AVG TCPD on.
14
-
15
- * tmpdir (default: /tmp)
16
-
17
- AVG TCPD requires that the message be written to disk and scanned. This setting configures where any temporary files are written to. After scanning, the temporary files are automatically removed.
18
-
19
- * connect\_timeout (default: 10)
20
-
21
- Maximum seconds to wait for the socket to connect. Connections taking longer will cause a temporary failure to be sent to the remote MTA.
22
-
23
- * session\_timeout
24
-
25
- Maximum number of seconds to wait for a reply to a command before failing. A timeout will cause a temporary failure to be sent to the remote MTA.
26
-
27
- * [defer]
28
-
29
- By default, this plugin defers when errors or timeouts are encountered. To
30
- fail open (let messages pass when errors are enounctered), set the error
31
- and/or timeout values to false.
32
-
33
- [defer]
34
- error=true
35
- timeout=true