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
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const child_process = require('node:child_process')
|
|
4
|
+
const fs = require('node:fs/promises')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
|
|
7
|
+
const { Address } = require('../address')
|
|
8
|
+
const config = require('haraka-config')
|
|
9
|
+
const utils = require('haraka-utils')
|
|
10
|
+
|
|
11
|
+
const logger = require('../logger')
|
|
12
|
+
const TimerQueue = utils.TimerQueue
|
|
13
|
+
const HMailItem = require('./hmail')
|
|
14
|
+
const obc = require('./config')
|
|
15
|
+
const _qfile = require('./qfile')
|
|
16
|
+
const obtls = require('./tls')
|
|
17
|
+
|
|
18
|
+
class Queue {
|
|
19
|
+
constructor(worker) {
|
|
20
|
+
this.worker = worker
|
|
21
|
+
this.tasks = []
|
|
22
|
+
this.running = 0
|
|
23
|
+
this._scheduled = false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
push(task) {
|
|
27
|
+
this.tasks.push(task)
|
|
28
|
+
this._schedule()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
length() {
|
|
32
|
+
return this.tasks.length + this.running
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_schedule() {
|
|
36
|
+
if (this._scheduled) return
|
|
37
|
+
this._scheduled = true
|
|
38
|
+
setImmediate(() => {
|
|
39
|
+
this._scheduled = false
|
|
40
|
+
this._process()
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_process() {
|
|
45
|
+
while (this.running < obc.cfg.concurrency_max && this.tasks.length > 0) {
|
|
46
|
+
this.running++
|
|
47
|
+
|
|
48
|
+
this.worker(this.tasks.shift())
|
|
49
|
+
.catch((err) => {
|
|
50
|
+
logger.error(exports, `Queue worker error: ${err}`)
|
|
51
|
+
})
|
|
52
|
+
.finally(() => {
|
|
53
|
+
this.running--
|
|
54
|
+
this._schedule()
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
exports.name = 'outbound/queue'
|
|
61
|
+
|
|
62
|
+
if (config.get('queue_dir')) {
|
|
63
|
+
exports.queue_dir = path.resolve(config.get('queue_dir'))
|
|
64
|
+
} else if (process.env.HARAKA) {
|
|
65
|
+
exports.queue_dir = path.resolve(process.env.HARAKA, 'queue')
|
|
66
|
+
} else {
|
|
67
|
+
exports.queue_dir = path.resolve('test', 'test-queue')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const load_queue = new Queue(async (file) => {
|
|
71
|
+
const hmail = new HMailItem(file, path.join(exports.queue_dir, file))
|
|
72
|
+
exports._add_hmail(hmail)
|
|
73
|
+
await new Promise((resolve, reject) => {
|
|
74
|
+
const onReady = () => {
|
|
75
|
+
hmail.off('error', onError)
|
|
76
|
+
resolve()
|
|
77
|
+
}
|
|
78
|
+
const onError = (err) => {
|
|
79
|
+
hmail.off('ready', onReady)
|
|
80
|
+
reject(err)
|
|
81
|
+
}
|
|
82
|
+
hmail.once('ready', onReady)
|
|
83
|
+
hmail.once('error', onError)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
let in_progress = 0
|
|
88
|
+
const delivery_queue = (exports.delivery_queue = new Queue(async (hmail) => {
|
|
89
|
+
in_progress++
|
|
90
|
+
await new Promise((resolve) => {
|
|
91
|
+
hmail.next_cb = () => {
|
|
92
|
+
in_progress--
|
|
93
|
+
resolve()
|
|
94
|
+
}
|
|
95
|
+
if (obtls.cfg) {
|
|
96
|
+
hmail.send()
|
|
97
|
+
} else {
|
|
98
|
+
obtls.init(() => {
|
|
99
|
+
hmail.send()
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}))
|
|
104
|
+
|
|
105
|
+
const temp_fail_queue = (exports.temp_fail_queue = new TimerQueue(1000, { logger }))
|
|
106
|
+
|
|
107
|
+
let queue_count = 0
|
|
108
|
+
|
|
109
|
+
exports.get_stats = () => `${in_progress}/${exports.delivery_queue.length()}/${exports.temp_fail_queue.length()}`
|
|
110
|
+
|
|
111
|
+
exports.list_queue = async () => {
|
|
112
|
+
return exports._load_cur_queue(null, exports._list_file)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
exports._stat_file = async () => {
|
|
116
|
+
queue_count++
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
exports.stat_queue = async () => {
|
|
120
|
+
await exports._load_cur_queue(null, exports._stat_file)
|
|
121
|
+
return exports.stats()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
exports.init_queue = async (pid) => {
|
|
125
|
+
// Initialise and load queue
|
|
126
|
+
// This function is called first when not running under cluster,
|
|
127
|
+
await exports.ensure_queue_dir()
|
|
128
|
+
await exports.delete_dot_files()
|
|
129
|
+
|
|
130
|
+
await exports._load_cur_queue(pid, exports._add_file)
|
|
131
|
+
logger.info(exports, `[pid: ${pid}] ${delivery_queue.length()} files in my delivery queue`)
|
|
132
|
+
logger.info(exports, `[pid: ${pid}] ${load_queue.length()} files in my load queue`)
|
|
133
|
+
logger.info(exports, `[pid: ${pid}] ${temp_fail_queue.length()} files in my temp fail queue`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
exports._load_cur_queue = async (pid, iteratee) => {
|
|
137
|
+
logger.info(exports, 'Loading outbound queue from ', exports.queue_dir)
|
|
138
|
+
let files
|
|
139
|
+
try {
|
|
140
|
+
files = await fs.readdir(exports.queue_dir)
|
|
141
|
+
} catch (err) {
|
|
142
|
+
logger.error(exports, `Failed to load queue directory (${exports.queue_dir}): ${err}`)
|
|
143
|
+
throw err
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
exports.cur_time = new Date() // set once so we're not calling it a lot
|
|
147
|
+
|
|
148
|
+
return await exports.load_queue_files(pid, files, iteratee)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
exports.read_parts = (file) => {
|
|
152
|
+
if (file.startsWith(_qfile.platformDOT)) {
|
|
153
|
+
logger.warn(exports, `'Skipping' dot-file in queue folder: ${file}`)
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (file.startsWith('error.')) {
|
|
158
|
+
logger.warn(exports, `'Skipping' error file in queue folder: ${file}`)
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const parts = _qfile.parts(file)
|
|
163
|
+
if (!parts) {
|
|
164
|
+
logger.error(exports, `Unrecognized file in queue folder: ${file}`)
|
|
165
|
+
return false
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return parts
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
exports.rename_to_actual_pid = async (file, parts) => {
|
|
172
|
+
// maintain some original details for the rename
|
|
173
|
+
const new_filename = _qfile.name({
|
|
174
|
+
arrival: parts.arrival,
|
|
175
|
+
uid: parts.uid,
|
|
176
|
+
next_attempt: parts.next_attempt,
|
|
177
|
+
attempts: parts.attempts,
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
await fs.rename(path.join(exports.queue_dir, file), path.join(exports.queue_dir, new_filename))
|
|
182
|
+
return new_filename
|
|
183
|
+
} catch (err) {
|
|
184
|
+
throw new Error(`Unable to rename queue file: ${file} to ${new_filename}`, {
|
|
185
|
+
cause: err,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
exports._add_file = async (file) => {
|
|
191
|
+
const parts = _qfile.parts(file)
|
|
192
|
+
|
|
193
|
+
if (parts.next_attempt <= exports.cur_time) {
|
|
194
|
+
logger.debug(exports, `File ${file} needs processing now`)
|
|
195
|
+
load_queue.push(file)
|
|
196
|
+
} else {
|
|
197
|
+
logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt - exports.cur_time}ms`)
|
|
198
|
+
temp_fail_queue.add(file, parts.next_attempt - exports.cur_time, () => {
|
|
199
|
+
load_queue.push(file)
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
return file
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
exports.load_queue_files = async (pid, input_files, iteratee) => {
|
|
206
|
+
const searchPid = parseInt(pid)
|
|
207
|
+
|
|
208
|
+
let stat_renamed = 0
|
|
209
|
+
let stat_loaded = 0
|
|
210
|
+
|
|
211
|
+
if (searchPid) {
|
|
212
|
+
logger.info(exports, `Grabbing queue files for pid: ${pid}`)
|
|
213
|
+
} else {
|
|
214
|
+
logger.info(exports, 'Loading the queue...')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const results = await Promise.all(
|
|
218
|
+
input_files.map(async (file) => {
|
|
219
|
+
const parts = exports.read_parts(file)
|
|
220
|
+
if (!parts) return null
|
|
221
|
+
|
|
222
|
+
if (!searchPid) {
|
|
223
|
+
stat_loaded++
|
|
224
|
+
return file
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (parts.pid !== searchPid) return null
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const renamed_file = await exports.rename_to_actual_pid(file, parts)
|
|
231
|
+
stat_renamed++
|
|
232
|
+
stat_loaded++
|
|
233
|
+
return renamed_file
|
|
234
|
+
} catch (error) {
|
|
235
|
+
logger.error(exports, `${error.message}`)
|
|
236
|
+
return null
|
|
237
|
+
}
|
|
238
|
+
}),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if (searchPid) logger.info(exports, `[pid: ${pid}] ${stat_renamed} files old PID queue fixed up`)
|
|
242
|
+
logger.debug(exports, `[pid: ${pid}] ${stat_loaded} files loaded`)
|
|
243
|
+
|
|
244
|
+
const iterateeResults = await Promise.all(results.filter((i) => i).map(async (item) => await iteratee(item)))
|
|
245
|
+
|
|
246
|
+
return iterateeResults.filter((result) => result !== null && result !== undefined)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
exports.stats = () => {
|
|
250
|
+
return {
|
|
251
|
+
queue_dir: exports.queue_dir,
|
|
252
|
+
queue_count,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// position `position`. Loops to handle partial reads.
|
|
257
|
+
// Read exactly `length` bytes into `buffer` starting at `offset`, from file
|
|
258
|
+
async function readFull(handle, buffer, offset, length, position) {
|
|
259
|
+
let totalRead = 0
|
|
260
|
+
while (totalRead < length) {
|
|
261
|
+
const { bytesRead } = await handle.read(buffer, offset + totalRead, length - totalRead, position + totalRead)
|
|
262
|
+
if (bytesRead === 0) {
|
|
263
|
+
throw new Error(`Unexpected end of file: read ${totalRead} of ${length} bytes`)
|
|
264
|
+
}
|
|
265
|
+
totalRead += bytesRead
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
exports._list_file = async (file) => {
|
|
270
|
+
let handle
|
|
271
|
+
try {
|
|
272
|
+
const filePath = path.join(exports.queue_dir, file)
|
|
273
|
+
|
|
274
|
+
handle = await fs.open(filePath, 'r')
|
|
275
|
+
|
|
276
|
+
// Read first 4 bytes to get the todo length
|
|
277
|
+
const buf = Buffer.alloc(4)
|
|
278
|
+
await readFull(handle, buf, 0, 4, 0)
|
|
279
|
+
const todo_len = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]
|
|
280
|
+
|
|
281
|
+
const todoBuf = Buffer.alloc(todo_len)
|
|
282
|
+
await readFull(handle, todoBuf, 0, todo_len, 4)
|
|
283
|
+
|
|
284
|
+
const todo = todoBuf.toString('utf8')
|
|
285
|
+
const todo_struct = JSON.parse(todo)
|
|
286
|
+
todo_struct.rcpt_to = todo_struct.rcpt_to.map((a) => new Address(a))
|
|
287
|
+
todo_struct.mail_from = new Address(todo_struct.mail_from)
|
|
288
|
+
todo_struct.file = file
|
|
289
|
+
todo_struct.full_path = filePath
|
|
290
|
+
const parts = _qfile.parts(file)
|
|
291
|
+
todo_struct.pid = parts?.pid || null
|
|
292
|
+
return todo_struct
|
|
293
|
+
} catch (err) {
|
|
294
|
+
console.error(`Error reading queue file: ${file}:`, err)
|
|
295
|
+
return null
|
|
296
|
+
} finally {
|
|
297
|
+
if (handle)
|
|
298
|
+
await handle.close().catch((err) => console.error(`Failed to close queue file handle for ${file}:`, err))
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
exports.flush_queue = async (domain, pid) => {
|
|
303
|
+
if (domain) {
|
|
304
|
+
try {
|
|
305
|
+
const qlist = await exports.list_queue()
|
|
306
|
+
for (const todo of qlist) {
|
|
307
|
+
if (todo.domain.toLowerCase() !== domain.toLowerCase()) continue
|
|
308
|
+
if (pid && todo.pid !== pid) continue
|
|
309
|
+
delivery_queue.push(new HMailItem(todo.file, todo.full_path))
|
|
310
|
+
}
|
|
311
|
+
} catch (err) {
|
|
312
|
+
logger.error(exports, `Failed to load queue: ${err.message}`)
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
temp_fail_queue.drain()
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
exports.load_pid_queue = async (pid) => {
|
|
320
|
+
logger.info(exports, `Loading queue for pid: ${pid}`)
|
|
321
|
+
await exports.init_queue(pid)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
exports.ensure_queue_dir = async () => {
|
|
325
|
+
// this code is only run at start-up.
|
|
326
|
+
try {
|
|
327
|
+
await fs.access(exports.queue_dir)
|
|
328
|
+
return // directory already exists
|
|
329
|
+
} catch (ignore) {
|
|
330
|
+
// directory doesn't exist, try to create it
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
logger.debug(exports, `Creating queue directory ${exports.queue_dir}`)
|
|
334
|
+
try {
|
|
335
|
+
await fs.mkdir(exports.queue_dir, { mode: 493 }) // 493 == 0755
|
|
336
|
+
const cfg = config.get('smtp.ini')
|
|
337
|
+
let uid
|
|
338
|
+
let gid
|
|
339
|
+
if (cfg.user) uid = parseInt(child_process.execSync(`id -u ${cfg.user}`).toString().trim(), 10)
|
|
340
|
+
if (cfg.group) gid = parseInt(child_process.execSync(`id -g ${cfg.group}`).toString().trim(), 10)
|
|
341
|
+
if (uid && gid) {
|
|
342
|
+
await fs.chown(exports.queue_dir, uid, gid)
|
|
343
|
+
} else if (uid) {
|
|
344
|
+
await fs.chown(exports.queue_dir, uid, -1)
|
|
345
|
+
}
|
|
346
|
+
} catch (err) {
|
|
347
|
+
if (err.code !== 'EEXIST') {
|
|
348
|
+
logger.error(exports, `Error creating queue directory: ${err}`)
|
|
349
|
+
throw err
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
exports.delete_dot_files = async () => {
|
|
355
|
+
try {
|
|
356
|
+
const files = await fs.readdir(exports.queue_dir)
|
|
357
|
+
for (const file of files) {
|
|
358
|
+
if (file.startsWith(_qfile.platformDOT)) {
|
|
359
|
+
logger.warn(exports, `Removing left over dot-file: ${file}`)
|
|
360
|
+
await fs.unlink(path.join(exports.queue_dir, file))
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} catch (err) {
|
|
364
|
+
logger.error(exports, `Error deleting dot files: ${err}`)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
exports._add_hmail = (hmail) => {
|
|
369
|
+
if (hmail.next_process <= exports.cur_time) {
|
|
370
|
+
delivery_queue.push(hmail)
|
|
371
|
+
} else {
|
|
372
|
+
temp_fail_queue.add(hmail.filename, hmail.next_process - exports.cur_time, () => {
|
|
373
|
+
delivery_queue.push(hmail)
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
exports.scan_queue_pids = async () => {
|
|
379
|
+
// Under cluster, this is called first by the master
|
|
380
|
+
await exports.ensure_queue_dir()
|
|
381
|
+
await exports.delete_dot_files()
|
|
382
|
+
|
|
383
|
+
let files
|
|
384
|
+
try {
|
|
385
|
+
files = await fs.readdir(exports.queue_dir)
|
|
386
|
+
} catch (err) {
|
|
387
|
+
logger.error(exports, `Failed to load queue directory (${exports.queue_dir}): ${err}`)
|
|
388
|
+
throw err
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const pids = {}
|
|
392
|
+
|
|
393
|
+
for (const file of files) {
|
|
394
|
+
const parts = exports.read_parts(file)
|
|
395
|
+
if (parts) pids[parts.pid] = true
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return Object.keys(pids)
|
|
399
|
+
}
|
package/outbound/tls.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const net = require('node:net')
|
|
4
|
+
|
|
5
|
+
const config = require('haraka-config')
|
|
6
|
+
const hkredis = require('haraka-plugin-redis')
|
|
7
|
+
|
|
8
|
+
const logger = require('../logger')
|
|
9
|
+
const tls_socket = require('../tls_socket')
|
|
10
|
+
|
|
11
|
+
class OutboundTLS {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.config = config
|
|
14
|
+
this.name = 'OutboundTLS'
|
|
15
|
+
logger.add_log_methods(this)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test_config(tls_config, our_config) {
|
|
19
|
+
tls_socket.config = tls_config
|
|
20
|
+
this.config = our_config
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
load_config() {
|
|
24
|
+
const tls_cfg = tls_socket.load_tls_ini({ role: 'client' })
|
|
25
|
+
this.cfg = tls_socket.load_plugin_tls_options(tls_cfg.outbound || {})
|
|
26
|
+
this.cfg.redis = tls_cfg.redis // outbound-only: TLS NO-GO db (don't clone — has methods)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
init(cb) {
|
|
30
|
+
this.load_config()
|
|
31
|
+
// changing this var in-flight won't work
|
|
32
|
+
if (this.cfg.redis && !this.cfg.redis.disable_for_failed_hosts) return cb()
|
|
33
|
+
logger.debug(this, 'Will disable outbound TLS for failing TLS hosts')
|
|
34
|
+
Object.assign(this, hkredis)
|
|
35
|
+
this.merge_redis_ini()
|
|
36
|
+
this.init_redis_plugin(cb)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get_tls_options(mx) {
|
|
40
|
+
// do NOT set servername to an IP address
|
|
41
|
+
if (net.isIP(mx.exchange)) {
|
|
42
|
+
// when mx.exchange looked up in DNS, from_dns has the hostname
|
|
43
|
+
if (mx.from_dns) return { ...this.cfg, servername: mx.from_dns }
|
|
44
|
+
return { ...this.cfg }
|
|
45
|
+
} else {
|
|
46
|
+
// mx.exchange is a hostname
|
|
47
|
+
return { ...this.cfg, servername: mx.exchange }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check for if host is prohibited from TLS negotiation
|
|
52
|
+
check_tls_nogo(host, cb_ok, cb_nogo) {
|
|
53
|
+
if (!this.cfg.redis.disable_for_failed_hosts) return cb_ok()
|
|
54
|
+
|
|
55
|
+
const dbkey = `no_tls|${host}`
|
|
56
|
+
this.db
|
|
57
|
+
.get(dbkey)
|
|
58
|
+
.then((dbr) => {
|
|
59
|
+
dbr ? cb_nogo(dbr) : cb_ok()
|
|
60
|
+
})
|
|
61
|
+
.catch((err) => {
|
|
62
|
+
logger.debug(this, `Redis returned error: ${err}`)
|
|
63
|
+
cb_ok()
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
mark_tls_nogo(host, cb) {
|
|
68
|
+
const dbkey = `no_tls|${host}`
|
|
69
|
+
const expiry = this.cfg.redis.disable_expiry || 604800
|
|
70
|
+
|
|
71
|
+
if (!this.cfg.redis.disable_for_failed_hosts) return cb()
|
|
72
|
+
|
|
73
|
+
logger.notice(this, `TLS connection failed. Marking ${host} as non-TLS for ${expiry} seconds`)
|
|
74
|
+
|
|
75
|
+
this.db
|
|
76
|
+
.setEx(dbkey, expiry, new Date().toISOString())
|
|
77
|
+
.then(cb)
|
|
78
|
+
.catch((err) => {
|
|
79
|
+
logger.error(this, `Redis returned error: ${err}`)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// this is a singleton
|
|
85
|
+
module.exports = new OutboundTLS()
|
package/outbound/todo.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// queue file header data
|
|
4
|
+
class TODOItem {
|
|
5
|
+
constructor(domain, recipients, transaction) {
|
|
6
|
+
this.queue_time = Date.now()
|
|
7
|
+
this.domain = domain
|
|
8
|
+
this.rcpt_to = recipients
|
|
9
|
+
this.mail_from = transaction.mail_from
|
|
10
|
+
this.message_stream = transaction.message_stream
|
|
11
|
+
this.notes = transaction.notes
|
|
12
|
+
this.uuid = transaction.uuid
|
|
13
|
+
this.force_tls = false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = TODOItem
|
package/package.json
CHANGED
|
@@ -1,40 +1,102 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"author": "Matt Sergeant <helpme@gmail.com> (http://baudehlo.com/)",
|
|
3
3
|
"name": "haraka",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "An SMTP Server project.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"haraka",
|
|
8
|
+
"smtp",
|
|
9
|
+
"server",
|
|
10
|
+
"email"
|
|
11
|
+
],
|
|
12
|
+
"version": "3.3.0",
|
|
13
|
+
"homepage": "http://haraka.github.io",
|
|
7
14
|
"repository": {
|
|
8
15
|
"type": "git",
|
|
9
|
-
"url": "git
|
|
16
|
+
"url": "git://github.com/haraka/Haraka.git"
|
|
17
|
+
},
|
|
18
|
+
"main": "haraka.js",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
10
21
|
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@haraka/email-address": "~3.1.5",
|
|
24
|
+
"haraka-config": "~1.6.2",
|
|
25
|
+
"haraka-constants": "~1.0.8",
|
|
26
|
+
"haraka-dsn": "~1.2.0",
|
|
27
|
+
"haraka-email-message": "~1.4.0",
|
|
28
|
+
"haraka-net-utils": "~1.9.1",
|
|
29
|
+
"haraka-notes": "~1.1.3",
|
|
30
|
+
"haraka-plugin-redis": "~2.1.0",
|
|
31
|
+
"haraka-results": "~2.3.2",
|
|
32
|
+
"haraka-tld": "~1.3.5",
|
|
33
|
+
"haraka-utils": "~2.2.0",
|
|
34
|
+
"ipaddr.js": "~2.4.0",
|
|
35
|
+
"node-gyp": "~12.3.0",
|
|
36
|
+
"nopt": "~10.0.0",
|
|
37
|
+
"redis": "~6.0.0",
|
|
38
|
+
"semver": "~7.8.1"
|
|
39
|
+
},
|
|
40
|
+
"optionalDependencies": {
|
|
41
|
+
"@haraka/ocsp": "~1.2.0",
|
|
42
|
+
"haraka-plugin-access": "~1.4.0",
|
|
43
|
+
"haraka-plugin-aliases": "~1.1.0",
|
|
44
|
+
"haraka-plugin-asn": "~2.2.0",
|
|
45
|
+
"haraka-plugin-attachment": "~1.2.1",
|
|
46
|
+
"haraka-plugin-bounce": "~2.2.0",
|
|
47
|
+
"haraka-plugin-clamd": "~1.0.3",
|
|
48
|
+
"haraka-plugin-dcc": "~1.0.3",
|
|
49
|
+
"haraka-plugin-dkim": "~1.2.0",
|
|
50
|
+
"haraka-plugin-dns-list": "~1.3.0",
|
|
51
|
+
"haraka-plugin-early_talker": "~1.0.2",
|
|
52
|
+
"haraka-plugin-fcrdns": "~1.2.1",
|
|
53
|
+
"haraka-plugin-geoip": "~1.1.2",
|
|
54
|
+
"haraka-plugin-greylist": "~1.2.1",
|
|
55
|
+
"haraka-plugin-headers": "~1.2.0",
|
|
56
|
+
"haraka-plugin-helo.checks": "~1.1.1",
|
|
57
|
+
"haraka-plugin-karma": "~2.5.1",
|
|
58
|
+
"haraka-plugin-known-senders": "~1.2.0",
|
|
59
|
+
"haraka-plugin-limit": "~1.3.1",
|
|
60
|
+
"haraka-plugin-mail_from.is_resolvable": "~1.3.0",
|
|
61
|
+
"haraka-plugin-messagesniffer": "~1.0.2",
|
|
62
|
+
"haraka-plugin-qmail-deliverable": "~1.4.0",
|
|
63
|
+
"haraka-plugin-relay": "~1.0.2",
|
|
64
|
+
"haraka-plugin-rspamd": "~1.6.0",
|
|
65
|
+
"haraka-plugin-spamassassin": "~1.1.0",
|
|
66
|
+
"haraka-plugin-spf": "~1.3.0",
|
|
67
|
+
"haraka-plugin-syslog": "~1.1.1",
|
|
68
|
+
"haraka-plugin-uribl": "~2.0.0"
|
|
14
69
|
},
|
|
15
70
|
"devDependencies": {
|
|
16
|
-
"@
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"eslint-plugin-jsx-a11y": "6.1.1",
|
|
28
|
-
"eslint-plugin-react": "7.10.0",
|
|
29
|
-
"rollup": "0.64.1",
|
|
30
|
-
"rollup-plugin-babel": "3.0.7"
|
|
71
|
+
"@haraka/eslint-config": "~3.0.0",
|
|
72
|
+
"haraka-test-fixtures": "^1.7.1",
|
|
73
|
+
"mock-require": "~3.0.3"
|
|
74
|
+
},
|
|
75
|
+
"bugs": {
|
|
76
|
+
"mail": "haraka.mail@gmail.com",
|
|
77
|
+
"url": "https://github.com/haraka/Haraka/issues"
|
|
78
|
+
},
|
|
79
|
+
"bin": {
|
|
80
|
+
"haraka": "./bin/haraka",
|
|
81
|
+
"haraka_grep": "./bin/haraka_grep"
|
|
31
82
|
},
|
|
32
83
|
"scripts": {
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
84
|
+
"prepare": "git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true",
|
|
85
|
+
"format": "npm run prettier:fix && npm run lint:fix",
|
|
86
|
+
"lint": "npx eslint *.js outbound plugins plugins/*/*.js test test/*/*.js test/*/*/*.js",
|
|
87
|
+
"lint:fix": "npx eslint --fix *.js outbound plugins plugins/*/*.js test test/*/*.js test/*/*/*.js",
|
|
88
|
+
"prettier": "npx prettier . --check",
|
|
89
|
+
"prettier:fix": "npx prettier . --write --log-level=warn",
|
|
90
|
+
"qlty": "qlty smells --all",
|
|
91
|
+
"test": "sh ./run_tests",
|
|
92
|
+
"test:coverage": "node --test --test-concurrency=1 --experimental-test-coverage",
|
|
93
|
+
"test:coverage:lcov": "mkdir -p coverage && node --test --test-concurrency=1 --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info",
|
|
94
|
+
"versions": "npx npm-dep-mgr check",
|
|
95
|
+
"versions:fix": "npx npm-dep-mgr update"
|
|
36
96
|
},
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
97
|
+
"prettier": {
|
|
98
|
+
"singleQuote": true,
|
|
99
|
+
"printWidth": 120,
|
|
100
|
+
"semi": false
|
|
101
|
+
}
|
|
40
102
|
}
|