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.
Files changed (254) hide show
  1. package/.githooks/pre-commit +41 -0
  2. package/.prettierignore +7 -0
  3. package/.qlty/.gitignore +7 -0
  4. package/.qlty/configs/.shellcheckrc +1 -0
  5. package/.qlty/qlty.toml +15 -0
  6. package/CHANGELOG.md +1898 -0
  7. package/CONTRIBUTORS.md +34 -0
  8. package/Dockerfile +50 -0
  9. package/LICENSE +22 -0
  10. package/Plugins.md +227 -0
  11. package/README.md +119 -4
  12. package/SECURITY.md +178 -0
  13. package/TODO +22 -0
  14. package/bin/haraka +593 -0
  15. package/bin/haraka_grep +32 -0
  16. package/config/aliases +2 -0
  17. package/config/auth_flat_file.ini +7 -0
  18. package/config/auth_vpopmaild.ini +9 -0
  19. package/config/connection.ini +79 -0
  20. package/config/delay_deny.ini +7 -0
  21. package/config/host_list +3 -0
  22. package/config/host_list_regex +6 -0
  23. package/config/http.ini +11 -0
  24. package/config/lmtp.ini +7 -0
  25. package/config/log.ini +11 -0
  26. package/config/outbound.bounce_message +18 -0
  27. package/config/outbound.bounce_message_html +36 -0
  28. package/config/outbound.bounce_message_image +106 -0
  29. package/config/outbound.ini +24 -0
  30. package/config/plugins +67 -0
  31. package/config/smtp.ini +37 -0
  32. package/config/smtp_bridge.ini +4 -0
  33. package/config/smtp_forward.ini +31 -0
  34. package/config/smtp_proxy.ini +27 -0
  35. package/config/tarpit.timeout +1 -0
  36. package/config/tls.ini +83 -0
  37. package/config/watch.ini +12 -0
  38. package/config/xclient.hosts +2 -0
  39. package/connection.js +1865 -0
  40. package/contrib/Haraka.cf +6 -0
  41. package/contrib/Haraka.pm +35 -0
  42. package/contrib/bad_smtp_server.pl +25 -0
  43. package/contrib/bsd-rc.d/haraka +63 -0
  44. package/contrib/debian-init.d/haraka +87 -0
  45. package/contrib/haraka.init +96 -0
  46. package/contrib/haraka.service +23 -0
  47. package/contrib/plugin2npm.sh +81 -0
  48. package/contrib/ubuntu-upstart/haraka.conf +27 -0
  49. package/docs/Body.md +1 -0
  50. package/docs/Config.md +1 -0
  51. package/docs/Connection.md +153 -0
  52. package/docs/CoreConfig.md +96 -0
  53. package/docs/CustomReturnCodes.md +3 -0
  54. package/docs/HAProxy.md +62 -0
  55. package/docs/Header.md +1 -0
  56. package/docs/Logging.md +129 -0
  57. package/docs/Outbound.md +210 -0
  58. package/docs/Plugins.md +372 -0
  59. package/docs/Results.md +7 -0
  60. package/docs/Transaction.md +135 -0
  61. package/docs/Tutorial.md +183 -0
  62. package/docs/deprecated/access.md +3 -0
  63. package/docs/deprecated/backscatterer.md +9 -0
  64. package/docs/deprecated/connect.rdns_access.md +53 -0
  65. package/docs/deprecated/data.headers.md +3 -0
  66. package/docs/deprecated/data.nomsgid.md +7 -0
  67. package/docs/deprecated/data.noreceived.md +11 -0
  68. package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
  69. package/docs/deprecated/dkim_sign.md +97 -0
  70. package/docs/deprecated/dkim_verify.md +28 -0
  71. package/docs/deprecated/dnsbl.md +80 -0
  72. package/docs/deprecated/dnswl.md +73 -0
  73. package/docs/deprecated/lookup_rdns.strict.md +67 -0
  74. package/docs/deprecated/mail_from.access.md +52 -0
  75. package/docs/deprecated/mail_from.blocklist.md +18 -0
  76. package/docs/deprecated/mail_from.nobounces.md +8 -0
  77. package/docs/deprecated/rcpt_to.access.md +53 -0
  78. package/docs/deprecated/rcpt_to.blocklist.md +18 -0
  79. package/docs/deprecated/rcpt_to.routes.md +3 -0
  80. package/docs/deprecated/rdns.regexp.md +30 -0
  81. package/docs/plugins/aliases.md +3 -0
  82. package/docs/plugins/auth/auth_bridge.md +34 -0
  83. package/docs/plugins/auth/auth_ldap.md +4 -0
  84. package/docs/plugins/auth/auth_proxy.md +36 -0
  85. package/docs/plugins/auth/auth_vpopmaild.md +33 -0
  86. package/docs/plugins/auth/flat_file.md +40 -0
  87. package/docs/plugins/block_me.md +18 -0
  88. package/docs/plugins/data.signatures.md +11 -0
  89. package/docs/plugins/delay_deny.md +23 -0
  90. package/docs/plugins/max_unrecognized_commands.md +6 -0
  91. package/docs/plugins/prevent_credential_leaks.md +22 -0
  92. package/docs/plugins/process_title.md +42 -0
  93. package/docs/plugins/queue/deliver.md +3 -0
  94. package/docs/plugins/queue/discard.md +32 -0
  95. package/docs/plugins/queue/lmtp.md +24 -0
  96. package/docs/plugins/queue/qmail-queue.md +16 -0
  97. package/docs/plugins/queue/quarantine.md +87 -0
  98. package/docs/plugins/queue/smtp_bridge.md +32 -0
  99. package/docs/plugins/queue/smtp_forward.md +127 -0
  100. package/docs/plugins/queue/smtp_proxy.md +68 -0
  101. package/docs/plugins/queue/test.md +7 -0
  102. package/docs/plugins/rcpt_to.in_host_list.md +34 -0
  103. package/docs/plugins/rcpt_to.max_count.md +3 -0
  104. package/docs/plugins/record_envelope_addresses.md +20 -0
  105. package/docs/plugins/relay.md +3 -0
  106. package/docs/plugins/reseed_rng.md +16 -0
  107. package/docs/plugins/status.md +41 -0
  108. package/docs/plugins/tarpit.md +50 -0
  109. package/docs/plugins/tls.md +235 -0
  110. package/docs/plugins/toobusy.md +27 -0
  111. package/docs/plugins/xclient.md +10 -0
  112. package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
  113. package/docs/tutorials/SettingUpOutbound.md +62 -0
  114. package/eslint.config.mjs +2 -0
  115. package/haraka.js +74 -0
  116. package/haraka.sh +2 -0
  117. package/http/html/404.html +58 -0
  118. package/http/html/index.html +47 -0
  119. package/http/package.json +21 -0
  120. package/line_socket.js +24 -0
  121. package/logger.js +322 -0
  122. package/outbound/client_pool.js +59 -0
  123. package/outbound/config.js +134 -0
  124. package/outbound/hmail.js +1504 -0
  125. package/outbound/index.js +349 -0
  126. package/outbound/qfile.js +93 -0
  127. package/outbound/queue.js +399 -0
  128. package/outbound/tls.js +85 -0
  129. package/outbound/todo.js +17 -0
  130. package/package.json +100 -4
  131. package/plugins/.eslintrc.yaml +3 -0
  132. package/plugins/auth/auth_base.js +261 -0
  133. package/plugins/auth/auth_bridge.js +20 -0
  134. package/plugins/auth/auth_proxy.js +227 -0
  135. package/plugins/auth/auth_vpopmaild.js +162 -0
  136. package/plugins/auth/flat_file.js +44 -0
  137. package/plugins/block_me.js +88 -0
  138. package/plugins/data.signatures.js +30 -0
  139. package/plugins/delay_deny.js +153 -0
  140. package/plugins/prevent_credential_leaks.js +61 -0
  141. package/plugins/process_title.js +197 -0
  142. package/plugins/profile.js +11 -0
  143. package/plugins/queue/deliver.js +12 -0
  144. package/plugins/queue/discard.js +27 -0
  145. package/plugins/queue/lmtp.js +45 -0
  146. package/plugins/queue/qmail-queue.js +93 -0
  147. package/plugins/queue/quarantine.js +133 -0
  148. package/plugins/queue/smtp_bridge.js +45 -0
  149. package/plugins/queue/smtp_forward.js +371 -0
  150. package/plugins/queue/smtp_proxy.js +142 -0
  151. package/plugins/queue/test.js +15 -0
  152. package/plugins/rcpt_to.host_list_base.js +65 -0
  153. package/plugins/rcpt_to.in_host_list.js +56 -0
  154. package/plugins/record_envelope_addresses.js +17 -0
  155. package/plugins/reseed_rng.js +7 -0
  156. package/plugins/status.js +274 -0
  157. package/plugins/tarpit.js +45 -0
  158. package/plugins/tls.js +164 -0
  159. package/plugins/toobusy.js +47 -0
  160. package/plugins/xclient.js +124 -0
  161. package/plugins.js +605 -0
  162. package/run_tests +11 -0
  163. package/server.js +827 -0
  164. package/smtp_client.js +504 -0
  165. package/test/.eslintrc.yaml +11 -0
  166. package/test/config/auth_flat_file.ini +5 -0
  167. package/test/config/block_me.recipient +1 -0
  168. package/test/config/block_me.senders +1 -0
  169. package/test/config/dhparams.pem +8 -0
  170. package/test/config/host_list +2 -0
  171. package/test/config/outbound_tls_cert.pem +1 -0
  172. package/test/config/outbound_tls_key.pem +1 -0
  173. package/test/config/plugins +7 -0
  174. package/test/config/smtp.ini +11 -0
  175. package/test/config/smtp_forward.ini +30 -0
  176. package/test/config/tls/example.com/_.example.com.key +28 -0
  177. package/test/config/tls/example.com/example.com.crt +25 -0
  178. package/test/config/tls/haraka.local.pem +51 -0
  179. package/test/config/tls.ini +45 -0
  180. package/test/config/tls_cert.pem +21 -0
  181. package/test/config/tls_key.pem +28 -0
  182. package/test/connection.js +820 -0
  183. package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
  184. package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
  185. package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
  186. package/test/fixtures/line_socket.js +21 -0
  187. package/test/fixtures/todo_qfile.txt +0 -0
  188. package/test/fixtures/util_hmailitem.js +156 -0
  189. package/test/installation/config/test-plugin-flat +1 -0
  190. package/test/installation/config/test-plugin.ini +10 -0
  191. package/test/installation/config/tls.ini +1 -0
  192. package/test/installation/node_modules/load_first/index.js +5 -0
  193. package/test/installation/node_modules/load_first/package.json +11 -0
  194. package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
  195. package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
  196. package/test/installation/node_modules/test-plugin/package.json +5 -0
  197. package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
  198. package/test/installation/plugins/base_plugin.js +3 -0
  199. package/test/installation/plugins/folder_plugin/index.js +3 -0
  200. package/test/installation/plugins/folder_plugin/package.json +11 -0
  201. package/test/installation/plugins/inherits.js +7 -0
  202. package/test/installation/plugins/load_first.js +3 -0
  203. package/test/installation/plugins/plugin.js +1 -0
  204. package/test/installation/plugins/tls.js +3 -0
  205. package/test/logger.js +217 -0
  206. package/test/loud/config/dhparams.pem +0 -0
  207. package/test/loud/config/tls/goobered.pem +45 -0
  208. package/test/loud/config/tls.ini +43 -0
  209. package/test/mail_specimen/base64-root-part.txt +23 -0
  210. package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
  211. package/test/outbound/bounce_net_errors.js +133 -0
  212. package/test/outbound/bounce_rfc3464.js +226 -0
  213. package/test/outbound/hmail.js +210 -0
  214. package/test/outbound/index.js +385 -0
  215. package/test/outbound/qfile.js +124 -0
  216. package/test/outbound/queue.js +325 -0
  217. package/test/plugins/auth/auth_base.js +620 -0
  218. package/test/plugins/auth/auth_bridge.js +80 -0
  219. package/test/plugins/auth/auth_vpopmaild.js +81 -0
  220. package/test/plugins/auth/flat_file.js +123 -0
  221. package/test/plugins/block_me.js +141 -0
  222. package/test/plugins/data.signatures.js +111 -0
  223. package/test/plugins/delay_deny.js +262 -0
  224. package/test/plugins/prevent_credential_leaks.js +174 -0
  225. package/test/plugins/process_title.js +141 -0
  226. package/test/plugins/queue/deliver.js +98 -0
  227. package/test/plugins/queue/discard.js +78 -0
  228. package/test/plugins/queue/lmtp.js +137 -0
  229. package/test/plugins/queue/qmail-queue.js +98 -0
  230. package/test/plugins/queue/quarantine.js +80 -0
  231. package/test/plugins/queue/smtp_bridge.js +152 -0
  232. package/test/plugins/queue/smtp_forward.js +1023 -0
  233. package/test/plugins/queue/smtp_proxy.js +138 -0
  234. package/test/plugins/rcpt_to.host_list_base.js +102 -0
  235. package/test/plugins/rcpt_to.in_host_list.js +186 -0
  236. package/test/plugins/record_envelope_addresses.js +66 -0
  237. package/test/plugins/reseed_rng.js +34 -0
  238. package/test/plugins/status.js +207 -0
  239. package/test/plugins/tarpit.js +90 -0
  240. package/test/plugins/tls.js +86 -0
  241. package/test/plugins/toobusy.js +198 -0
  242. package/test/plugins/xclient.js +119 -0
  243. package/test/plugins.js +230 -0
  244. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  245. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  246. package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  247. package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  248. package/test/queue/zero-length +0 -0
  249. package/test/server.js +1012 -0
  250. package/test/smtp_client.js +1303 -0
  251. package/test/tls_socket.js +321 -0
  252. package/test/transaction.js +554 -0
  253. package/tls_socket.js +771 -0
  254. package/transaction.js +267 -0
