haraka 0.0.33 → 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.
Files changed (309) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/.githooks/pre-commit +41 -0
  3. package/.prettierignore +6 -0
  4. package/.qlty/.gitignore +7 -0
  5. package/.qlty/configs/.shellcheckrc +1 -0
  6. package/.qlty/qlty.toml +15 -0
  7. package/CHANGELOG.md +1894 -0
  8. package/CLAUDE.md +40 -0
  9. package/CONTRIBUTORS.md +34 -0
  10. package/Dockerfile +50 -0
  11. package/GEMINI.md +38 -0
  12. package/LICENSE +22 -0
  13. package/Plugins.md +227 -0
  14. package/README.md +119 -4
  15. package/SECURITY.md +178 -0
  16. package/TODO +22 -0
  17. package/address.js +53 -0
  18. package/bin/haraka +593 -0
  19. package/bin/haraka_grep +32 -0
  20. package/config/aliases +2 -0
  21. package/config/auth_flat_file.ini +7 -0
  22. package/config/auth_vpopmaild.ini +9 -0
  23. package/config/connection.ini +79 -0
  24. package/config/delay_deny.ini +7 -0
  25. package/config/dhparams.pem +8 -0
  26. package/config/host_list +3 -0
  27. package/config/host_list_regex +6 -0
  28. package/config/http.ini +11 -0
  29. package/config/lmtp.ini +7 -0
  30. package/config/log.ini +11 -0
  31. package/config/me +1 -0
  32. package/config/outbound.bounce_message +18 -0
  33. package/config/outbound.bounce_message_html +36 -0
  34. package/config/outbound.bounce_message_image +106 -0
  35. package/config/outbound.ini +24 -0
  36. package/config/plugins +67 -0
  37. package/config/smtp.ini +37 -0
  38. package/config/smtp_bridge.ini +4 -0
  39. package/config/smtp_forward.ini +31 -0
  40. package/config/smtp_proxy.ini +27 -0
  41. package/config/tarpit.timeout +1 -0
  42. package/config/tls.ini +83 -0
  43. package/config/tls_cert.pem +23 -0
  44. package/config/tls_key.pem +28 -0
  45. package/config/watch.ini +12 -0
  46. package/config/xclient.hosts +2 -0
  47. package/connection.js +1863 -0
  48. package/contrib/Haraka.cf +6 -0
  49. package/contrib/Haraka.pm +35 -0
  50. package/contrib/bad_smtp_server.pl +25 -0
  51. package/contrib/bsd-rc.d/haraka +61 -0
  52. package/contrib/debian-init.d/haraka +87 -0
  53. package/contrib/haraka.init +96 -0
  54. package/contrib/haraka.service +23 -0
  55. package/contrib/plugin2npm.sh +81 -0
  56. package/contrib/ubuntu-upstart/haraka.conf +27 -0
  57. package/coverage/coverage-final.json +2 -0
  58. package/coverage/coverage-summary.json +33 -0
  59. package/coverage/tmp/coverage-79131-1779241025146-0.json +1 -0
  60. package/coverage/tmp/coverage-79132-1779240999690-0.json +1 -0
  61. package/coverage/tmp/coverage-79172-1779241000095-0.json +1 -0
  62. package/coverage/tmp/coverage-79210-1779241000156-0.json +1 -0
  63. package/coverage/tmp/coverage-79211-1779241000209-0.json +1 -0
  64. package/coverage/tmp/coverage-79212-1779241000266-0.json +1 -0
  65. package/coverage/tmp/coverage-79213-1779241000441-0.json +1 -0
  66. package/coverage/tmp/coverage-79214-1779241000626-0.json +1 -0
  67. package/coverage/tmp/coverage-79215-1779241000795-0.json +1 -0
  68. package/coverage/tmp/coverage-79216-1779241000965-0.json +1 -0
  69. package/coverage/tmp/coverage-79218-1779241001013-0.json +1 -0
  70. package/coverage/tmp/coverage-79219-1779241001179-0.json +1 -0
  71. package/coverage/tmp/coverage-79220-1779241006249-0.json +1 -0
  72. package/coverage/tmp/coverage-79227-1779241011453-0.json +1 -0
  73. package/coverage/tmp/coverage-79229-1779241011537-0.json +1 -0
  74. package/coverage/tmp/coverage-79230-1779241011647-0.json +1 -0
  75. package/coverage/tmp/coverage-79231-1779241011765-0.json +1 -0
  76. package/coverage/tmp/coverage-79232-1779241011841-0.json +1 -0
  77. package/coverage/tmp/coverage-79233-1779241011909-0.json +1 -0
  78. package/coverage/tmp/coverage-79234-1779241011984-0.json +1 -0
  79. package/coverage/tmp/coverage-79235-1779241012055-0.json +1 -0
  80. package/coverage/tmp/coverage-79236-1779241012230-0.json +1 -0
  81. package/coverage/tmp/coverage-79237-1779241012300-0.json +1 -0
  82. package/coverage/tmp/coverage-79238-1779241012368-0.json +1 -0
  83. package/coverage/tmp/coverage-79239-1779241012438-0.json +1 -0
  84. package/coverage/tmp/coverage-79240-1779241012511-0.json +1 -0
  85. package/coverage/tmp/coverage-79241-1779241012582-0.json +1 -0
  86. package/coverage/tmp/coverage-79242-1779241012652-0.json +1 -0
  87. package/coverage/tmp/coverage-79243-1779241012814-0.json +1 -0
  88. package/coverage/tmp/coverage-79244-1779241012931-0.json +1 -0
  89. package/coverage/tmp/coverage-79245-1779241013007-0.json +1 -0
  90. package/coverage/tmp/coverage-79246-1779241013106-0.json +1 -0
  91. package/coverage/tmp/coverage-79247-1779241013178-0.json +1 -0
  92. package/coverage/tmp/coverage-79248-1779241013244-0.json +1 -0
  93. package/coverage/tmp/coverage-79249-1779241013409-0.json +1 -0
  94. package/coverage/tmp/coverage-79250-1779241013697-0.json +1 -0
  95. package/coverage/tmp/coverage-79251-1779241013847-0.json +1 -0
  96. package/coverage/tmp/coverage-79252-1779241014288-0.json +1 -0
  97. package/coverage/tmp/coverage-79253-1779241014378-0.json +1 -0
  98. package/coverage/tmp/coverage-79254-1779241014428-0.json +1 -0
  99. package/coverage/tmp/coverage-79255-1779241021774-0.json +1 -0
  100. package/coverage/tmp/coverage-80382-1779241021949-0.json +1 -0
  101. package/coverage/tmp/coverage-80383-1779241025019-0.json +1 -0
  102. package/coverage/tmp/coverage-80384-1779241025133-0.json +1 -0
  103. package/docs/Body.md +1 -0
  104. package/docs/Config.md +1 -0
  105. package/docs/Connection.md +153 -0
  106. package/docs/CoreConfig.md +96 -0
  107. package/docs/CustomReturnCodes.md +3 -0
  108. package/docs/HAProxy.md +62 -0
  109. package/docs/Header.md +1 -0
  110. package/docs/Logging.md +129 -0
  111. package/docs/Outbound.md +210 -0
  112. package/docs/Plugins.md +372 -0
  113. package/docs/Results.md +7 -0
  114. package/docs/Transaction.md +135 -0
  115. package/docs/Tutorial.md +183 -0
  116. package/docs/deprecated/access.md +3 -0
  117. package/docs/deprecated/backscatterer.md +9 -0
  118. package/docs/deprecated/connect.rdns_access.md +53 -0
  119. package/docs/deprecated/data.headers.md +3 -0
  120. package/docs/deprecated/data.nomsgid.md +7 -0
  121. package/docs/deprecated/data.noreceived.md +11 -0
  122. package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
  123. package/docs/deprecated/dkim_sign.md +97 -0
  124. package/docs/deprecated/dkim_verify.md +28 -0
  125. package/docs/deprecated/dnsbl.md +80 -0
  126. package/docs/deprecated/dnswl.md +73 -0
  127. package/docs/deprecated/lookup_rdns.strict.md +67 -0
  128. package/docs/deprecated/mail_from.access.md +52 -0
  129. package/docs/deprecated/mail_from.blocklist.md +18 -0
  130. package/docs/deprecated/mail_from.nobounces.md +8 -0
  131. package/docs/deprecated/rcpt_to.access.md +53 -0
  132. package/docs/deprecated/rcpt_to.blocklist.md +18 -0
  133. package/docs/deprecated/rcpt_to.routes.md +3 -0
  134. package/docs/deprecated/rdns.regexp.md +30 -0
  135. package/docs/plugins/aliases.md +3 -0
  136. package/docs/plugins/auth/auth_bridge.md +34 -0
  137. package/docs/plugins/auth/auth_ldap.md +4 -0
  138. package/docs/plugins/auth/auth_proxy.md +36 -0
  139. package/docs/plugins/auth/auth_vpopmaild.md +33 -0
  140. package/docs/plugins/auth/flat_file.md +40 -0
  141. package/docs/plugins/block_me.md +18 -0
  142. package/docs/plugins/data.signatures.md +11 -0
  143. package/docs/plugins/delay_deny.md +23 -0
  144. package/docs/plugins/max_unrecognized_commands.md +6 -0
  145. package/docs/plugins/prevent_credential_leaks.md +22 -0
  146. package/docs/plugins/process_title.md +42 -0
  147. package/docs/plugins/queue/deliver.md +3 -0
  148. package/docs/plugins/queue/discard.md +32 -0
  149. package/docs/plugins/queue/lmtp.md +24 -0
  150. package/docs/plugins/queue/qmail-queue.md +16 -0
  151. package/docs/plugins/queue/quarantine.md +87 -0
  152. package/docs/plugins/queue/smtp_bridge.md +32 -0
  153. package/docs/plugins/queue/smtp_forward.md +127 -0
  154. package/docs/plugins/queue/smtp_proxy.md +68 -0
  155. package/docs/plugins/queue/test.md +7 -0
  156. package/docs/plugins/rcpt_to.in_host_list.md +34 -0
  157. package/docs/plugins/rcpt_to.max_count.md +3 -0
  158. package/docs/plugins/record_envelope_addresses.md +20 -0
  159. package/docs/plugins/relay.md +3 -0
  160. package/docs/plugins/reseed_rng.md +16 -0
  161. package/docs/plugins/status.md +41 -0
  162. package/docs/plugins/tarpit.md +50 -0
  163. package/docs/plugins/tls.md +235 -0
  164. package/docs/plugins/toobusy.md +27 -0
  165. package/docs/plugins/xclient.md +10 -0
  166. package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
  167. package/docs/tutorials/SettingUpOutbound.md +62 -0
  168. package/eslint.config.mjs +2 -0
  169. package/haraka.js +74 -0
  170. package/haraka.sh +2 -0
  171. package/http/html/404.html +58 -0
  172. package/http/html/index.html +47 -0
  173. package/http/package.json +21 -0
  174. package/line_socket.js +24 -0
  175. package/logger.js +322 -0
  176. package/outbound/client_pool.js +59 -0
  177. package/outbound/config.js +134 -0
  178. package/outbound/hmail.js +1504 -0
  179. package/outbound/index.js +349 -0
  180. package/outbound/qfile.js +93 -0
  181. package/outbound/queue.js +399 -0
  182. package/outbound/tls.js +85 -0
  183. package/outbound/todo.js +17 -0
  184. package/package.json +99 -4
  185. package/plugins/.eslintrc.yaml +3 -0
  186. package/plugins/auth/auth_base.js +261 -0
  187. package/plugins/auth/auth_bridge.js +20 -0
  188. package/plugins/auth/auth_proxy.js +227 -0
  189. package/plugins/auth/auth_vpopmaild.js +162 -0
  190. package/plugins/auth/flat_file.js +44 -0
  191. package/plugins/block_me.js +88 -0
  192. package/plugins/data.signatures.js +30 -0
  193. package/plugins/delay_deny.js +153 -0
  194. package/plugins/prevent_credential_leaks.js +61 -0
  195. package/plugins/process_title.js +197 -0
  196. package/plugins/profile.js +11 -0
  197. package/plugins/queue/deliver.js +12 -0
  198. package/plugins/queue/discard.js +27 -0
  199. package/plugins/queue/lmtp.js +45 -0
  200. package/plugins/queue/qmail-queue.js +93 -0
  201. package/plugins/queue/quarantine.js +133 -0
  202. package/plugins/queue/smtp_bridge.js +45 -0
  203. package/plugins/queue/smtp_forward.js +371 -0
  204. package/plugins/queue/smtp_proxy.js +142 -0
  205. package/plugins/queue/test.js +15 -0
  206. package/plugins/rcpt_to.host_list_base.js +65 -0
  207. package/plugins/rcpt_to.in_host_list.js +56 -0
  208. package/plugins/record_envelope_addresses.js +17 -0
  209. package/plugins/reseed_rng.js +7 -0
  210. package/plugins/status.js +274 -0
  211. package/plugins/tarpit.js +45 -0
  212. package/plugins/tls.js +164 -0
  213. package/plugins/toobusy.js +47 -0
  214. package/plugins/xclient.js +124 -0
  215. package/plugins.js +604 -0
  216. package/queue/1772642154987_1775581346001_4_82235_TGwgfd_2_mattbook-m3.home.simerson.net +0 -0
  217. package/run_tests +11 -0
  218. package/server.js +827 -0
  219. package/smtp_client.js +504 -0
  220. package/test/.eslintrc.yaml +11 -0
  221. package/test/config/auth_flat_file.ini +5 -0
  222. package/test/config/block_me.recipient +1 -0
  223. package/test/config/block_me.senders +1 -0
  224. package/test/config/dhparams.pem +8 -0
  225. package/test/config/host_list +2 -0
  226. package/test/config/outbound_tls_cert.pem +1 -0
  227. package/test/config/outbound_tls_key.pem +1 -0
  228. package/test/config/plugins +7 -0
  229. package/test/config/smtp.ini +11 -0
  230. package/test/config/smtp_forward.ini +30 -0
  231. package/test/config/tls/example.com/_.example.com.key +28 -0
  232. package/test/config/tls/example.com/example.com.crt +25 -0
  233. package/test/config/tls/haraka.local.pem +51 -0
  234. package/test/config/tls.ini +45 -0
  235. package/test/config/tls_cert.pem +21 -0
  236. package/test/config/tls_key.pem +28 -0
  237. package/test/connection.js +817 -0
  238. package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
  239. package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
  240. package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
  241. package/test/fixtures/line_socket.js +21 -0
  242. package/test/fixtures/todo_qfile.txt +0 -0
  243. package/test/fixtures/util_hmailitem.js +156 -0
  244. package/test/installation/config/test-plugin-flat +1 -0
  245. package/test/installation/config/test-plugin.ini +10 -0
  246. package/test/installation/config/tls.ini +1 -0
  247. package/test/installation/node_modules/load_first/index.js +5 -0
  248. package/test/installation/node_modules/load_first/package.json +11 -0
  249. package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
  250. package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
  251. package/test/installation/node_modules/test-plugin/package.json +5 -0
  252. package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
  253. package/test/installation/plugins/base_plugin.js +3 -0
  254. package/test/installation/plugins/folder_plugin/index.js +3 -0
  255. package/test/installation/plugins/folder_plugin/package.json +11 -0
  256. package/test/installation/plugins/inherits.js +7 -0
  257. package/test/installation/plugins/load_first.js +3 -0
  258. package/test/installation/plugins/plugin.js +1 -0
  259. package/test/installation/plugins/tls.js +3 -0
  260. package/test/logger.js +217 -0
  261. package/test/loud/config/dhparams.pem +0 -0
  262. package/test/loud/config/tls/goobered.pem +45 -0
  263. package/test/loud/config/tls.ini +43 -0
  264. package/test/mail_specimen/base64-root-part.txt +23 -0
  265. package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
  266. package/test/outbound/bounce_net_errors.js +133 -0
  267. package/test/outbound/bounce_rfc3464.js +226 -0
  268. package/test/outbound/hmail.js +210 -0
  269. package/test/outbound/index.js +385 -0
  270. package/test/outbound/qfile.js +124 -0
  271. package/test/outbound/queue.js +325 -0
  272. package/test/plugins/auth/auth_base.js +620 -0
  273. package/test/plugins/auth/auth_bridge.js +80 -0
  274. package/test/plugins/auth/auth_vpopmaild.js +81 -0
  275. package/test/plugins/auth/flat_file.js +123 -0
  276. package/test/plugins/block_me.js +141 -0
  277. package/test/plugins/data.signatures.js +111 -0
  278. package/test/plugins/delay_deny.js +262 -0
  279. package/test/plugins/prevent_credential_leaks.js +174 -0
  280. package/test/plugins/process_title.js +141 -0
  281. package/test/plugins/queue/deliver.js +98 -0
  282. package/test/plugins/queue/discard.js +78 -0
  283. package/test/plugins/queue/lmtp.js +137 -0
  284. package/test/plugins/queue/qmail-queue.js +98 -0
  285. package/test/plugins/queue/quarantine.js +80 -0
  286. package/test/plugins/queue/smtp_bridge.js +152 -0
  287. package/test/plugins/queue/smtp_forward.js +1023 -0
  288. package/test/plugins/queue/smtp_proxy.js +138 -0
  289. package/test/plugins/rcpt_to.host_list_base.js +102 -0
  290. package/test/plugins/rcpt_to.in_host_list.js +186 -0
  291. package/test/plugins/record_envelope_addresses.js +66 -0
  292. package/test/plugins/reseed_rng.js +34 -0
  293. package/test/plugins/status.js +207 -0
  294. package/test/plugins/tarpit.js +90 -0
  295. package/test/plugins/tls.js +86 -0
  296. package/test/plugins/toobusy.js +21 -0
  297. package/test/plugins/xclient.js +119 -0
  298. package/test/plugins.js +230 -0
  299. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  300. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  301. package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  302. package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  303. package/test/queue/zero-length +0 -0
  304. package/test/server.js +1012 -0
  305. package/test/smtp_client.js +1303 -0
  306. package/test/tls_socket.js +321 -0
  307. package/test/transaction.js +554 -0
  308. package/tls_socket.js +771 -0
  309. package/transaction.js +267 -0
