haraka 0.0.32 → 3.3.0
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/.claude/settings.local.json +28 -0
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +6 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1872 -62
- package/CLAUDE.md +40 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/GEMINI.md +38 -0
- package/LICENSE +2 -1
- package/Plugins.md +227 -0
- package/README.md +100 -115
- package/SECURITY.md +178 -0
- package/TODO +22 -0
- package/address.js +53 -0
- package/bin/haraka +593 -0
- package/bin/haraka_grep +32 -0
- package/config/aliases +2 -0
- package/config/auth_flat_file.ini +7 -0
- package/config/auth_vpopmaild.ini +9 -0
- package/config/connection.ini +79 -0
- package/config/delay_deny.ini +7 -0
- package/config/dhparams.pem +8 -0
- package/config/host_list +3 -0
- package/config/host_list_regex +6 -0
- package/config/http.ini +11 -0
- package/config/lmtp.ini +7 -0
- package/config/log.ini +11 -0
- package/config/me +1 -0
- package/config/outbound.bounce_message +18 -0
- package/config/outbound.bounce_message_html +36 -0
- package/config/outbound.bounce_message_image +106 -0
- package/config/outbound.ini +24 -0
- package/config/plugins +67 -0
- package/config/smtp.ini +37 -0
- package/config/smtp_bridge.ini +4 -0
- package/config/smtp_forward.ini +31 -0
- package/config/smtp_proxy.ini +27 -0
- package/config/tarpit.timeout +1 -0
- package/config/tls.ini +83 -0
- package/config/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/config/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1863 -0
- package/contrib/Haraka.cf +6 -0
- package/contrib/Haraka.pm +35 -0
- package/contrib/bad_smtp_server.pl +25 -0
- package/contrib/bsd-rc.d/haraka +61 -0
- package/contrib/debian-init.d/haraka +87 -0
- package/contrib/haraka.init +96 -0
- package/contrib/haraka.service +23 -0
- package/contrib/plugin2npm.sh +81 -0
- package/contrib/ubuntu-upstart/haraka.conf +27 -0
- package/coverage/coverage-final.json +2 -0
- package/coverage/coverage-summary.json +33 -0
- package/coverage/tmp/coverage-79131-1779241025146-0.json +1 -0
- package/coverage/tmp/coverage-79132-1779240999690-0.json +1 -0
- package/coverage/tmp/coverage-79172-1779241000095-0.json +1 -0
- package/coverage/tmp/coverage-79210-1779241000156-0.json +1 -0
- package/coverage/tmp/coverage-79211-1779241000209-0.json +1 -0
- package/coverage/tmp/coverage-79212-1779241000266-0.json +1 -0
- package/coverage/tmp/coverage-79213-1779241000441-0.json +1 -0
- package/coverage/tmp/coverage-79214-1779241000626-0.json +1 -0
- package/coverage/tmp/coverage-79215-1779241000795-0.json +1 -0
- package/coverage/tmp/coverage-79216-1779241000965-0.json +1 -0
- package/coverage/tmp/coverage-79218-1779241001013-0.json +1 -0
- package/coverage/tmp/coverage-79219-1779241001179-0.json +1 -0
- package/coverage/tmp/coverage-79220-1779241006249-0.json +1 -0
- package/coverage/tmp/coverage-79227-1779241011453-0.json +1 -0
- package/coverage/tmp/coverage-79229-1779241011537-0.json +1 -0
- package/coverage/tmp/coverage-79230-1779241011647-0.json +1 -0
- package/coverage/tmp/coverage-79231-1779241011765-0.json +1 -0
- package/coverage/tmp/coverage-79232-1779241011841-0.json +1 -0
- package/coverage/tmp/coverage-79233-1779241011909-0.json +1 -0
- package/coverage/tmp/coverage-79234-1779241011984-0.json +1 -0
- package/coverage/tmp/coverage-79235-1779241012055-0.json +1 -0
- package/coverage/tmp/coverage-79236-1779241012230-0.json +1 -0
- package/coverage/tmp/coverage-79237-1779241012300-0.json +1 -0
- package/coverage/tmp/coverage-79238-1779241012368-0.json +1 -0
- package/coverage/tmp/coverage-79239-1779241012438-0.json +1 -0
- package/coverage/tmp/coverage-79240-1779241012511-0.json +1 -0
- package/coverage/tmp/coverage-79241-1779241012582-0.json +1 -0
- package/coverage/tmp/coverage-79242-1779241012652-0.json +1 -0
- package/coverage/tmp/coverage-79243-1779241012814-0.json +1 -0
- package/coverage/tmp/coverage-79244-1779241012931-0.json +1 -0
- package/coverage/tmp/coverage-79245-1779241013007-0.json +1 -0
- package/coverage/tmp/coverage-79246-1779241013106-0.json +1 -0
- package/coverage/tmp/coverage-79247-1779241013178-0.json +1 -0
- package/coverage/tmp/coverage-79248-1779241013244-0.json +1 -0
- package/coverage/tmp/coverage-79249-1779241013409-0.json +1 -0
- package/coverage/tmp/coverage-79250-1779241013697-0.json +1 -0
- package/coverage/tmp/coverage-79251-1779241013847-0.json +1 -0
- package/coverage/tmp/coverage-79252-1779241014288-0.json +1 -0
- package/coverage/tmp/coverage-79253-1779241014378-0.json +1 -0
- package/coverage/tmp/coverage-79254-1779241014428-0.json +1 -0
- package/coverage/tmp/coverage-79255-1779241021774-0.json +1 -0
- package/coverage/tmp/coverage-80382-1779241021949-0.json +1 -0
- package/coverage/tmp/coverage-80383-1779241025019-0.json +1 -0
- package/coverage/tmp/coverage-80384-1779241025133-0.json +1 -0
- package/docs/Body.md +1 -0
- package/docs/Config.md +1 -0
- package/docs/Connection.md +153 -0
- package/docs/CoreConfig.md +96 -0
- package/docs/CustomReturnCodes.md +3 -0
- package/docs/HAProxy.md +62 -0
- package/docs/Header.md +1 -0
- package/docs/Logging.md +129 -0
- package/docs/Outbound.md +210 -0
- package/docs/Plugins.md +372 -0
- package/docs/Results.md +7 -0
- package/docs/Transaction.md +135 -0
- package/docs/Tutorial.md +183 -0
- package/docs/deprecated/access.md +3 -0
- package/docs/deprecated/backscatterer.md +9 -0
- package/docs/deprecated/connect.rdns_access.md +53 -0
- package/docs/deprecated/data.headers.md +3 -0
- package/docs/deprecated/data.nomsgid.md +7 -0
- package/docs/deprecated/data.noreceived.md +11 -0
- package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
- package/docs/deprecated/dkim_sign.md +97 -0
- package/docs/deprecated/dkim_verify.md +28 -0
- package/docs/deprecated/dnsbl.md +80 -0
- package/docs/deprecated/dnswl.md +73 -0
- package/docs/deprecated/lookup_rdns.strict.md +67 -0
- package/docs/deprecated/mail_from.access.md +52 -0
- package/docs/deprecated/mail_from.blocklist.md +18 -0
- package/docs/deprecated/mail_from.nobounces.md +8 -0
- package/docs/deprecated/rcpt_to.access.md +53 -0
- package/docs/deprecated/rcpt_to.blocklist.md +18 -0
- package/docs/deprecated/rcpt_to.routes.md +3 -0
- package/docs/deprecated/rdns.regexp.md +30 -0
- package/docs/plugins/aliases.md +3 -0
- package/docs/plugins/auth/auth_bridge.md +34 -0
- package/docs/plugins/auth/auth_ldap.md +4 -0
- package/docs/plugins/auth/auth_proxy.md +36 -0
- package/docs/plugins/auth/auth_vpopmaild.md +33 -0
- package/docs/plugins/auth/flat_file.md +40 -0
- package/docs/plugins/block_me.md +18 -0
- package/docs/plugins/data.signatures.md +11 -0
- package/docs/plugins/delay_deny.md +23 -0
- package/docs/plugins/max_unrecognized_commands.md +6 -0
- package/docs/plugins/prevent_credential_leaks.md +22 -0
- package/docs/plugins/process_title.md +42 -0
- package/docs/plugins/queue/deliver.md +3 -0
- package/docs/plugins/queue/discard.md +32 -0
- package/docs/plugins/queue/lmtp.md +24 -0
- package/docs/plugins/queue/qmail-queue.md +16 -0
- package/docs/plugins/queue/quarantine.md +87 -0
- package/docs/plugins/queue/smtp_bridge.md +32 -0
- package/docs/plugins/queue/smtp_forward.md +127 -0
- package/docs/plugins/queue/smtp_proxy.md +68 -0
- package/docs/plugins/queue/test.md +7 -0
- package/docs/plugins/rcpt_to.in_host_list.md +34 -0
- package/docs/plugins/rcpt_to.max_count.md +3 -0
- package/docs/plugins/record_envelope_addresses.md +20 -0
- package/docs/plugins/relay.md +3 -0
- package/docs/plugins/reseed_rng.md +16 -0
- package/docs/plugins/status.md +41 -0
- package/docs/plugins/tarpit.md +50 -0
- package/docs/plugins/tls.md +235 -0
- package/docs/plugins/toobusy.md +27 -0
- package/docs/plugins/xclient.md +10 -0
- package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
- package/docs/tutorials/SettingUpOutbound.md +62 -0
- package/eslint.config.mjs +2 -0
- package/haraka.js +74 -0
- package/haraka.sh +2 -0
- package/http/html/404.html +58 -0
- package/http/html/index.html +47 -0
- package/http/package.json +21 -0
- package/line_socket.js +24 -0
- package/logger.js +322 -0
- package/outbound/client_pool.js +59 -0
- package/outbound/config.js +134 -0
- package/outbound/hmail.js +1504 -0
- package/outbound/index.js +349 -0
- package/outbound/qfile.js +93 -0
- package/outbound/queue.js +399 -0
- package/outbound/tls.js +85 -0
- package/outbound/todo.js +17 -0
- package/package.json +91 -29
- package/plugins/.eslintrc.yaml +3 -0
- package/plugins/auth/auth_base.js +261 -0
- package/plugins/auth/auth_bridge.js +20 -0
- package/plugins/auth/auth_proxy.js +227 -0
- package/plugins/auth/auth_vpopmaild.js +162 -0
- package/plugins/auth/flat_file.js +44 -0
- package/plugins/block_me.js +88 -0
- package/plugins/data.signatures.js +30 -0
- package/plugins/delay_deny.js +153 -0
- package/plugins/prevent_credential_leaks.js +61 -0
- package/plugins/process_title.js +197 -0
- package/plugins/profile.js +11 -0
- package/plugins/queue/deliver.js +12 -0
- package/plugins/queue/discard.js +27 -0
- package/plugins/queue/lmtp.js +45 -0
- package/plugins/queue/qmail-queue.js +93 -0
- package/plugins/queue/quarantine.js +133 -0
- package/plugins/queue/smtp_bridge.js +45 -0
- package/plugins/queue/smtp_forward.js +371 -0
- package/plugins/queue/smtp_proxy.js +142 -0
- package/plugins/queue/test.js +15 -0
- package/plugins/rcpt_to.host_list_base.js +65 -0
- package/plugins/rcpt_to.in_host_list.js +56 -0
- package/plugins/record_envelope_addresses.js +17 -0
- package/plugins/reseed_rng.js +7 -0
- package/plugins/status.js +274 -0
- package/plugins/tarpit.js +45 -0
- package/plugins/tls.js +164 -0
- package/plugins/toobusy.js +47 -0
- package/plugins/xclient.js +124 -0
- package/plugins.js +604 -0
- package/queue/1772642154987_1775581346001_4_82235_TGwgfd_2_mattbook-m3.home.simerson.net +0 -0
- package/run_tests +11 -0
- package/server.js +827 -0
- package/smtp_client.js +504 -0
- package/test/.eslintrc.yaml +11 -0
- package/test/config/auth_flat_file.ini +5 -0
- package/test/config/block_me.recipient +1 -0
- package/test/config/block_me.senders +1 -0
- package/test/config/dhparams.pem +8 -0
- package/test/config/host_list +2 -0
- package/test/config/outbound_tls_cert.pem +1 -0
- package/test/config/outbound_tls_key.pem +1 -0
- package/test/config/plugins +7 -0
- package/test/config/smtp.ini +11 -0
- package/test/config/smtp_forward.ini +30 -0
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/config/tls/haraka.local.pem +51 -0
- package/test/config/tls.ini +45 -0
- package/test/config/tls_cert.pem +21 -0
- package/test/config/tls_key.pem +28 -0
- package/test/connection.js +817 -0
- package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
- package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
- package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
- package/test/fixtures/line_socket.js +21 -0
- package/test/fixtures/todo_qfile.txt +0 -0
- package/test/fixtures/util_hmailitem.js +156 -0
- package/test/installation/config/test-plugin-flat +1 -0
- package/test/installation/config/test-plugin.ini +10 -0
- package/test/installation/config/tls.ini +1 -0
- package/test/installation/node_modules/load_first/index.js +5 -0
- package/test/installation/node_modules/load_first/package.json +11 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
- package/test/installation/node_modules/test-plugin/package.json +5 -0
- package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
- package/test/installation/plugins/base_plugin.js +3 -0
- package/test/installation/plugins/folder_plugin/index.js +3 -0
- package/test/installation/plugins/folder_plugin/package.json +11 -0
- package/test/installation/plugins/inherits.js +7 -0
- package/test/installation/plugins/load_first.js +3 -0
- package/test/installation/plugins/plugin.js +1 -0
- package/test/installation/plugins/tls.js +3 -0
- package/test/logger.js +217 -0
- package/test/loud/config/dhparams.pem +0 -0
- package/test/loud/config/tls/goobered.pem +45 -0
- package/test/loud/config/tls.ini +43 -0
- package/test/mail_specimen/base64-root-part.txt +23 -0
- package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
- package/test/outbound/bounce_net_errors.js +133 -0
- package/test/outbound/bounce_rfc3464.js +226 -0
- package/test/outbound/hmail.js +210 -0
- package/test/outbound/index.js +385 -0
- package/test/outbound/qfile.js +124 -0
- package/test/outbound/queue.js +325 -0
- package/test/plugins/auth/auth_base.js +620 -0
- package/test/plugins/auth/auth_bridge.js +80 -0
- package/test/plugins/auth/auth_vpopmaild.js +81 -0
- package/test/plugins/auth/flat_file.js +123 -0
- package/test/plugins/block_me.js +141 -0
- package/test/plugins/data.signatures.js +111 -0
- package/test/plugins/delay_deny.js +262 -0
- package/test/plugins/prevent_credential_leaks.js +174 -0
- package/test/plugins/process_title.js +141 -0
- package/test/plugins/queue/deliver.js +98 -0
- package/test/plugins/queue/discard.js +78 -0
- package/test/plugins/queue/lmtp.js +137 -0
- package/test/plugins/queue/qmail-queue.js +98 -0
- package/test/plugins/queue/quarantine.js +80 -0
- package/test/plugins/queue/smtp_bridge.js +152 -0
- package/test/plugins/queue/smtp_forward.js +1023 -0
- package/test/plugins/queue/smtp_proxy.js +138 -0
- package/test/plugins/rcpt_to.host_list_base.js +102 -0
- package/test/plugins/rcpt_to.in_host_list.js +186 -0
- package/test/plugins/record_envelope_addresses.js +66 -0
- package/test/plugins/reseed_rng.js +34 -0
- package/test/plugins/status.js +207 -0
- package/test/plugins/tarpit.js +90 -0
- package/test/plugins/tls.js +86 -0
- package/test/plugins/toobusy.js +21 -0
- package/test/plugins/xclient.js +119 -0
- package/test/plugins.js +230 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- package/test/queue/zero-length +0 -0
- package/test/server.js +1012 -0
- package/test/smtp_client.js +1303 -0
- package/test/tls_socket.js +321 -0
- package/test/transaction.js +554 -0
- package/tls_socket.js +771 -0
- package/transaction.js +267 -0
- package/lib/index.js +0 -371
package/server.js
ADDED
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// smtp network server
|
|
3
|
+
|
|
4
|
+
const cluster = require('node:cluster')
|
|
5
|
+
const { spawn } = require('node:child_process')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const net = require('node:net')
|
|
8
|
+
const os = require('node:os')
|
|
9
|
+
const path = require('node:path')
|
|
10
|
+
const tls = require('node:tls')
|
|
11
|
+
const constants = require('haraka-constants')
|
|
12
|
+
const net_utils = require('haraka-net-utils')
|
|
13
|
+
|
|
14
|
+
const { endpoint } = require('haraka-net-utils')
|
|
15
|
+
const tls_socket = require('./tls_socket')
|
|
16
|
+
const conn = require('./connection')
|
|
17
|
+
const outbound = require('./outbound')
|
|
18
|
+
|
|
19
|
+
const Server = exports
|
|
20
|
+
Server.logger = require('./logger')
|
|
21
|
+
Server.config = require('haraka-config')
|
|
22
|
+
Server.plugins = require('./plugins')
|
|
23
|
+
Server.notes = {}
|
|
24
|
+
|
|
25
|
+
const { logger } = Server
|
|
26
|
+
|
|
27
|
+
// Need these here so we can run hooks
|
|
28
|
+
logger.add_log_methods(Server, 'server')
|
|
29
|
+
|
|
30
|
+
Server.listeners = []
|
|
31
|
+
|
|
32
|
+
Server.load_smtp_ini = () => {
|
|
33
|
+
Server.cfg = Server.config.get(
|
|
34
|
+
'smtp.ini',
|
|
35
|
+
{
|
|
36
|
+
booleans: ['-main.daemonize', '-main.graceful_shutdown'],
|
|
37
|
+
},
|
|
38
|
+
() => {
|
|
39
|
+
Server.load_smtp_ini()
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if (Server.cfg.main.nodes === undefined) {
|
|
44
|
+
Server.logwarn(`smtp.ini.nodes unset, using 1, see https://github.com/haraka/Haraka/wiki/Performance-Tuning`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const defaults = {
|
|
48
|
+
inactivity_timeout: 300,
|
|
49
|
+
daemon_log_file: '/var/log/haraka.log',
|
|
50
|
+
daemon_pid_file: '/var/run/haraka.pid',
|
|
51
|
+
force_shutdown_timeout: 30,
|
|
52
|
+
smtps_port: 465,
|
|
53
|
+
nodes: 1,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const key in defaults) {
|
|
57
|
+
if (Server.cfg.main[key] !== undefined) continue
|
|
58
|
+
Server.cfg.main[key] = defaults[key]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Server.load_http_ini = () => {
|
|
63
|
+
Server.http = {}
|
|
64
|
+
Server.http.cfg = Server.config.get('http.ini', () => {
|
|
65
|
+
Server.load_http_ini()
|
|
66
|
+
}).main
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Server.load_connection_ini = () => {
|
|
70
|
+
Server.connection = {}
|
|
71
|
+
Server.connection.cfg = Server.config.get('connection.ini', {
|
|
72
|
+
booleans: ['+haproxy.enabled'],
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Server.load_smtp_ini()
|
|
77
|
+
Server.load_http_ini()
|
|
78
|
+
Server.load_connection_ini()
|
|
79
|
+
|
|
80
|
+
Server.daemonize = function () {
|
|
81
|
+
const c = this.cfg.main
|
|
82
|
+
if (!c.daemonize) return
|
|
83
|
+
|
|
84
|
+
if (!process.env.__daemon) {
|
|
85
|
+
// Remove process.on('exit') listeners otherwise
|
|
86
|
+
// we get a spurious 'Exiting' log entry.
|
|
87
|
+
process.removeAllListeners('exit')
|
|
88
|
+
Server.lognotice('Daemonizing...')
|
|
89
|
+
|
|
90
|
+
const log_fd = fs.openSync(c.daemon_log_file, 'a')
|
|
91
|
+
const child = spawn(process.execPath, process.argv.slice(1), {
|
|
92
|
+
detached: true,
|
|
93
|
+
stdio: ['ignore', log_fd, log_fd],
|
|
94
|
+
env: { ...process.env, __daemon: '1' },
|
|
95
|
+
cwd: process.cwd(),
|
|
96
|
+
})
|
|
97
|
+
child.unref()
|
|
98
|
+
process.exit(0)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// We are the daemon from here on...
|
|
102
|
+
try {
|
|
103
|
+
fs.writeFileSync(c.daemon_pid_file, `${process.pid}\n`, { flag: 'wx' })
|
|
104
|
+
process.on('exit', () => {
|
|
105
|
+
try {
|
|
106
|
+
fs.unlinkSync(c.daemon_pid_file)
|
|
107
|
+
} catch {}
|
|
108
|
+
})
|
|
109
|
+
} catch (err) {
|
|
110
|
+
Server.logerror(err.message)
|
|
111
|
+
logger.dump_and_exit(1)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Server.flushQueue = async (domain) => {
|
|
116
|
+
if (!Server.cluster) {
|
|
117
|
+
await outbound.flush_queue(domain)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const id in cluster.workers) {
|
|
122
|
+
cluster.workers[id].send({ event: 'outbound.flush_queue', domain })
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let graceful_in_progress = false
|
|
127
|
+
|
|
128
|
+
Server.gracefulRestart = () => {
|
|
129
|
+
Server._graceful()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Server.stopListeners = () => {
|
|
133
|
+
Server.loginfo('Shutting down listeners')
|
|
134
|
+
for (const l of Server.listeners) {
|
|
135
|
+
l.close()
|
|
136
|
+
}
|
|
137
|
+
Server.listeners = []
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Server.performShutdown = () => {
|
|
141
|
+
if (Server.cfg.main.graceful_shutdown) {
|
|
142
|
+
return Server.gracefulShutdown()
|
|
143
|
+
}
|
|
144
|
+
Server.loginfo('Shutting down.')
|
|
145
|
+
process.exit(0)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
Server.gracefulShutdown = () => {
|
|
149
|
+
Server.stopListeners()
|
|
150
|
+
Server._graceful(() => {
|
|
151
|
+
// log();
|
|
152
|
+
Server.loginfo('Failed to shutdown naturally. Exiting.')
|
|
153
|
+
process.exit(0)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
Server._graceful = async (shutdown) => {
|
|
158
|
+
if (!Server.cluster && shutdown) {
|
|
159
|
+
for (const module of ['outbound', 'cfreader', 'plugins']) {
|
|
160
|
+
process.emit('message', { event: `${module}.shutdown` })
|
|
161
|
+
}
|
|
162
|
+
const t = setTimeout(shutdown, Server.cfg.main.force_shutdown_timeout * 1000)
|
|
163
|
+
return t.unref()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (graceful_in_progress) {
|
|
167
|
+
Server.lognotice('Restart currently in progress - ignoring request')
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
graceful_in_progress = true
|
|
172
|
+
// TODO: Make these configurable
|
|
173
|
+
const disconnect_timeout = 30
|
|
174
|
+
const exit_timeout = 30
|
|
175
|
+
cluster.removeAllListeners('exit')
|
|
176
|
+
|
|
177
|
+
// we reload using eachLimit where limit = num_workers - 1
|
|
178
|
+
// this kills all-but-one workers in parallel, leaving one running
|
|
179
|
+
// for new connections, and then restarts that one last worker.
|
|
180
|
+
|
|
181
|
+
const worker_ids = Object.keys(cluster.workers)
|
|
182
|
+
let limit = worker_ids.length - 1
|
|
183
|
+
if (limit < 2) limit = 1
|
|
184
|
+
|
|
185
|
+
const todo = []
|
|
186
|
+
|
|
187
|
+
for (const id of Object.keys(cluster.workers)) {
|
|
188
|
+
todo.push(() => {
|
|
189
|
+
return new Promise((resolve) => {
|
|
190
|
+
Server.lognotice(`Killing worker: ${id}`)
|
|
191
|
+
const worker = cluster.workers[id]
|
|
192
|
+
for (const module of ['outbound', 'cfreader', 'plugins']) {
|
|
193
|
+
worker.send({ event: `${module}.shutdown` })
|
|
194
|
+
}
|
|
195
|
+
worker.disconnect()
|
|
196
|
+
let disconnect_received = false
|
|
197
|
+
const disconnect_timer = setTimeout(() => {
|
|
198
|
+
if (!disconnect_received) {
|
|
199
|
+
Server.logcrit('Disconnect never received by worker. Killing.')
|
|
200
|
+
worker.kill()
|
|
201
|
+
}
|
|
202
|
+
}, disconnect_timeout * 1000)
|
|
203
|
+
|
|
204
|
+
worker.once('disconnect', () => {
|
|
205
|
+
clearTimeout(disconnect_timer)
|
|
206
|
+
disconnect_received = true
|
|
207
|
+
Server.lognotice('Disconnect complete')
|
|
208
|
+
let dead = false
|
|
209
|
+
const timer = setTimeout(() => {
|
|
210
|
+
if (!dead) {
|
|
211
|
+
Server.logcrit(`Worker ${id} failed to shutdown. Killing.`)
|
|
212
|
+
worker.kill()
|
|
213
|
+
}
|
|
214
|
+
}, exit_timeout * 1000)
|
|
215
|
+
worker.once('exit', () => {
|
|
216
|
+
dead = true
|
|
217
|
+
clearTimeout(timer)
|
|
218
|
+
if (shutdown) resolve()
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
if (!shutdown) {
|
|
222
|
+
const newWorker = cluster.fork()
|
|
223
|
+
newWorker.once('listening', () => {
|
|
224
|
+
Server.lognotice('Replacement worker online.')
|
|
225
|
+
newWorker.on('exit', (code, signal) => {
|
|
226
|
+
cluster_exit_listener(newWorker, code, signal)
|
|
227
|
+
})
|
|
228
|
+
resolve()
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
while (todo.length) {
|
|
236
|
+
// process batches of workers: invoke each queued thunk so we
|
|
237
|
+
// actually await the worker shutdown promises (passing the bare
|
|
238
|
+
// functions to Promise.all would resolve immediately).
|
|
239
|
+
await Promise.all(todo.splice(0, limit).map((fn) => fn()))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (shutdown) {
|
|
243
|
+
Server.loginfo('Workers closed. Shutting down master process subsystems')
|
|
244
|
+
for (const module of ['outbound', 'cfreader', 'plugins']) {
|
|
245
|
+
process.emit('message', { event: `${module}.shutdown` })
|
|
246
|
+
}
|
|
247
|
+
const t2 = setTimeout(shutdown, Server.cfg.main.force_shutdown_timeout * 1000)
|
|
248
|
+
return t2.unref()
|
|
249
|
+
}
|
|
250
|
+
graceful_in_progress = false
|
|
251
|
+
Server.lognotice(`Reload complete, workers: ${JSON.stringify(Object.keys(cluster.workers))}`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
Server.sendToMaster = (command, params) => {
|
|
255
|
+
// console.log("Send to master: ", command);
|
|
256
|
+
if (Server.cluster) {
|
|
257
|
+
if (Server.cluster.isMaster) {
|
|
258
|
+
Server.receiveAsMaster(command, params)
|
|
259
|
+
} else {
|
|
260
|
+
process.send({ cmd: command, params })
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
Server.receiveAsMaster(command, params)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
Server.receiveAsMaster = (command, params) => {
|
|
268
|
+
if (!Server[command]) {
|
|
269
|
+
Server.logerror(`Invalid command: ${command}`)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
Server[command].apply(Server, params)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function messageHandler(worker, msg) {
|
|
276
|
+
if (msg?.cmd) {
|
|
277
|
+
Server.receiveAsMaster(msg.cmd, msg.params)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Server.get_listen_addrs = (cfg, port) => {
|
|
282
|
+
if (!port) port = 25
|
|
283
|
+
let listeners = []
|
|
284
|
+
if (cfg?.listen) {
|
|
285
|
+
listeners = cfg.listen.split(/\s*,\s*/)
|
|
286
|
+
if (listeners[0] === '') listeners = []
|
|
287
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
288
|
+
const ep = endpoint(listeners[i], port)
|
|
289
|
+
if (ep instanceof Error) continue
|
|
290
|
+
listeners[i] = ep.toString()
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (cfg.port) {
|
|
294
|
+
let host = cfg.listen_host
|
|
295
|
+
if (!host) {
|
|
296
|
+
host = '[::0]'
|
|
297
|
+
Server.default_host = true
|
|
298
|
+
}
|
|
299
|
+
listeners.unshift(`${host}:${cfg.port}`)
|
|
300
|
+
}
|
|
301
|
+
if (listeners.length) return listeners
|
|
302
|
+
|
|
303
|
+
Server.default_host = true
|
|
304
|
+
listeners.push(`[::0]:${port}`)
|
|
305
|
+
|
|
306
|
+
return listeners
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
Server.createServer = (params) => {
|
|
310
|
+
const c = Server.cfg.main
|
|
311
|
+
for (const key in params) {
|
|
312
|
+
if (typeof params[key] === 'function') continue
|
|
313
|
+
c[key] = params[key]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
Server.notes = {}
|
|
317
|
+
Server.plugins.server = Server
|
|
318
|
+
Server.plugins.load_plugins()
|
|
319
|
+
|
|
320
|
+
const inactivity_timeout = (c.inactivity_timeout || 300) * 1000
|
|
321
|
+
|
|
322
|
+
if (!cluster || !c.nodes) {
|
|
323
|
+
Server.daemonize(c)
|
|
324
|
+
Server.setup_smtp_listeners(Server.plugins, 'master', inactivity_timeout)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Cluster
|
|
329
|
+
Server.cluster = cluster
|
|
330
|
+
|
|
331
|
+
// Cluster Workers
|
|
332
|
+
if (!cluster.isMaster) {
|
|
333
|
+
Server.setup_smtp_listeners(Server.plugins, 'child', inactivity_timeout)
|
|
334
|
+
return
|
|
335
|
+
} else {
|
|
336
|
+
// console.log("Setting up message handler");
|
|
337
|
+
cluster.on('message', messageHandler)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Cluster Master
|
|
341
|
+
// We fork workers in init_master_respond so that plugins
|
|
342
|
+
// can put handlers on cluster events before they are emitted.
|
|
343
|
+
Server.plugins.run_hooks('init_master', Server)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
Server.load_default_tls_config = (done) => {
|
|
347
|
+
// this fn exists solely for testing
|
|
348
|
+
if (Server.config.root_path != tls_socket.config.root_path) {
|
|
349
|
+
Server.loginfo(`resetting tls_config.config path to ${Server.config.root_path}`)
|
|
350
|
+
tls_socket.config = tls_socket.config.module_config(path.dirname(Server.config.root_path))
|
|
351
|
+
}
|
|
352
|
+
tls_socket.getSocketOpts('*').then((opts) => {
|
|
353
|
+
done(opts)
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
Server.create_smtps_server = (opts, onConnect) => {
|
|
358
|
+
let server
|
|
359
|
+
const socket_tls_state = new Map()
|
|
360
|
+
const proxyPrefix = Buffer.from('PROXY ')
|
|
361
|
+
// Defensive cap while waiting for a PROXY v1 line before TLS starts.
|
|
362
|
+
const proxyLineReadLimit = 512
|
|
363
|
+
|
|
364
|
+
const tlsServer = tls.createServer(opts, (cleartext) => {
|
|
365
|
+
const state_key = socket_tls_state_key(cleartext)
|
|
366
|
+
const smtps_state = socket_tls_state.get(state_key)
|
|
367
|
+
if (smtps_state) cleartext.haraka_smtps = smtps_state
|
|
368
|
+
socket_tls_state.delete(state_key)
|
|
369
|
+
|
|
370
|
+
onConnect(cleartext)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
function close_with_proxy_error(socket, timer, msg) {
|
|
374
|
+
clearTimeout(timer)
|
|
375
|
+
socket.removeAllListeners('data')
|
|
376
|
+
socket.end(`421 ${msg}\r\n`, () => {
|
|
377
|
+
socket.destroy()
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function socket_tls_state_key(socket) {
|
|
382
|
+
return JSON.stringify([socket.remoteAddress, socket.remotePort, socket.localAddress, socket.localPort])
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function start_tls(socket, proxy, peer_allowed) {
|
|
386
|
+
if (proxy || peer_allowed) {
|
|
387
|
+
const smtps_state = { peer_allowed }
|
|
388
|
+
if (proxy) {
|
|
389
|
+
smtps_state.proxy = {
|
|
390
|
+
...proxy,
|
|
391
|
+
proxy_ip: net_utils.normalize_ip(socket.remoteAddress) || socket.remoteAddress,
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
socket_tls_state.set(socket_tls_state_key(socket), smtps_state)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
tlsServer.emit('connection', socket)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
tlsServer.on('tlsClientError', (err, cleartext) => {
|
|
401
|
+
if (cleartext) socket_tls_state.delete(socket_tls_state_key(cleartext))
|
|
402
|
+
server.emit('tlsClientError', err, cleartext)
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
tlsServer.on('secureConnection', (cleartext) => {
|
|
406
|
+
server.emit('secureConnection', cleartext)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
function starts_with_proxy_prefix(data) {
|
|
410
|
+
if (!data.length) return true
|
|
411
|
+
if (data.length > proxyPrefix.length) return data.subarray(0, proxyPrefix.length).equals(proxyPrefix)
|
|
412
|
+
|
|
413
|
+
return proxyPrefix.subarray(0, data.length).equals(data)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function start_tls_with_buffer(socket, data, proxy, peer_allowed) {
|
|
417
|
+
// Preserve bytes already read by the PROXY pre-parser, then hand the
|
|
418
|
+
// paused socket to TLS before letting it read again.
|
|
419
|
+
socket.pause()
|
|
420
|
+
if (data?.length) socket.unshift(data)
|
|
421
|
+
setImmediate(() => {
|
|
422
|
+
start_tls(socket, proxy, peer_allowed)
|
|
423
|
+
socket.resume()
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
server = net.createServer((socket) => {
|
|
428
|
+
const remote_ip = net_utils.normalize_ip(socket.remoteAddress) || socket.remoteAddress
|
|
429
|
+
|
|
430
|
+
if (!net_utils.is_haproxy_allowed(remote_ip)) {
|
|
431
|
+
start_tls(socket)
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let current_data = null
|
|
436
|
+
const proxy_timer = setTimeout(() => {
|
|
437
|
+
close_with_proxy_error(socket, proxy_timer, 'PROXY timeout')
|
|
438
|
+
}, 30 * 1000)
|
|
439
|
+
|
|
440
|
+
function cleanup() {
|
|
441
|
+
clearTimeout(proxy_timer)
|
|
442
|
+
// Stop flowing before removing the pre-parser listener so TLS bytes
|
|
443
|
+
// cannot arrive between listener removal and TLS attachment.
|
|
444
|
+
socket.pause()
|
|
445
|
+
socket.removeListener('data', on_data)
|
|
446
|
+
socket.removeListener('close', cleanup)
|
|
447
|
+
socket.removeListener('error', cleanup)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function on_data(data) {
|
|
451
|
+
current_data = current_data ? Buffer.concat([current_data, data]) : data
|
|
452
|
+
|
|
453
|
+
if (!starts_with_proxy_prefix(current_data)) {
|
|
454
|
+
cleanup()
|
|
455
|
+
start_tls_with_buffer(socket, current_data, null, true)
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const offset = current_data.indexOf(0x0a)
|
|
460
|
+
if (offset === -1) {
|
|
461
|
+
if (current_data.length > proxyLineReadLimit) {
|
|
462
|
+
close_with_proxy_error(socket, proxy_timer, 'Invalid PROXY format')
|
|
463
|
+
}
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
if (offset > proxyLineReadLimit) {
|
|
467
|
+
close_with_proxy_error(socket, proxy_timer, 'Invalid PROXY format')
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
cleanup()
|
|
472
|
+
|
|
473
|
+
const proxy = net_utils.parse_proxy_line(current_data.slice(0, offset + 1))
|
|
474
|
+
if (!proxy) {
|
|
475
|
+
close_with_proxy_error(socket, proxy_timer, 'Invalid PROXY format')
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const rest = current_data.slice(offset + 1)
|
|
480
|
+
start_tls_with_buffer(socket, rest, proxy, true)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
socket.once('close', cleanup)
|
|
484
|
+
socket.once('error', cleanup)
|
|
485
|
+
socket.on('data', on_data)
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
server.tlsServer = tlsServer
|
|
489
|
+
|
|
490
|
+
return server
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
Server.get_smtp_server = async (ep, inactivity_timeout) => {
|
|
494
|
+
let server
|
|
495
|
+
|
|
496
|
+
function onConnect(client) {
|
|
497
|
+
client.setTimeout(inactivity_timeout)
|
|
498
|
+
const connection = conn.createConnection(client, server, Server.cfg)
|
|
499
|
+
|
|
500
|
+
if (server.has_tls) {
|
|
501
|
+
const cipher = client.getCipher()
|
|
502
|
+
cipher.version = client.getProtocol() // replace min with actual
|
|
503
|
+
|
|
504
|
+
connection.setTLS({
|
|
505
|
+
cipher,
|
|
506
|
+
verified: client.authorized,
|
|
507
|
+
verifyError: client.authorizationError,
|
|
508
|
+
peerCertificate: client.getPeerCertificate(),
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (client.haraka_smtps?.proxy) connection.apply_proxy(client.haraka_smtps.proxy)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (ep.port === parseInt(Server.cfg.main.smtps_port, 10)) {
|
|
516
|
+
Server.loginfo('getting SocketOpts for SMTPS server')
|
|
517
|
+
const opts = await tls_socket.getSocketOpts('*')
|
|
518
|
+
Server.loginfo(`Creating TLS server on ${ep}`)
|
|
519
|
+
|
|
520
|
+
opts.rejectUnauthorized = tls_socket.get_rejectUnauthorized(
|
|
521
|
+
opts.rejectUnauthorized,
|
|
522
|
+
ep.port,
|
|
523
|
+
tls_socket.cfg.main.requireAuthorized,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
server = Server.connection.cfg.haproxy.enabled
|
|
527
|
+
? Server.create_smtps_server(opts, onConnect)
|
|
528
|
+
: tls.createServer(opts, onConnect)
|
|
529
|
+
const tls_event_server = server.tlsServer || server
|
|
530
|
+
tls_socket.addOCSP(tls_event_server)
|
|
531
|
+
server.has_tls = true
|
|
532
|
+
tls_event_server.on('resumeSession', (id, rsDone) => {
|
|
533
|
+
Server.loginfo('client requested TLS resumeSession')
|
|
534
|
+
rsDone(null, null)
|
|
535
|
+
})
|
|
536
|
+
Server.listeners.push(server)
|
|
537
|
+
return server
|
|
538
|
+
} else {
|
|
539
|
+
server = tls_socket.createServer(onConnect)
|
|
540
|
+
server.has_tls = false
|
|
541
|
+
await tls_socket.getSocketOpts('*')
|
|
542
|
+
Server.listeners.push(server)
|
|
543
|
+
return server
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
Server.setup_smtp_listeners = async (plugins2, type, inactivity_timeout) => {
|
|
548
|
+
const errors = []
|
|
549
|
+
|
|
550
|
+
for (const [, ifObj] of Object.entries(os.networkInterfaces())) {
|
|
551
|
+
for (const addr of ifObj) {
|
|
552
|
+
if (addr.family === 'IPv6') {
|
|
553
|
+
if (!Server.notes.IPv6) Server.notes.IPv6 = true
|
|
554
|
+
} else if (addr.family === 'IPv4') {
|
|
555
|
+
if (!Server.notes.IPv4) Server.notes.IPv4 = true
|
|
556
|
+
} else {
|
|
557
|
+
console.error(addr)
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
for (const listen_address of Server.get_listen_addrs(Server.cfg.main)) {
|
|
563
|
+
const ep = endpoint(listen_address, 25)
|
|
564
|
+
|
|
565
|
+
if (ep instanceof Error) {
|
|
566
|
+
Server.logerror(`Invalid "listen" format in smtp.ini: ${listen_address}`)
|
|
567
|
+
continue
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const server = await Server.get_smtp_server(ep, inactivity_timeout)
|
|
571
|
+
if (!server) continue
|
|
572
|
+
|
|
573
|
+
server.notes = Server.notes
|
|
574
|
+
if (Server.cluster) server.cluster = Server.cluster
|
|
575
|
+
|
|
576
|
+
server
|
|
577
|
+
.on('listening', function () {
|
|
578
|
+
const addr = this.address()
|
|
579
|
+
Server.lognotice(`Listening on ${endpoint(addr)}`)
|
|
580
|
+
})
|
|
581
|
+
.on('close', () => {
|
|
582
|
+
Server.loginfo(`Listener ${ep} stopped`)
|
|
583
|
+
})
|
|
584
|
+
.on('error', (e) => {
|
|
585
|
+
errors.push(e)
|
|
586
|
+
Server.logerror(`Failed to setup listeners: ${e.message}`)
|
|
587
|
+
if (e.code !== 'EAFNOSUPPORT') {
|
|
588
|
+
Server.logerror(e)
|
|
589
|
+
return
|
|
590
|
+
}
|
|
591
|
+
// Fallback from IPv6 to IPv4 if not supported
|
|
592
|
+
// But only if we supplied the default of [::0]:25
|
|
593
|
+
if (/^::0/.test(ep.host) && Server.default_host) {
|
|
594
|
+
server.listen(ep.port, '0.0.0.0', 0)
|
|
595
|
+
return
|
|
596
|
+
}
|
|
597
|
+
// Pass error to callback
|
|
598
|
+
Server.logerror(e)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
await ep.bind(server, { backlog: 0 })
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (errors.length) {
|
|
605
|
+
for (const e of errors) {
|
|
606
|
+
Server.logerror(`Failed to setup listeners: ${e.message}`)
|
|
607
|
+
}
|
|
608
|
+
return logger.dump_and_exit(-1)
|
|
609
|
+
}
|
|
610
|
+
Server.listening()
|
|
611
|
+
plugins2.run_hooks(`init_${type}`, Server)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
Server.setup_http_listeners = async () => {
|
|
615
|
+
if (!Server.http?.cfg?.listen) return
|
|
616
|
+
|
|
617
|
+
const listeners = Server.get_listen_addrs(Server.http.cfg, 80)
|
|
618
|
+
if (!listeners.length) return
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
Server.http.express = require('express')
|
|
622
|
+
Server.loginfo('express loaded at Server.http.express')
|
|
623
|
+
} catch {
|
|
624
|
+
Server.logerror('express failed to load. No http server. Install express with: npm install -g express')
|
|
625
|
+
return
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const app = Server.http.express()
|
|
629
|
+
Server.http.app = app
|
|
630
|
+
Server.loginfo('express app is at Server.http.app')
|
|
631
|
+
|
|
632
|
+
for (const listen_address of listeners) {
|
|
633
|
+
const ep = endpoint(listen_address, 80)
|
|
634
|
+
if (ep instanceof Error) {
|
|
635
|
+
Server.logerror(`Invalid format for listen in http.ini: ${listen_address}`)
|
|
636
|
+
continue
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (443 == ep.port) {
|
|
640
|
+
const tlsOpts = { ...tls_socket.certsByHost['*'] }
|
|
641
|
+
tlsOpts.requestCert = false // not appropriate for HTTPS
|
|
642
|
+
Server.http.server = require('https').createServer(tlsOpts, app)
|
|
643
|
+
} else {
|
|
644
|
+
Server.http.server = require('http').createServer(app)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
Server.listeners.push(Server.http.server)
|
|
648
|
+
|
|
649
|
+
Server.http.server.on('listening', function () {
|
|
650
|
+
Server.lognotice(`Listening on ${endpoint(this.address())}`)
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
Server.http.server.on('error', (e) => {
|
|
654
|
+
Server.logerror(e)
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
await ep.bind(Server.http.server, { backlog: 0 })
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
Server.plugins.run_hooks('init_http', Server)
|
|
661
|
+
app.use(Server.http.express.static(Server.get_http_docroot()))
|
|
662
|
+
app.use(Server.handle404)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
Server.init_master_respond = async (retval, msg) => {
|
|
666
|
+
if (!(retval === constants.ok || retval === constants.cont)) {
|
|
667
|
+
Server.logerror(`init_master returned error${msg ? `: ${msg}` : ''}`)
|
|
668
|
+
return logger.dump_and_exit(1)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const c = Server.cfg.main
|
|
672
|
+
Server.ready = 1
|
|
673
|
+
|
|
674
|
+
// Load the queue if we're just one process
|
|
675
|
+
if (!(cluster && c.nodes)) {
|
|
676
|
+
try {
|
|
677
|
+
await outbound.init_queue()
|
|
678
|
+
} catch {
|
|
679
|
+
Server.logcrit('Loading queue failed. Shutting down.')
|
|
680
|
+
return logger.dump_and_exit(1)
|
|
681
|
+
}
|
|
682
|
+
Server.setup_http_listeners()
|
|
683
|
+
return
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Running under cluster, fork children here, so that
|
|
687
|
+
// cluster events can be registered in init_master hooks.
|
|
688
|
+
try {
|
|
689
|
+
const pids = await outbound.scan_queue_pids()
|
|
690
|
+
Server.daemonize()
|
|
691
|
+
// Fork workers
|
|
692
|
+
const workers = c.nodes === 'cpus' ? os.cpus().length : c.nodes
|
|
693
|
+
const new_workers = []
|
|
694
|
+
for (let i = 0; i < workers; i++) {
|
|
695
|
+
new_workers.push(cluster.fork({ CLUSTER_MASTER_PID: process.pid }))
|
|
696
|
+
}
|
|
697
|
+
for (let j = 0; j < pids.length; j++) {
|
|
698
|
+
new_workers[j % new_workers.length].send({
|
|
699
|
+
event: 'outbound.load_pid_queue',
|
|
700
|
+
data: pids[j],
|
|
701
|
+
})
|
|
702
|
+
}
|
|
703
|
+
cluster.on('online', (worker) => {
|
|
704
|
+
Server.lognotice('worker started', {
|
|
705
|
+
worker: worker.id,
|
|
706
|
+
pid: worker.process.pid,
|
|
707
|
+
})
|
|
708
|
+
})
|
|
709
|
+
cluster.on('listening', (worker, address) => {
|
|
710
|
+
Server.lognotice(`worker ${worker.id} listening on ${endpoint(address)}`)
|
|
711
|
+
})
|
|
712
|
+
cluster.on('exit', cluster_exit_listener)
|
|
713
|
+
} catch {
|
|
714
|
+
Server.logcrit('Scanning queue failed. Shutting down.')
|
|
715
|
+
logger.dump_and_exit(1)
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function cluster_exit_listener(worker, code, signal) {
|
|
720
|
+
if (signal) {
|
|
721
|
+
Server.lognotice(`worker ${worker.id} killed by signal ${signal}`)
|
|
722
|
+
} else if (code !== 0) {
|
|
723
|
+
Server.lognotice(`worker ${worker.id} exited with error code: ${code}`)
|
|
724
|
+
}
|
|
725
|
+
if (signal || code !== 0) {
|
|
726
|
+
// Restart worker
|
|
727
|
+
const new_worker = cluster.fork({
|
|
728
|
+
CLUSTER_MASTER_PID: process.pid,
|
|
729
|
+
})
|
|
730
|
+
new_worker.send({
|
|
731
|
+
event: 'outbound.load_pid_queue',
|
|
732
|
+
data: worker.process.pid,
|
|
733
|
+
})
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
Server.init_child_respond = (retval, msg) => {
|
|
738
|
+
switch (retval) {
|
|
739
|
+
case constants.ok:
|
|
740
|
+
case constants.cont:
|
|
741
|
+
Server.setup_http_listeners()
|
|
742
|
+
return
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const pid = process.env.CLUSTER_MASTER_PID
|
|
746
|
+
Server.logerror(`init_child returned error ${msg ? `: ${msg}` : ''}`)
|
|
747
|
+
try {
|
|
748
|
+
if (pid) {
|
|
749
|
+
process.kill(pid)
|
|
750
|
+
Server.logerror(`Killing master (pid=${pid})`)
|
|
751
|
+
}
|
|
752
|
+
} catch {
|
|
753
|
+
Server.logerror('Terminating child')
|
|
754
|
+
}
|
|
755
|
+
logger.dump_and_exit(1)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
Server.listening = () => {
|
|
759
|
+
const c = Server.cfg.main
|
|
760
|
+
|
|
761
|
+
// Drop privileges
|
|
762
|
+
if (c.group) {
|
|
763
|
+
Server.lognotice(`Switching from current gid: ${process.getgid()}`)
|
|
764
|
+
process.setgid(c.group)
|
|
765
|
+
Server.lognotice(`New gid: ${process.getgid()}`)
|
|
766
|
+
}
|
|
767
|
+
if (c.user) {
|
|
768
|
+
Server.lognotice(`Switching from current uid: ${process.getuid()}`)
|
|
769
|
+
process.setuid(c.user)
|
|
770
|
+
Server.lognotice(`New uid: ${process.getuid()}`)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
Server.ready = 1
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
Server.init_http_respond = () => {
|
|
777
|
+
Server.loginfo('init_http_respond')
|
|
778
|
+
|
|
779
|
+
let WebSocketServer
|
|
780
|
+
try {
|
|
781
|
+
WebSocketServer = require('ws').Server
|
|
782
|
+
} catch {
|
|
783
|
+
Server.logerror(`unable to load ws.\n did you: npm install -g ws?`)
|
|
784
|
+
return
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (!WebSocketServer) {
|
|
788
|
+
Server.logerror('ws failed to load')
|
|
789
|
+
return
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
Server.http.wss = new WebSocketServer({ server: Server.http.server })
|
|
793
|
+
Server.loginfo('Server.http.wss loaded')
|
|
794
|
+
|
|
795
|
+
Server.plugins.run_hooks('init_wss', Server)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
Server.init_wss_respond = () => {
|
|
799
|
+
Server.loginfo('init_wss_respond')
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
Server.get_http_docroot = () => {
|
|
803
|
+
if (Server.http.cfg.docroot) return Server.http.cfg.docroot
|
|
804
|
+
|
|
805
|
+
Server.http.cfg.docroot = path.join(process.env.HARAKA || __dirname, 'http', 'html')
|
|
806
|
+
Server.loginfo(`using html docroot: ${Server.http.cfg.docroot}`)
|
|
807
|
+
return Server.http.cfg.docroot
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
Server.handle404 = (req, res) => {
|
|
811
|
+
// abandon all hope, serve up a 404
|
|
812
|
+
const docroot = Server.get_http_docroot()
|
|
813
|
+
|
|
814
|
+
// respond with html page
|
|
815
|
+
if (req.accepts('html')) {
|
|
816
|
+
res.status(404).sendFile('404.html', { root: docroot })
|
|
817
|
+
return
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// respond with json
|
|
821
|
+
if (req.accepts('json')) {
|
|
822
|
+
res.status(404).send({ err: 'Not found' })
|
|
823
|
+
return
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
res.status(404).send('Not found!')
|
|
827
|
+
}
|