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.
- package/.eslintrc.yaml +5 -9
- package/.prettierrc.yml +1 -0
- package/CONTRIBUTORS.md +11 -0
- package/Changes.md +1365 -1214
- package/Plugins.md +117 -105
- package/README.md +4 -13
- package/bin/haraka +197 -298
- package/config/auth_flat_file.ini +1 -0
- package/config/dhparams.pem +8 -0
- package/config/mail_from.is_resolvable.ini +4 -2
- package/config/me +1 -0
- package/config/outbound.ini +0 -2
- package/config/plugins +36 -35
- package/config/smtp.ini +0 -1
- package/config/smtp.json +17 -0
- package/config/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/connection.js +46 -73
- package/contrib/bsd-rc.d/haraka +3 -1
- package/contrib/plugin2npm.sh +6 -36
- package/docs/CoreConfig.md +2 -2
- package/docs/Logging.md +7 -21
- package/docs/Outbound.md +104 -201
- package/docs/Plugins.md +2 -2
- package/docs/Transaction.md +59 -82
- package/docs/plugins/queue/smtp_proxy.md +5 -10
- package/docs/plugins/tls.md +29 -9
- package/endpoint.js +16 -13
- package/haraka.js +10 -14
- package/host_pool.js +5 -5
- package/line_socket.js +3 -4
- package/logger.js +44 -28
- package/outbound/client_pool.js +27 -23
- package/outbound/config.js +4 -6
- package/outbound/fsync_writestream.js +1 -1
- package/outbound/hmail.js +178 -218
- package/outbound/index.js +86 -99
- package/outbound/qfile.js +1 -1
- package/outbound/queue.js +51 -44
- package/outbound/timer_queue.js +3 -2
- package/outbound/tls.js +19 -7
- package/package.json +59 -48
- package/plugins/.eslintrc.yaml +0 -6
- package/plugins/auth/auth_base.js +4 -2
- package/plugins/auth/auth_proxy.js +14 -12
- package/plugins/auth/auth_vpopmaild.js +1 -1
- package/plugins/block_me.js +1 -1
- package/plugins/data.signatures.js +2 -4
- package/plugins/early_talker.js +2 -1
- package/plugins/mail_from.is_resolvable.js +65 -135
- package/plugins/queue/deliver.js +4 -5
- package/plugins/queue/lmtp.js +11 -14
- package/plugins/queue/qmail-queue.js +2 -2
- package/plugins/queue/quarantine.js +2 -2
- package/plugins/queue/rabbitmq.js +16 -17
- package/plugins/queue/smtp_forward.js +3 -3
- package/plugins/queue/smtp_proxy.js +10 -1
- package/plugins/queue/test.js +2 -2
- package/plugins/rcpt_to.host_list_base.js +5 -5
- package/plugins/rcpt_to.in_host_list.js +2 -2
- package/plugins/relay.js +6 -7
- package/plugins/reseed_rng.js +1 -1
- package/plugins/status.js +37 -33
- package/plugins/tls.js +2 -2
- package/plugins/xclient.js +3 -2
- package/plugins.js +50 -54
- package/run_tests +3 -30
- package/server.js +190 -190
- package/smtp_client.js +30 -23
- package/{tests → test}/config/plugins +0 -2
- package/{tests → test}/config/smtp.ini +1 -1
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/connection.js +302 -0
- package/test/endpoint.js +94 -0
- package/{tests → test}/fixtures/line_socket.js +1 -1
- package/{tests → test}/fixtures/util_hmailitem.js +19 -25
- package/{tests → test}/host_pool.js +42 -57
- package/test/logger.js +258 -0
- package/test/outbound/hmail.js +141 -0
- package/test/outbound/index.js +220 -0
- package/test/outbound/qfile.js +126 -0
- package/test/outbound_bounce_net_errors.js +142 -0
- package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
- package/test/plugins/auth/auth_base.js +484 -0
- package/test/plugins/auth/auth_vpopmaild.js +83 -0
- package/test/plugins/early_talker.js +104 -0
- package/test/plugins/mail_from.is_resolvable.js +35 -0
- package/test/plugins/queue/smtp_forward.js +206 -0
- package/test/plugins/rcpt_to.host_list_base.js +122 -0
- package/test/plugins/rcpt_to.in_host_list.js +193 -0
- package/test/plugins/relay.js +303 -0
- package/test/plugins/status.js +130 -0
- package/test/plugins/tls.js +70 -0
- package/test/plugins.js +228 -0
- package/test/rfc1869.js +73 -0
- package/test/server.js +491 -0
- package/test/smtp_client.js +299 -0
- package/test/tls_socket.js +273 -0
- package/test/transaction.js +270 -0
- package/tls_socket.js +202 -252
- package/transaction.js +8 -23
- package/CONTRIBUTING.md +0 -1
- package/bin/dkimverify +0 -40
- package/config/access.domains +0 -13
- package/config/attachment.ctype.regex +0 -2
- package/config/attachment.filename.regex +0 -1
- package/config/avg.ini +0 -5
- package/config/bounce.ini +0 -15
- package/config/data.headers.ini +0 -61
- package/config/dkim/dkim_key_gen.sh +0 -78
- package/config/dkim_sign.ini +0 -4
- package/config/dkim_verify.ini +0 -7
- package/config/dnsbl.ini +0 -23
- package/config/greylist.ini +0 -43
- package/config/helo.checks.ini +0 -52
- package/config/messagesniffer.ini +0 -18
- package/config/spamassassin.ini +0 -56
- package/dkim.js +0 -614
- package/docs/plugins/avg.md +0 -35
- package/docs/plugins/bounce.md +0 -69
- package/docs/plugins/clamd.md +0 -147
- package/docs/plugins/esets.md +0 -8
- package/docs/plugins/greylist.md +0 -90
- package/docs/plugins/helo.checks.md +0 -135
- package/docs/plugins/messagesniffer.md +0 -163
- package/docs/plugins/spamassassin.md +0 -180
- package/outbound/mx_lookup.js +0 -70
- package/plugins/auth/auth_ldap.js +0 -3
- package/plugins/avg.js +0 -162
- package/plugins/backscatterer.js +0 -25
- package/plugins/bounce.js +0 -381
- package/plugins/clamd.js +0 -382
- package/plugins/data.uribl.js +0 -4
- package/plugins/dkim_sign.js +0 -395
- package/plugins/dkim_verify.js +0 -62
- package/plugins/dns_list_base.js +0 -221
- package/plugins/dnsbl.js +0 -146
- package/plugins/dnswl.js +0 -58
- package/plugins/esets.js +0 -71
- package/plugins/graph.js +0 -5
- package/plugins/greylist.js +0 -645
- package/plugins/helo.checks.js +0 -533
- package/plugins/messagesniffer.js +0 -381
- package/plugins/rcpt_to.ldap.js +0 -3
- package/plugins/rcpt_to.max_count.js +0 -24
- package/plugins/spamassassin.js +0 -384
- package/tests/config/dkim/example.com/dns +0 -29
- package/tests/config/dkim/example.com/private +0 -6
- package/tests/config/dkim/example.com/public +0 -4
- package/tests/config/dkim/example.com/selector +0 -1
- package/tests/config/dkim.private.key +0 -6
- package/tests/config/dkim_sign.ini +0 -4
- package/tests/config/helo.checks.ini +0 -52
- package/tests/connection.js +0 -327
- package/tests/endpoint.js +0 -128
- package/tests/fixtures/vm_harness.js +0 -59
- package/tests/logger.js +0 -327
- package/tests/outbound/hmail.js +0 -112
- package/tests/outbound/index.js +0 -324
- package/tests/outbound/qfile.js +0 -67
- package/tests/outbound_bounce_net_errors.js +0 -173
- package/tests/plugins/auth/auth_base.js +0 -463
- package/tests/plugins/auth/auth_vpopmaild.js +0 -91
- package/tests/plugins/bounce.js +0 -307
- package/tests/plugins/clamd.js +0 -224
- package/tests/plugins/deprecated/relay_acl.js +0 -140
- package/tests/plugins/deprecated/relay_all.js +0 -59
- package/tests/plugins/dkim_sign.js +0 -315
- package/tests/plugins/dkim_signer.js +0 -108
- package/tests/plugins/dns_list_base.js +0 -259
- package/tests/plugins/dnsbl.js +0 -101
- package/tests/plugins/early_talker.js +0 -115
- package/tests/plugins/greylist.js +0 -58
- package/tests/plugins/helo.checks.js +0 -525
- package/tests/plugins/mail_from.is_resolvable.js +0 -116
- package/tests/plugins/queue/smtp_forward.js +0 -221
- package/tests/plugins/rcpt_to.host_list_base.js +0 -132
- package/tests/plugins/rcpt_to.in_host_list.js +0 -218
- package/tests/plugins/relay.js +0 -339
- package/tests/plugins/spamassassin.js +0 -171
- package/tests/plugins/status.js +0 -138
- package/tests/plugins/tls.js +0 -84
- package/tests/plugins.js +0 -247
- package/tests/rfc1869.js +0 -61
- package/tests/server.js +0 -510
- package/tests/smtp_client/auth.js +0 -105
- package/tests/smtp_client/basic.js +0 -101
- package/tests/smtp_client.js +0 -80
- package/tests/tls_socket.js +0 -333
- package/tests/transaction.js +0 -284
- /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
- /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
- /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
- /package/docs/{plugins → deprecated}/dnswl.md +0 -0
- /package/{tests → test}/.eslintrc.yaml +0 -0
- /package/{tests → test}/config/auth_flat_file.ini +0 -0
- /package/{tests → test}/config/dhparams.pem +0 -0
- /package/{tests → test}/config/host_list +0 -0
- /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
- /package/{tests → test}/config/outbound_tls_key.pem +0 -0
- /package/{tests → test}/config/smtp_forward.ini +0 -0
- /package/{tests → test}/config/tls/ec.pem +0 -0
- /package/{tests → test}/config/tls/haraka.local.pem +0 -0
- /package/{tests → test}/config/tls/mismatched.pem +0 -0
- /package/{tests → test}/config/tls.ini +0 -0
- /package/{tests → test}/config/tls_cert.pem +0 -0
- /package/{tests → test}/config/tls_key.pem +0 -0
- /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
- /package/{tests → test}/installation/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/config/tls.ini +0 -0
- /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
- /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
- /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
- /package/{tests → test}/installation/plugins/inherits.js +0 -0
- /package/{tests → test}/installation/plugins/load_first.js +0 -0
- /package/{tests → test}/installation/plugins/plugin.js +0 -0
- /package/{tests → test}/installation/plugins/tls.js +0 -0
- /package/{tests → test}/loud/config/dhparams.pem +0 -0
- /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
- /package/{tests → test}/loud/config/tls.ini +0 -0
- /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
- /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- /package/{tests → test}/queue/multibyte +0 -0
- /package/{tests → test}/queue/plain +0 -0
- /package/{tests → test}/queue/zero-length +0 -0
- /package/{tests → test}/test-queue/delete-me +0 -0
package/docs/Transaction.md
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
Transaction Object
|
|
2
|
-
==================
|
|
1
|
+
# Transaction Object
|
|
3
2
|
|
|
4
3
|
An SMTP transaction is valid from MAIL FROM time until RSET or "final-dot".
|
|
5
4
|
|
|
6
|
-
API
|
|
7
|
-
---
|
|
5
|
+
## API
|
|
8
6
|
|
|
9
7
|
* transaction.uuid
|
|
10
8
|
|
|
11
|
-
A unique UUID for this transaction. Is equal to the connection.uuid + '.N'
|
|
12
|
-
where N increments for each transaction on this connection.
|
|
9
|
+
A unique UUID for this transaction. Is equal to the connection.uuid + '.N' where N increments for each transaction on this connection.
|
|
13
10
|
|
|
14
11
|
* transaction.mail\_from
|
|
15
12
|
|
|
16
|
-
The value of the MAIL FROM command
|
|
13
|
+
The value of the MAIL FROM command is an `Address`[1] object.
|
|
17
14
|
|
|
18
15
|
* transaction.rcpt\_to
|
|
19
16
|
|
|
@@ -25,13 +22,13 @@ A node.js Readable Stream object for the message.
|
|
|
25
22
|
|
|
26
23
|
You use it like this:
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
```js
|
|
26
|
+
transaction.message_stream.pipe(WritableStream, options)
|
|
27
|
+
```
|
|
29
28
|
|
|
30
|
-
Where WritableStream is a node.js Writable Stream object such as a
|
|
31
|
-
net.socket, fs.writableStream, process.stdout/stderr or custom stream.
|
|
29
|
+
Where WritableStream is a node.js Writable Stream object such as a net.socket, fs.writableStream, process.stdout/stderr or custom stream.
|
|
32
30
|
|
|
33
|
-
The options argument should be an object that overrides the following
|
|
34
|
-
properties:
|
|
31
|
+
The options argument should be an object that overrides the following properties:
|
|
35
32
|
|
|
36
33
|
* line_endings (default: "\r\n")
|
|
37
34
|
* dot_stuffing (default: false)
|
|
@@ -42,7 +39,9 @@ properties:
|
|
|
42
39
|
|
|
43
40
|
e.g.
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
```js
|
|
43
|
+
transaction.message_stream.pipe(socket, { dot_stuffing: true, ending_dot: true });
|
|
44
|
+
```
|
|
46
45
|
|
|
47
46
|
* transaction.data\_bytes
|
|
48
47
|
|
|
@@ -50,8 +49,7 @@ The number of bytes in the email after DATA.
|
|
|
50
49
|
|
|
51
50
|
* transaction.add\_data(line)
|
|
52
51
|
|
|
53
|
-
Adds a line of data to the email. Note this is RAW email - it isn't useful
|
|
54
|
-
for adding banners to the email.
|
|
52
|
+
Adds a line of data to the email. Note this is RAW email - it isn't useful for adding banners to the email.
|
|
55
53
|
|
|
56
54
|
* transaction.notes
|
|
57
55
|
|
|
@@ -59,8 +57,7 @@ A safe place to store transaction specific values. See also [haraka-results](htt
|
|
|
59
57
|
|
|
60
58
|
* transaction.add\_leading\_header(key, value)
|
|
61
59
|
|
|
62
|
-
Adds a header to the top of the header list. This should only be used in
|
|
63
|
-
very specific cases. Most people will want to use `add_header()` instead.
|
|
60
|
+
Adds a header to the top of the header list. This should only be used in very specific cases. Most cases will use `add_header()` instead.
|
|
64
61
|
|
|
65
62
|
* transaction.add\_header(key, value)
|
|
66
63
|
|
|
@@ -76,8 +73,7 @@ The header of the email. See `Header Object`.
|
|
|
76
73
|
|
|
77
74
|
* transaction.parse\_body = true|false [default: false]
|
|
78
75
|
|
|
79
|
-
Set to `true` to enable parsing of the mail body. Make sure you set this in
|
|
80
|
-
hook\_data or before.
|
|
76
|
+
Set to `true` to enable parsing of the mail body. Make sure you set this in hook\_data or before. Storing a transaction hook (with transaction.attachment\_hooks) will set this to true.
|
|
81
77
|
|
|
82
78
|
* transaction.body
|
|
83
79
|
|
|
@@ -85,81 +81,62 @@ The body of the email if you set `parse_body` above. See `Body Object`.
|
|
|
85
81
|
|
|
86
82
|
* transaction.attachment\_hooks(start)
|
|
87
83
|
|
|
88
|
-
Sets a callback for when we see an attachment
|
|
89
|
-
|
|
90
|
-
The `start` event will receive `(content_type, filename, body, stream)` as
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
stream.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
stream.pipe(ws);
|
|
125
|
-
stream.resume();
|
|
126
|
-
ws.on('close', function () {
|
|
127
|
-
connection.loginfo("End of stream reached");
|
|
128
|
-
fs.fstat(fd, function (err, stats) {
|
|
129
|
-
connection.loginfo("Got data of length: " + stats.size);
|
|
130
|
-
// Close the tmp file descriptor
|
|
131
|
-
fs.close(fd, function(){});
|
|
132
|
-
});
|
|
84
|
+
Sets a callback for when we see an attachment.
|
|
85
|
+
|
|
86
|
+
The `start` event will receive `(content_type, filename, body, stream)` as parameters.
|
|
87
|
+
|
|
88
|
+
The stream is a [ReadableStream](http://nodejs.org/api/stream.html)
|
|
89
|
+
|
|
90
|
+
If you set stream.connection then the stream will apply backpressure to the connection, allowing you to process attachments before the connection has ended. Here is an example which stores attachments in temporary files using the `tmp` library from npm and tells us the size of the file:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
exports.hook_data = function (next, connection) {
|
|
94
|
+
// enable mail body parsing
|
|
95
|
+
connection.transaction.attachment_hooks(
|
|
96
|
+
function (ct, fn, body, stream) {
|
|
97
|
+
start_att(connection, ct, fn, body, stream)
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
next();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function start_att (connection, ct, fn, body, stream) {
|
|
104
|
+
connection.loginfo(`Got attachment: ${ct}, ${fn} for user id: ${connection.transaction.notes.hubdoc_user.email}`)
|
|
105
|
+
connection.transaction.notes.attachment_count++
|
|
106
|
+
|
|
107
|
+
stream.connection = connection; // Allow backpressure
|
|
108
|
+
stream.pause();
|
|
109
|
+
|
|
110
|
+
require('tmp').file((err, path, fd) => {
|
|
111
|
+
connection.loginfo(`Got tempfile: ${path} (${fd})`)
|
|
112
|
+
const ws = fs.createWriteStream(path)
|
|
113
|
+
stream.pipe(ws);
|
|
114
|
+
stream.resume();
|
|
115
|
+
ws.on('close', () => {
|
|
116
|
+
connection.loginfo("End of stream reached");
|
|
117
|
+
fs.fstat(fd, (err, stats) => {
|
|
118
|
+
connection.loginfo(`Got data of length: ${stats.size}`);
|
|
119
|
+
fs.close(fd, () => {}); // Close the tmp file descriptor
|
|
133
120
|
});
|
|
134
121
|
});
|
|
135
|
-
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
```
|
|
136
125
|
|
|
137
126
|
* transaction.discard\_data = true|false [default: false]
|
|
138
127
|
|
|
139
|
-
Set this flag to true to discard all data as it arrives and not store in
|
|
140
|
-
memory or on disk (in the message\_stream property). You can still access
|
|
141
|
-
the attachments and body if you set parse\_body to true. This is useful
|
|
142
|
-
for systems which do not need the full email, just the attachments or
|
|
143
|
-
mail text.
|
|
128
|
+
Set this flag to true to discard all data as it arrives and not store in memory or on disk (in the message\_stream property). You can still access the attachments and body if you set parse\_body to true. This is useful for systems which do not need the full email, just the attachments or mail text.
|
|
144
129
|
|
|
145
130
|
* transaction.set\_banner(text, html)
|
|
146
131
|
|
|
147
|
-
Sets a banner to be added to the end of the email. If the html part is not
|
|
148
|
-
given (optional) then the text part will have each line ending replaced with
|
|
149
|
-
`<br/>` when being inserted into HTML parts.
|
|
132
|
+
Sets a banner to be added to the end of the email. If the html part is not given (optional) then the text part will have each line ending replaced with `<br/>` when being inserted into HTML parts.
|
|
150
133
|
|
|
151
134
|
* transaction.add\_body\_filter(ct_match, filter)
|
|
152
135
|
|
|
153
|
-
Adds a filter to be applied to body parts in the email.
|
|
154
|
-
regular expression to match against the full content-type line, or a string to
|
|
155
|
-
match at the start, e.g. `/^text\/html/` or `'text/plain'`. filter will be
|
|
156
|
-
called when each body part matching ct_match is complete. It receives three
|
|
157
|
-
parameters, the content-type line, the encoding name, and a buffer with the
|
|
158
|
-
full body part. It should return a buffer with the desired contents of the
|
|
159
|
-
body in the same encoding.
|
|
136
|
+
Adds a filter to be applied to body parts in the email. ct\_match should be a regular expression to match against the full content-type line, or a string to match at the start, e.g. `/^text\/html/` or `'text/plain'`. filter will be called when each body part matching ct_match is complete. It receives three parameters: the content-type line, the encoding name, and a buffer with the full body part. It should return a buffer with the desired contents of the body in the same encoding.
|
|
160
137
|
|
|
161
138
|
* transaction.results
|
|
162
139
|
|
|
163
|
-
Store
|
|
140
|
+
Store [results](https://github.com/haraka/haraka-results) of processing in a structured format.
|
|
164
141
|
|
|
165
|
-
[1]: `Address` objects are address-rfc2821
|
|
142
|
+
[1]: `Address` objects are [address-rfc2821](https://github.com/haraka/node-address-rfc2821) objects.
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
# queue/smtp\_proxy
|
|
2
2
|
================
|
|
3
3
|
|
|
4
|
-
This plugin delivers to another mail server. This is a common setup when you
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
MAIL FROM time to the ongoing SMTP server. This can be a benefit in that
|
|
10
|
-
you get any SMTP-time filtering that the ongoing server provides, in
|
|
11
|
-
particular one important facility to some setups is recipient filtering.
|
|
12
|
-
However be aware that other than connect and HELO-time filtering, you will
|
|
13
|
-
have as many connections to your ongoing SMTP server as you have to Haraka.
|
|
4
|
+
This plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.
|
|
5
|
+
|
|
6
|
+
In comparison to `queue/smtp_forward`, this plugin makes a connection at MAIL FROM time to the ongoing SMTP server. This can be a benefit in that you get any SMTP-time filtering that the ongoing server provides, in particular one important facility to some setups is recipient filtering.
|
|
7
|
+
|
|
8
|
+
Be aware that other than connect and HELO-time filtering, you will have as many connections to your ongoing SMTP server as you have to Haraka.
|
|
14
9
|
|
|
15
10
|
## Configuration
|
|
16
11
|
-------------
|
package/docs/plugins/tls.md
CHANGED
|
@@ -4,9 +4,11 @@ This plugin enables the use of TLS (via `STARTTLS`) in Haraka.
|
|
|
4
4
|
|
|
5
5
|
For this plugin to work you must have SSL certificates installed correctly.
|
|
6
6
|
|
|
7
|
+
Haraka has [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) support. When the remote MUA/MTA presents a servername during the TLS handshake and a TLS certificate with that Common Name matches, that certificate will be presented. If no match is found, the default certificate (see Certificate Files) is presented.
|
|
8
|
+
|
|
7
9
|
## Certificate Files
|
|
8
10
|
|
|
9
|
-
Defaults are shown and can be overridden in `config/tls.ini`.
|
|
11
|
+
Defaults settings are shown and can be overridden in `config/tls.ini`.
|
|
10
12
|
|
|
11
13
|
```ini
|
|
12
14
|
key=tls_key.pem
|
|
@@ -16,20 +18,39 @@ dhparam=dhparams.pem
|
|
|
16
18
|
|
|
17
19
|
## Certificate Directory
|
|
18
20
|
|
|
19
|
-
If the directory `config/tls` exists,
|
|
21
|
+
If the directory `config/tls` exists, files within the directory are PEM encoded TLS files in one of two formats: bundles or Wild Wild West.
|
|
22
|
+
|
|
23
|
+
### Certificate bundles
|
|
24
|
+
|
|
25
|
+
Generate PEM bundles in The Usual Way[TM] by concatenating the key, certificate, and CA/chain certs in that order. Example:
|
|
20
26
|
|
|
21
27
|
```sh
|
|
22
|
-
cat example.com.key example.com.crt ca.crt > config/tls/example.com.pem
|
|
28
|
+
cat example.com.key example.com.crt ca-int.crt > haraka/config/tls/example.com.pem
|
|
23
29
|
```
|
|
24
30
|
|
|
25
|
-
An example [acme.sh](https://acme.sh) deployment [script](https://github.com/msimerson/Mail-Toaster-6/blob/master/provision/letsencrypt.sh)
|
|
31
|
+
An example [acme.sh](https://acme.sh) deployment [script](https://github.com/msimerson/Mail-Toaster-6/blob/master/provision/letsencrypt.sh) installs [Let's Encrypt](https://letsencrypt.org) certificate bundles to the Haraka `config/tls`directory.
|
|
32
|
+
|
|
33
|
+
### Wild Wild West
|
|
34
|
+
|
|
35
|
+
PEM encoded TLS certificates and keys can be stored in files in `config/tls`. The certificate loader is recursive, so TLS files can be in subdirs like `config/tls/mx1.example.com`. The certificate names are parsed from the 1st cert in each file and indexed by the certs Common Name(s). Subject Alternate Names are supported. The file name containing the certificates does *not* matter. Additional certificates within each file are presumed to be CA chain (intermediate) certificates.
|
|
36
|
+
|
|
37
|
+
If the TLS key is stored in the same file as the matching certificate, then the name of the file does not matter. If the TLS key is alone in a file, the file MUST be named with the keys Common Name. The file extension does not matter, `.pem` and `.key` are common. If the key is used for multiple CNs, the key must be stored in a file name matching each CN. Examples of working TLS key/cert file pairs for the Common Name mx1.example.com:
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
1. certificate bundle (see above), key & cert in same file
|
|
40
|
+
- config/tls/mx1.example.com.pem (recommended)
|
|
41
|
+
- config/tls/any-unique-name.pem (CN is extracted from 1st cert)
|
|
42
|
+
2. files in TLS dir
|
|
43
|
+
- config/tls/mx1.example.com.crt
|
|
44
|
+
- config/tls/mx1.example.com.key
|
|
45
|
+
3. files in subdir
|
|
46
|
+
- config/tls/example.com/mx1.cert
|
|
47
|
+
- config/tls/example.com/mx1.example.com.key
|
|
48
|
+
4. wildcard bundle on Windows platform (* is not allowed in file names)
|
|
49
|
+
- config/tls/_.example.com.pem
|
|
28
50
|
|
|
29
51
|
## Purchased Certificate
|
|
30
52
|
|
|
31
|
-
|
|
32
|
-
files to the certificate in this order:
|
|
53
|
+
For purchased certificate, append any intermediate/chained/ca-cert files to the certificate in this order:
|
|
33
54
|
|
|
34
55
|
1. The CA signed SSL cert
|
|
35
56
|
2. Any intermediate certificates
|
|
@@ -39,8 +60,7 @@ See also [Setting Up TLS](https://github.com/haraka/Haraka/wiki/Setting-up-TLS-w
|
|
|
39
60
|
|
|
40
61
|
## Self Issued (unsigned) Certificate
|
|
41
62
|
|
|
42
|
-
Create a certificate and key file in the config directory with the following
|
|
43
|
-
command:
|
|
63
|
+
Create a certificate and key file in the config directory with the following command:
|
|
44
64
|
|
|
45
65
|
openssl req -x509 -nodes -days 2190 -newkey rsa:2048 \
|
|
46
66
|
-keyout config/tls_key.pem -out config/tls_cert.pem
|
package/endpoint.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// Socket address parser/formatter and server binding helper
|
|
3
3
|
|
|
4
|
-
const fs = require('fs');
|
|
4
|
+
const fs = require('node:fs/promises');
|
|
5
5
|
const sockaddr = require('sockaddr');
|
|
6
6
|
|
|
7
7
|
module.exports = function endpoint (addr, defaultPort) {
|
|
@@ -46,21 +46,24 @@ class Endpoint {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// Make server listen on this endpoint, w/optional options
|
|
49
|
-
bind (server, opts) {
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
async bind (server, opts) {
|
|
50
|
+
opts = {...opts};
|
|
51
|
+
|
|
52
|
+
const mode = this.mode ? parseInt(this.mode, 8) : false;
|
|
52
53
|
if (this.path) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
done = () => fs.chmodSync(path, mode);
|
|
57
|
-
}
|
|
58
|
-
if (fs.existsSync(path)) fs.unlinkSync(path);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
54
|
+
opts.path = this.path;
|
|
55
|
+
await fs.rm(this.path, { force: true }); // errors are ignored when force is true
|
|
56
|
+
} else {
|
|
61
57
|
opts.host = this.host;
|
|
62
58
|
opts.port = this.port;
|
|
63
59
|
}
|
|
64
|
-
|
|
60
|
+
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
server.listen(opts, async (err) => {
|
|
63
|
+
if(err) return reject(err);
|
|
64
|
+
if (mode) await fs.chmod(opts.path, mode);
|
|
65
|
+
resolve()
|
|
66
|
+
});
|
|
67
|
+
});
|
|
65
68
|
}
|
|
66
69
|
}
|
package/haraka.js
CHANGED
|
@@ -20,20 +20,18 @@ catch (e) {
|
|
|
20
20
|
require('module')._initPaths(); // Horrible hack
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const utils = require('haraka-utils');
|
|
24
24
|
const logger = require('./logger');
|
|
25
25
|
const server = require('./server');
|
|
26
26
|
|
|
27
|
-
exports.version =
|
|
28
|
-
fs.readFileSync(path.join(__dirname, './package.json'), 'utf8')
|
|
29
|
-
).version;
|
|
27
|
+
exports.version = utils.getVersion(__dirname)
|
|
30
28
|
|
|
31
29
|
process.on('uncaughtException', err => {
|
|
32
30
|
if (err.stack) {
|
|
33
|
-
err.stack.split("\n").forEach(line => logger.
|
|
31
|
+
err.stack.split("\n").forEach(line => logger.crit(line));
|
|
34
32
|
}
|
|
35
33
|
else {
|
|
36
|
-
logger.
|
|
34
|
+
logger.crit(`Caught exception: ${JSON.stringify(err)}`);
|
|
37
35
|
}
|
|
38
36
|
logger.dump_and_exit(1);
|
|
39
37
|
});
|
|
@@ -41,18 +39,16 @@ process.on('uncaughtException', err => {
|
|
|
41
39
|
let shutting_down = false;
|
|
42
40
|
const signals = ['SIGINT'];
|
|
43
41
|
|
|
44
|
-
if (process.pid === 1)
|
|
45
|
-
signals.push('SIGTERM')
|
|
46
|
-
}
|
|
42
|
+
if (process.pid === 1) signals.push('SIGTERM')
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
for (const sig of signals) {
|
|
49
45
|
process.on(sig, () => {
|
|
50
46
|
if (shutting_down) return process.exit(1);
|
|
51
47
|
shutting_down = true;
|
|
52
48
|
const [, filename] = process.argv;
|
|
53
49
|
process.title = path.basename(filename, '.js');
|
|
54
50
|
|
|
55
|
-
logger.
|
|
51
|
+
logger.notice(`${sig} received`);
|
|
56
52
|
logger.dump_and_exit(() => {
|
|
57
53
|
if (server.cluster?.isMaster) {
|
|
58
54
|
server.performShutdown();
|
|
@@ -62,10 +58,10 @@ signals.forEach((sig) => {
|
|
|
62
58
|
}
|
|
63
59
|
});
|
|
64
60
|
});
|
|
65
|
-
}
|
|
61
|
+
}
|
|
66
62
|
|
|
67
63
|
process.on('SIGHUP', () => {
|
|
68
|
-
logger.
|
|
64
|
+
logger.notice('Flushing the temp fail queue');
|
|
69
65
|
server.flushQueue();
|
|
70
66
|
});
|
|
71
67
|
|
|
@@ -74,7 +70,7 @@ process.on('exit', code => {
|
|
|
74
70
|
const [, filename] = process.argv;
|
|
75
71
|
process.title = path.basename(filename, '.js');
|
|
76
72
|
|
|
77
|
-
logger.
|
|
73
|
+
logger.notice('Shutting down');
|
|
78
74
|
logger.dump_logs();
|
|
79
75
|
});
|
|
80
76
|
|
package/host_pool.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const net = require('net');
|
|
3
|
+
const net = require('node:net');
|
|
4
4
|
const utils = require('haraka-utils');
|
|
5
5
|
|
|
6
6
|
/* HostPool:
|
|
@@ -61,7 +61,7 @@ class HostPool {
|
|
|
61
61
|
self.dead_hosts[key] = true;
|
|
62
62
|
|
|
63
63
|
function cb_if_still_dead () {
|
|
64
|
-
logger.
|
|
64
|
+
logger.warn(`${host} ${key} is still dead, will retry in ${self.retry_secs} secs`);
|
|
65
65
|
self.dead_hosts[key] = true;
|
|
66
66
|
// console.log(1);
|
|
67
67
|
setTimeout(() => {
|
|
@@ -71,7 +71,7 @@ class HostPool {
|
|
|
71
71
|
|
|
72
72
|
function cb_if_alive () {
|
|
73
73
|
// console.log(2);
|
|
74
|
-
logger.
|
|
74
|
+
logger.info(`${host} ${key} is back! adding back into pool`);
|
|
75
75
|
delete self.dead_hosts[key];
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -90,7 +90,7 @@ class HostPool {
|
|
|
90
90
|
probe_dead_host (
|
|
91
91
|
host, port, cb_if_still_dead, cb_if_alive
|
|
92
92
|
){
|
|
93
|
-
logger.
|
|
93
|
+
logger.info(`probing dead host ${host}:${port}`);
|
|
94
94
|
|
|
95
95
|
const connect_timeout_ms = 200; // keep it snappy
|
|
96
96
|
let s;
|
|
@@ -162,7 +162,7 @@ class HostPool {
|
|
|
162
162
|
return host;
|
|
163
163
|
}
|
|
164
164
|
else {
|
|
165
|
-
logger.
|
|
165
|
+
logger.warn(
|
|
166
166
|
`no working hosts found, retrying a dead one, config (probably from smtp_forward.forwarding_host_pool) is '${this.hostports_str}'`);
|
|
167
167
|
this.last_i = first_i;
|
|
168
168
|
return this.hosts[first_i];
|
package/line_socket.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// A subclass of Socket which reads data by line
|
|
3
3
|
|
|
4
|
-
const net = require('net');
|
|
4
|
+
const net = require('node:net');
|
|
5
5
|
const utils = require('haraka-utils');
|
|
6
6
|
|
|
7
7
|
const tls_socket = require('./tls_socket');
|
|
@@ -37,17 +37,16 @@ function setup_line_processor (socket) {
|
|
|
37
37
|
exports.Socket = Socket;
|
|
38
38
|
|
|
39
39
|
// New interface - uses TLS
|
|
40
|
-
exports.connect = (port, host
|
|
40
|
+
exports.connect = (port, host) => {
|
|
41
41
|
let options = {};
|
|
42
42
|
if (typeof port === 'object') {
|
|
43
43
|
options = port;
|
|
44
|
-
cb = host;
|
|
45
44
|
}
|
|
46
45
|
else {
|
|
47
46
|
options.port = port;
|
|
48
47
|
options.host = host;
|
|
49
48
|
}
|
|
50
|
-
const sock = tls_socket.connect(options
|
|
49
|
+
const sock = tls_socket.connect(options);
|
|
51
50
|
setup_line_processor(sock);
|
|
52
51
|
return sock;
|
|
53
52
|
}
|
package/logger.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// Log class
|
|
3
3
|
|
|
4
|
-
const util = require('util');
|
|
5
|
-
const tty = require('tty');
|
|
4
|
+
const util = require('node:util');
|
|
5
|
+
const tty = require('node:tty');
|
|
6
6
|
|
|
7
7
|
const config = require('haraka-config');
|
|
8
8
|
const constants = require('haraka-constants');
|
|
9
9
|
|
|
10
10
|
let plugins;
|
|
11
|
-
let connection;
|
|
12
|
-
let outbound;
|
|
13
11
|
|
|
14
12
|
const regex = /(^$|[ ="\\])/;
|
|
15
13
|
const escape_replace_regex = /["\\]/g;
|
|
@@ -48,6 +46,7 @@ logger.levels = {
|
|
|
48
46
|
ALERT: 1,
|
|
49
47
|
EMERG: 0,
|
|
50
48
|
}
|
|
49
|
+
const level_names = Object.keys(logger.levels)
|
|
51
50
|
|
|
52
51
|
for (const le in logger.levels) {
|
|
53
52
|
logger.levels[`LOG${le}`] = logger.levels[le];
|
|
@@ -64,6 +63,7 @@ logger.loglevel = logger.levels.WARN;
|
|
|
64
63
|
logger.format = logger.formats.DEFAULT;
|
|
65
64
|
logger.timestamps = false;
|
|
66
65
|
logger.deferred_logs = [];
|
|
66
|
+
logger.name = 'logger'
|
|
67
67
|
|
|
68
68
|
logger.colors = {
|
|
69
69
|
"DATA" : "green",
|
|
@@ -235,14 +235,16 @@ logger._init_timestamps = function () {
|
|
|
235
235
|
|
|
236
236
|
logger._init();
|
|
237
237
|
|
|
238
|
-
logger.log_if_level = (level, key,
|
|
239
|
-
if (logger.loglevel < logger[key])
|
|
238
|
+
logger.log_if_level = (level, key, origin) => function () {
|
|
239
|
+
if (logger.loglevel < logger[key]) return;
|
|
240
|
+
|
|
240
241
|
let logobj = {
|
|
241
242
|
level,
|
|
242
243
|
uuid: '-',
|
|
243
|
-
origin: (
|
|
244
|
+
origin: (origin || 'core'),
|
|
244
245
|
message: ''
|
|
245
246
|
};
|
|
247
|
+
|
|
246
248
|
for (const data of arguments) {
|
|
247
249
|
if (typeof data !== 'object') {
|
|
248
250
|
logobj.message += (data);
|
|
@@ -251,24 +253,17 @@ logger.log_if_level = (level, key, plugin) => function () {
|
|
|
251
253
|
if (!data) continue;
|
|
252
254
|
|
|
253
255
|
// if the object is a connection, add the connection id
|
|
254
|
-
if (data
|
|
256
|
+
if (data.constructor?.name === 'Connection') {
|
|
255
257
|
logobj.uuid = data.uuid;
|
|
256
258
|
if (data.tran_count > 0) logobj.uuid += `.${data.tran_count}`;
|
|
257
259
|
}
|
|
258
260
|
else if (data instanceof plugins.Plugin) {
|
|
259
261
|
logobj.origin = data.name;
|
|
260
262
|
}
|
|
261
|
-
else if (data
|
|
263
|
+
else if (Object.hasOwn(data, 'name')) { // outbound
|
|
262
264
|
logobj.origin = data.name;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
logobj.origin = 'outbound';
|
|
266
|
-
if (data.todo) {
|
|
267
|
-
if (data.todo.uuid) logobj.uuid = data.todo.uuid;
|
|
268
|
-
if (data.todo.client_uuid) { // dirty hack
|
|
269
|
-
logobj.origin = `outbound] [${data.todo.client_uuid}`;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
265
|
+
if (Object.hasOwn(data, 'uuid')) logobj.uuid = data.uuid;
|
|
266
|
+
if (data.todo?.uuid) logobj.uuid = data.todo.uuid; // outbound/hmail
|
|
272
267
|
}
|
|
273
268
|
else if (
|
|
274
269
|
logger.format === logger.formats.LOGFMT && data.constructor === Object) {
|
|
@@ -278,7 +273,7 @@ logger.log_if_level = (level, key, plugin) => function () {
|
|
|
278
273
|
logger.format === logger.formats.JSON && data.constructor === Object) {
|
|
279
274
|
logobj = Object.assign(logobj, data);
|
|
280
275
|
}
|
|
281
|
-
else if (
|
|
276
|
+
else if (Object.hasOwn(data, 'uuid')) { // outbound/client_pool
|
|
282
277
|
logobj.uuid = data.uuid;
|
|
283
278
|
}
|
|
284
279
|
else if (data.constructor === Object) {
|
|
@@ -313,14 +308,37 @@ logger.log_if_level = (level, key, plugin) => function () {
|
|
|
313
308
|
return true;
|
|
314
309
|
}
|
|
315
310
|
|
|
316
|
-
logger.add_log_methods = (object,
|
|
317
|
-
if (!object) return
|
|
318
|
-
if (typeof(object) !== 'object') return;
|
|
311
|
+
logger.add_log_methods = (object, logName) => {
|
|
312
|
+
if (!object) return
|
|
319
313
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
314
|
+
if (typeof object === 'function') {
|
|
315
|
+
// add logging methods to class prototypes (Connection, Plugin, etc.)
|
|
316
|
+
|
|
317
|
+
for (const level of level_names.map(l => l.toLowerCase())) {
|
|
318
|
+
object.prototype[`log${level}`] = (function (level) {
|
|
319
|
+
return function () {
|
|
320
|
+
logger[level].apply(logger, [ this, ...arguments ]);
|
|
321
|
+
};
|
|
322
|
+
})(`log${level}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else if (typeof object === 'object') {
|
|
326
|
+
// add logging methods to objects
|
|
327
|
+
|
|
328
|
+
for (const level of level_names) {
|
|
329
|
+
// objects gets log function names: loginfo, logwarn, logdebug, ...
|
|
330
|
+
const fnNames = [`log${level.toLowerCase()}`]
|
|
331
|
+
|
|
332
|
+
// logger also gets short names
|
|
333
|
+
if (Object.hasOwn(object, 'name') && object.name === 'logger') {
|
|
334
|
+
fnNames.push(level.toLowerCase())
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for (const fnName of fnNames) {
|
|
338
|
+
if (Object.hasOwn(object, fnName)) continue; // already added
|
|
339
|
+
object[fnName] = logger.log_if_level(level, `LOG${level}`, logName);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
324
342
|
}
|
|
325
343
|
}
|
|
326
344
|
|
|
@@ -328,5 +346,3 @@ logger.add_log_methods(logger);
|
|
|
328
346
|
|
|
329
347
|
// load these down here so it sees all the logger methods compiled above
|
|
330
348
|
plugins = require('./plugins');
|
|
331
|
-
connection = require('./connection');
|
|
332
|
-
outbound = require('./outbound');
|