Haraka 3.0.0 → 3.0.2

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.
@@ -26,10 +26,12 @@ exports.register = function () {
26
26
  this.register_hook('queue', 'queue_forward');
27
27
 
28
28
  if (this.cfg.main.enable_outbound) {
29
+ // deliver local message via smtp forward when relaying=true
29
30
  this.register_hook('queue_outbound', 'queue_forward');
30
- }
31
31
 
32
- this.register_hook('get_mx', 'get_mx'); // for relaying outbound messages
32
+ // may specify more specific routes for outbound
33
+ this.register_hook('get_mx', 'get_mx');
34
+ }
33
35
  }
34
36
 
35
37
  exports.load_smtp_forward_ini = function () {
@@ -75,7 +77,7 @@ exports.is_outbound_enabled = function (dom_cfg) {
75
77
  if ('enable_outbound' in dom_cfg) return dom_cfg.enable_outbound; // per-domain flag
76
78
 
77
79
  return this.cfg.main.enable_outbound; // follow the global configuration
78
- };
80
+ }
79
81
 
80
82
  exports.check_sender = function (next, connection, params) {
81
83
  const txn = connection?.transaction;
@@ -99,7 +101,7 @@ exports.check_sender = function (next, connection, params) {
99
101
  }
100
102
 
101
103
  txn.results.add(this, {pass: 'mail_from'});
102
- return next();
104
+ next();
103
105
  }
104
106
 
