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