haraka 0.0.33 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +7 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1898 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/LICENSE +22 -0
- package/Plugins.md +227 -0
- package/README.md +119 -4
- package/SECURITY.md +178 -0
- package/TODO +22 -0
- package/bin/haraka +593 -0
- package/bin/haraka_grep +32 -0
- package/config/aliases +2 -0
- package/config/auth_flat_file.ini +7 -0
- package/config/auth_vpopmaild.ini +9 -0
- package/config/connection.ini +79 -0
- package/config/delay_deny.ini +7 -0
- package/config/host_list +3 -0
- package/config/host_list_regex +6 -0
- package/config/http.ini +11 -0
- package/config/lmtp.ini +7 -0
- package/config/log.ini +11 -0
- package/config/outbound.bounce_message +18 -0
- package/config/outbound.bounce_message_html +36 -0
- package/config/outbound.bounce_message_image +106 -0
- package/config/outbound.ini +24 -0
- package/config/plugins +67 -0
- package/config/smtp.ini +37 -0
- package/config/smtp_bridge.ini +4 -0
- package/config/smtp_forward.ini +31 -0
- package/config/smtp_proxy.ini +27 -0
- package/config/tarpit.timeout +1 -0
- package/config/tls.ini +83 -0
- package/config/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1865 -0
- package/contrib/Haraka.cf +6 -0
- package/contrib/Haraka.pm +35 -0
- package/contrib/bad_smtp_server.pl +25 -0
- package/contrib/bsd-rc.d/haraka +63 -0
- package/contrib/debian-init.d/haraka +87 -0
- package/contrib/haraka.init +96 -0
- package/contrib/haraka.service +23 -0
- package/contrib/plugin2npm.sh +81 -0
- package/contrib/ubuntu-upstart/haraka.conf +27 -0
- package/docs/Body.md +1 -0
- package/docs/Config.md +1 -0
- package/docs/Connection.md +153 -0
- package/docs/CoreConfig.md +96 -0
- package/docs/CustomReturnCodes.md +3 -0
- package/docs/HAProxy.md +62 -0
- package/docs/Header.md +1 -0
- package/docs/Logging.md +129 -0
- package/docs/Outbound.md +210 -0
- package/docs/Plugins.md +372 -0
- package/docs/Results.md +7 -0
- package/docs/Transaction.md +135 -0
- package/docs/Tutorial.md +183 -0
- package/docs/deprecated/access.md +3 -0
- package/docs/deprecated/backscatterer.md +9 -0
- package/docs/deprecated/connect.rdns_access.md +53 -0
- package/docs/deprecated/data.headers.md +3 -0
- package/docs/deprecated/data.nomsgid.md +7 -0
- package/docs/deprecated/data.noreceived.md +11 -0
- package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
- package/docs/deprecated/dkim_sign.md +97 -0
- package/docs/deprecated/dkim_verify.md +28 -0
- package/docs/deprecated/dnsbl.md +80 -0
- package/docs/deprecated/dnswl.md +73 -0
- package/docs/deprecated/lookup_rdns.strict.md +67 -0
- package/docs/deprecated/mail_from.access.md +52 -0
- package/docs/deprecated/mail_from.blocklist.md +18 -0
- package/docs/deprecated/mail_from.nobounces.md +8 -0
- package/docs/deprecated/rcpt_to.access.md +53 -0
- package/docs/deprecated/rcpt_to.blocklist.md +18 -0
- package/docs/deprecated/rcpt_to.routes.md +3 -0
- package/docs/deprecated/rdns.regexp.md +30 -0
- package/docs/plugins/aliases.md +3 -0
- package/docs/plugins/auth/auth_bridge.md +34 -0
- package/docs/plugins/auth/auth_ldap.md +4 -0
- package/docs/plugins/auth/auth_proxy.md +36 -0
- package/docs/plugins/auth/auth_vpopmaild.md +33 -0
- package/docs/plugins/auth/flat_file.md +40 -0
- package/docs/plugins/block_me.md +18 -0
- package/docs/plugins/data.signatures.md +11 -0
- package/docs/plugins/delay_deny.md +23 -0
- package/docs/plugins/max_unrecognized_commands.md +6 -0
- package/docs/plugins/prevent_credential_leaks.md +22 -0
- package/docs/plugins/process_title.md +42 -0
- package/docs/plugins/queue/deliver.md +3 -0
- package/docs/plugins/queue/discard.md +32 -0
- package/docs/plugins/queue/lmtp.md +24 -0
- package/docs/plugins/queue/qmail-queue.md +16 -0
- package/docs/plugins/queue/quarantine.md +87 -0
- package/docs/plugins/queue/smtp_bridge.md +32 -0
- package/docs/plugins/queue/smtp_forward.md +127 -0
- package/docs/plugins/queue/smtp_proxy.md +68 -0
- package/docs/plugins/queue/test.md +7 -0
- package/docs/plugins/rcpt_to.in_host_list.md +34 -0
- package/docs/plugins/rcpt_to.max_count.md +3 -0
- package/docs/plugins/record_envelope_addresses.md +20 -0
- package/docs/plugins/relay.md +3 -0
- package/docs/plugins/reseed_rng.md +16 -0
- package/docs/plugins/status.md +41 -0
- package/docs/plugins/tarpit.md +50 -0
- package/docs/plugins/tls.md +235 -0
- package/docs/plugins/toobusy.md +27 -0
- package/docs/plugins/xclient.md +10 -0
- package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
- package/docs/tutorials/SettingUpOutbound.md +62 -0
- package/eslint.config.mjs +2 -0
- package/haraka.js +74 -0
- package/haraka.sh +2 -0
- package/http/html/404.html +58 -0
- package/http/html/index.html +47 -0
- package/http/package.json +21 -0
- package/line_socket.js +24 -0
- package/logger.js +322 -0
- package/outbound/client_pool.js +59 -0
- package/outbound/config.js +134 -0
- package/outbound/hmail.js +1504 -0
- package/outbound/index.js +349 -0
- package/outbound/qfile.js +93 -0
- package/outbound/queue.js +399 -0
- package/outbound/tls.js +85 -0
- package/outbound/todo.js +17 -0
- package/package.json +100 -4
- package/plugins/.eslintrc.yaml +3 -0
- package/plugins/auth/auth_base.js +261 -0
- package/plugins/auth/auth_bridge.js +20 -0
- package/plugins/auth/auth_proxy.js +227 -0
- package/plugins/auth/auth_vpopmaild.js +162 -0
- package/plugins/auth/flat_file.js +44 -0
- package/plugins/block_me.js +88 -0
- package/plugins/data.signatures.js +30 -0
- package/plugins/delay_deny.js +153 -0
- package/plugins/prevent_credential_leaks.js +61 -0
- package/plugins/process_title.js +197 -0
- package/plugins/profile.js +11 -0
- package/plugins/queue/deliver.js +12 -0
- package/plugins/queue/discard.js +27 -0
- package/plugins/queue/lmtp.js +45 -0
- package/plugins/queue/qmail-queue.js +93 -0
- package/plugins/queue/quarantine.js +133 -0
- package/plugins/queue/smtp_bridge.js +45 -0
- package/plugins/queue/smtp_forward.js +371 -0
- package/plugins/queue/smtp_proxy.js +142 -0
- package/plugins/queue/test.js +15 -0
- package/plugins/rcpt_to.host_list_base.js +65 -0
- package/plugins/rcpt_to.in_host_list.js +56 -0
- package/plugins/record_envelope_addresses.js +17 -0
- package/plugins/reseed_rng.js +7 -0
- package/plugins/status.js +274 -0
- package/plugins/tarpit.js +45 -0
- package/plugins/tls.js +164 -0
- package/plugins/toobusy.js +47 -0
- package/plugins/xclient.js +124 -0
- package/plugins.js +605 -0
- package/run_tests +11 -0
- package/server.js +827 -0
- package/smtp_client.js +504 -0
- package/test/.eslintrc.yaml +11 -0
- package/test/config/auth_flat_file.ini +5 -0
- package/test/config/block_me.recipient +1 -0
- package/test/config/block_me.senders +1 -0
- package/test/config/dhparams.pem +8 -0
- package/test/config/host_list +2 -0
- package/test/config/outbound_tls_cert.pem +1 -0
- package/test/config/outbound_tls_key.pem +1 -0
- package/test/config/plugins +7 -0
- package/test/config/smtp.ini +11 -0
- package/test/config/smtp_forward.ini +30 -0
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/config/tls/haraka.local.pem +51 -0
- package/test/config/tls.ini +45 -0
- package/test/config/tls_cert.pem +21 -0
- package/test/config/tls_key.pem +28 -0
- package/test/connection.js +820 -0
- package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
- package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
- package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
- package/test/fixtures/line_socket.js +21 -0
- package/test/fixtures/todo_qfile.txt +0 -0
- package/test/fixtures/util_hmailitem.js +156 -0
- package/test/installation/config/test-plugin-flat +1 -0
- package/test/installation/config/test-plugin.ini +10 -0
- package/test/installation/config/tls.ini +1 -0
- package/test/installation/node_modules/load_first/index.js +5 -0
- package/test/installation/node_modules/load_first/package.json +11 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
- package/test/installation/node_modules/test-plugin/package.json +5 -0
- package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
- package/test/installation/plugins/base_plugin.js +3 -0
- package/test/installation/plugins/folder_plugin/index.js +3 -0
- package/test/installation/plugins/folder_plugin/package.json +11 -0
- package/test/installation/plugins/inherits.js +7 -0
- package/test/installation/plugins/load_first.js +3 -0
- package/test/installation/plugins/plugin.js +1 -0
- package/test/installation/plugins/tls.js +3 -0
- package/test/logger.js +217 -0
- package/test/loud/config/dhparams.pem +0 -0
- package/test/loud/config/tls/goobered.pem +45 -0
- package/test/loud/config/tls.ini +43 -0
- package/test/mail_specimen/base64-root-part.txt +23 -0
- package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
- package/test/outbound/bounce_net_errors.js +133 -0
- package/test/outbound/bounce_rfc3464.js +226 -0
- package/test/outbound/hmail.js +210 -0
- package/test/outbound/index.js +385 -0
- package/test/outbound/qfile.js +124 -0
- package/test/outbound/queue.js +325 -0
- package/test/plugins/auth/auth_base.js +620 -0
- package/test/plugins/auth/auth_bridge.js +80 -0
- package/test/plugins/auth/auth_vpopmaild.js +81 -0
- package/test/plugins/auth/flat_file.js +123 -0
- package/test/plugins/block_me.js +141 -0
- package/test/plugins/data.signatures.js +111 -0
- package/test/plugins/delay_deny.js +262 -0
- package/test/plugins/prevent_credential_leaks.js +174 -0
- package/test/plugins/process_title.js +141 -0
- package/test/plugins/queue/deliver.js +98 -0
- package/test/plugins/queue/discard.js +78 -0
- package/test/plugins/queue/lmtp.js +137 -0
- package/test/plugins/queue/qmail-queue.js +98 -0
- package/test/plugins/queue/quarantine.js +80 -0
- package/test/plugins/queue/smtp_bridge.js +152 -0
- package/test/plugins/queue/smtp_forward.js +1023 -0
- package/test/plugins/queue/smtp_proxy.js +138 -0
- package/test/plugins/rcpt_to.host_list_base.js +102 -0
- package/test/plugins/rcpt_to.in_host_list.js +186 -0
- package/test/plugins/record_envelope_addresses.js +66 -0
- package/test/plugins/reseed_rng.js +34 -0
- package/test/plugins/status.js +207 -0
- package/test/plugins/tarpit.js +90 -0
- package/test/plugins/tls.js +86 -0
- package/test/plugins/toobusy.js +198 -0
- package/test/plugins/xclient.js +119 -0
- package/test/plugins.js +230 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- package/test/queue/zero-length +0 -0
- package/test/server.js +1012 -0
- package/test/smtp_client.js +1303 -0
- package/test/tls_socket.js +321 -0
- package/test/transaction.js +554 -0
- package/tls_socket.js +771 -0
- package/transaction.js +267 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// Base authentication plugin.
|
|
2
|
+
// This cannot be used on its own. You need to inherit from it.
|
|
3
|
+
// See plugins/auth/flat_file.js for an example.
|
|
4
|
+
|
|
5
|
+
// Note: You can disable setting `connection.notes.auth_passwd` by `plugin.blankout_password = true`
|
|
6
|
+
|
|
7
|
+
const crypto = require('node:crypto')
|
|
8
|
+
|
|
9
|
+
const tlds = require('haraka-tld')
|
|
10
|
+
const utils = require('haraka-utils')
|
|
11
|
+
|
|
12
|
+
const AUTH_COMMAND = 'AUTH'
|
|
13
|
+
const AUTH_METHOD_CRAM_MD5 = 'CRAM-MD5'
|
|
14
|
+
const AUTH_METHOD_PLAIN = 'PLAIN'
|
|
15
|
+
const AUTH_METHOD_LOGIN = 'LOGIN'
|
|
16
|
+
const LOGIN_STRING1 = 'VXNlcm5hbWU6' //Username: base64 coded
|
|
17
|
+
const LOGIN_STRING2 = 'UGFzc3dvcmQ6' //Password: base64 coded
|
|
18
|
+
|
|
19
|
+
exports.hook_capabilities = (next, connection) => {
|
|
20
|
+
// Don't offer AUTH capabilities unless session is encrypted
|
|
21
|
+
if (!connection.tls.enabled) return next()
|
|
22
|
+
|
|
23
|
+
const methods = ['PLAIN', 'LOGIN', 'CRAM-MD5']
|
|
24
|
+
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
25
|
+
connection.notes.allowed_auth_methods = methods
|
|
26
|
+
next()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Override this at a minimum. Run cb(passwd) to provide a password.
|
|
30
|
+
exports.get_plain_passwd = (user, connection, cb) => cb()
|
|
31
|
+
|
|
32
|
+
exports.hook_unrecognized_command = function (next, connection, params) {
|
|
33
|
+
if (params[0].toUpperCase() === AUTH_COMMAND && params[1]) {
|
|
34
|
+
return this.select_auth_method(next, connection, params.slice(1).join(' '))
|
|
35
|
+
}
|
|
36
|
+
if (!connection.notes.authenticating) return next()
|
|
37
|
+
|
|
38
|
+
const am = connection.notes.auth_method
|
|
39
|
+
if (am === AUTH_METHOD_CRAM_MD5 && connection.notes.auth_ticket) {
|
|
40
|
+
return this.auth_cram_md5(next, connection, params)
|
|
41
|
+
}
|
|
42
|
+
if (am === AUTH_METHOD_LOGIN) {
|
|
43
|
+
return this.auth_login(next, connection, params)
|
|
44
|
+
}
|
|
45
|
+
if (am === AUTH_METHOD_PLAIN) {
|
|
46
|
+
return this.auth_plain(next, connection, params)
|
|
47
|
+
}
|
|
48
|
+
next()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
52
|
+
function callback(plain_pw) {
|
|
53
|
+
cb(plain_pw === null ? false : plain_pw === passwd)
|
|
54
|
+
}
|
|
55
|
+
if (this.get_plain_passwd.length == 2) {
|
|
56
|
+
this.get_plain_passwd(user, callback)
|
|
57
|
+
} else if (this.get_plain_passwd.length == 3) {
|
|
58
|
+
this.get_plain_passwd(user, connection, callback)
|
|
59
|
+
} else {
|
|
60
|
+
throw 'Invalid number of arguments for get_plain_passwd'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exports.check_cram_md5_passwd = function (connection, user, passwd, cb) {
|
|
65
|
+
function callback(plain_pw) {
|
|
66
|
+
if (plain_pw == null) return cb(false)
|
|
67
|
+
|
|
68
|
+
const hmac = crypto.createHmac('md5', plain_pw.toString())
|
|
69
|
+
hmac.update(connection.notes.auth_ticket)
|
|
70
|
+
|
|
71
|
+
if (hmac.digest('hex') === passwd) return cb(true)
|
|
72
|
+
|
|
73
|
+
cb(false)
|
|
74
|
+
}
|
|
75
|
+
if (this.get_plain_passwd.length == 2) {
|
|
76
|
+
this.get_plain_passwd(user, callback)
|
|
77
|
+
} else if (this.get_plain_passwd.length == 3) {
|
|
78
|
+
this.get_plain_passwd(user, connection, callback)
|
|
79
|
+
} else {
|
|
80
|
+
throw 'Invalid number of arguments for get_plain_passwd'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
exports.check_user = function (next, connection, credentials, method) {
|
|
85
|
+
const plugin = this
|
|
86
|
+
connection.notes.authenticating = false
|
|
87
|
+
if (!(credentials[0] && credentials[1])) {
|
|
88
|
+
connection.respond(504, 'Invalid AUTH string', () => {
|
|
89
|
+
connection.reset_transaction(() => next(OK))
|
|
90
|
+
})
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// valid: (true|false)
|
|
95
|
+
// opts: ({ message, code }|String)
|
|
96
|
+
function passwd_ok(valid, opts) {
|
|
97
|
+
const status_code = (typeof opts === 'object' && opts.code) || (valid ? 235 : 535)
|
|
98
|
+
const status_message =
|
|
99
|
+
(typeof opts === 'object' ? opts.message : opts) ||
|
|
100
|
+
(valid ? '2.7.0 Authentication successful' : '5.7.8 Authentication failed')
|
|
101
|
+
|
|
102
|
+
// The AUTH username is attacker-controlled (base64-decoded). Strip
|
|
103
|
+
// control chars before it is stored in notes or emitted into the
|
|
104
|
+
// Authentication-Results header (header injection).
|
|
105
|
+
// eslint-disable-next-line no-control-regex
|
|
106
|
+
const safe_user = String(credentials[0] ?? '').replace(/[\x00-\x1f\x7f]/g, '')
|
|
107
|
+
|
|
108
|
+
if (valid) {
|
|
109
|
+
connection.relaying = true
|
|
110
|
+
connection.results.add({ name: 'relay' }, { pass: plugin.name })
|
|
111
|
+
|
|
112
|
+
connection.results.add(
|
|
113
|
+
{ name: 'auth' },
|
|
114
|
+
{
|
|
115
|
+
pass: plugin.name,
|
|
116
|
+
method,
|
|
117
|
+
user: safe_user,
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
connection.respond(status_code, status_message, () => {
|
|
122
|
+
connection.authheader = '(authenticated bits=0)\n'
|
|
123
|
+
connection.auth_results(`auth=pass (${method.toLowerCase()})`)
|
|
124
|
+
connection.notes.auth_user = safe_user
|
|
125
|
+
if (!plugin.blankout_password) connection.notes.auth_passwd = credentials[1]
|
|
126
|
+
next(OK)
|
|
127
|
+
})
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!connection.notes.auth_fails) connection.notes.auth_fails = 0
|
|
132
|
+
|
|
133
|
+
connection.notes.auth_fails++
|
|
134
|
+
connection.results.add({ name: 'auth' }, { fail: `${plugin.name}/${method}` })
|
|
135
|
+
|
|
136
|
+
let delay = Math.pow(2, connection.notes.auth_fails - 1)
|
|
137
|
+
if (plugin.timeout && delay >= plugin.timeout) {
|
|
138
|
+
delay = plugin.timeout - 1
|
|
139
|
+
}
|
|
140
|
+
connection.lognotice(plugin, `delaying for ${delay} seconds`)
|
|
141
|
+
// here we include the username, as shown in RFC 5451 example
|
|
142
|
+
connection.auth_results(`auth=fail (${method.toLowerCase()}) smtp.auth=${safe_user}`)
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
connection.respond(status_code, status_message, () => {
|
|
145
|
+
connection.reset_transaction(() => next(OK))
|
|
146
|
+
})
|
|
147
|
+
}, delay * 1000)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (method === AUTH_METHOD_PLAIN || method === AUTH_METHOD_LOGIN) {
|
|
151
|
+
plugin.check_plain_passwd(connection, credentials[0], credentials[1], passwd_ok)
|
|
152
|
+
} else if (method === AUTH_METHOD_CRAM_MD5) {
|
|
153
|
+
plugin.check_cram_md5_passwd(connection, credentials[0], credentials[1], passwd_ok)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
exports.select_auth_method = function (next, connection, method) {
|
|
158
|
+
const split = method.split(/\s+/)
|
|
159
|
+
method = split.shift().toUpperCase()
|
|
160
|
+
if (!connection.notes.allowed_auth_methods) return next()
|
|
161
|
+
if (!connection.notes.allowed_auth_methods.includes(method)) return next()
|
|
162
|
+
|
|
163
|
+
if (connection.notes.authenticating) return next(DENYDISCONNECT, 'bad protocol')
|
|
164
|
+
|
|
165
|
+
connection.notes.authenticating = true
|
|
166
|
+
connection.notes.auth_method = method
|
|
167
|
+
|
|
168
|
+
if (method === AUTH_METHOD_PLAIN) return this.auth_plain(next, connection, split)
|
|
169
|
+
if (method === AUTH_METHOD_LOGIN) return this.auth_login(next, connection, split)
|
|
170
|
+
if (method === AUTH_METHOD_CRAM_MD5) return this.auth_cram_md5(next, connection)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
exports.auth_plain = function (next, connection, params) {
|
|
174
|
+
// one parameter given on line, either:
|
|
175
|
+
// AUTH PLAIN <param> or
|
|
176
|
+
// AUTH PLAIN\n
|
|
177
|
+
//...
|
|
178
|
+
// <param>
|
|
179
|
+
if (params[0]) {
|
|
180
|
+
const credentials = utils.unbase64(params[0]).split(/\0/)
|
|
181
|
+
credentials.shift() // Discard authid
|
|
182
|
+
this.check_user(next, connection, credentials, AUTH_METHOD_PLAIN)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (connection.notes.auth_plain_asked_login) {
|
|
187
|
+
return next(DENYDISCONNECT, 'bad protocol')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
connection.respond(334, ' ', () => {
|
|
191
|
+
connection.notes.auth_plain_asked_login = true
|
|
192
|
+
next(OK)
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
exports.auth_login = function (next, connection, params) {
|
|
197
|
+
if (
|
|
198
|
+
(!connection.notes.auth_login_asked_login && params[0]) ||
|
|
199
|
+
(connection.notes.auth_login_asked_login && !connection.notes.auth_login_userlogin)
|
|
200
|
+
) {
|
|
201
|
+
if (!params[0]) return next(DENYDISCONNECT, 'bad protocol')
|
|
202
|
+
|
|
203
|
+
const login = utils.unbase64(params[0])
|
|
204
|
+
connection.respond(334, LOGIN_STRING2, () => {
|
|
205
|
+
connection.notes.auth_login_userlogin = login
|
|
206
|
+
connection.notes.auth_login_asked_login = true
|
|
207
|
+
next(OK)
|
|
208
|
+
})
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (connection.notes.auth_login_userlogin) {
|
|
213
|
+
const credentials = [connection.notes.auth_login_userlogin, utils.unbase64(params[0])]
|
|
214
|
+
|
|
215
|
+
connection.notes.auth_login_userlogin = null
|
|
216
|
+
connection.notes.auth_login_asked_login = false
|
|
217
|
+
|
|
218
|
+
return this.check_user(next, connection, credentials, AUTH_METHOD_LOGIN)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
connection.respond(334, LOGIN_STRING1, () => {
|
|
222
|
+
connection.notes.auth_login_asked_login = true
|
|
223
|
+
next(OK)
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
exports.auth_cram_md5 = function (next, connection, params) {
|
|
228
|
+
if (params) {
|
|
229
|
+
const credentials = utils.unbase64(params[0]).split(' ')
|
|
230
|
+
return this.check_user(next, connection, credentials, AUTH_METHOD_CRAM_MD5)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const ticket = `<${this.hexi(Math.floor(Math.random() * 1000000))}.${this.hexi(Date.now())}@${connection.local.host}>`
|
|
234
|
+
|
|
235
|
+
connection.loginfo(this, `ticket: ${ticket}`)
|
|
236
|
+
connection.respond(334, utils.base64(ticket), () => {
|
|
237
|
+
connection.notes.auth_ticket = ticket
|
|
238
|
+
next(OK)
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
exports.hexi = (number) => String(Math.abs(parseInt(number)).toString(16))
|
|
243
|
+
|
|
244
|
+
exports.constrain_sender = function (next, connection, params) {
|
|
245
|
+
if (this?.cfg?.main?.constrain_sender === false) return next()
|
|
246
|
+
|
|
247
|
+
const au = connection.results.get('auth')?.user
|
|
248
|
+
if (!au) return next()
|
|
249
|
+
|
|
250
|
+
const ad = /@/.test(au) ? au.split('@').pop() : null
|
|
251
|
+
const ed = params[0].host
|
|
252
|
+
|
|
253
|
+
if (!ad || !ed) return next()
|
|
254
|
+
|
|
255
|
+
const auth_od = tlds.get_organizational_domain(ad)
|
|
256
|
+
const envelope_od = tlds.get_organizational_domain(ed)
|
|
257
|
+
|
|
258
|
+
if (auth_od === envelope_od) return next()
|
|
259
|
+
|
|
260
|
+
next(DENY, `Envelope domain '${envelope_od}' doesn't match AUTH domain '${auth_od}'`)
|
|
261
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Bridge AUTH requests to SMTP server
|
|
2
|
+
|
|
3
|
+
exports.register = function () {
|
|
4
|
+
this.inherits('auth/auth_proxy')
|
|
5
|
+
this.load_flat_ini()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
exports.load_flat_ini = function () {
|
|
9
|
+
this.cfg = this.config.get('smtp_bridge.ini', () => {
|
|
10
|
+
this.load_flat_ini()
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
15
|
+
let { host } = this.cfg.main
|
|
16
|
+
if (this.cfg.main.port) {
|
|
17
|
+
host = `${host}:${this.cfg.main.port}`
|
|
18
|
+
}
|
|
19
|
+
this.try_auth_proxy(connection, host, user, passwd, cb)
|
|
20
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Proxy AUTH requests selectively by domain
|
|
2
|
+
|
|
3
|
+
const utils = require('haraka-utils')
|
|
4
|
+
const net_utils = require('haraka-net-utils')
|
|
5
|
+
|
|
6
|
+
const tls_socket = require('./tls_socket')
|
|
7
|
+
|
|
8
|
+
const smtp_regexp = /^(\d{3})([ -])(.*)/
|
|
9
|
+
|
|
10
|
+
exports.register = function () {
|
|
11
|
+
this.inherits('auth/auth_base')
|
|
12
|
+
this.load_tls_ini()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.load_tls_ini = function () {
|
|
16
|
+
this.tls_cfg = this.config.get('tls.ini', () => {
|
|
17
|
+
this.load_tls_ini()
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
exports.hook_capabilities = (next, connection) => {
|
|
22
|
+
if (connection.tls.enabled) {
|
|
23
|
+
const methods = ['PLAIN', 'LOGIN']
|
|
24
|
+
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
25
|
+
connection.notes.allowed_auth_methods = methods
|
|
26
|
+
}
|
|
27
|
+
next()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
31
|
+
let domain = /@([^@]+)$/.exec(user)
|
|
32
|
+
if (domain) {
|
|
33
|
+
domain = domain[1].toLowerCase()
|
|
34
|
+
} else {
|
|
35
|
+
// AUTH user not in user@domain.com format
|
|
36
|
+
connection.logerror(this, `AUTH user="${user}" error="not in required format"`)
|
|
37
|
+
return cb(false)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if domain exists in configuration file
|
|
41
|
+
const config = this.config.get('auth_proxy.ini')
|
|
42
|
+
if (!config.domains[domain]) {
|
|
43
|
+
connection.logerror(this, `AUTH user="${user}" error="domain '${domain}' is not defined"`)
|
|
44
|
+
return cb(false)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.try_auth_proxy(connection, config.domains[domain].split(/[,; ]/), user, passwd, cb)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
51
|
+
if (!hosts || (hosts && !hosts.length)) return cb(false)
|
|
52
|
+
if (typeof hosts !== 'object') {
|
|
53
|
+
hosts = [hosts]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const self = this
|
|
57
|
+
const ep = net_utils.endpoint(hosts.shift(), 25)
|
|
58
|
+
if (ep instanceof Error) {
|
|
59
|
+
connection.logerror(this, `invalid host: ${ep.message}`)
|
|
60
|
+
return this.try_auth_proxy(connection, hosts, user, passwd, cb)
|
|
61
|
+
}
|
|
62
|
+
const { host, port } = ep
|
|
63
|
+
let methods = []
|
|
64
|
+
let auth_complete = false
|
|
65
|
+
let auth_success = false
|
|
66
|
+
let command = 'connect'
|
|
67
|
+
let response = []
|
|
68
|
+
let secure = false
|
|
69
|
+
|
|
70
|
+
const socket = tls_socket.connect({ host, port })
|
|
71
|
+
net_utils.add_line_processor(socket)
|
|
72
|
+
connection.logdebug(this, `attempting connection to host=${host} port=${port}`)
|
|
73
|
+
socket.setTimeout(30 * 1000)
|
|
74
|
+
socket.on('connect', () => {})
|
|
75
|
+
socket.on('close', () => {
|
|
76
|
+
if (!auth_complete) {
|
|
77
|
+
// Try next host
|
|
78
|
+
return this.try_auth_proxy(connection, hosts, user, passwd, cb)
|
|
79
|
+
}
|
|
80
|
+
connection.loginfo(this, `AUTH user="${user}" host="${host}" success=${auth_success}`)
|
|
81
|
+
cb(auth_success)
|
|
82
|
+
})
|
|
83
|
+
socket.on('timeout', () => {
|
|
84
|
+
connection.logerror(this, 'connection timed out')
|
|
85
|
+
socket.end()
|
|
86
|
+
// Try next host
|
|
87
|
+
this.try_auth_proxy(connection, hosts, user, passwd, cb)
|
|
88
|
+
})
|
|
89
|
+
socket.on('error', (err) => {
|
|
90
|
+
connection.logerror(this, `connection failed to host ${host}: ${err}`)
|
|
91
|
+
socket.end()
|
|
92
|
+
})
|
|
93
|
+
socket.send_command = function (cmd, data) {
|
|
94
|
+
let line = cmd + (data ? ` ${data}` : '')
|
|
95
|
+
if (cmd === 'dot') {
|
|
96
|
+
line = '.'
|
|
97
|
+
}
|
|
98
|
+
// Don't leak proxied SASL credentials (AUTH PLAIN <base64>) to logs
|
|
99
|
+
const safe = line.replace(/^(AUTH\s+\S+\s+).+$/i, '$1[redacted]')
|
|
100
|
+
connection.logprotocol(self, `C: ${safe}`)
|
|
101
|
+
command = cmd.toLowerCase()
|
|
102
|
+
this.write(`${line}\r\n`)
|
|
103
|
+
// Clear response buffer from previous command
|
|
104
|
+
response = []
|
|
105
|
+
}
|
|
106
|
+
socket.on('line', function (line) {
|
|
107
|
+
connection.logprotocol(self, `S: ${line}`)
|
|
108
|
+
const matches = smtp_regexp.exec(line)
|
|
109
|
+
if (!matches) {
|
|
110
|
+
connection.logerror(self, `unrecognised response: ${line}`)
|
|
111
|
+
socket.end()
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const code = matches[1]
|
|
116
|
+
const cont = matches[2]
|
|
117
|
+
const rest = matches[3]
|
|
118
|
+
response.push(rest)
|
|
119
|
+
if (cont !== ' ') return
|
|
120
|
+
|
|
121
|
+
let key
|
|
122
|
+
let cert
|
|
123
|
+
|
|
124
|
+
connection.logdebug(self, `command state: ${command}`)
|
|
125
|
+
if (command === 'ehlo') {
|
|
126
|
+
if (code.startsWith('5')) {
|
|
127
|
+
// EHLO command rejected; abort
|
|
128
|
+
socket.send_command('QUIT')
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
// Parse CAPABILITIES
|
|
132
|
+
for (const i in response) {
|
|
133
|
+
if (/^STARTTLS/.test(response[i])) {
|
|
134
|
+
if (secure) continue // silly remote, we've already upgraded
|
|
135
|
+
// Opportunistic TLS: a client does not need its own
|
|
136
|
+
// certificate to negotiate TLS, so always STARTTLS when
|
|
137
|
+
// the backend offers it. The local key/cert are only
|
|
138
|
+
// attached if configured (mutual TLS), not required.
|
|
139
|
+
/* eslint no-useless-assignment: 0 */
|
|
140
|
+
key = self.config.get(self.tls_cfg.main.key || 'tls_key.pem', 'binary')
|
|
141
|
+
cert = self.config.get(self.tls_cfg.main.cert || 'tls_cert.pem', 'binary')
|
|
142
|
+
this.on('secure', () => {
|
|
143
|
+
if (secure) return
|
|
144
|
+
secure = true
|
|
145
|
+
socket.send_command('EHLO', connection.local.host)
|
|
146
|
+
})
|
|
147
|
+
socket.send_command('STARTTLS')
|
|
148
|
+
return
|
|
149
|
+
} else if (/^AUTH /.test(response[i])) {
|
|
150
|
+
// Parse supported AUTH methods
|
|
151
|
+
const parse = /^AUTH (.+)$/.exec(response[i])
|
|
152
|
+
methods = parse[1].split(/\s+/)
|
|
153
|
+
connection.logdebug(self, `found supported AUTH methods: ${methods}`)
|
|
154
|
+
// Prefer PLAIN as it's easiest
|
|
155
|
+
if (methods.includes('PLAIN')) {
|
|
156
|
+
socket.send_command('AUTH', `PLAIN ${utils.base64(`\0${user}\0${passwd}`)}`)
|
|
157
|
+
return
|
|
158
|
+
} else if (methods.includes('LOGIN')) {
|
|
159
|
+
socket.send_command('AUTH', 'LOGIN')
|
|
160
|
+
return
|
|
161
|
+
} else {
|
|
162
|
+
// No compatible methods; abort...
|
|
163
|
+
connection.logdebug(self, 'no compatible AUTH methods')
|
|
164
|
+
socket.send_command('QUIT')
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (command === 'auth') {
|
|
171
|
+
// Handle LOGIN
|
|
172
|
+
if (code.startsWith('3') && response[0] === 'VXNlcm5hbWU6') {
|
|
173
|
+
// Write to the socket directly to keep the state at 'auth'
|
|
174
|
+
this.write(`${utils.base64(user)}\r\n`)
|
|
175
|
+
response = []
|
|
176
|
+
return
|
|
177
|
+
} else if (code.startsWith('3') && response[0] === 'UGFzc3dvcmQ6') {
|
|
178
|
+
this.write(`${utils.base64(passwd)}\r\n`)
|
|
179
|
+
response = []
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
if (code.startsWith('5')) {
|
|
183
|
+
// Initial attempt failed; strip domain and retry.
|
|
184
|
+
const u = /^([^@]+)@.+$/.exec(user)
|
|
185
|
+
if (u) {
|
|
186
|
+
user = u[1]
|
|
187
|
+
if (methods.includes('PLAIN')) {
|
|
188
|
+
socket.send_command('AUTH', `PLAIN ${utils.base64(`\0${user}\0${passwd}`)}`)
|
|
189
|
+
} else if (methods.includes('LOGIN')) {
|
|
190
|
+
socket.send_command('AUTH', 'LOGIN')
|
|
191
|
+
}
|
|
192
|
+
return
|
|
193
|
+
} else {
|
|
194
|
+
// Don't attempt any other hosts
|
|
195
|
+
auth_complete = true
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (/^[345]/.test(code)) {
|
|
200
|
+
// Got an unhandled error
|
|
201
|
+
connection.logdebug(self, `error: ${line}`)
|
|
202
|
+
socket.send_command('QUIT')
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
switch (command) {
|
|
206
|
+
case 'starttls':
|
|
207
|
+
this.upgrade({ key, cert })
|
|
208
|
+
break
|
|
209
|
+
case 'connect':
|
|
210
|
+
socket.send_command('EHLO', connection.local.host)
|
|
211
|
+
break
|
|
212
|
+
case 'auth':
|
|
213
|
+
// AUTH was successful
|
|
214
|
+
auth_complete = true
|
|
215
|
+
auth_success = true
|
|
216
|
+
socket.send_command('QUIT')
|
|
217
|
+
break
|
|
218
|
+
case 'ehlo':
|
|
219
|
+
case 'helo':
|
|
220
|
+
case 'quit':
|
|
221
|
+
socket.end()
|
|
222
|
+
break
|
|
223
|
+
default:
|
|
224
|
+
throw new Error(`[auth/auth_proxy] unknown command: ${command}`)
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Auth against vpopmaild
|
|
2
|
+
|
|
3
|
+
const net = require('node:net')
|
|
4
|
+
|
|
5
|
+
exports.register = function () {
|
|
6
|
+
this.inherits('auth/auth_base')
|
|
7
|
+
this.blankout_password = true
|
|
8
|
+
|
|
9
|
+
this.load_vpopmaild_ini()
|
|
10
|
+
|
|
11
|
+
if (this.cfg.main.constrain_sender) {
|
|
12
|
+
this.register_hook('mail', 'constrain_sender')
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
exports.load_vpopmaild_ini = function () {
|
|
17
|
+
this.cfg = this.config.get(
|
|
18
|
+
'auth_vpopmaild.ini',
|
|
19
|
+
{
|
|
20
|
+
booleans: ['+main.constrain_sender'],
|
|
21
|
+
},
|
|
22
|
+
() => {
|
|
23
|
+
this.load_vpopmaild_ini()
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.hook_capabilities = function (next, connection) {
|
|
29
|
+
if (!connection.tls.enabled) return next()
|
|
30
|
+
|
|
31
|
+
const methods = ['PLAIN', 'LOGIN']
|
|
32
|
+
if (this.cfg.main.sysadmin) methods.push('CRAM-MD5')
|
|
33
|
+
|
|
34
|
+
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
35
|
+
connection.notes.allowed_auth_methods = methods
|
|
36
|
+
|
|
37
|
+
next()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
41
|
+
let chunk_count = 0
|
|
42
|
+
let auth_success = false
|
|
43
|
+
|
|
44
|
+
const socket = this.get_vpopmaild_socket(user)
|
|
45
|
+
socket.setEncoding('utf8')
|
|
46
|
+
|
|
47
|
+
socket.on('data', (chunk) => {
|
|
48
|
+
chunk_count++
|
|
49
|
+
if (chunk_count === 1) {
|
|
50
|
+
if (/^\+OK/.test(chunk)) {
|
|
51
|
+
socket.write(`slogin ${user} ${passwd}\n\r`)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
socket.end()
|
|
55
|
+
}
|
|
56
|
+
if (chunk_count === 2) {
|
|
57
|
+
if (/^\+OK/.test(chunk)) {
|
|
58
|
+
// slogin reply
|
|
59
|
+
auth_success = true
|
|
60
|
+
socket.write('quit\n\r')
|
|
61
|
+
}
|
|
62
|
+
socket.end() // disconnect
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
socket.on('end', () => {
|
|
67
|
+
connection.loginfo(this, `AUTH user="${user}" success=${auth_success}`)
|
|
68
|
+
cb(auth_success)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
exports.get_sock_opts = function (user) {
|
|
73
|
+
this.sock_opts = {
|
|
74
|
+
port: 89,
|
|
75
|
+
host: '127.0.0.1',
|
|
76
|
+
sysadmin: undefined,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const domain = user.split('@')[1]
|
|
80
|
+
let sect = this.cfg.main
|
|
81
|
+
if (domain && this.cfg[domain]) sect = this.cfg[domain]
|
|
82
|
+
|
|
83
|
+
if (sect.port) this.sock_opts.port = sect.port
|
|
84
|
+
if (sect.host) this.sock_opts.host = sect.host
|
|
85
|
+
if (sect.sysadmin) this.sock_opts.sysadmin = sect.sysadmin
|
|
86
|
+
|
|
87
|
+
this.logdebug(`sock: ${this.sock_opts.host}:${this.sock_opts.port}`)
|
|
88
|
+
return this.sock_opts
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
exports.get_vpopmaild_socket = function (user) {
|
|
92
|
+
this.get_sock_opts(user)
|
|
93
|
+
|
|
94
|
+
const socket = new net.Socket()
|
|
95
|
+
socket.connect(this.sock_opts.port, this.sock_opts.host)
|
|
96
|
+
socket.setTimeout(300 * 1000)
|
|
97
|
+
socket.setEncoding('utf8')
|
|
98
|
+
|
|
99
|
+
socket.on('timeout', () => {
|
|
100
|
+
this.logerror('vpopmaild connection timed out')
|
|
101
|
+
socket.end()
|
|
102
|
+
})
|
|
103
|
+
socket.on('error', (err) => {
|
|
104
|
+
this.logerror(`vpopmaild connection failed: ${err}`)
|
|
105
|
+
socket.end()
|
|
106
|
+
})
|
|
107
|
+
socket.on('connect', () => {
|
|
108
|
+
this.logdebug('vpopmail connected')
|
|
109
|
+
})
|
|
110
|
+
return socket
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
exports.get_plain_passwd = function (user, connection, cb) {
|
|
114
|
+
const socket = this.get_vpopmaild_socket(user)
|
|
115
|
+
if (!this.sock_opts.sysadmin) {
|
|
116
|
+
this.logerror('missing sysadmin credentials')
|
|
117
|
+
return cb(null)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const sys = this.sock_opts.sysadmin.split(':')
|
|
121
|
+
let plain_pass = null
|
|
122
|
+
let chunk_count = 0
|
|
123
|
+
|
|
124
|
+
socket.on('data', (chunk) => {
|
|
125
|
+
chunk_count++
|
|
126
|
+
this.logdebug(`${chunk_count}\t${chunk}`)
|
|
127
|
+
if (chunk_count === 1) {
|
|
128
|
+
if (/^\+OK/.test(chunk)) {
|
|
129
|
+
socket.write(`slogin ${sys[0]} ${sys[1]}\n\r`)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
this.logerror('no ok to start')
|
|
133
|
+
socket.end() // disconnect
|
|
134
|
+
}
|
|
135
|
+
// slogin reply
|
|
136
|
+
if (chunk_count === 2) {
|
|
137
|
+
if (/^\+OK/.test(chunk)) {
|
|
138
|
+
this.logdebug('login success, getting user info')
|
|
139
|
+
socket.write(`user_info ${user}\n\r`)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
this.logerror('syadmin login failed')
|
|
143
|
+
socket.end() // disconnect
|
|
144
|
+
}
|
|
145
|
+
if (chunk_count > 2) {
|
|
146
|
+
if (/^-ERR/.test(chunk)) {
|
|
147
|
+
this.lognotice(`get_plain failed: ${chunk}`)
|
|
148
|
+
socket.end() // disconnect
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
if (!/clear_text_password/.test(chunk)) {
|
|
152
|
+
return // pass might be in the next chunk
|
|
153
|
+
}
|
|
154
|
+
const pass = chunk.match(/clear_text_password\s(\S+)\s/)
|
|
155
|
+
plain_pass = pass[1]
|
|
156
|
+
socket.write('quit\n\r')
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
socket.on('end', () => {
|
|
160
|
+
cb(plain_pass ? plain_pass.toString() : plain_pass)
|
|
161
|
+
})
|
|
162
|
+
}
|