@@ -0,0 +1,81 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert/strict')
4
+ const path = require('node:path')
5
+ const { describe, it, beforeEach } = require('node:test')
6
+
7
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
8
+
9
+ const _set_up = () => {
10
+ this.backup = {}
11
+
12
+ this.plugin = makePlugin('auth/auth_vpopmaild', { register: false })
13
+ this.plugin.inherits('auth/auth_base')
14
+
15
+ // reset the config/root_path
16
+ this.plugin.config.root_path = path.resolve(__dirname, '../../../config')
17
+ this.plugin.cfg = this.plugin.config.get('auth_vpopmaild.ini')
18
+
19
+ this.connection = makeConnection()
20
+ this.connection.capabilities = null
21
+ }
22
+
23
+ describe('hook_capabilities', () => {
24
+ beforeEach(_set_up)
25
+
26
+ it('no TLS', (t, done) => {
27
+ this.plugin.hook_capabilities((rc, msg) => {
28
+ assert.equal(undefined, rc)
29
+ assert.equal(undefined, msg)
30
+ assert.equal(null, this.connection.capabilities)
31
+ done()
32
+ }, this.connection)
33
+ })
34
+
35
+ it('with TLS', (t, done) => {
36
+ this.connection.tls.enabled = true
37
+ this.connection.capabilities = []
38
+ this.plugin.hook_capabilities((rc, msg) => {
39
+ assert.equal(undefined, rc)
40
+ assert.equal(undefined, msg)
41
+ assert.ok(this.connection.capabilities.length)
42
+ done()
43
+ }, this.connection)
44
+ })
45
+
46
+ it('with TLS, sysadmin', (t, done) => {
47
+ this.connection.tls.enabled = true
48
+ this.connection.capabilities = []
49
+ this.plugin.hook_capabilities((rc, msg) => {
50
+ assert.equal(undefined, rc)
51
+ assert.equal(undefined, msg)
52
+ assert.ok(this.connection.capabilities.length)
53
+ done()
54
+ }, this.connection)
55
+ })
56
+ })
57
+
58
+ describe('get_vpopmaild_socket', () => {
59
+ beforeEach(_set_up)
60
+
61
+ it('any', () => {
62
+ const socket = this.plugin.get_vpopmaild_socket('foo@localhost.com')
63
+ assert.ok(socket)
64
+ socket.destroy()
65
+ })
66
+ })
67
+
68
+ describe('get_plain_passwd', () => {
69
+ beforeEach(_set_up)
70
+
71
+ it('matt@example.com', (t, done) => {
72
+ if (this.plugin.cfg['example.com'].sysadmin) {
73
+ this.plugin.get_plain_passwd('matt@example.com', (pass) => {
74
+ assert.ok(pass)
75
+ done()
76
+ })
77
+ } else {
78
+ done()
79
+ }
80
+ })
81
+ })
@@ -0,0 +1,123 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const { describe, it, beforeEach } = require('node:test')
5
+
6
+ const { callHook, makeConnection, makePlugin } = require('haraka-test-fixtures')
7
+
8
+ describe('auth/flat_file', () => {
9
+ let plugin
10
+
11
+ beforeEach(() => {
12
+ plugin = makePlugin('auth/flat_file', { register: false })
13
+ plugin.inherits('auth/auth_base')
14
+ plugin.load_flat_ini()
15
+ })
16
+
17
+ describe('load_flat_ini', () => {
18
+ it('populates cfg.users as an object', () => {
19
+ assert.ok(typeof plugin.cfg.users === 'object')
20
+ })
21
+
22
+ it('cfg.users defaults to empty object when not configured', () => {
23
+ // default config has no real users
24
+ assert.deepEqual(plugin.cfg.users, {})
25
+ })
26
+ })
27
+
28
+ describe('hook_capabilities', () => {
29
+ let conn
30
+
31
+ beforeEach(() => {
32
+ conn = makeConnection()
33
+ conn.capabilities = []
34
+ conn.notes.allowed_auth_methods = []
35
+ conn.remote.is_private = false
36
+ conn.tls.enabled = false
37
+ })
38
+
39
+ it('skips for public non-TLS connection', (t, done) => {
40
+ callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
41
+ assert.equal(rc, undefined)
42
+ assert.equal(conn.capabilities.length, 0)
43
+ done()
44
+ })
45
+ })
46
+
47
+ it('adds AUTH methods for private connection (non-TLS)', (t, done) => {
48
+ conn.remote.is_private = true
49
+ plugin.cfg.core.methods = 'PLAIN,LOGIN'
50
+ callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
51
+ assert.equal(rc, undefined)
52
+ assert.ok(
53
+ conn.capabilities.some((c) => c.startsWith('AUTH ')),
54
+ 'AUTH capability should be present',
55
+ )
56
+ done()
57
+ })
58
+ })
59
+
60
+ it('adds AUTH methods when TLS is enabled', (t, done) => {
61
+ conn.tls.enabled = true
62
+ plugin.cfg.core.methods = 'PLAIN,LOGIN'
63
+ callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
64
+ assert.equal(rc, undefined)
65
+ assert.ok(conn.capabilities.some((c) => c.startsWith('AUTH ')))
66
+ done()
67
+ })
68
+ })
69
+
70
+ it('sets allowed_auth_methods on connection notes', (t, done) => {
71
+ conn.tls.enabled = true
72
+ plugin.cfg.core.methods = 'PLAIN,LOGIN'
73
+ callHook(plugin, 'hook_capabilities', conn).then(() => {
74
+ assert.deepEqual(conn.notes.allowed_auth_methods, ['PLAIN', 'LOGIN'])
75
+ done()
76
+ })
77
+ })
78
+
79
+ it('does not add AUTH when no methods configured', (t, done) => {
80
+ conn.tls.enabled = true
81
+ plugin.cfg.core.methods = null
82
+ callHook(plugin, 'hook_capabilities', conn).then(() => {
83
+ assert.equal(conn.capabilities.length, 0)
84
+ done()
85
+ })
86
+ })
87
+ })
88
+
89
+ describe('get_plain_passwd', () => {
90
+ beforeEach(() => {
91
+ plugin.cfg.users = { alice: 'secret', bob: 'hunter2' }
92
+ })
93
+
94
+ it('returns password for known user', (t, done) => {
95
+ plugin.get_plain_passwd('alice', {}, (pw) => {
96
+ assert.equal(pw, 'secret')
97
+ done()
98
+ })
99
+ })
100
+
101
+ it('calls cb with no args for unknown user', (t, done) => {
102
+ plugin.get_plain_passwd('unknown', {}, (pw) => {
103
+ assert.equal(pw, undefined)
104
+ done()
105
+ })
106
+ })
107
+
108
+ it('handles multiple users', (t, done) => {
109
+ plugin.get_plain_passwd('bob', {}, (pw) => {
110
+ assert.equal(pw, 'hunter2')
111
+ done()
112
+ })
113
+ })
114
+
115
+ it('coerces password to string via toString()', (t, done) => {
116
+ plugin.cfg.users.numericuser = 12345
117
+ plugin.get_plain_passwd('numericuser', {}, (pw) => {
118
+ assert.equal(pw, '12345')
119
+ done()
120
+ })
121
+ })
122
+ })
123
+ })
@@ -0,0 +1,141 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+ const assert = require('node:assert/strict')
6
+ const { describe, it, beforeEach, after } = require('node:test')
7
+
8
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
9
+ require('haraka-constants').import(global)
10
+
11
+ // block_me appends to <config>/mail_from.blocklist when a sender is blocked;
12
+ // remove the artifact the 'sets block_me note' test produces.
13
+ after(() => {
14
+ fs.rmSync(path.resolve('test/config/mail_from.blocklist'), { force: true })
15
+ })
16
+
17
+ describe('block_me', () => {
18
+ let plugin
19
+
20
+ // Read config (block_me.recipient, block_me.senders) from test/config rather
21
+ // than the real config dir. block_me also appends matched senders to
22
+ // mail_from.blocklist; with this override that write lands in test/config too.
23
+ beforeEach(() => {
24
+ plugin = makePlugin('block_me', { register: false, configDir: 'test' })
25
+ })
26
+
27
+ describe('hook_data', () => {
28
+ it('enables body parsing and calls next', (t, done) => {
29
+ const conn = makeConnection({ withTxn: true })
30
+ conn.transaction.parse_body = false
31
+ plugin.hook_data((rc) => {
32
+ assert.equal(rc, undefined)
33
+ assert.equal(conn.transaction.parse_body, true)
34
+ done()
35
+ }, conn)
36
+ })
37
+ })
38
+
39
+ describe('hook_data_post', () => {
40
+ it('calls next when not relaying', (t, done) => {
41
+ const conn = makeConnection({ withTxn: true })
42
+ plugin.hook_data_post((rc) => {
43
+ assert.equal(rc, undefined)
44
+ done()
45
+ }, conn)
46
+ })
47
+
48
+ it('calls next when transaction is missing', (t, done) => {
49
+ const conn = makeConnection()
50
+ conn.relaying = true
51
+ conn.transaction = null
52
+ plugin.hook_data_post((rc) => {
53
+ assert.equal(rc, undefined)
54
+ done()
55
+ }, conn)
56
+ })
57
+
58
+ it('calls next when more than one recipient', (t, done) => {
59
+ const conn = makeConnection({
60
+ relaying: true,
61
+ rcptTo: ['blocklist@example.com', 'other@example.com'],
62
+ })
63
+ plugin.hook_data_post((rc) => {
64
+ assert.equal(rc, undefined)
65
+ done()
66
+ }, conn)
67
+ })
68
+
69
+ it('calls next when recipient does not match configured address', (t, done) => {
70
+ const conn = makeConnection({ relaying: true, rcptTo: ['other@example.com'] })
71
+ plugin.hook_data_post((rc) => {
72
+ assert.equal(rc, undefined)
73
+ done()
74
+ }, conn)
75
+ })
76
+
77
+ it('denies when sender is not in the allowed senders list', (t, done) => {
78
+ const conn = makeConnection({
79
+ relaying: true,
80
+ mailFrom: 'notallowed@example.com',
81
+ rcptTo: ['blocklist@example.com'],
82
+ })
83
+ plugin.hook_data_post((rc, msg) => {
84
+ assert.equal(rc, DENY)
85
+ assert.ok(msg.includes('not allowed'))
86
+ done()
87
+ }, conn)
88
+ })
89
+
90
+ it('calls next when no From header found in body', (t, done) => {
91
+ const conn = makeConnection({
92
+ relaying: true,
93
+ mailFrom: 'sender@example.com',
94
+ rcptTo: ['blocklist@example.com'],
95
+ })
96
+ conn.transaction.body = { bodytext: 'No from header here', children: [] }
97
+ plugin.hook_data_post((rc) => {
98
+ assert.equal(rc, undefined)
99
+ // note should not be set since no From header
100
+ assert.equal(conn.transaction.notes.block_me, undefined)
101
+ done()
102
+ }, conn)
103
+ })
104
+
105
+ it('sets block_me note and calls next when From is extracted', (t, done) => {
106
+ const conn = makeConnection({
107
+ relaying: true,
108
+ mailFrom: 'sender@example.com',
109
+ rcptTo: ['blocklist@example.com'],
110
+ })
111
+ conn.transaction.body = {
112
+ bodytext: 'From: Test User <block_target@example.com>',
113
+ children: [],
114
+ }
115
+ plugin.hook_data_post((rc) => {
116
+ assert.equal(rc, undefined)
117
+ assert.equal(conn.transaction.notes.block_me, 1)
118
+ done()
119
+ }, conn)
120
+ })
121
+ })
122
+
123
+ describe('hook_queue', () => {
124
+ it('returns OK when block_me note is set on transaction', (t, done) => {
125
+ const conn = makeConnection({ withTxn: true })
126
+ conn.transaction.notes.block_me = 1
127
+ plugin.hook_queue((rc) => {
128
+ assert.equal(rc, OK)
129
+ done()
130
+ }, conn)
131
+ })
132
+
133
+ it('calls next when block_me note is not set', (t, done) => {
134
+ const conn = makeConnection({ withTxn: true })
135
+ plugin.hook_queue((rc) => {
136
+ assert.equal(rc, undefined)
137
+ done()
138
+ }, conn)
139
+ })
140
+ })
141
+ })
@@ -0,0 +1,111 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert/strict')
4
+ const { describe, it, beforeEach } = require('node:test')
5
+
6
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
7
+ require('haraka-constants').import(global)
8
+
9
+ describe('data.signatures', () => {
10
+ let plugin
11
+
12
+ beforeEach(() => {
13
+ plugin = makePlugin('data.signatures', { register: false })
14
+ })
15
+
16
+ describe('hook_data', () => {
17
+ it('enables body parsing', (t, done) => {
18
+ const conn = makeConnection({ withTxn: true })
19
+ conn.transaction.parse_body = false
20
+ plugin.hook_data((rc) => {
21
+ assert.equal(rc, undefined)
22
+ assert.equal(conn.transaction.parse_body, true)
23
+ done()
24
+ }, conn)
25
+ })
26
+
27
+ it('calls next when there is no transaction', (t, done) => {
28
+ const conn = makeConnection()
29
+ conn.transaction = null
30
+ plugin.hook_data((rc) => {
31
+ assert.equal(rc, undefined)
32
+ done()
33
+ }, conn)
34
+ })
35
+ })
36
+
37
+ describe('hook_data_post', () => {
38
+ it('calls next when there is no transaction', (t, done) => {
39
+ const conn = makeConnection()
40
+ conn.transaction = null
41
+ plugin.hook_data_post((rc) => {
42
+ assert.equal(rc, undefined)
43
+ done()
44
+ }, conn)
45
+ })
46
+
47
+ it('calls next when signature list is empty', (t, done) => {
48
+ plugin.config.get = (name, type) => (type === 'list' ? [] : {})
49
+ const conn = makeConnection({ withTxn: true })
50
+ conn.transaction.body = { bodytext: 'This is some email body text', children: [] }
51
+ plugin.hook_data_post((rc) => {
52
+ assert.equal(rc, undefined)
53
+ done()
54
+ }, conn)
55
+ })
56
+
57
+ it('denies when body matches a signature', (t, done) => {
58
+ plugin.config.get = (name, type) => (type === 'list' ? ['spam_signature_text'] : {})
59
+ const conn = makeConnection({ withTxn: true })
60
+ conn.transaction.body = { bodytext: 'Buy cheap meds! spam_signature_text here', children: [] }
61
+ plugin.hook_data_post((rc, msg) => {
62
+ assert.equal(rc, DENY)
63
+ assert.ok(msg.includes('spam'))
64
+ done()
65
+ }, conn)
66
+ })
67
+
68
+ it('calls next when body does not match any signature', (t, done) => {
69
+ plugin.config.get = (name, type) => (type === 'list' ? ['bad_pattern'] : {})
70
+ const conn = makeConnection({ withTxn: true })
71
+ conn.transaction.body = { bodytext: 'Totally normal email body', children: [] }
72
+ plugin.hook_data_post((rc) => {
73
+ assert.equal(rc, undefined)
74
+ done()
75
+ }, conn)
76
+ })
77
+
78
+ it('denies when a child body part matches a signature', (t, done) => {
79
+ plugin.config.get = (name, type) => (type === 'list' ? ['spam_in_child'] : {})
80
+ const conn = makeConnection({ withTxn: true })
81
+ conn.transaction.body = {
82
+ bodytext: 'clean parent text',
83
+ children: [{ bodytext: 'spam_in_child content here', children: [] }],
84
+ }
85
+ plugin.hook_data_post((rc) => {
86
+ assert.equal(rc, DENY)
87
+ done()
88
+ }, conn)
89
+ })
90
+
91
+ it('calls next when multiple signatures do not match', (t, done) => {
92
+ plugin.config.get = (name, type) => (type === 'list' ? ['sig_one', 'sig_two', 'sig_three'] : {})
93
+ const conn = makeConnection({ withTxn: true })
94
+ conn.transaction.body = { bodytext: 'No matching signatures here at all', children: [] }
95
+ plugin.hook_data_post((rc) => {
96
+ assert.equal(rc, undefined)
97
+ done()
98
+ }, conn)
99
+ })
100
+
101
+ it('matches the first of multiple signatures', (t, done) => {
102
+ plugin.config.get = (name, type) => (type === 'list' ? ['no_match', 'buy_cheap_pills'] : {})
103
+ const conn = makeConnection({ withTxn: true })
104
+ conn.transaction.body = { bodytext: 'This message has buy_cheap_pills for you', children: [] }
105
+ plugin.hook_data_post((rc) => {
106
+ assert.equal(rc, DENY)
107
+ done()
108
+ }, conn)
109
+ })
110
+ })
111
+ })