Haraka 2.8.28 → 3.0.1

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 (135) hide show
  1. package/.eslintrc.yaml +2 -10
  2. package/Changes.md +84 -2
  3. package/Dockerfile +1 -1
  4. package/Plugins.md +9 -4
  5. package/README.md +2 -6
  6. package/bin/haraka +5 -4
  7. package/config/outbound.ini +0 -7
  8. package/config/plugins +1 -1
  9. package/config/smtp.ini +1 -1
  10. package/config/smtp_forward.ini +2 -8
  11. package/config/smtp_proxy.ini +0 -6
  12. package/connection.js +178 -204
  13. package/coverage/lcov.info +13863 -0
  14. package/coverage/tmp/coverage-42958-1658373250585-0.json +1 -0
  15. package/coverage/tmp/coverage-42961-1658373250529-0.json +1 -0
  16. package/dkim.js +66 -73
  17. package/docs/Body.md +1 -22
  18. package/docs/CoreConfig.md +2 -2
  19. package/docs/Header.md +1 -47
  20. package/docs/Outbound.md +8 -36
  21. package/endpoint.js +1 -1
  22. package/haraka.js +1 -1
  23. package/host_pool.js +8 -12
  24. package/logger.js +25 -32
  25. package/outbound/client_pool.js +11 -153
  26. package/outbound/config.js +5 -11
  27. package/outbound/hmail.js +109 -143
  28. package/outbound/index.js +13 -25
  29. package/outbound/mx_lookup.js +10 -7
  30. package/outbound/queue.js +8 -12
  31. package/outbound/timer_queue.js +2 -4
  32. package/outbound/tls.js +17 -18
  33. package/outbound/todo.js +1 -0
  34. package/package.json +57 -55
  35. package/plugins/auth/auth_base.js +39 -63
  36. package/plugins/auth/auth_bridge.js +3 -4
  37. package/plugins/auth/auth_proxy.js +16 -16
  38. package/plugins/auth/auth_vpopmaild.js +30 -37
  39. package/plugins/auth/flat_file.js +9 -13
  40. package/plugins/avg.js +9 -11
  41. package/plugins/backscatterer.js +1 -1
  42. package/plugins/block_me.js +2 -6
  43. package/plugins/bounce.js +106 -124
  44. package/plugins/clamd.js +59 -63
  45. package/plugins/data.signatures.js +6 -6
  46. package/plugins/data.uribl.js +1 -415
  47. package/plugins/delay_deny.js +19 -20
  48. package/plugins/dkim_sign.js +56 -62
  49. package/plugins/dkim_verify.js +9 -8
  50. package/plugins/dns_list_base.js +43 -42
  51. package/plugins/dnsbl.js +41 -46
  52. package/plugins/dnswl.js +23 -26
  53. package/plugins/early_talker.js +24 -28
  54. package/plugins/esets.js +8 -11
  55. package/plugins/greylist.js +161 -190
  56. package/plugins/helo.checks.js +175 -197
  57. package/plugins/mail_from.is_resolvable.js +38 -38
  58. package/plugins/messagesniffer.js +33 -40
  59. package/plugins/prevent_credential_leaks.js +7 -5
  60. package/plugins/process_title.js +16 -17
  61. package/plugins/queue/deliver.js +2 -2
  62. package/plugins/queue/lmtp.js +5 -6
  63. package/plugins/queue/qmail-queue.js +11 -13
  64. package/plugins/queue/quarantine.js +25 -34
  65. package/plugins/queue/rabbitmq.js +3 -2
  66. package/plugins/queue/rabbitmq_amqplib.js +9 -9
  67. package/plugins/queue/smtp_bridge.js +5 -4
  68. package/plugins/queue/smtp_forward.js +81 -89
  69. package/plugins/queue/smtp_proxy.js +21 -22
  70. package/plugins/queue/test.js +2 -1
  71. package/plugins/rcpt_to.host_list_base.js +20 -30
  72. package/plugins/rcpt_to.in_host_list.js +12 -14
  73. package/plugins/rcpt_to.max_count.js +7 -5
  74. package/plugins/record_envelope_addresses.js +4 -6
  75. package/plugins/relay.js +64 -74
  76. package/plugins/reseed_rng.js +1 -2
  77. package/plugins/spamassassin.js +56 -68
  78. package/plugins/status.js +2 -3
  79. package/plugins/tarpit.js +8 -11
  80. package/plugins/tls.js +14 -17
  81. package/plugins/toobusy.js +6 -8
  82. package/plugins/xclient.js +14 -25
  83. package/plugins.js +24 -29
  84. package/rfc1869.js +2 -2
  85. package/server.js +3 -13
  86. package/smtp_client.js +138 -215
  87. package/tests/config/smtp_forward.ini +0 -6
  88. package/tests/fixtures/line_socket.js +1 -1
  89. package/tests/fixtures/util_hmailitem.js +5 -7
  90. package/tests/fixtures/vm_harness.js +2 -2
  91. package/tests/host_pool.js +13 -14
  92. package/tests/installation/plugins/inherits.js +1 -2
  93. package/tests/logger.js +2 -2
  94. package/tests/plugins/bounce.js +6 -8
  95. package/tests/plugins/dkim_signer.js +7 -7
  96. package/tests/plugins/dns_list_base.js +7 -7
  97. package/tests/plugins/helo.checks.js +1 -1
  98. package/tests/plugins/mail_from.is_resolvable.js +10 -54
  99. package/tests/plugins/queue/smtp_forward.js +11 -11
  100. package/tests/plugins/rcpt_to.host_list_base.js +1 -1
  101. package/tests/plugins/rcpt_to.in_host_list.js +1 -1
  102. package/tests/plugins/spamassassin.js +1 -1
  103. package/tests/queue/multibyte +0 -0
  104. package/tests/queue/plain +0 -0
  105. package/tests/rfc1869.js +4 -1
  106. package/tests/server.js +15 -9
  107. package/tests/smtp_client/auth.js +4 -14
  108. package/tests/smtp_client/basic.js +5 -15
  109. package/tests/smtp_client.js +7 -3
  110. package/tests/transaction.js +72 -19
  111. package/tls_socket.js +75 -85
  112. package/transaction.js +7 -9
  113. package/attachment_stream.js +0 -118
  114. package/bin/spf +0 -48
  115. package/chunkemitter.js +0 -75
  116. package/config/data.uribl.excludes +0 -202
  117. package/config/data.uribl.ini +0 -37
  118. package/config/spf.ini +0 -1
  119. package/docs/plugins/attachment.md +0 -92
  120. package/docs/plugins/data.uribl.md +0 -120
  121. package/docs/plugins/spf.md +0 -142
  122. package/mailbody.js +0 -502
  123. package/mailheader.js +0 -304
  124. package/messagestream.js +0 -441
  125. package/plugins/aliases.js +0 -120
  126. package/plugins/attachment.js +0 -503
  127. package/plugins/connect.p0f.js +0 -5
  128. package/plugins/spf.js +0 -327
  129. package/spf.js +0 -689
  130. package/tests/mailbody.js +0 -348
  131. package/tests/mailheader.js +0 -138
  132. package/tests/messagestream.js +0 -34
  133. package/tests/plugins/aliases.js +0 -376
  134. package/tests/plugins/spf.js +0 -251
  135. package/tests/spf.js +0 -96
