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/smtp_client.js ADDED
@@ -0,0 +1,504 @@
1
+ 'use strict'
2
+ // SMTP client object and class. This allows every part of the client
3
+ // protocol to be hooked for different levels of control, such as
4
+ // smtp_forward and smtp_proxy queue plugins.
5
+ // It can use HostPool to get a connection to a pool of
6
+ // possible hosts in the configuration value "forwarding_host_pool", rather
7
+ // than a bunch of connections to a single host from the configuration values
8
+ // in "host" and "port" (see host_pool.js).
9
+
10
+ const events = require('node:events')
11
+
12
+ const ipaddr = require('ipaddr.js')
13
+ const net_utils = require('haraka-net-utils')
14
+ const utils = require('haraka-utils')
15
+
16
+ const tls_socket = require('./tls_socket')
17
+ const logger = require('./logger')
18
+ const HostPool = net_utils.HostPool
19
+
20
+ const smtp_regexp = /^(\d{3})([ -])(.*)/
21
+ const STATE = {
22
+ IDLE: 1,
23
+ ACTIVE: 2,
24
+ RELEASED: 3,
25
+ DESTROYED: 4,
26
+ }
27
+
28
+ class SMTPClient extends events.EventEmitter {
29
+ constructor(opts = {}) {
30
+ super()
31
+ this.uuid = utils.uuid()
32
+ this.connect_timeout = parseInt(opts.connect_timeout) || 30
33
+ this.socket = opts.socket || this.get_socket(opts)
34
+ this.socket.setTimeout(this.connect_timeout * 1000)
35
+ this.socket.setKeepAlive(true)
36
+ this.state = STATE.IDLE
37
+ this.command = 'greeting'
38
+ this.response = []
39
+ this.connected = false
40
+ this.authenticating = false
41
+ this.authenticated = false
42
+ this.auth_capabilities = []
43
+ this.host = opts.host
44
+ this.port = opts.port
45
+ this.smtputf8 = false
46
+
47
+ const client = this
48
+
49
+ client.socket.on('line', (line) => {
50
+ client.emit('server_protocol', line)
51
+ const matches = smtp_regexp.exec(line)
52
+ if (!matches) {
53
+ client.emit('error', `${client.uuid}: Unrecognized response from upstream server: ${line}`)
54
+ client.destroy()
55
+ return
56
+ }
57
+
58
+ const code = matches[1]
59
+ const cont = matches[2]
60
+ const msg = matches[3]
61
+
62
+ client.response.push(msg)
63
+ if (cont !== ' ') return
64
+
65
+ if (client.command === 'auth' || client.authenticating) {
66
+ logger.info(
67
+ `SERVER RESPONSE, CLIENT ${client.command}, authenticating=${client.authenticating},code=${code},cont=${cont},msg=${msg}`,
68
+ )
69
+ if (
70
+ /^3/.test(code) &&
71
+ (msg === 'VXNlcm5hbWU6' || msg === 'dXNlcm5hbWU6') // Workaround ill-mannered SMTP servers (namely smtp.163.com)
72
+ ) {
73
+ client.emit('auth_username')
74
+ return
75
+ }
76
+ if (/^3/.test(code) && msg === 'UGFzc3dvcmQ6') {
77
+ client.emit('auth_password')
78
+ return
79
+ }
80
+ if (/^2/.test(code) && client.authenticating) {
81
+ logger.info('AUTHENTICATED')
82
+ client.authenticating = false
83
+ client.authenticated = true
84
+ client.emit('auth')
85
+ return
86
+ }
87
+ }
88
+
89
+ if (client.command === 'ehlo') {
90
+ if (code.match(/^5/)) {
91
+ // Handle fallback to HELO if EHLO is rejected
92
+ client.emit('greeting', 'HELO')
93
+ return
94
+ }
95
+ client.emit('capabilities')
96
+ if (client.command !== 'ehlo') {
97
+ return
98
+ }
99
+ }
100
+
101
+ if (client.command === 'xclient' && /^5/.test(code)) {
102
+ // XCLIENT command was rejected (no permission?)
103
+ // Carry on without XCLIENT
104
+ client.command = 'helo'
105
+ } else if (/^[45]/.test(code)) {
106
+ client.emit('bad_code', code, client.response.join(' '))
107
+ if (client.state !== STATE.ACTIVE) {
108
+ return
109
+ }
110
+ }
111
+
112
+ if (/^441/.test(code)) {
113
+ if (/Connection timed out/i.test(msg)) {
114
+ client.destroy()
115
+ }
116
+ }
117
+
118
+ switch (client.command) {
119
+ case 'xclient':
120
+ client.xclient = true
121
+ client.emit('xclient', 'EHLO')
122
+ break
123
+ case 'starttls':
124
+ client.upgrade(client.tls_options)
125
+ break
126
+ case 'greeting':
127
+ client.connected = true
128
+ client.emit('greeting', 'EHLO')
129
+ break
130
+ case 'ehlo':
131
+ client.emit('helo')
132
+ break
133
+ case 'helo':
134
+ case 'mail':
135
+ case 'rcpt':
136
+ case 'data':
137
+ case 'dot':
138
+ case 'rset':
139
+ case 'auth':
140
+ client.emit(client.command)
141
+ break
142
+ case 'quit':
143
+ client.emit('quit')
144
+ client.destroy()
145
+ break
146
+ default:
147
+ throw new Error(`Unknown command: ${client.command}`)
148
+ }
149
+ })
150
+
151
+ client.socket.on('connect', () => {
152
+ // Replace connection timeout with idle timeout
153
+ client.socket.setTimeout((opts.idle_timeout || 300) * 1000)
154
+ if (!client.socket.remoteAddress) {
155
+ // "Value may be undefined if the socket is destroyed"
156
+ logger.debug('socket.remoteAddress undefined')
157
+ return
158
+ }
159
+ client.remote_ip = ipaddr.process(client.socket.remoteAddress).toString()
160
+ })
161
+
162
+ function closed(msg) {
163
+ return (error) => {
164
+ if (!error) error = ''
165
+
166
+ // error is e.g. "Error: connect ECONNREFUSED"
167
+ const errMsg = `${client.uuid}: [${client.host}:${client.port}] SMTP connection ${msg} ${error}`
168
+
169
+ /* eslint-disable no-fallthrough */
170
+ switch (client.state) {
171
+ case STATE.ACTIVE:
172
+ client.emit('error', errMsg)
173
+ case STATE.IDLE:
174
+ case STATE.RELEASED:
175
+ client.destroy()
176
+ break
177
+ case STATE.DESTROYED:
178
+ if (msg === 'errored' || msg === 'timed out') {
179
+ client.emit('connection-error', errMsg)
180
+ }
181
+ break
182
+ default:
183
+ }
184
+
185
+ logger.debug(`[smtp_client] ${errMsg} (state=${client.state})`)
186
+ }
187
+ }
188
+
189
+ client.socket.on('error', closed('errored'))
190
+ client.socket.on('timeout', closed('timed out'))
191
+ client.socket.on('close', closed('closed'))
192
+ client.socket.on('end', closed('ended'))
193
+ }
194
+
195
+ load_tls_options(opts = {}) {
196
+ this.tls_options = { servername: this.host, ...opts }
197
+ }
198
+
199
+ send_command(command, data) {
200
+ const line = command === 'dot' ? '.' : command + (data ? ` ${data}` : '')
201
+ this.emit('client_protocol', line)
202
+ this.command = command.toLowerCase()
203
+ this.response = []
204
+ this.socket.write(`${line}\r\n`)
205
+ }
206
+
207
+ start_data(data) {
208
+ this.response = []
209
+ this.command = 'dot'
210
+ data.pipe(this.socket, {
211
+ dot_stuffed: false,
212
+ ending_dot: true,
213
+ end: false,
214
+ })
215
+ }
216
+
217
+ release() {
218
+ if (this.state === STATE.DESTROYED) return
219
+ const listeners = [
220
+ 'auth',
221
+ 'bad_code',
222
+ 'capabilities',
223
+ 'client_protocol',
224
+ 'connection-error',
225
+ 'data',
226
+ 'dot',
227
+ 'error',
228
+ 'greeting',
229
+ 'helo',
230
+ 'mail',
231
+ 'rcpt',
232
+ 'rset',
233
+ 'server_protocol',
234
+ 'xclient',
235
+ ]
236
+ logger.debug(`[smtp_client] ${this.uuid} releasing, state=${this.state}`)
237
+ for (const l of listeners) {
238
+ this.removeAllListeners(l)
239
+ }
240
+
241
+ if (this.connected) this.send_command('QUIT')
242
+ this.destroy()
243
+ }
244
+
245
+ destroy() {
246
+ if (this.state === STATE.DESTROYED) return
247
+ this.state = STATE.DESTROYED
248
+ this.socket.destroy()
249
+ }
250
+
251
+ upgrade(tls_options) {
252
+ this.socket.upgrade(tls_options, (verified, verifyError, cert, cipher) => {
253
+ logger.info(
254
+ `secured:${
255
+ cipher ? ` cipher=${cipher.name} version=${cipher.version}` : ''
256
+ } verified=${verified}${verifyError ? ` error="${verifyError}"` : ''}${
257
+ cert?.subject ? ` cn="${cert.subject.CN}" organization="${cert.subject.O}"` : ''
258
+ }${cert?.issuer ? ` issuer="${cert.issuer.O}"` : ''}${
259
+ cert?.valid_to ? ` expires="${cert.valid_to}"` : ''
260
+ }${cert?.fingerprint ? ` fingerprint=${cert.fingerprint}` : ''}`,
261
+ )
262
+ })
263
+ }
264
+
265
+ is_dead_sender(plugin, connection) {
266
+ if (connection?.transaction) return false
267
+
268
+ // This likely means the sender went away on us, cleanup.
269
+ connection.logwarn(plugin, 'transaction went away, releasing smtp_client')
270
+ this.release()
271
+ return true
272
+ }
273
+
274
+ get_socket(opts) {
275
+ const socket = tls_socket.connect({
276
+ host: opts.host,
277
+ port: opts.port,
278
+ timeout: this.connect_timeout,
279
+ })
280
+ net_utils.add_line_processor(socket)
281
+ return socket
282
+ }
283
+ }
284
+
285
+ exports.smtp_client = SMTPClient
286
+
287
+ // Get a smtp_client for the given attributes.
288
+ // used only in testing
289
+ exports.get_client = (server, callback, opts = {}) => {
290
+ const smtp_client = new SMTPClient(opts)
291
+ logger.debug(`[smtp_client] uuid=${smtp_client.uuid} host=${opts.host} port=${opts.port} created`)
292
+ callback(smtp_client)
293
+ }
294
+
295
+ exports.onCapabilitiesOutbound = (smtp_client, secured, connection, config, on_secured) => {
296
+ for (const line in smtp_client.response) {
297
+ if (/^XCLIENT/.test(smtp_client.response[line])) {
298
+ if (!smtp_client.xclient) {
299
+ smtp_client.send_command('XCLIENT', `ADDR=${connection.remote.ip}`)
300
+ return
301
+ }
302
+ }
303
+
304
+ if (/^SMTPUTF8/.test(smtp_client.response[line])) {
305
+ smtp_client.smtputf8 = true
306
+ }
307
+
308
+ if (/^STARTTLS/.test(smtp_client.response[line]) && !secured) {
309
+ let hostBanned = false
310
+ let serverBanned = false
311
+
312
+ // Check if there are any banned TLS hosts
313
+ if (smtp_client.tls_options.no_tls_hosts) {
314
+ // If there are check if these hosts are in the blacklist
315
+ hostBanned = net_utils.ip_in_list(smtp_client.tls_options.no_tls_hosts, config.host)
316
+ serverBanned = net_utils.ip_in_list(smtp_client.tls_options.no_tls_hosts, smtp_client.remote_ip)
317
+ }
318
+
319
+ if (!hostBanned && !serverBanned && config.enable_tls) {
320
+ smtp_client.socket.on('secure', on_secured)
321
+ smtp_client.secured = false // have to wait in forward plugin before we can do auth, even if capabilities are there on first EHLO
322
+ smtp_client.send_command('STARTTLS')
323
+ return
324
+ }
325
+ }
326
+
327
+ let auth_matches = smtp_client.response[line].match(/^AUTH (.*)$/)
328
+ if (auth_matches) {
329
+ smtp_client.auth_capabilities = []
330
+ auth_matches = auth_matches[1].split(' ')
331
+ for (const authMatch of auth_matches) {
332
+ smtp_client.auth_capabilities.push(authMatch.toLowerCase())
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ // Get a smtp_client for the given attributes and set up the common
339
+ // config and listeners for plugins. This is what smtp_proxy and
340
+ // smtp_forward have in common.
341
+ exports.get_client_plugin = (plugin, connection, c, callback) => {
342
+ // c = config
343
+ // Merge in authentication settings from smtp_forward/proxy.ini if present
344
+ // FIXME: config.auth could be changed when API isn't frozen
345
+ if (c.auth_type || c.auth_user || c.auth_pass) {
346
+ c.auth = {
347
+ type: c.auth_type,
348
+ user: c.auth_user,
349
+ pass: c.auth_pass,
350
+ }
351
+ }
352
+
353
+ const hostport = get_hostport(connection, c)
354
+ const smtp_client = new SMTPClient(hostport)
355
+ logger.info(`[smtp_client] uuid=${smtp_client.uuid} host=${hostport.host} port=${hostport.port} created`)
356
+
357
+ connection.logdebug(plugin, `Got smtp_client: ${smtp_client.uuid}`)
358
+
359
+ let secured = false
360
+
361
+ smtp_client.load_tls_options(plugin.tls_options)
362
+
363
+ smtp_client.call_next = function (retval, msg) {
364
+ if (this.next) {
365
+ const { next } = this
366
+ delete this.next
367
+ next(retval, msg)
368
+ }
369
+ }
370
+
371
+ smtp_client.on('client_protocol', (line) => {
372
+ // Don't leak SASL credentials (e.g. AUTH PLAIN <base64>) into
373
+ // protocol logs.
374
+ const safe = String(line).replace(/^(AUTH\s+\S+\s+).+$/i, '$1[redacted]')
375
+ connection.logprotocol(plugin, `C: ${safe}`)
376
+ })
377
+
378
+ smtp_client.on('server_protocol', (line) => {
379
+ connection.logprotocol(plugin, `S: ${line}`)
380
+ })
381
+
382
+ function helo(command) {
383
+ if (smtp_client.xclient) {
384
+ smtp_client.send_command(command, connection.hello.host)
385
+ } else {
386
+ smtp_client.send_command(command, connection.local.host)
387
+ }
388
+ }
389
+ smtp_client.on('greeting', helo)
390
+ smtp_client.on('xclient', helo)
391
+
392
+ function on_secured() {
393
+ if (secured) return
394
+ secured = true
395
+ smtp_client.secured = true
396
+ smtp_client.emit('greeting', 'EHLO')
397
+ }
398
+
399
+ smtp_client.on('capabilities', () => {
400
+ exports.onCapabilitiesOutbound(smtp_client, secured, connection, c, on_secured)
401
+ })
402
+
403
+ smtp_client.on('helo', () => {
404
+ if (!c.auth || smtp_client.authenticated) {
405
+ if (smtp_client.is_dead_sender(plugin, connection)) return
406
+
407
+ smtp_client.send_command('MAIL', `FROM:${connection.transaction.mail_from.format(!smtp_client.smtputf8)}`)
408
+ return
409
+ }
410
+
411
+ if (c.auth.type === null || typeof c.auth.type === 'undefined') return // Ignore blank
412
+ const auth_type = c.auth.type.toLowerCase()
413
+ // This listener runs from the socket line handler; an uncaught
414
+ // throw here crashes the forwarding worker. Route failures
415
+ // through the existing smtp_client 'error' flow (logwarn +
416
+ // call_next) so a misconfigured/hostile upstream degrades to a
417
+ // normal SMTP error path instead.
418
+ if (!smtp_client.auth_capabilities.includes(auth_type)) {
419
+ return smtp_client.emit(
420
+ 'error',
421
+ `Auth type "${auth_type}" not supported by server (supports: ${smtp_client.auth_capabilities.join(',')})`,
422
+ )
423
+ }
424
+ switch (auth_type) {
425
+ case 'plain':
426
+ if (!c.auth.user || !c.auth.pass) {
427
+ return smtp_client.emit('error', 'Must include auth.user and auth.pass for PLAIN auth.')
428
+ }
429
+ logger.debug(`[smtp_client] uuid=${smtp_client.uuid} authenticating as "${c.auth.user}"`)
430
+ smtp_client.send_command(
431
+ 'AUTH',
432
+ `PLAIN ${utils.base64(`${c.auth.user}\0${c.auth.user}\0${c.auth.pass}`)}`,
433
+ )
434
+ break
435
+ case 'cram-md5':
436
+ return smtp_client.emit('error', `AUTH ${auth_type} not implemented`)
437
+ default:
438
+ return smtp_client.emit('error', `Unknown AUTH type: ${auth_type}`)
439
+ }
440
+ })
441
+
442
+ smtp_client.on('auth', () => {
443
+ // if authentication has been handled by plugin(s)
444
+ if (smtp_client.authenticating) return
445
+
446
+ if (smtp_client.is_dead_sender(plugin, connection)) return
447
+
448
+ smtp_client.authenticated = true
449
+ smtp_client.send_command('MAIL', `FROM:${connection.transaction.mail_from.format(!smtp_client.smtputf8)}`)
450
+ })
451
+
452
+ // these errors only get thrown when the connection is still active
453
+ smtp_client.on('error', (msg) => {
454
+ connection.logwarn(plugin, msg)
455
+ smtp_client.call_next()
456
+ })
457
+
458
+ // these are the errors thrown when the connection is dead
459
+ smtp_client.on('connection-error', (error) => {
460
+ // error contains e.g. "Error: connect ECONNREFUSE"
461
+ logger.error(`backend failure: ${smtp_client.host}:${smtp_client.port} - ${error}`)
462
+ const { host_pool } = connection.server.notes
463
+ // only exists for if forwarding_host_pool is set in the config
464
+ if (host_pool) {
465
+ host_pool.failed(smtp_client.host, smtp_client.port)
466
+ }
467
+ smtp_client.call_next()
468
+ })
469
+
470
+ if (smtp_client.connected) {
471
+ if (smtp_client.xclient) {
472
+ smtp_client.send_command('XCLIENT', `ADDR=${connection.remote.ip}`)
473
+ } else {
474
+ smtp_client.emit('helo')
475
+ }
476
+ }
477
+
478
+ callback(null, smtp_client)
479
+ }
480
+
481
+ function get_hostport(connection, cfg) {
482
+ const server = connection.server
483
+ if (cfg.forwarding_host_pool) {
484
+ if (!server.notes.host_pool) {
485
+ connection.logwarn(`creating host_pool from ${cfg.forwarding_host_pool}`)
486
+ server.notes.host_pool = new HostPool(
487
+ cfg.forwarding_host_pool, // 1.2.3.4:420, 5.6.7.8:420
488
+ cfg.dead_forwarding_host_retry_secs,
489
+ { logger },
490
+ )
491
+ }
492
+
493
+ const host = server.notes.host_pool.get_host()
494
+ if (host) return host // { host: 1.2.3.4, port: 567 }
495
+
496
+ logger.error('[smtp_client] no backend hosts in pool!')
497
+ throw new Error('no backend hosts found in pool!')
498
+ }
499
+
500
+ if (cfg.host && cfg.port) return { host: cfg.host, port: cfg.port }
501
+
502
+ logger.warn('[smtp_client] forwarding_host_pool or host and port were not found in config file')
503
+ throw new Error('You must specify either forwarding_host_pool or host and port')
504
+ }
@@ -0,0 +1,11 @@
1
+ globals:
2
+ test: true
3
+ OK: true
4
+ CONT: true
5
+ DENY: true
6
+ DENYSOFT: true
7
+ DENYDISCONNECT: true
8
+ DENYSOFTDISCONNECT: true
9
+ HMailItem: true
10
+ TODOItem: true
11
+ test_queue_dir: true
@@ -0,0 +1,5 @@
1
+ [core]
2
+ methods=CRAM-MD5
3
+
4
+ [users]
5
+ matt=goodPass
@@ -0,0 +1 @@
1
+ blocklist@example.com
@@ -0,0 +1 @@
1
+ sender@example.com
@@ -0,0 +1,8 @@
1
+ -----BEGIN DH PARAMETERS-----
2
+ MIIBCAKCAQEA5u0Bg9gCrKvYbkCmUe7cZUjZYFbqvbo9UPaR27K5yPklpy2Fy7bT
3
+ +Jbzb/C0zHRD3mx3hoapa/3jB1Zhw31cxNmmwGvblpWNjToBoSydVDAY2BphJxHn
4
+ CMEz3VGqiA4FXKx0R+jLeq5p5rTEuMbXTyxnj/hJesquORc2sy8L410Kw+6UvbPE
5
+ tw9WKwzrpdMxwVSme2voHlpLZuvGqE/paxxnp0kmFp/esda2Aj8xVMEtAMh8lw8v
6
+ fPaeTO94I4/SKJtzLl8j9J6mrq+aTYjljMryt2GJwZNrH41CPuOZYb5MyGAGPCWW
7
+ l/RlqtnAit2IQ4VA1MrITAzoepC144w5CwIBAg==
8
+ -----END DH PARAMETERS-----
@@ -0,0 +1,2 @@
1
+ # tests hosts to accept mail for
2
+ haraka.local
@@ -0,0 +1 @@
1
+ OutboundTlsCertLoaded
@@ -0,0 +1 @@
1
+ OutboundTlsKeyLoaded
@@ -0,0 +1,7 @@
1
+ tls
2
+
3
+ auth/flat_file
4
+
5
+ rcpt_to.in_host_list
6
+
7
+ queue/discard
@@ -0,0 +1,11 @@
1
+ listen=0.0.0.0:2500
2
+
3
+ ignore_bad_plugins=0
4
+
5
+ show_version=true
6
+
7
+ nodes=0
8
+
9
+ daemonize=false
10
+
11
+ spool_dir=test/tmp/queue
@@ -0,0 +1,30 @@
1
+ ; host to connect to
2
+ host=localhost
3
+ ;
4
+ ; port to connect to
5
+ port=2555
6
+ ;
7
+ ; uncomment to enable TLS to the backend SMTP server
8
+ enable_tls=true
9
+ ;
10
+ ; for messages that have multiple RCPT, send a separate message for each RCPT
11
+ ; when forwarding.
12
+ one_message_per_rcpt=true
13
+ ;
14
+ ; uncomment to use smtp client authorization
15
+ ;auth_type=plain
16
+ ;auth_user=
17
+ ;auth_pass=
18
+
19
+ [test.com]
20
+ host=1.2.3.4
21
+ enable_tls=true
22
+ auth_user=postmaster@test.com
23
+ auth_pass=superDuperSecret
24
+
25
+ [test1.com]
26
+ host=1.2.3.4
27
+ enable_tls=false
28
+
29
+ [test2.com]
30
+ host=2.3.4.5
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCYpxuOx4M2s6sI
3
+ iOEInjet7AcJddg/3uVoqoKRW6iTwAUKnP4CsU1Xe/rMU3wr/UcCs8zzFm7L6/cq
4
+ nbj2Pg1dnEvd5arYFWN59XhWjI6vua2ubZkTwBEEGSgSvwnBGU/9qdrXKTeuq+8C
5
+ BgV+SJzeEAmHEyhCDDT0M9Z1p5TFx0ynkaChy2yjffC2dbiRtK7RAja0I97MmSZL
6
+ ym+bVpY0ucTBNNwSNNh8UhWvWZSvVP92WGd2L72txCyfT333i0u1+SoexnJI7Wvn
7
+ 7YUaMNA3HSJ5/JjTW0/rOIZGyrfnjFEs5UmULCs6RZSE0eeEuJVRL+Kd+ncYCMlC
8
+ pmsqeIlHAgMBAAECggEAO0n8LBJVZjOWJDR1opFA8u4PNZ9tpDEATQyctbQx32Df
9
+ FGYxSf5vGaFvoVhzi+pNYEFRQsDdu5okX4ruwcUMD+Wamc6P8mksP7wVRxhEev/U
10
+ 80BiCge5FCxpIg7MzRD1voHwG01I8TCaHeEU1R2Cv8TeznWkVzLChm5zxzKVV9Mc
11
+ 08eqSxOg6M7wF3RpFpn1sBKch2ZNypu0IU4sP+NlRwxsRs/GBZqnyE/dmqvjHeCg
12
+ izcaji/ev8UdiYqASNZ2zzti9SME8VQebAWb71T6MMic03S9cfA+p3FZbzoYrnbG
13
+ pWf1AEsXiyclf6jaxd7ZV1ho6JbcaGdIPXzHZ5VIsQKBgQDLqg9M2oUBrBjJ31LN
14
+ 6OtjvMj9SO5BA5eMS/xC2j5cbb9bn8PV3Lge4IDlRMafZW/FwpAt3S68f81lHi9Y
15
+ KwK7zid8J16XFIEh+E95d0O5WOtdm1EU3T19ky8MbfCEIwcmurUusRZoyCYV/9cX
16
+ 1n9pDA3hXZtN1OcoHgOaubc+WwKBgQC/4Uh/KIz11YDcWgSO3Z37vwjh8JTtkjLg
17
+ DHRY0zOR6lzrUh5dT9nNT7TjeNxRk8ypPW5BET7T7NBTsoblN53d/nI2lI/sikg0
18
+ nOzqauXAlAX7s6tI/5LSsGF9OHZaiw7R2+egquIv4zN2Keq3Eq4WbGtcV0jiWFsC
19
+ oaOCXyCshQKBgDh2YCGFX2R0SrcEs9ckIMYY23vk0TCzBzu9ASWjjbBgOLH1G/zZ
20
+ YS4mPXXSWGJuY8tmwkQE0uUtZUsIUEXYPrzETYwM+htWcupxBc998gebkDz2R0dK
21
+ grairGN8wzZO47eoAXz9WWIZQv3MXNxd+hqsXdjB88FjKeakU4l8vUGLAoGAG7/z
22
+ AiDVMgBsoHGMUzUN0giwuixW/Xy1St3CPc5dmO6x/X5k0c3oi97JJFSoWEvtv1QZ
23
+ C+P4mCGZh2E8TQ4cEKzpy6b0oZrmEmXXhZdsHsvJibtUPDxp+Xp0vu1ZgIK34/XP
24
+ q9bK224aVS5+uXdEIg4QAMzGx6VLlDfYM9SaHxkCgYBOXl6pRe6UNhRKCfcbKH5e
25
+ JxwHbIyv2d9cL7yqy2lsYlXPhp7S8ClXChoaP5c6v1wAmY2RwPxP/7a541dzGxG5
26
+ RtUY78HHzKQGyobH55YNTyJq+qEUrP/DH5nhLJNvyV1SO7cpMnIO1p3dvnYawVzW
27
+ q+4Yrrdi6tWFPXnjWTGSjw==
28
+ -----END PRIVATE KEY-----
@@ -0,0 +1,25 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEMTCCAxmgAwIBAgIUel2F7YVWKevz0bTFV4mbjr6ZWbIwDQYJKoZIhvcNAQEL
3
+ BQAwgaYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQH
4
+ DAdTZWF0dGxlMR0wGwYDVQQKDBRFeGFtcGxlIFdpZGdldHMgSW5jLjETMBEGA1UE
5
+ CwwKRW1haWwgVGVhbTEWMBQGA1UEAwwNKi5leGFtcGxlLmNvbTEkMCIGCSqGSIb3
6
+ DQEJARYVaGFyYWthLm1haWxAZ21haWwuY29tMCAXDTI0MDUwMjE4MzMwMVoYDzIw
7
+ NTEwOTE3MTgzMzAxWjCBpjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0
8
+ b24xEDAOBgNVBAcMB1NlYXR0bGUxHTAbBgNVBAoMFEV4YW1wbGUgV2lkZ2V0cyBJ
9
+ bmMuMRMwEQYDVQQLDApFbWFpbCBUZWFtMRYwFAYDVQQDDA0qLmV4YW1wbGUuY29t
10
+ MSQwIgYJKoZIhvcNAQkBFhVoYXJha2EubWFpbEBnbWFpbC5jb20wggEiMA0GCSqG
11
+ SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYpxuOx4M2s6sIiOEInjet7AcJddg/3uVo
12
+ qoKRW6iTwAUKnP4CsU1Xe/rMU3wr/UcCs8zzFm7L6/cqnbj2Pg1dnEvd5arYFWN5
13
+ 9XhWjI6vua2ubZkTwBEEGSgSvwnBGU/9qdrXKTeuq+8CBgV+SJzeEAmHEyhCDDT0
14
+ M9Z1p5TFx0ynkaChy2yjffC2dbiRtK7RAja0I97MmSZLym+bVpY0ucTBNNwSNNh8
15
+ UhWvWZSvVP92WGd2L72txCyfT333i0u1+SoexnJI7Wvn7YUaMNA3HSJ5/JjTW0/r
16
+ OIZGyrfnjFEs5UmULCs6RZSE0eeEuJVRL+Kd+ncYCMlCpmsqeIlHAgMBAAGjUzBR
17
+ MB0GA1UdDgQWBBSuPzKGYEvrv9ZuBlSbpgUYG2/gSjAfBgNVHSMEGDAWgBSuPzKG
18
+ YEvrv9ZuBlSbpgUYG2/gSjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
19
+ A4IBAQCCqek5MhytpmmEjVHxfSrbSBJQ63oDa1AllqpDduWCcZZE60zOZOfrGGRO
20
+ A/JUXlJL5otPuxxJX65dJU57Y+ONbGVp9sFOT2bGrL+uwsqS7L+XyUz281sfgO0o
21
+ wsVZq6NyBVaMvtD5ViXPTZslcIZWygcvDVaUEyQ4ri8wqXNQeCNZiZikfM4U03oH
22
+ 9kgW/4IuW28Utmnt9f8vPR5XTHURX20p3Q0xhM74mZRtmldwyO3grfe25S7ZOZ5W
23
+ TFuLfxuftJNl5t1w5CdTZre7BYzbWA+Jg34eNWMiHkHE3lVMCLMqZFb8FY0TOLxw
24
+ 7YZFDrLYrA2G/alivbHs7ipj6D7i
25
+ -----END CERTIFICATE-----