105
107
  exports.set_queue = function (connection, queue_wanted, domain) {
@@ -110,7 +112,6 @@ exports.set_queue = function (connection, queue_wanted, domain) {
110
112
  if (!queue_wanted) queue_wanted = dom_cfg.queue || this.cfg.main.queue;
111
113
  if (!queue_wanted) return true;
112
114
 
113
-
114
115
  let dst_host = dom_cfg.host || this.cfg.main.host;
115
116
  if (dst_host) dst_host = `smtp://${dst_host}`;
116
117
 
@@ -164,7 +165,7 @@ exports.check_recipient = function (next, connection, params) {
164
165
  // the MAIL FROM domain is not local and neither is the RCPT TO
165
166
  // Another RCPT plugin may vouch for this recipient.
166
167
  txn.results.add(this, {msg: 'rcpt!local'});
167
- return next();
168
+ next();
168
169
  }
169
170
 
170
171
  exports.auth = function (cfg, connection, smtp_client) {
@@ -303,20 +304,24 @@ exports.queue_forward = function (next, connection) {
303
304
 
304
305
  smtp_client.on('bad_code', (code, msg) => {
305
306
  if (dead_sender() || !txn) return;
306
- smtp_client.call_next(((code && code[0] === '5') ? DENY : DENYSOFT),
307
- msg);
307
+ smtp_client.call_next(((code && code[0] === '5') ? DENY : DENYSOFT), msg);
308
308
  smtp_client.release();
309
309
  });
310
310
  });
311
311
  }
312
312
 
313
313
  exports.get_mx_next_hop = next_hop => {
314
- const dest = url.parse(next_hop);
314
+ // queue.wants && queue.next_hop are mechanisms for fine-grained MX routing.
315
+ // Plugins can specify a queue to perform the delivery as well as a route. A
316
+ // plugin that uses this is qmail-deliverable, which can direct email delivery
317
+ // via smtp_forward, outbound (SMTP), and outbound (LMTP).
318
+ const dest = new url.URL(next_hop);
315
319
  const mx = {
316
320
  priority: 0,
317
- port: dest.port || 25,
321
+ port: dest.port || (dest.protocol === 'lmtp:' ? 24 : 25),
318
322
  exchange: dest.hostname,
319
323
  }
324
+ if (dest.protocol === 'lmtp:') mx.using_lmtp = true;
320
325
  if (dest.auth) {
321
326
  mx.auth_type = 'plain';
322
327
  mx.auth_user = dest.auth.split(':')[0];
@@ -327,9 +332,11 @@ exports.get_mx_next_hop = next_hop => {
327
332
 
328
333
  exports.get_mx = function (next, hmail, domain) {
329
334
 
330
- // hmail.todo not defined in tests.
331
- if (hmail.todo.notes.next_hop) {
332
- return next(OK, this.get_mx_next_hop(hmail.todo.notes.next_hop));
335
+ const qw = hmail.todo.notes.get('queue.wants')
336
+ if (qw && qw !== 'smtp_forward') return next()
337
+
338
+ if (qw === 'smtp_forward' && hmail.todo.notes.get('queue.next_hop')) {
339
+ return next(OK, this.get_mx_next_hop(hmail.todo.notes.get('queue.next_hop')));
333
340
  }
334
341
 
335
342
  const dom = this.cfg.main.domain_selector === 'mail_from' ? hmail.todo.mail_from.host.toLowerCase() : domain.toLowerCase();
@@ -341,8 +348,7 @@ exports.get_mx = function (next, hmail, domain) {
341
348
  }
342
349
 
343
350
  const mx_opts = [
344
- 'auth_type', 'auth_user', 'auth_pass', 'bind', 'bind_helo',
345
- 'using_lmtp'
351
+ 'auth_type', 'auth_user', 'auth_pass', 'bind', 'bind_helo', 'using_lmtp'
346
352
  ]
347
353
 
348
354
  const mx = {
@@ -357,5 +363,5 @@ exports.get_mx = function (next, hmail, domain) {
357
363
  mx[o] = this.cfg[dom][o];
358
364
  })
359
365
 
360
- return next(OK, mx);
366
+ next(OK, mx);
361
367
  }
package/plugins/tls.js CHANGED
@@ -75,7 +75,7 @@ exports.set_notls = function (connection) {
75
75
 
76
76
  this.lognotice(connection, `STARTTLS failed. Marking ${connection.remote.ip} as non-TLS host for ${expiry} seconds`);
77
77
 
78
- server.notes.redis.setex(`no_tls|${connection.remote.ip}`, expiry, (new Date()).toISOString());
78
+ server.notes.redis.setEx(`no_tls|${connection.remote.ip}`, expiry, (new Date()).toISOString());
79
79
  }
80
80
 
81
81
  exports.upgrade_connection = function (next, connection, params) {
package/smtp_client.js CHANGED
@@ -168,10 +168,10 @@ class SMTPClient extends events.EventEmitter {
168
168
  // error is e.g. "Error: connect ECONNREFUSED"
169
169
  const errMsg = `${client.uuid}: [${client.host}:${client.port}] SMTP connection ${msg} ${error}`;
170
170
 
171
+ /* eslint-disable no-fallthrough */
171
172
  switch (client.state) {
172
173
  case STATE.ACTIVE:
173
174
  client.emit('error', errMsg);
174
- // eslint-disable no-fallthrough
175
175
  case STATE.IDLE:
176
176
  case STATE.RELEASED:
177
177
  client.destroy();
@@ -0,0 +1,52 @@
1
+ ; disable checks or reject for each test if you are worried about strictness
2
+
3
+ ;dns_timeout=30
4
+
5
+ [check]
6
+ match_re=true
7
+ bare_ip=true
8
+ dynamic=true
9
+ big_company=true
10
+ ; literal_mismatch: 1 = exact IP match, 2 = IP/24 match, 3 = /24 or RFC1918
11
+ literal_mismatch=2
12
+ valid_hostname=true
13
+ forward_dns=true
14
+ rdns_match=true
15
+ ; host_mismatch: hostname differs between EHLO invocations
16
+ host_mismatch=true
17
+ proto_mismatch: host sent EHLO but then tries to sent HELO or vice-versa
18
+ proto_mismatch=true
19
+
20
+ [reject]
21
+ host_mismatch=true
22
+ proto_mismatch=true
23
+ rdns_match=true
24
+ dynamic=true
25
+ bare_ip=true
26
+ literal_mismatch=true
27
+ valid_hostname=true
28
+ forward_dns=true
29
+ big_company=true
30
+
31
+ [skip]
32
+ private_ip=true
33
+ relaying=true
34
+ whitelist=true
35
+
36
+ [bigco]
37
+ msn.com=msn.com
38
+ hotmail.com=hotmail.com
39
+ yahoo.com=yahoo.com,yahoo.co.jp
40
+ yahoo.co.jp=yahoo.com,yahoo.co.jp
41
+ yahoo.co.uk=yahoo.co.uk
42
+ excite.com=excite.com,excitenetwork.com
43
+ mailexcite.com=excite.com,excitenetwork.com
44
+ yahoo.co.jp=yahoo.com,yahoo.co.jp
45
+ mailexcite.com=excite.com,excitenetwork.com
46
+ aol.com=aol.com
47
+ compuserve.com=compuserve.com,adelphia.net
48
+ nortelnetworks.com=nortelnetworks.com,nortel.com
49
+ earthlink.net=earthlink.net
50
+ earthling.net=earthling.net
51
+ google.com=google.com
52
+ gmail.com=google.com,gmail.com
@@ -69,7 +69,7 @@ exports.multi = {
69
69
  setUp : _set_up,
70
70
  'Spamcop' (test) {
71
71
  test.expect(4);
72
- function cb (err, zone, a, pending) {
72
+ this.plugin.multi('127.0.0.2', 'bl.spamcop.net', (err, zone, a, pending) => {
73
73
  test.equal(null, err);
74
74
  if (pending) {
75
75
  test.ok((Array.isArray(a) && a.length > 0));
@@ -78,12 +78,11 @@ exports.multi = {
78
78
  else {
79
79
  test.done();
80
80
  }
81
- }
82
- this.plugin.multi('127.0.0.2', 'bl.spamcop.net', cb);
81
+ })
83
82
  },
84
83
  'CBL' (test) {
85
84
  test.expect(4);
86
- function cb (err, zone, a, pending) {
85
+ this.plugin.multi('127.0.0.2', 'xbl.spamhaus.org', (err, zone, a, pending) => {
87
86
  test.equal(null, err);
88
87
  if (pending) {
89
88
  test.ok((Array.isArray(a) && a.length > 0));
@@ -92,12 +91,12 @@ exports.multi = {
92
91
  else {
93
92
  test.done();
94
93
  }
95
- }
96
- this.plugin.multi('127.0.0.2', 'xbl.spamhaus.org', cb);
94
+ })
97
95
  },
98
96
  'Spamcop + CBL' (test) {
99
97
  test.expect(12);
100
- function cb (err, zone, a, pending) {
98
+ const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
99
+ this.plugin.multi('127.0.0.2', dnsbls, (err, zone, a, pending) => {
101
100
  test.equal(null, err);
102
101
  if (pending) {
103
102
  test.ok(zone);
@@ -110,15 +109,20 @@ exports.multi = {
110
109
  test.equal(false, pending);
111
110
  test.done();
112
111
  }
113
- }
114
- const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
115
- this.plugin.multi('127.0.0.2', dnsbls, cb);
112
+ })
116
113
  },
117
114
  'Spamcop + CBL + negative result' (test) {
118
115
  test.expect(12);
119
- function cb (err, zone, a, pending) {
116
+ const dnsbls = [ 'bl.spamcop.net','xbl.spamhaus.org' ];
117
+ this.plugin.multi('127.0.0.1', dnsbls, (err, zone, a, pending) => {
120
118
  test.equal(null, err);
121
- test.equal(null, a);
119
+ if (a && a[0] && a[0] === '127.255.255.254') {
120
+ test.deepEqual(['127.255.255.254'], a)
121
+ console.warn(`ERROR: DNSBLs don't work with PUBLIC DNS!`)
122
+ }
123
+ else {
124
+ test.equal(null, a)
125
+ }
122
126
  if (pending) {
123
127
  test.equal(true, pending);
124
128
  test.ok(zone);
@@ -128,14 +132,19 @@ exports.multi = {
128
132
  test.equal(null, zone);
129
133
  test.done();
130
134
  }
131
- }
132
- const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
133
- this.plugin.multi('127.0.0.1', dnsbls, cb);
135
+ })
134
136
  },
135
137
  'IPv6 addresses supported' (test) {
136
138
  test.expect(12);
137
- function cb (err, zone, a, pending) {
138
- test.equal(null, a);
139
+ const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
140
+ this.plugin.multi('::1', dnsbls, (err, zone, a, pending) => {
141
+ if (a && a[0] && a[0] === '127.255.255.254') {
142
+ test.deepEqual(['127.255.255.254'], a)
143
+ console.warn(`ERROR: DNSBLs don't work with PUBLIC DNS!`)
144
+ }
145
+ else {
146
+ test.equal(null, a);
147
+ }
139
148
  if (pending) {
140
149
  test.deepEqual(null, err);
141
150
  test.equal(true, pending);
@@ -147,9 +156,7 @@ exports.multi = {
147
156
  test.equal(null, zone);
148
157
  test.done();
149
158
  }
150
- }
151
- const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
152
- this.plugin.multi('::1', dnsbls, cb);
159
+ })
153
160
  }
154
161
  }
155
162
 
@@ -157,25 +164,28 @@ exports.first = {
157
164
  setUp : _set_up,
158
165
  'positive result' (test) {
159
166
  test.expect(3);
160
- function cb (err, zone, a) {
167
+ const dnsbls = [ 'xbl.spamhaus.org', 'bl.spamcop.net' ];
168
+ this.plugin.first('127.0.0.2', dnsbls, (err, zone, a) => {
161
169
  test.equal(null, err);
162
170
  test.ok(zone);
163
171
  test.ok((Array.isArray(a) && a.length > 0));
164
172
  test.done();
165
- }
166
- const dnsbls = [ 'xbl.spamhaus.org', 'bl.spamcop.net' ];
167
- this.plugin.first('127.0.0.2', dnsbls , cb);
173
+ })
168
174
  },
169
175
  'negative result' (test) {
170
- test.expect(3);
171
- function cb (err, zone, a) {
176
+ test.expect(2);
177
+ const dnsbls = [ 'xbl.spamhaus.org', 'bl.spamcop.net' ];
178
+ this.plugin.first('127.0.0.1', dnsbls, (err, zone, a) => {
172
179
  test.equal(null, err);
173
- test.equal(null, zone);
174
- test.equal(null, a);
180
+ if (a && a[0] && a[0] === '127.255.255.254') {
181
+ test.deepEqual(['127.255.255.254'], a)
182
+ console.warn(`ERROR: DNSBLs don't work with PUBLIC DNS!`)
183
+ }
184
+ else {
185
+ test.equal(null, a);
186
+ }
175
187
  test.done();
176
- }
177
- const dnsbls = [ 'xbl.spamhaus.org', 'bl.spamcop.net' ];
178
- this.plugin.first('127.0.0.1', dnsbls, cb);
188
+ })
179
189
  },
180
190
  'each_cb' (test) {
181
191
  test.expect(7);