package/logger.js CHANGED
@@ -87,14 +87,13 @@ logger._init = function () {
87
87
  }
88
88
 
89
89
  logger.load_log_ini = function () {
90
- const self = this;
91
- self.cfg = config.get('log.ini', {
90
+ this.cfg = config.get('log.ini', {
92
91
  booleans: [
93
92
  '+main.timestamps',
94
93
  ]
95
94
  },
96
95
  () => {
97
- self.load_log_ini();
96
+ this.load_log_ini();
98
97
  });
99
98
 
100
99
  this.set_loglevel(this.cfg.main.level);
@@ -135,8 +134,7 @@ logger.log = (level, data, logobj) => {
135
134
  if (level === 'PROTOCOL') {
136
135
  data = data.replace(/\n/g, '\\n');
137
136
  }
138
- data = data.replace(/\r/g, '\\r')
139
- .replace(/\n$/, '');
137
+ data = data.replace(/\r/g, '\\r').replace(/\n$/, '');
140
138
 
141
139
  const item = { level, data, obj: logobj};
142
140
 
@@ -159,18 +157,19 @@ logger.log = (level, data, logobj) => {
159
157
 
160
158
  logger.log_respond = (retval, msg, data) => {
161
159
  // any other return code is irrelevant
162
- if (retval !== constants.cont) { return false; }
160
+ if (retval !== constants.cont) return false;
161
+
163
162
  let timestamp_string = '';
164
- if (logger.timestamps) {
165
- timestamp_string = `${new Date().toISOString()} `;
166
- }
163
+ if (logger.timestamps) timestamp_string = `${new Date().toISOString()} `;
164
+
167
165
  const color = logger.colors[data.level];
168
166
  if (color && stdout_is_tty) {
169
167
  process.stdout.write(`${timestamp_string}${logger.colorize(color,data.data)}\n`);
170
- return true;
168
+ }
169
+ else {
170
+ process.stdout.write(`${timestamp_string}${data.data}\n`);
171
171
  }
172
172
 
173
- process.stdout.write(`${timestamp_string}${data.data}\n`);
174
173
  return true;
175
174
  }
176
175
 
@@ -207,17 +206,16 @@ logger.set_format = function (format) {
207
206
  }
208
207
 
209
208
  logger._init_loglevel = function () {
210
- const self = this;
211
209
 
212
210
  const _loglevel = config.get('loglevel', 'value', () => {
213
- self._init_loglevel();
211
+ this._init_loglevel();
214
212
  });
215
213
 
216
- self.set_loglevel(_loglevel);
214
+ this.set_loglevel(_loglevel);
217
215
  }
218
216
 
219
217
  logger.would_log = level => {
220
- if (logger.loglevel < level) { return false; }
218
+ if (logger.loglevel < level) return false;
221
219
  return true;
222
220
  }
223
221
 
@@ -226,15 +224,13 @@ logger.set_timestamps = value => {
226
224
  }
227
225
 
228
226
  logger._init_timestamps = function () {
229
- const self = this;
230
227
 
231
228
  const _timestamps = config.get('log_timestamps', 'value', () => {
232
- self._init_timestamps();
229
+ this._init_timestamps();
233
230
  });
234
231
 
235
- // If we've already been toggled to true by the cfg, we should respect
236
- // this.
237
- self.set_timestamps(logger.timestamps || _timestamps);
232
+ // If we've already been toggled to true by the cfg, we should respect this.
233
+ this.set_timestamps(logger.timestamps || _timestamps);
238
234
  }
239
235
 
240
236
  logger._init();
@@ -247,8 +243,7 @@ logger.log_if_level = (level, key, plugin) => function () {
247
243
  origin: (plugin || 'core'),
248
244
  message: ''
249
245
  };
250
- for (let i=0; i < arguments.length; i++) {
251
- const data = arguments[i];
246
+ for (const data of arguments) {
252
247
  if (typeof data !== 'object') {
253
248
  logobj.message += (data);
254
249
  continue;
@@ -258,9 +253,7 @@ logger.log_if_level = (level, key, plugin) => function () {
258
253
  // if the object is a connection, add the connection id
259
254
  if (data instanceof connection.Connection) {
260
255
  logobj.uuid = data.uuid;
261
- if (data.tran_count > 0) {
262
- logobj.uuid += `.${data.tran_count}`;
263
- }
256
+ if (data.tran_count > 0) logobj.uuid += `.${data.tran_count}`;
264
257
  }
265
258
  else if (data instanceof plugins.Plugin) {
266
259
  logobj.origin = data.name;
@@ -271,10 +264,8 @@ logger.log_if_level = (level, key, plugin) => function () {
271
264
  else if (data instanceof outbound.HMailItem) {
272
265
  logobj.origin = 'outbound';
273
266
  if (data.todo) {
274
- if (data.todo.uuid)
275
- logobj.uuid = data.todo.uuid;
276
- if (data.todo.client_uuid) {
277
- // dirty hack
267
+ if (data.todo.uuid) logobj.uuid = data.todo.uuid;
268
+ if (data.todo.client_uuid) { // dirty hack
278
269
  logobj.origin = `outbound] [${data.todo.client_uuid}`;
279
270
  }
280
271
  }
@@ -298,32 +289,34 @@ logger.log_if_level = (level, key, plugin) => function () {
298
289
  logobj.message += (util.inspect(data));
299
290
  }
300
291
  }
292
+
301
293
  switch (logger.format) {
302
294
  case logger.formats.LOGFMT:
303
295
  logger.log(
304
296
  level,
305
297
  stringify(logobj)
306
298
  );
307
- return true;
299
+ break
308
300
  case logger.formats.JSON:
309
301
  logger.log(
310
302
  level,
311
303
  JSON.stringify(logobj)
312
304
  );
313
- return true;
305
+ break
314
306
  case logger.formats.DEFAULT:
315
307
  default:
316
308
  logger.log(
317
309
  level,
318
310
  `[${logobj.level}] [${logobj.uuid}] [${logobj.origin}] ${logobj.message}`
319
311
  );
320
- return true;
321
312
  }
313
+ return true;
322
314
  }
323
315
 
324
316
  logger.add_log_methods = (object, plugin) => {
325
317
  if (!object) return;
326
318
  if (typeof(object) !== 'object') return;
319
+
327
320
  for (const level in logger.levels) {
328
321
  const fname = `log${level.toLowerCase()}`;
329
322
  if (object[fname]) continue; // already added
@@ -1,29 +1,19 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
- const generic_pool = require('generic-pool');
4
3
  const utils = require('haraka-utils');
5
4
 
6
5
  const sock = require('../line_socket');
7
- const server = require('../server');
8
6
  const logger = require('../logger');
9
7
 
10
8
  const obc = require('./config');
11
9
 
12
- function _create_socket (pool_name, port, host, local_addr, is_unix_socket, callback) {
13
- const socket = is_unix_socket ? sock.connect({path: host}) :
14
- sock.connect({port, host, localAddress: local_addr});
15
- socket.__pool_name = pool_name;
10
+ function _create_socket (name, port, host, local_addr, is_unix_socket, callback) {
11
+
12
+ const socket = is_unix_socket ? sock.connect({path: host}) : sock.connect({port, host, localAddress: local_addr});
13
+ socket.name = name;
16
14
  socket.__uuid = utils.uuid();
17
15
  socket.setTimeout(obc.cfg.connect_timeout * 1000);
18
- logger.logdebug(
19
- '[outbound] created',
20
- {
21
- uuid: socket.__uuid,
22
- host,
23
- port,
24
- pool_timeout: obc.cfg.pool_timeout
25
- }
26
- );
16
+ logger.logdebug(`[outbound] created. host: ${host} port: ${port}`, { uuid: socket.__uuid });
27
17
  socket.once('connect', () => {
28
18
  socket.removeAllListeners('error'); // these get added after callback
29
19
  socket.removeAllListeners('timeout');
@@ -43,148 +33,16 @@ function _create_socket (pool_name, port, host, local_addr, is_unix_socket, call
43
33
  });
44
34
  }
45
35
 
46
- // Separate pools are kept for each set of server attributes.
47
- function get_pool (port, host, local_addr, is_unix_socket, max) {
48
- port = port || 25;
49
- host = host || 'localhost';
50
- const name = `outbound::${port}:${host}:${local_addr}:${obc.cfg.pool_timeout}`;
51
- if (!server.notes.pool) server.notes.pool = {};
52
- if (server.notes.pool[name]) return server.notes.pool[name];
53
-
54
- const pool = generic_pool.Pool({
55
- name,
56
- create (done) {
57
- _create_socket(this.name, port, host, local_addr, is_unix_socket, done);
58
- },
59
- validate: socket => socket.__fromPool && socket.writable,
60
- destroy: socket => {
61
- logger.logdebug(`[outbound] destroying pool entry ${socket.__uuid} for ${host}:${port}`);
62
- socket.removeAllListeners();
63
- socket.__fromPool = false;
64
- socket.on('line', line => {
65
- // Just assume this is a valid response
66
- logger.logprotocol(`[outbound] S: ${line}`);
67
- });
68
- socket.once('error', err => {
69
- logger.logwarn(`[outbound] Socket got an error while shutting down: ${err}`);
70
- });
71
- socket.once('end', () => {
72
- logger.loginfo("[outbound] Remote end half closed during destroy()");
73
- socket.destroy();
74
- })
75
- if (socket.writable) {
76
- logger.logprotocol(`[outbound] [${socket.__uuid}] C: QUIT`);
77
- socket.write("QUIT\r\n");
78
- }
79
- socket.end(); // half close
80
- },
81
- max: max || 10,
82
- idleTimeoutMillis: obc.cfg.pool_timeout * 1000,
83
- log: (str, level) => {
84
- if (/this._availableObjects.length=/.test(str)) return;
85
- level = (level === 'verbose') ? 'debug' : level;
86
- logger[`log${level}`](`[outbound] [${name}] ${str}`);
87
- }
88
- });
89
- server.notes.pool[name] = pool;
90
-
91
- return pool;
92
- }
93
36
 
94
37
  // Get a socket for the given attributes.
95
- exports.get_client = (port, host, local_addr, is_unix_socket, callback) => {
96
- if (obc.cfg.pool_concurrency_max == 0) {
97
- return _create_socket(null, port, host, local_addr, is_unix_socket, callback);
98
- }
38
+ exports.get_client = (port = 25, host = 'localhost', local_addr, is_unix_socket, callback) => {
39
+ const name = `outbound::${port}:${host}:${local_addr}`;
99
40
 
100
- const pool = get_pool(port, host, local_addr, is_unix_socket, obc.cfg.pool_concurrency_max);
101
- if (pool.waitingClientsCount() >= obc.cfg.pool_concurrency_max) {
102
- return callback("Too many waiting clients for pool", null);
103
- }
104
- pool.acquire((err, socket) => {
105
- if (err) return callback(err);
106
- socket.__acquired = true;
107
- logger.loginfo(`[outbound] acquired socket ${socket.__uuid} for ${socket.__pool_name}`);
108
- callback(null, socket);
109
- });
41
+ _create_socket(name, port, host, local_addr, is_unix_socket, callback)
110
42
  }
111
43
 
112
44
  exports.release_client = (socket, port, host, local_addr, error) => {
113
45
  logger.logdebug(`[outbound] release_client: ${socket.__uuid} ${host}:${port} to ${local_addr}`);
114
-
115
- const name = socket.__pool_name;
116
-
117
- if (!name && obc.cfg.pool_concurrency_max == 0) {
118
- return sockend();
119
- }
120
-
121
- if (!socket.__acquired) {
122
- logger.logwarn(`Release an un-acquired socket. Stack: ${(new Error()).stack}`);
123
- return;
124
- }
125
- socket.__acquired = false;
126
-
127
- if (!(server.notes && server.notes.pool)) {
128
- logger.logcrit(`[outbound] Releasing a pool (${name}) that doesn't exist!`);
129
- return;
130
- }
131
- const pool = server.notes.pool[name];
132
- if (!pool) {
133
- logger.logcrit(`[outbound] Releasing a pool (${name}) that doesn't exist!`);
134
- return;
135
- }
136
-
137
- if (error) {
138
- return sockend();
139
- }
140
-
141
- if (obc.cfg.pool_timeout == 0) {
142
- logger.loginfo("[outbound] Pool_timeout is zero - shutting it down");
143
- return sockend();
144
- }
145
-
146
- for (const event of ['close','error','end','timeout','line']) {
147
- socket.removeAllListeners(event);
148
- }
149
-
150
- socket.__fromPool = true;
151
-
152
- socket.once('error', err => {
153
- logger.logwarn(`[outbound] Socket [${name}] in pool got an error: ${err}`);
154
- sockend();
155
- });
156
-
157
- socket.once('end', () => {
158
- logger.loginfo(`[outbound] Socket [${name}] in pool got FIN`);
159
- socket.writable = false;
160
- sockend();
161
- });
162
-
163
- pool.release(socket);
164
-
165
- function sockend () {
166
- socket.__fromPool = false;
167
- if (server.notes.pool && server.notes.pool[name]) {
168
- server.notes.pool[name].destroy(socket);
169
- }
170
- else {
171
- socket.removeAllListeners();
172
- socket.destroy();
173
- }
174
- }
175
- }
176
-
177
- exports.drain_pools = () => {
178
- if (!server.notes.pool || Object.keys(server.notes.pool).length == 0) {
179
- return logger.logdebug("[outbound] Drain pools: No pools available");
180
- }
181
- Object.keys(server.notes.pool).forEach(p => {
182
- logger.logdebug(`[outbound] Drain pools: Draining SMTP connection pool ${p}`);
183
- server.notes.pool[p].drain(() => {
184
- if (!server.notes.pool[p]) return;
185
- server.notes.pool[p].destroyAllNow();
186
- delete server.notes.pool[p];
187
- });
188
- });
189
- logger.logdebug("[outbound] Drain pools: Pools shut down");
46
+ socket.removeAllListeners();
47
+ socket.destroy();
190
48
  }
@@ -35,12 +35,6 @@ function load_config () {
35
35
  if (!cfg.connect_timeout) {
36
36
  cfg.connect_timeout = 30;
37
37
  }
38
- if (cfg.pool_timeout === undefined) {
39
- cfg.pool_timeout = 50;
40
- }
41
- if (cfg.pool_concurrency_max === undefined) {
42
- cfg.pool_concurrency_max = 10;
43
- }
44
38
  if (!cfg.ipv6_enabled && config.get('outbound.ipv6_enabled')) {
45
39
  cfg.ipv6_enabled = true;
46
40
  }
@@ -58,13 +52,13 @@ exports.set_temp_fail_intervals = function () {
58
52
  // it with the equivalent times of maxTempFailures using the original 2^N formula
59
53
  // 3) the word "none" can be specified if you do not want to retry a temp failure,
60
54
  // equivalent behavior of specifying maxTempFailures=1
61
- const cfg = this.cfg;
55
+ const { cfg } = this;
62
56
 
63
57
  // Fallback function to create an array of the original retry times
64
58
  function set_old_defaults () {
65
59
  cfg.temp_fail_intervals = [];
66
60
  for (let i=1; i<cfg.maxTempFailures; i++) {
67
- cfg.temp_fail_intervals.push(Math.pow(2, (i + 5)));
61
+ cfg.temp_fail_intervals.push(2 ** (i + 5));
68
62
  }
69
63
  }
70
64
 
@@ -109,13 +103,13 @@ exports.set_temp_fail_intervals = function () {
109
103
  // do nothing, this is the base unit
110
104
  break;
111
105
  case 'm':
112
- num = num * 60;
106
+ num *= 60;
113
107
  break;
114
108
  case 'h':
115
- num = num * 3600;
109
+ num *= 3600;
116
110
  break;
117
111
  case 'd':
118
- num = num * 86400;
112
+ num *= 86400;
119
113
  break;
120
114
  default:
121
115
  return error(i, 'invalid time span symbol');