haraka 0.0.33 → 3.3.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.
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +7 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1898 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/LICENSE +22 -0
- package/Plugins.md +227 -0
- package/README.md +119 -4
- package/SECURITY.md +178 -0
- package/TODO +22 -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/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/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/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1865 -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 +63 -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/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 +100 -4
- 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 +605 -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 +820 -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 +198 -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/plugins.js
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// load all defined plugins
|
|
3
|
+
|
|
4
|
+
// node built-ins
|
|
5
|
+
const fs = require('node:fs')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
const vm = require('node:vm')
|
|
8
|
+
|
|
9
|
+
// npm modules
|
|
10
|
+
exports.config = require('haraka-config')
|
|
11
|
+
const constants = require('haraka-constants')
|
|
12
|
+
|
|
13
|
+
// local modules
|
|
14
|
+
const logger = require('./logger')
|
|
15
|
+
|
|
16
|
+
exports.registered_hooks = {}
|
|
17
|
+
exports.registered_plugins = {}
|
|
18
|
+
exports.plugin_list = []
|
|
19
|
+
|
|
20
|
+
let order = 0
|
|
21
|
+
|
|
22
|
+
class Plugin {
|
|
23
|
+
constructor(name) {
|
|
24
|
+
this.name = name
|
|
25
|
+
this.base = {}
|
|
26
|
+
this.timeout = get_timeout(name)
|
|
27
|
+
this._get_plugin_path()
|
|
28
|
+
this.config = this._get_config()
|
|
29
|
+
this.hooks = {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
haraka_require(name) {
|
|
33
|
+
return require(`./${name}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_get_plugin_path() {
|
|
37
|
+
/* From https://github.com/haraka/Haraka/pull/1278#issuecomment-168856528
|
|
38
|
+
In Development mode, or install via a plain "git clone":
|
|
39
|
+
|
|
40
|
+
Plain plugin in plugins/ folder
|
|
41
|
+
Plugin in a folder in plugins/<name>/ folder. Contains a package.json.
|
|
42
|
+
Plugin in node_modules. Contains a package.json file.
|
|
43
|
+
|
|
44
|
+
In "installed" mode (via haraka -i <path>):
|
|
45
|
+
|
|
46
|
+
Plain plugin in <path>/plugins/ folder
|
|
47
|
+
Plugin in a folder in <path>/plugins/<name>/folder. (same concept as above)
|
|
48
|
+
Plugin in <path>/node_modules. Contains a package.json file.
|
|
49
|
+
Core plugin in <core_haraka_dir>/plugins/ folder
|
|
50
|
+
Plugin in a folder in <core_haraka_dir>/plugins/<name>/ folder. (same concept as above)
|
|
51
|
+
Plugin in <core_haraka_dir>/node_modules.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
this.hasPackageJson = false
|
|
55
|
+
const name = this.name.startsWith('haraka-plugin-') ? this.name.slice(14) : this.name
|
|
56
|
+
if (this.name !== name) this.name = name
|
|
57
|
+
|
|
58
|
+
let paths = []
|
|
59
|
+
if (process.env.HARAKA) {
|
|
60
|
+
// Installed mode - started via bin/haraka
|
|
61
|
+
paths = [...paths, ...plugin_search_paths(process.env.HARAKA, name)]
|
|
62
|
+
|
|
63
|
+
// permit local "folder" plugins (/$name/package.json) (see #1649)
|
|
64
|
+
paths.push(
|
|
65
|
+
path.resolve(process.env.HARAKA, 'plugins', name, 'package.json'),
|
|
66
|
+
path.resolve(process.env.HARAKA, 'node_modules', name, 'package.json'),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// development mode
|
|
71
|
+
paths = [...paths, ...plugin_search_paths(__dirname, name)]
|
|
72
|
+
for (const pp of paths) {
|
|
73
|
+
if (this.plugin_path) continue
|
|
74
|
+
try {
|
|
75
|
+
fs.statSync(pp)
|
|
76
|
+
this.plugin_path = pp
|
|
77
|
+
if (path.basename(pp) === 'package.json') {
|
|
78
|
+
this.hasPackageJson = true
|
|
79
|
+
}
|
|
80
|
+
} catch (ignore) {}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_get_config() {
|
|
85
|
+
if (this.hasPackageJson) {
|
|
86
|
+
// It's a package/folder plugin - look in plugin folder for defaults,
|
|
87
|
+
// haraka/config folder for overrides
|
|
88
|
+
return exports.config.module_config(path.dirname(this.plugin_path), process.env.HARAKA || __dirname)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (process.env.HARAKA) {
|
|
92
|
+
// Plain .js file, installed mode - look in core folder for defaults,
|
|
93
|
+
// install dir for overrides
|
|
94
|
+
return exports.config.module_config(__dirname, process.env.HARAKA)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (process.env.HARAKA_TEST_DIR) {
|
|
98
|
+
return exports.config.module_config(process.env.HARAKA_TEST_DIR)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Plain .js file, git mode - just look in this folder
|
|
102
|
+
return exports.config.module_config(__dirname)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
register_hook(hook_name, method_name, priority) {
|
|
106
|
+
priority = parseInt(priority)
|
|
107
|
+
if (!priority) priority = 0
|
|
108
|
+
if (priority > 100) priority = 100
|
|
109
|
+
if (priority < -100) priority = -100
|
|
110
|
+
|
|
111
|
+
if (!Array.isArray(exports.registered_hooks[hook_name])) {
|
|
112
|
+
exports.registered_hooks[hook_name] = []
|
|
113
|
+
}
|
|
114
|
+
exports.registered_hooks[hook_name].push({
|
|
115
|
+
plugin: this.name,
|
|
116
|
+
method: method_name,
|
|
117
|
+
priority,
|
|
118
|
+
timeout: this.timeout,
|
|
119
|
+
order: order++,
|
|
120
|
+
})
|
|
121
|
+
this.hooks[hook_name] = this.hooks[hook_name] || []
|
|
122
|
+
this.hooks[hook_name].push(method_name)
|
|
123
|
+
|
|
124
|
+
plugins.logdebug(`registered hook ${hook_name} to ${this.name}.${method_name} priority ${priority}`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
register() {} // noop
|
|
128
|
+
|
|
129
|
+
inherits(parent_name) {
|
|
130
|
+
const parent_plugin = plugins._load_and_compile_plugin(parent_name)
|
|
131
|
+
for (const method in parent_plugin) {
|
|
132
|
+
if (!this[method]) {
|
|
133
|
+
this[method] = parent_plugin[method]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (parent_plugin.register) {
|
|
137
|
+
parent_plugin.register.call(this)
|
|
138
|
+
}
|
|
139
|
+
this.base[parent_name] = parent_plugin
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_make_custom_require() {
|
|
143
|
+
return (module) => {
|
|
144
|
+
if (this.hasPackageJson) {
|
|
145
|
+
const mod = require(module)
|
|
146
|
+
constants.import(global)
|
|
147
|
+
global.server = plugins.server
|
|
148
|
+
return mod
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (module === './config') {
|
|
152
|
+
return this.config
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!/^\./.test(module)) {
|
|
156
|
+
return require(module)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const ext of [`${module}.js`, module]) {
|
|
160
|
+
if (fs.existsSync(path.join(__dirname, ext))) {
|
|
161
|
+
return require(module)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return require(path.join(path.dirname(this.plugin_path), module))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_get_code(pi_path) {
|
|
170
|
+
if (this.hasPackageJson) {
|
|
171
|
+
let packageDir = path.dirname(pi_path)
|
|
172
|
+
if (/^win(32|64)/.test(process.platform)) {
|
|
173
|
+
// escape the c:\path\back\slashes else they disappear
|
|
174
|
+
packageDir = packageDir.replace(/\\/g, '\\\\')
|
|
175
|
+
}
|
|
176
|
+
return `var _p = require("${packageDir}"); for (var k in _p) { exports[k] = _p[k] }`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
return `"use strict";${fs.readFileSync(pi_path)}`
|
|
181
|
+
} catch (err) {
|
|
182
|
+
if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
|
|
183
|
+
plugins.logcrit(`Loading ${this.name} failed: ${err}`)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
throw `Loading plugin ${this.name} failed: ${err}`
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_compile() {
|
|
191
|
+
const pp = this.plugin_path
|
|
192
|
+
const code = this._get_code(pp)
|
|
193
|
+
if (!code) return
|
|
194
|
+
|
|
195
|
+
const sandbox = {
|
|
196
|
+
require: this._make_custom_require(),
|
|
197
|
+
__filename: pp,
|
|
198
|
+
__dirname: path.dirname(pp),
|
|
199
|
+
exports: this,
|
|
200
|
+
fetch,
|
|
201
|
+
clearTimeout,
|
|
202
|
+
clearInterval,
|
|
203
|
+
process,
|
|
204
|
+
setInterval,
|
|
205
|
+
setTimeout,
|
|
206
|
+
Buffer,
|
|
207
|
+
Math,
|
|
208
|
+
server: plugins.server,
|
|
209
|
+
setImmediate,
|
|
210
|
+
}
|
|
211
|
+
if (this.hasPackageJson) {
|
|
212
|
+
delete sandbox.__filename
|
|
213
|
+
}
|
|
214
|
+
constants.import(sandbox)
|
|
215
|
+
try {
|
|
216
|
+
vm.runInNewContext(code, sandbox, pp)
|
|
217
|
+
} catch (err) {
|
|
218
|
+
plugins.logcrit(`compiling '${this.name}' failed`)
|
|
219
|
+
if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
|
|
220
|
+
plugins.logcrit(`Loading '${this.name}' failed: ${err.message} - skipping`)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
throw err // default is to re-throw and stop Haraka
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
exports.shutdown_plugins = () => {
|
|
229
|
+
for (const i in exports.registered_plugins) {
|
|
230
|
+
if (exports.registered_plugins[i].shutdown) {
|
|
231
|
+
exports.registered_plugins[i].shutdown()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
process.on('message', (msg) => {
|
|
237
|
+
if (msg.event && msg.event == 'plugins.shutdown') {
|
|
238
|
+
plugins.loginfo('Shutting down')
|
|
239
|
+
exports.shutdown_plugins()
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
function plugin_search_paths(prefix, name) {
|
|
244
|
+
return [
|
|
245
|
+
// Haraka/plugins/*.js
|
|
246
|
+
path.resolve(prefix, 'plugins', `${name}.js`),
|
|
247
|
+
|
|
248
|
+
// Haraka/node_modules/haraka-plugin-*/package.json
|
|
249
|
+
path.resolve(prefix, 'node_modules', `haraka-plugin-${name}`, 'package.json'),
|
|
250
|
+
|
|
251
|
+
// global node_modules/haraka-plugin-*/package.json
|
|
252
|
+
path.resolve(prefix, '..', `haraka-plugin-${name}`, 'package.json'),
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function get_timeout(name) {
|
|
257
|
+
let timeout = parseFloat(exports.config.get(`${name}.timeout`))
|
|
258
|
+
if (isNaN(timeout)) {
|
|
259
|
+
plugins.logdebug(`no timeout in ${name}.timeout`)
|
|
260
|
+
timeout = parseFloat(exports.config.get('plugin_timeout'))
|
|
261
|
+
}
|
|
262
|
+
if (isNaN(timeout)) {
|
|
263
|
+
plugins.logdebug('no timeout in plugin_timeout')
|
|
264
|
+
timeout = 30
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
plugins.logdebug(`plugin ${name} timeout is: ${timeout}s`)
|
|
268
|
+
return timeout
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
logger.add_log_methods(Plugin)
|
|
272
|
+
|
|
273
|
+
const plugins = exports
|
|
274
|
+
|
|
275
|
+
logger.add_log_methods(plugins, 'plugins')
|
|
276
|
+
|
|
277
|
+
plugins.Plugin = Plugin
|
|
278
|
+
|
|
279
|
+
plugins.load_plugins = (override) => {
|
|
280
|
+
plugins.logdebug('Loading')
|
|
281
|
+
let plugin_list
|
|
282
|
+
if (override) {
|
|
283
|
+
if (!Array.isArray(override)) override = [override]
|
|
284
|
+
plugin_list = override
|
|
285
|
+
} else {
|
|
286
|
+
plugin_list = exports.config.get('plugins', 'list')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for (let plugin of plugin_list) {
|
|
290
|
+
if (plugin.startsWith('haraka-plugin-')) plugin = plugin.substring(14)
|
|
291
|
+
if (plugins.deprecated[plugin]) {
|
|
292
|
+
plugins.lognotice(
|
|
293
|
+
`${plugin} has been replaced by '${plugins.deprecated[plugin]}'. Please update config/plugins`,
|
|
294
|
+
)
|
|
295
|
+
plugins.load_plugin(plugins.deprecated[plugin])
|
|
296
|
+
} else {
|
|
297
|
+
plugins.load_plugin(plugin)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
plugins.plugin_list = Object.keys(plugins.registered_plugins)
|
|
302
|
+
|
|
303
|
+
// Sort registered_hooks by priority
|
|
304
|
+
for (const hook of Object.keys(plugins.registered_hooks)) {
|
|
305
|
+
plugins.registered_hooks[hook].sort((a, b) => {
|
|
306
|
+
if (a.priority < b.priority) return -1
|
|
307
|
+
if (a.priority > b.priority) return 1
|
|
308
|
+
if (a.priority == b.priority) {
|
|
309
|
+
if (a.order > b.order) return 1
|
|
310
|
+
return -1
|
|
311
|
+
}
|
|
312
|
+
return 0
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
logger.dump_logs() // now logging plugins are loaded.
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
plugins.deprecated = {
|
|
320
|
+
'auth/auth_ldap': 'auth-ldap',
|
|
321
|
+
backscatterer: 'dns-list',
|
|
322
|
+
'connect.asn': 'asn',
|
|
323
|
+
'connect.fcrdns': 'fcrdns',
|
|
324
|
+
'connect.geoip': 'geoip',
|
|
325
|
+
'connect.p0f': 'p0f',
|
|
326
|
+
'connect.rdns_access': 'access',
|
|
327
|
+
'data.nomsgid': 'headers',
|
|
328
|
+
'data.noreceived': 'headers',
|
|
329
|
+
'data.rfc5322_header_checks': 'headers',
|
|
330
|
+
'data.headers': 'headers',
|
|
331
|
+
dkim_sign: 'dkim',
|
|
332
|
+
dkim_verify: 'dkim',
|
|
333
|
+
'data.uribl': 'uribl',
|
|
334
|
+
dnsbl: 'dns-list',
|
|
335
|
+
dnswl: 'dns-list',
|
|
336
|
+
'log.syslog': 'syslog',
|
|
337
|
+
'mail_from.access': 'access',
|
|
338
|
+
'mail_from.blocklist': 'access',
|
|
339
|
+
'mail_from.nobounces': 'bounce',
|
|
340
|
+
max_unrecognized_commands: 'limit',
|
|
341
|
+
rate_limit: 'limit',
|
|
342
|
+
'rcpt_to.access': 'access',
|
|
343
|
+
'rcpt_to.blocklist': 'access',
|
|
344
|
+
'rcpt_to.ldap': 'rcpt-ldap',
|
|
345
|
+
'rcpt_to.max_count': 'limit',
|
|
346
|
+
'rcpt_to.qmail_deliverable': 'qmail-deliverable',
|
|
347
|
+
'rdns.regexp': 'access',
|
|
348
|
+
relay_acl: 'relay',
|
|
349
|
+
relay_all: 'relay',
|
|
350
|
+
relay_force_routing: 'relay',
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
plugins.load_plugin = (name) => {
|
|
354
|
+
plugins.loginfo(`loading ${name}`)
|
|
355
|
+
|
|
356
|
+
const plugin = plugins._load_and_compile_plugin(name)
|
|
357
|
+
if (plugin) {
|
|
358
|
+
plugins._register_plugin(plugin)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
plugins.registered_plugins[name] = plugin
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Set in server.js; initialized to empty object
|
|
365
|
+
// to prevent it from blowing up any unit tests.
|
|
366
|
+
plugins.server = { notes: {} }
|
|
367
|
+
|
|
368
|
+
plugins._load_and_compile_plugin = (name) => {
|
|
369
|
+
const plugin = new Plugin(name)
|
|
370
|
+
if (!plugin.plugin_path) {
|
|
371
|
+
const err = `Loading plugin ${plugin.name} failed: No plugin with this name found`
|
|
372
|
+
if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
|
|
373
|
+
plugins.logcrit(err)
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
throw err
|
|
377
|
+
}
|
|
378
|
+
plugin._compile()
|
|
379
|
+
return plugin
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
plugins._register_plugin = (plugin) => {
|
|
383
|
+
plugin.register()
|
|
384
|
+
|
|
385
|
+
// register any hook_blah methods.
|
|
386
|
+
for (const method in plugin) {
|
|
387
|
+
const result = method.match(/^hook_(\w+)\b/)
|
|
388
|
+
if (result) {
|
|
389
|
+
plugin.register_hook(result[1], method)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
plugins.run_hooks = (hook, object, params) => {
|
|
395
|
+
if (client_disconnected(object) && !is_required_hook(hook)) {
|
|
396
|
+
object.logdebug(`aborting ${hook} hook`)
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (hook !== 'log') object.logdebug(`running ${hook} hooks`)
|
|
401
|
+
|
|
402
|
+
if (is_required_hook(hook) && object.current_hook) {
|
|
403
|
+
object.current_hook[2]() // call cancel function
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!is_required_hook(hook) && hook !== 'deny' && object.hooks_to_run && object.hooks_to_run.length) {
|
|
407
|
+
throw new Error('We are already running hooks! Fatal error!')
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (hook === 'deny') {
|
|
411
|
+
// Save the hooks_to_run list so that we can run any remaining
|
|
412
|
+
// plugins on the previous hook once this hook is complete.
|
|
413
|
+
object.saved_hooks_to_run = object.hooks_to_run
|
|
414
|
+
}
|
|
415
|
+
object.hooks_to_run = []
|
|
416
|
+
|
|
417
|
+
if (plugins.registered_hooks[hook]) {
|
|
418
|
+
for (const item of plugins.registered_hooks[hook]) {
|
|
419
|
+
const plugin = plugins.registered_plugins[item.plugin]
|
|
420
|
+
object.hooks_to_run.push([plugin, item.method])
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
plugins.run_next_hook(hook, object, params)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
plugins.run_next_hook = (hook, object, params) => {
|
|
428
|
+
if (client_disconnected(object) && !is_required_hook(hook)) {
|
|
429
|
+
object.logdebug(`aborting ${hook} hook`)
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
let called_once = false
|
|
433
|
+
let timeout_id
|
|
434
|
+
let timed_out = false
|
|
435
|
+
let cancelled = false
|
|
436
|
+
let item
|
|
437
|
+
|
|
438
|
+
function cancel() {
|
|
439
|
+
if (timeout_id) clearTimeout(timeout_id)
|
|
440
|
+
cancelled = true
|
|
441
|
+
}
|
|
442
|
+
function callback(retval, msg) {
|
|
443
|
+
if (timeout_id) clearTimeout(timeout_id)
|
|
444
|
+
object.current_hook = null
|
|
445
|
+
if (cancelled) return // This hook has been cancelled
|
|
446
|
+
|
|
447
|
+
// Bail if client has disconnected
|
|
448
|
+
if (client_disconnected(object) && !is_required_hook(hook)) {
|
|
449
|
+
object.logdebug(`ignoring ${item[0].name} plugin callback`)
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
if (called_once && hook !== 'log') {
|
|
453
|
+
if (!timed_out) {
|
|
454
|
+
object.logerror(`${item[0].name} plugin ran callback ` + `multiple times - ignoring subsequent calls`)
|
|
455
|
+
// Write a stack trace to the log to aid debugging
|
|
456
|
+
object.logerror(new Error().stack)
|
|
457
|
+
}
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
called_once = true
|
|
461
|
+
if (!retval) retval = constants.cont
|
|
462
|
+
|
|
463
|
+
log_run_item(item, hook, retval, object, params, msg)
|
|
464
|
+
|
|
465
|
+
if (object.hooks_to_run.length !== 0) {
|
|
466
|
+
if (retval === constants.cont) {
|
|
467
|
+
return plugins.run_next_hook(hook, object, params)
|
|
468
|
+
}
|
|
469
|
+
if (hook === 'connect_init' || hook === 'disconnect') {
|
|
470
|
+
// these hooks ignore retval and always run for every plugin
|
|
471
|
+
return plugins.run_next_hook(hook, object, params)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const respond_method = `${hook}_respond`
|
|
476
|
+
if (item && is_deny_retval(retval) && hook.substring(0, 5) !== 'init_') {
|
|
477
|
+
object.deny_respond = get_denyfn(object, hook, params, retval, msg, respond_method)
|
|
478
|
+
plugins.run_hooks('deny', object, [retval, msg, item[0].name, item[1], params, hook])
|
|
479
|
+
} else {
|
|
480
|
+
object.hooks_to_run = []
|
|
481
|
+
object[respond_method](retval, msg, params)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (!object.hooks_to_run.length) return callback()
|
|
486
|
+
|
|
487
|
+
// shift the next one off the stack and run it.
|
|
488
|
+
item = object.hooks_to_run.shift()
|
|
489
|
+
item.push(cancel)
|
|
490
|
+
|
|
491
|
+
if (hook !== 'log' && item[0].timeout) {
|
|
492
|
+
timeout_id = setTimeout(() => {
|
|
493
|
+
timed_out = true
|
|
494
|
+
object.logcrit(`Plugin ${item[0].name} timed out on hook ${hook} - make sure it calls the callback`)
|
|
495
|
+
callback(constants.denysoft, 'plugin timeout')
|
|
496
|
+
}, item[0].timeout * 1000)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (hook !== 'log') {
|
|
500
|
+
object.logdebug(`running ${hook} hook in ${item[0].name} plugin`)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (object.transaction?.notes.skip_plugins.includes(item[0].name)) {
|
|
504
|
+
object.logdebug(`skipping ${item[0].name}_${hook} by request in notes`)
|
|
505
|
+
return callback()
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
object.current_hook = item
|
|
510
|
+
object.hook = hook
|
|
511
|
+
item[0][item[1]].call(item[0], callback, object, params)
|
|
512
|
+
} catch (err) {
|
|
513
|
+
if (hook !== 'log') {
|
|
514
|
+
object.logcrit(`Plugin ${item[0].name} failed: ${err.stack || err}`)
|
|
515
|
+
}
|
|
516
|
+
callback()
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function client_disconnected(object) {
|
|
521
|
+
if (object.constructor.name === 'Connection' && object.state >= constants.connection.state.DISCONNECTING) {
|
|
522
|
+
object.logdebug('client has disconnected')
|
|
523
|
+
return true
|
|
524
|
+
}
|
|
525
|
+
return false
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function is_required_hook(hook) {
|
|
529
|
+
// Hooks that must always run
|
|
530
|
+
switch (hook) {
|
|
531
|
+
case 'reset_transaction':
|
|
532
|
+
case 'disconnect':
|
|
533
|
+
case 'log':
|
|
534
|
+
return true
|
|
535
|
+
default:
|
|
536
|
+
return false
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function log_run_item(item, hook, retval, object, params, msg) {
|
|
541
|
+
if (!item) return
|
|
542
|
+
if (hook === 'log') return
|
|
543
|
+
|
|
544
|
+
let log = 'logdebug'
|
|
545
|
+
const is_not_cont = retval !== constants.cont && logger.would_log(logger.LOGINFO)
|
|
546
|
+
if (is_not_cont) log = 'loginfo'
|
|
547
|
+
if (is_not_cont || logger.would_log(logger.LOGDEBUG)) {
|
|
548
|
+
object[log]({
|
|
549
|
+
hook,
|
|
550
|
+
plugin: item[0].name,
|
|
551
|
+
function: item[1],
|
|
552
|
+
params: params ? (typeof params === 'string' ? params : params[0]) : '',
|
|
553
|
+
retval: constants.translate(retval),
|
|
554
|
+
msg: sanitize(msg),
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function sanitize(msg) {
|
|
560
|
+
if (!msg) return ''
|
|
561
|
+
if (typeof msg === 'string') return msg
|
|
562
|
+
if (typeof msg === 'object') {
|
|
563
|
+
if (msg.constructor.name === 'DSN') return msg.reply
|
|
564
|
+
const sanitized = { ...msg } // copy the message
|
|
565
|
+
for (const priv of ['password', 'auth_pass']) {
|
|
566
|
+
delete sanitized[priv]
|
|
567
|
+
}
|
|
568
|
+
return JSON.stringify(sanitized)
|
|
569
|
+
}
|
|
570
|
+
logger.logerror(`what is ${msg} (typeof ${typeof msg})?`)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function is_deny_retval(val) {
|
|
574
|
+
switch (val) {
|
|
575
|
+
case constants.deny:
|
|
576
|
+
case constants.denysoft:
|
|
577
|
+
case constants.denydisconnect:
|
|
578
|
+
case constants.denysoftdisconnect:
|
|
579
|
+
return true
|
|
580
|
+
}
|
|
581
|
+
return false
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function get_denyfn(object, hook, params, retval, msg, respond_method) {
|
|
585
|
+
return (deny_retval, deny_msg) => {
|
|
586
|
+
switch (deny_retval) {
|
|
587
|
+
case constants.ok:
|
|
588
|
+
// Override rejection
|
|
589
|
+
object.loginfo(`deny(soft?) overridden by deny hook${deny_msg ? ': deny_msg' : ''}`)
|
|
590
|
+
// Restore hooks_to_run with saved copy so that
|
|
591
|
+
// any other plugins on this hook can also run.
|
|
592
|
+
if (object.saved_hooks_to_run.length > 0) {
|
|
593
|
+
object.hooks_to_run = object.saved_hooks_to_run
|
|
594
|
+
plugins.run_next_hook(hook, object, params)
|
|
595
|
+
} else {
|
|
596
|
+
object[respond_method](constants.cont, deny_msg, params)
|
|
597
|
+
}
|
|
598
|
+
break
|
|
599
|
+
default:
|
|
600
|
+
object.saved_hooks_to_run = []
|
|
601
|
+
object.hooks_to_run = []
|
|
602
|
+
object[respond_method](retval, msg, params)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
package/run_tests
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Files using node:test
|
|
4
|
+
NODE_TEST_FILES="test/*.js test/outbound/*.js test/plugins/*.js test/plugins/auth/*.js test/plugins/queue/*.js"
|
|
5
|
+
|
|
6
|
+
if [ -n "$1" ]; then
|
|
7
|
+
node --test "$1"
|
|
8
|
+
else
|
|
9
|
+
# default, run 'em all
|
|
10
|
+
node --test --test-concurrency=1 $NODE_TEST_FILES
|
|
11
|
+
fi
|