package/bin/haraka ADDED
@@ -0,0 +1,593 @@
1
+ #!/usr/bin/env node
2
+
3
+ // this script takes inspiration from:
4
+ // https://github.com/tnantoka/LooseLeaf/blob/master/bin/looseleaf
5
+
6
+ const child = require('node:child_process')
7
+ const fs = require('node:fs')
8
+ const net = require('node:net')
9
+ const path = require('node:path')
10
+ const os = require('node:os')
11
+
12
+ const nopt = require('nopt')
13
+ const utils = require('haraka-utils')
14
+ const base = path.join(__dirname, '..')
15
+ const ver = utils.getVersion(base)
16
+ const knownOpts = {
17
+ version: Boolean,
18
+ verbose: Boolean,
19
+ help: [String, null],
20
+ configs: path,
21
+ install: path,
22
+ list: Boolean,
23
+ plugin: Array,
24
+ force: Boolean,
25
+ qlist: Boolean,
26
+ qstat: Boolean,
27
+ qempty: Boolean,
28
+ qunstick: [String, null],
29
+ graceful: Boolean,
30
+ order: Boolean,
31
+ test: [String, Array],
32
+ ip: String,
33
+ helo: String,
34
+ ehlo: String,
35
+ envfrom: String,
36
+ envrcpt: [String, Array],
37
+ message: path,
38
+ 'dump-mime': Boolean,
39
+ 'dump-stream': Boolean,
40
+ 'skip-deny': Boolean,
41
+ 'set-relay': Boolean,
42
+ }
43
+ const shortHands = {
44
+ v: ['--version'],
45
+ h: ['--help'],
46
+ c: ['--configs'],
47
+ i: ['--install'],
48
+ l: ['--list'],
49
+ p: ['--plugin'],
50
+ f: ['--force'],
51
+ o: ['--order'],
52
+ t: ['--test'],
53
+ }
54
+ const parsed = nopt(knownOpts, shortHands, process.argv, 2)
55
+
56
+ const usage = `\x1B[32;40mHaraka.js\x1B[0m — A Node.js Email Server project
57
+ Usage: haraka [options] [path]
58
+ Options:
59
+ -v, --version \t\tOutputs version number
60
+ -h, --help \t\tOutputs this help message
61
+ -h NAME \t\tShows help for NAME
62
+ -c, --configs \t\tPath to your config directory
63
+ -i, --install \t\tCopies the default configs to a specified dir
64
+ -l, --list \t\tList the plugins bundled with Haraka
65
+ -p, --plugin \t\tGenerate a new plugin with the given name
66
+ -f, --force \t\tForce overwriting of old files
67
+ --qlist \t\tList the outbound queue
68
+ --qstat \t\tGet statistics on the outbound queue
69
+ --qunstick \t\tUnstick (force delivery) for a given domain
70
+ -o, --order \t\tShow all registered plugins and their run order
71
+ -t PLUGIN \t\tPlugin test mode
72
+ --------------- PLUGIN TEST MODE OPTIONS (all optional) --------------
73
+ --ip IP \t\tIP address to use
74
+ --helo HELO \t\tHELO to use
75
+ --ehlo EHLO \t\tEHLO to use
76
+ --envfrom FROM\t\tMAIL FROM to use
77
+ --envfrom TO \t\tRCPT TO(s) to use
78
+ --message FILE\t\tMessage file to use
79
+ --dump-mime \t\tDump the MIME structure and body text
80
+ --dump-stream \t\tDump the MessageStream to stdout
81
+ --skip-deny \t\tContinue running hooks after DENY/DENYSOFT
82
+ --set-relay \t\tSet connection.relaying
83
+ `
84
+
85
+ function listPlugins(b, dir = 'plugins/') {
86
+ const inital_dir = path.join(b ?? base, dir)
87
+ const plugin_dirs = [inital_dir]
88
+
89
+ for (const d of fs.readdirSync(inital_dir)) {
90
+ if (fs.statSync(path.join(inital_dir, d)).isDirectory()) {
91
+ plugin_dirs.push(path.join(inital_dir, d))
92
+ }
93
+ }
94
+
95
+ let plugin_list = ``
96
+ for (const pd of plugin_dirs) {
97
+ plugin_list += `\n${pd.match(/plugins.*$/)[0]}\n`
98
+
99
+ for (const d of fs.readdirSync(pd)) {
100
+ if (fs.statSync(path.join(pd, d)).isFile() && ~d.search('.js')) {
101
+ plugin_list += `\t${d.replace('.js', '')}\n`
102
+ }
103
+ }
104
+ }
105
+
106
+ plugin_list += `\nNPM packages (${b ?? base})\n`
107
+ const npm_plugins = []
108
+ for (const entry of fs.readdirSync(path.join(b ?? base, 'node_modules'))) {
109
+ if (!/^haraka-plugin-/.test(entry)) continue
110
+ npm_plugins.push(entry.split('-').slice(2).join('-'))
111
+ }
112
+ plugin_list += `\t${npm_plugins.join('\n\t')}\n`
113
+
114
+ return plugin_list
115
+ }
116
+
117
+ // Warning messsage
118
+ function warning(msg) {
119
+ console.error(`\x1b[31mwarning\x1b[0m: ${msg}`)
120
+ }
121
+
122
+ function fail(msg) {
123
+ console.error(`\x1b[31merror\x1b[0m: ${msg}`)
124
+ process.exit(-1)
125
+ }
126
+
127
+ function setupHostname(confPath) {
128
+ utils.mkDir(confPath)
129
+
130
+ const hostname = `${os.hostname()}${os.EOL}`
131
+
132
+ ;['me', 'host_list'].forEach((f) => {
133
+ const cfPath = path.join(confPath, f)
134
+
135
+ try {
136
+ if (fs.statSync(cfPath).isFile()) return
137
+ } catch (ignore) {}
138
+
139
+ try {
140
+ fs.writeFileSync(cfPath, hostname)
141
+ } catch (err) {
142
+ warning(`Unable to write to config/${f}: ${err.message}`)
143
+ }
144
+ })
145
+ }
146
+
147
+ function setupBaseConfig(confPath) {
148
+ utils.copyFile(path.join(base, 'config', 'connection.ini'), path.join(confPath, 'connection.ini'))
149
+ utils.copyFile(path.join(base, 'config', 'smtp.ini'), path.join(confPath, 'smtp.ini'))
150
+ utils.copyFile(path.join(base, 'config', 'log.ini'), path.join(confPath, 'log.ini'))
151
+ utils.copyFile(path.join(base, 'config', 'plugins'), path.join(confPath, 'plugins'))
152
+ }
153
+
154
+ function setupRequire() {
155
+ process.env.HARAKA = parsed.configs
156
+ try {
157
+ require.paths.push(path.join(process.env.HARAKA, 'node_modules'))
158
+ } catch (e) {
159
+ process.env.NODE_PATH = process.env.NODE_PATH
160
+ ? `${process.env.NODE_PATH}:${path.join(process.env.HARAKA, 'node_modules')}`
161
+ : path.join(process.env.HARAKA, 'node_modules')
162
+ require('module')._initPaths() // Horrible hack
163
+ }
164
+ }
165
+
166
+ function noop() {}
167
+
168
+ function tryCreateFile(filePath, data, info = {}, force = false) {
169
+ try {
170
+ utils.createFile(...arguments)
171
+ } catch (e) {
172
+ warning(`EEXIST, File exists '${filePath}'`)
173
+ }
174
+ }
175
+
176
+ const readme = `Haraka
177
+
178
+ Congratulations on creating a new installation of Haraka.
179
+
180
+ This directory contains two key directories for how Haraka will function:
181
+
182
+ - config
183
+
184
+ This directory contains configuration files for Haraka. The
185
+ directory contains the default configuration. You probably want
186
+ to modify some files in here, particularly 'smtp.ini'.
187
+
188
+ - plugins
189
+
190
+ This directory contains custom plugins which you write to run in
191
+ Haraka. The plugins which ship with Haraka are still available
192
+ to use.
193
+
194
+ - docs/plugins
195
+
196
+ This directory contains documentation for your plugins.
197
+
198
+ Documentation for Haraka is available via 'haraka -h <name>' where the name
199
+ is either the name of a plugin (without the .js extension) or the name of
200
+ a core Haraka module, such as 'Connection' or 'Transaction'.
201
+
202
+ To get documentation on writing a plugin type 'haraka -h Plugins'.
203
+ `
204
+
205
+ const packageJson = JSON.stringify(
206
+ {
207
+ name: 'haraka_local',
208
+ description: 'An SMTP Server',
209
+ version: '0.0.1',
210
+ dependencies: {},
211
+ repository: '',
212
+ license: 'MIT',
213
+ },
214
+ null,
215
+ 2,
216
+ )
217
+
218
+ const plugin_src = [
219
+ '// %plugin%',
220
+ '',
221
+ '// documentation via: haraka -c %config% -h plugins/%plugin%',
222
+ '',
223
+ '// Put your plugin code here',
224
+ '// type: `haraka -h Plugins` for documentation on how to create a plugin',
225
+ '',
226
+ ].join('\n')
227
+
228
+ const plugin_doc = [
229
+ '%plugin%',
230
+ '========',
231
+ '',
232
+ 'Describe what your plugin does here.',
233
+ '',
234
+ 'Configuration',
235
+ '-------------',
236
+ '',
237
+ '* `config/some_file` - describe what effect this config file has',
238
+ '',
239
+ ].join('\n')
240
+
241
+ function getHooks() {
242
+ // see haraka/Haraka#3306
243
+ const pi_path = path.resolve(parsed.configs, 'docs', 'Plugins.md')
244
+ return fs
245
+ .readFileSync(pi_path)
246
+ .toString()
247
+ .split('## Available Hooks')[1] // discard everything before this string
248
+ .split('### rcpt')[0] // discard everything after this string
249
+ .match(/^-\s+\w+/gmu) // grab hook names from lines starting with '- '
250
+ .map((a) => a.replace(/^-\s+/u, '')) // strip list prefix
251
+ }
252
+
253
+ let config
254
+ let logger
255
+ let outbound
256
+ let plugins
257
+ if (parsed.version) {
258
+ console.log(`\x1B[32;40mHaraka.js\x1B[0m — Version: ${ver}`)
259
+ } else if (parsed.list) {
260
+ console.log(`\x1B[32;40m*global\x1B[0m\n${listPlugins()}`)
261
+ if (parsed.configs) {
262
+ console.log(`\x1B[32;40m*local\x1B[0m\n${listPlugins(parsed.configs)}`)
263
+ }
264
+ } else if (parsed.help) {
265
+ if (parsed.help === 'true') {
266
+ console.log(usage)
267
+ return
268
+ }
269
+
270
+ let md_path
271
+ const md_paths = [
272
+ path.join(base, 'docs', `${parsed.help}.md`),
273
+ path.join(base, 'docs', 'plugins', `${parsed.help}.md`),
274
+ path.join(base, 'docs', 'deprecated', `${parsed.help}.md`),
275
+ path.join(base, 'node_modules', `haraka-plugin-${parsed.help}`, 'README.md'),
276
+ ]
277
+ if (parsed.configs) {
278
+ md_paths.unshift(path.join(parsed.configs, 'docs', 'plugins', `${parsed.help}.md`))
279
+ md_paths.unshift(path.join(parsed.configs, 'docs', `${parsed.help}.md`))
280
+ }
281
+ for (let i = 0, j = md_paths.length; i < j; i++) {
282
+ const _md_path = md_paths[i]
283
+ if (fs.existsSync(_md_path)) {
284
+ md_path = [_md_path]
285
+ break
286
+ }
287
+ }
288
+ if (!md_path) {
289
+ warning(`No documentation found for: ${parsed.help}`)
290
+ process.exit()
291
+ }
292
+ let pager = 'less'
293
+ if (process.env.PAGER) {
294
+ const pager_split = process.env.PAGER.split(/ +/)
295
+ pager = pager_split.shift()
296
+ md_path = [...pager_split, ...md_path]
297
+ }
298
+
299
+ const less = child.spawn(pager, md_path, { stdio: 'inherit' })
300
+ less.on('exit', function () {
301
+ process.exit(0)
302
+ })
303
+ } else if (parsed.configs && parsed.plugin) {
304
+ const js_path = path.join(parsed.configs, 'plugins', `${parsed.plugin}.js`)
305
+ utils.createFile(js_path, plugin_src, { plugin: parsed.plugin, config: parsed.configs }, parsed.force)
306
+
307
+ const doc_path = path.join(parsed.configs, 'docs', 'plugins', `${parsed.plugin}.md`)
308
+ utils.createFile(doc_path, plugin_doc, { plugin: parsed.plugin, config: parsed.configs }, parsed.force)
309
+
310
+ console.log(`Plugin ${parsed.plugin} created`)
311
+ console.log(`Now edit javascript in: ${js_path}`)
312
+ console.log(`Add the plugin to config: ${path.join(parsed.configs, 'config', 'plugins')}`)
313
+ console.log(`And edit documentation in: ${doc_path}`)
314
+ } else if (parsed.qlist) {
315
+ if (!parsed.configs) fail('qlist option requires config path')
316
+ process.env.HARAKA = parsed.configs
317
+ logger = require(path.join(base, 'logger'))
318
+ if (!parsed.verbose) logger.log = noop // disable logging
319
+ outbound = require(path.join(base, 'outbound'))
320
+ ;(async () => {
321
+ const qlist = await outbound.list_queue()
322
+ for (const todo of qlist) {
323
+ console.log(`Q: ${todo.file} rcpt:${todo.rcpt_to.length} from:${todo.mail_from} domain:${todo.domain}`)
324
+ }
325
+ process.exit()
326
+ })()
327
+ } else if (parsed.qstat) {
328
+ if (!parsed.configs) fail('qstat option requires config path')
329
+
330
+ process.env.HARAKA = parsed.configs
331
+ logger = require(path.join(base, 'logger'))
332
+ if (!parsed.verbose) logger.log = noop // disable logging
333
+ outbound = require(path.join(base, 'outbound'))
334
+ ;(async () => {
335
+ const stats = await outbound.stat_queue()
336
+ console.log(stats)
337
+ process.exit()
338
+ })()
339
+ } else if (parsed.qunstick) {
340
+ if (!parsed.configs) fail('qunstick option requires config path')
341
+ const domain = parsed.qunstick.toLowerCase()
342
+ process.env.HARAKA = parsed.configs
343
+ logger = require(path.join(base, 'logger'))
344
+ if (!parsed.verbose) logger.log = noop // disable logging
345
+ const cb = function () {
346
+ process.exit()
347
+ }
348
+ if (domain == 'true') {
349
+ send_internal_command('flushQueue', cb)
350
+ } else {
351
+ send_internal_command(`flushQueue ${domain}`, cb)
352
+ }
353
+ } else if (parsed.graceful) {
354
+ if (!parsed.configs) fail('graceful option requires config path')
355
+
356
+ process.env.HARAKA = parsed.configs
357
+ logger = require(path.join(base, 'logger'))
358
+ if (!parsed.verbose) logger.log = noop // disable logging
359
+ config = require('haraka-config')
360
+ if (!config.get('smtp.ini').main.nodes) {
361
+ console.log('Graceful restart not possible without `nodes` value in smtp.ini')
362
+ process.exit()
363
+ } else {
364
+ send_internal_command('gracefulRestart', () => {
365
+ process.exit()
366
+ })
367
+ }
368
+ } else if (parsed.qempty) {
369
+ if (!parsed.configs) fail('qempty option requires config path')
370
+ fail('qempty is unimplemented')
371
+ } else if (parsed.order) {
372
+ if (!parsed.configs) fail('order option requires config path')
373
+ setupRequire()
374
+ logger = require(path.join(base, 'logger'))
375
+ if (!parsed.verbose) logger.log = noop // disable logging
376
+ plugins = require(path.join(base, 'plugins'))
377
+ plugins.load_plugins()
378
+ console.log('')
379
+ for (const hook of getHooks()) {
380
+ if (!plugins.registered_hooks[hook]) continue
381
+ console.log(`Hook: ${hook} `.padEnd(80, '-'))
382
+ console.log(`${'Plugin'.padEnd(35)} ${'Method'.padEnd(35)} ${'Prio'.padEnd(4)} T/O`)
383
+ console.log('-'.repeat(80))
384
+ for (let p = 0; p < plugins.registered_hooks[hook].length; p++) {
385
+ const item = plugins.registered_hooks[hook][p]
386
+ console.log(
387
+ `${item.plugin.padEnd(35)} ${item.method.padEnd(35)} ${String(item.priority).padStart(4)} ${String(item.timeout).padStart(3)}`,
388
+ )
389
+ }
390
+ console.log('')
391
+ }
392
+ process.exit()
393
+ } else if (parsed.test) {
394
+ if (!parsed.configs) fail('test option requires config path')
395
+
396
+ setupRequire()
397
+
398
+ logger = require(path.join(base, 'logger'))
399
+ logger.loglevel = logger.levels.PROTOCOL
400
+
401
+ // Attempt to load message early
402
+ let msg
403
+ if (parsed.message) {
404
+ try {
405
+ msg = fs.readFileSync(parsed.message)
406
+ } catch (e) {
407
+ logger.crit(e.message)
408
+ logger.dump_logs(1)
409
+ }
410
+ }
411
+
412
+ plugins = require(path.join(base, 'plugins'))
413
+ plugins.server = { notes: {} }
414
+ plugins.load_plugins(parsed.test && parsed.test[0] !== 'all' ? parsed.test : null)
415
+ const Connection = require(path.join(base, 'connection'))
416
+ // var Transaction = require(path.join(base, "transaction"));
417
+ const Address = require('../address').Address
418
+ const Notes = require('haraka-notes')
419
+ const constants = require('haraka-constants')
420
+ const client = {
421
+ remote: {
422
+ address: parsed.ip ? parsed.ip : '1.2.3.4',
423
+ port: 1234,
424
+ },
425
+ destroy() {},
426
+ on(event) {},
427
+ end() {
428
+ process.exit()
429
+ },
430
+ write(buf) {},
431
+ resume() {},
432
+ pause() {},
433
+ }
434
+ const server = {
435
+ address() {
436
+ return { port: 25, family: 'ipv4', address: '127.0.0.1' }
437
+ },
438
+ cfg: require('haraka-config').get('smtp.ini'),
439
+ notes: new Notes(),
440
+ }
441
+ const connection = Connection.createConnection(client, server, server.cfg)
442
+ if (parsed['set-relay']) connection.relaying = true
443
+
444
+ const run_next_hook = function () {
445
+ const args = Array.prototype.slice.call(arguments)
446
+ const code = args.shift()
447
+ if (!parsed['skip-deny'] && code !== constants.ok && code !== constants.cont) {
448
+ plugins.run_hooks('quit', connection)
449
+ } else {
450
+ plugins.run_hooks.apply(this, args)
451
+ }
452
+ }
453
+
454
+ connection.connect_respond = function () {
455
+ let helo = 'test.example.com'
456
+ let mode = 'ehlo'
457
+ if (parsed.ehlo) {
458
+ helo = parsed.ehlo
459
+ } else if (parsed.helo) {
460
+ helo = parsed.helo
461
+ mode = 'helo'
462
+ }
463
+ connection.hello.host = helo
464
+ run_next_hook(arguments[0], mode, connection, helo)
465
+ }
466
+ connection.helo_respond = connection.ehlo_respond = function () {
467
+ const args = arguments
468
+ const mail = new Address(parsed.envfrom ? parsed.envfrom : 'test@example.com')
469
+ connection.init_transaction(function () {
470
+ connection.transaction.mail_from = mail
471
+ run_next_hook(args[0], 'mail', connection, [mail, null])
472
+ })
473
+ }
474
+ connection.mail_respond = function () {
475
+ const rcpt = new Address(parsed.envrcpt ? parsed.envrcpt : 'test@example.com')
476
+ this.transaction.rcpt_to.push(rcpt)
477
+ run_next_hook(arguments[0], 'rcpt', connection, [rcpt, null])
478
+ }
479
+ connection.rcpt_respond = function () {
480
+ connection.transaction.parse_body = true
481
+ run_next_hook(arguments[0], 'data', connection)
482
+ }
483
+ connection.data_respond = function () {
484
+ const args = arguments
485
+ // Add data to stream
486
+ if (msg) {
487
+ let buf = msg
488
+ let offset
489
+ while ((offset = utils.indexOfLF(buf)) !== -1) {
490
+ const line = buf.slice(0, offset + 1)
491
+ if (buf.length > offset) {
492
+ buf = buf.slice(offset + 1)
493
+ }
494
+ connection.transaction.add_data(line)
495
+ connection.transaction.data_bytes += line.length
496
+ }
497
+ } else {
498
+ // Add empty data to initialize message_stream
499
+ connection.transaction.add_data('')
500
+ }
501
+ connection.data_done(function () {
502
+ run_next_hook(args[0], 'data_post', connection)
503
+ })
504
+ }
505
+ connection.data_post_respond = function () {
506
+ const args = arguments
507
+ // Dump MIME structure and decoded body text?
508
+ function dump_mime_structure(body) {
509
+ console.log(`Found MIME part ${body.ct}`)
510
+ console.log(body.bodytext)
511
+ for (let m = 0, l = body.children.length; m < l; m++) {
512
+ dump_mime_structure(body.children[m])
513
+ }
514
+ }
515
+ if (parsed['dump-mime']) {
516
+ dump_mime_structure(connection.transaction.body)
517
+ }
518
+ if (parsed['dump-stream']) {
519
+ console.log('STREAM:')
520
+ connection.transaction.message_stream.on('end', function () {
521
+ run_next_hook(args[0], 'queue', connection)
522
+ })
523
+ connection.transaction.message_stream.pipe(process.stdout)
524
+ } else {
525
+ run_next_hook(args[0], 'queue', connection)
526
+ }
527
+ }
528
+ connection.queue_respond = function () {
529
+ run_next_hook(arguments[0], 'queue_ok', connection)
530
+ }
531
+ connection.queue_ok_respond = function () {
532
+ run_next_hook(arguments[0], 'quit', connection)
533
+ }
534
+ } else if (parsed.configs) {
535
+ const haraka_path = path.join(base, 'haraka.js')
536
+
537
+ const base_dir = process.argv[3]
538
+ const err_msg = `Did you install a Haraka config? (haraka -i ${base_dir})`
539
+ if (!fs.existsSync(base_dir)) {
540
+ fail(`No such directory: ${base_dir}\n${err_msg}`)
541
+ }
542
+
543
+ const smtp_ini_path = path.join(base_dir, 'config', 'smtp.ini')
544
+ const smtp_json = path.join(base_dir, 'config', 'smtp.json')
545
+ const smtp_yaml = path.join(base_dir, 'config', 'smtp.yaml')
546
+ if (!fs.existsSync(smtp_ini_path) && !fs.existsSync(smtp_json) && !fs.existsSync(smtp_yaml)) {
547
+ fail(`No smtp.ini at: ${smtp_ini_path}\n${err_msg}`)
548
+ }
549
+
550
+ process.argv[1] = haraka_path
551
+ process.env.HARAKA = parsed.configs
552
+ require(haraka_path)
553
+ } else if (parsed.install) {
554
+ const pa = parsed.install
555
+ utils.mkDir(parsed.install)
556
+ for (const d of ['plugins', 'docs', 'config']) {
557
+ utils.mkDir(path.join(pa, d))
558
+ }
559
+ utils.copyFile(path.join(base, 'docs', 'Plugins.md'), path.join(pa, 'docs', 'Plugins.md'))
560
+ tryCreateFile(path.join(pa, 'README'), readme, {}, parsed.force)
561
+ tryCreateFile(path.join(pa, 'package.json'), packageJson, {}, parsed.force)
562
+ const bytes = require('crypto').randomBytes(32)
563
+ tryCreateFile(path.join(pa, 'config', 'internalcmd_key'), bytes.toString('hex'), {}, parsed.force)
564
+ setupHostname(path.join(pa, 'config'))
565
+ setupBaseConfig(path.join(pa, 'config'))
566
+ } else {
567
+ console.log('\033[31;40mError\033[0m: Undefined or erroneous arguments\n')
568
+ console.log(usage)
569
+ }
570
+
571
+ function send_internal_command(cmd, done) {
572
+ config = require('haraka-config')
573
+ const key = config.get('internalcmd_key')
574
+ const smtp_ini = config.get('smtp.ini')
575
+ const listen_addrs = require(path.join(base, 'server')).get_listen_addrs(smtp_ini.main)
576
+ const hp = /^\[?([^\]]+)\]?:(\d+)$/.exec(listen_addrs[0])
577
+ if (!hp) throw 'No listen address in smtp.ini'
578
+ // console.log("Connecting to " + listen_addrs[0]);
579
+ const sock = net.connect(hp[2], hp[1], function () {
580
+ sock.once('data', function (data) {
581
+ // this is the greeting. Ignore it...
582
+ sock.write(`INTERNALCMD ${key ? `key:${key} ` : ''}${cmd}\r\n`)
583
+ sock.once('data', function (data2) {
584
+ console.log(data2.toString().replace(/\r?\n$/, ''))
585
+ sock.write('QUIT\r\n')
586
+ sock.once('data', function (data3) {
587
+ sock.end()
588
+ })
589
+ })
590
+ })
591
+ })
592
+ sock.on('end', done)
593
+ }
@@ -0,0 +1,32 @@
1
+ #!/bin/sh
2
+
3
+ if [ -z $1 ]; then
4
+ echo "$0 email@example.com"
5
+ exit
6
+ fi
7
+
8
+ LOG="/var/log/maillog"
9
+ UUIDS=$(grep $1 $LOG | awk '{ print $7 }' | cut -d. -f1 | cut -f2 -d[ | sort -u)
10
+ echo $UUIDS
11
+
12
+ ARCHIVES=0
13
+ if [ -z $UUIDS ]; then
14
+ echo "no recent matches, checking archives"
15
+ UUIDS=$(/usr/bin/bzgrep $1 $LOG.?.bz2 | awk '{ print $7 }' | cut -d. -f1 | cut -f2 -d[ | sort -u)
16
+ ARCHIVES=1
17
+ #echo "$UUIDS"
18
+ fi
19
+
20
+ show_uuids()
21
+ {
22
+ for u in $UUIDS
23
+ do
24
+ echo; echo
25
+ echo $u
26
+ grep $u $LOG
27
+ if [ "$ARCHIVES" = "1" ]; then
28
+ bzgrep $u $LOG.0.bz2
29
+ fi
30
+ done
31
+ }
32
+ show_uuids
package/config/aliases ADDED
@@ -0,0 +1,2 @@
1
+ {
2
+ }
@@ -0,0 +1,7 @@
1
+
2
+ [core]
3
+ methods=CRAM-MD5
4
+ ; constrain_sender=true
5
+
6
+ [users]
7
+ ; matt=test
@@ -0,0 +1,9 @@
1
+ [main]
2
+ host=127.0.0.6
3
+ port=89
4
+ ; sysadmin=postmaster@example.com:sekret
5
+ ; constrain_sender=true
6
+
7
+ [example.com]
8
+ host=127.0.0.10
9
+ ; sysadmin=postmaster@example.com:sekret