Haraka 3.1.1 → 3.1.2
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/.prettierignore +4 -0
- package/CONTRIBUTORS.md +5 -5
- package/Changes.md +62 -50
- package/Plugins.md +3 -1
- package/README.md +1 -1
- package/bin/haraka +475 -479
- package/config/outbound.ini +3 -0
- package/connection.js +1072 -1108
- package/docs/Connection.md +29 -30
- package/docs/CoreConfig.md +38 -39
- package/docs/CustomReturnCodes.md +0 -1
- package/docs/HAProxy.md +2 -2
- package/docs/Header.md +1 -1
- package/docs/Logging.md +29 -5
- package/docs/Outbound.md +93 -78
- package/docs/Plugins.md +103 -108
- package/docs/Transaction.md +49 -51
- package/docs/Tutorial.md +127 -143
- package/docs/deprecated/access.md +0 -1
- package/docs/deprecated/backscatterer.md +2 -3
- package/docs/deprecated/connect.rdns_access.md +18 -27
- package/docs/deprecated/data.headers.md +0 -1
- package/docs/deprecated/data.nomsgid.md +1 -2
- package/docs/deprecated/data.noreceived.md +1 -2
- package/docs/deprecated/data.rfc5322_header_checks.md +1 -2
- package/docs/deprecated/dkim_sign.md +13 -17
- package/docs/deprecated/dkim_verify.md +9 -17
- package/docs/deprecated/dnsbl.md +36 -38
- package/docs/deprecated/dnswl.md +41 -43
- package/docs/deprecated/lookup_rdns.strict.md +21 -34
- package/docs/deprecated/mail_from.access.md +17 -25
- package/docs/deprecated/mail_from.blocklist.md +9 -12
- package/docs/deprecated/mail_from.nobounces.md +1 -2
- package/docs/deprecated/rcpt_to.access.md +20 -27
- package/docs/deprecated/rcpt_to.blocklist.md +10 -13
- package/docs/deprecated/rcpt_to.routes.md +0 -1
- package/docs/deprecated/rdns.regexp.md +13 -15
- package/docs/plugins/aliases.md +89 -89
- package/docs/plugins/auth/auth_bridge.md +5 -7
- package/docs/plugins/auth/auth_ldap.md +11 -14
- package/docs/plugins/auth/auth_proxy.md +10 -12
- package/docs/plugins/auth/auth_vpopmaild.md +5 -6
- package/docs/plugins/auth/flat_file.md +4 -4
- package/docs/plugins/block_me.md +3 -3
- package/docs/plugins/data.signatures.md +1 -2
- package/docs/plugins/delay_deny.md +3 -4
- package/docs/plugins/max_unrecognized_commands.md +4 -4
- package/docs/plugins/prevent_credential_leaks.md +6 -6
- package/docs/plugins/process_title.md +18 -18
- package/docs/plugins/queue/deliver.md +2 -3
- package/docs/plugins/queue/discard.md +4 -4
- package/docs/plugins/queue/lmtp.md +1 -3
- package/docs/plugins/queue/qmail-queue.md +7 -9
- package/docs/plugins/queue/quarantine.md +16 -21
- package/docs/plugins/queue/rabbitmq.md +8 -11
- package/docs/plugins/queue/rabbitmq_amqplib.md +43 -39
- package/docs/plugins/queue/smtp_bridge.md +7 -10
- package/docs/plugins/queue/smtp_forward.md +42 -34
- package/docs/plugins/queue/smtp_proxy.md +30 -29
- package/docs/plugins/queue/test.md +1 -3
- package/docs/plugins/rcpt_to.in_host_list.md +6 -6
- package/docs/plugins/rcpt_to.max_count.md +1 -1
- package/docs/plugins/record_envelope_addresses.md +3 -3
- package/docs/plugins/reseed_rng.md +6 -6
- package/docs/plugins/status.md +9 -8
- package/docs/plugins/tarpit.md +7 -11
- package/docs/plugins/tls.md +12 -17
- package/docs/plugins/toobusy.md +4 -4
- package/docs/plugins/xclient.md +3 -3
- package/docs/tutorials/Migrating_from_v1_to_v2.md +19 -41
- package/docs/tutorials/SettingUpOutbound.md +6 -9
- package/endpoint.js +35 -38
- package/eslint.config.mjs +22 -19
- package/haraka.js +42 -47
- package/host_pool.js +75 -79
- package/http/html/404.html +45 -49
- package/http/html/index.html +39 -28
- package/http/package.json +2 -4
- package/line_socket.js +27 -28
- package/logger.js +182 -201
- package/outbound/client_pool.js +33 -33
- package/outbound/config.js +64 -59
- package/outbound/fsync_writestream.js +24 -25
- package/outbound/hmail.js +888 -835
- package/outbound/index.js +194 -187
- package/outbound/qfile.js +49 -52
- package/outbound/queue.js +197 -190
- package/outbound/timer_queue.js +41 -43
- package/outbound/tls.js +68 -61
- package/outbound/todo.js +11 -11
- package/package.json +32 -32
- package/plugins/.eslintrc.yaml +0 -1
- package/plugins/auth/auth_base.js +123 -127
- package/plugins/auth/auth_bridge.js +7 -7
- package/plugins/auth/auth_proxy.js +121 -126
- package/plugins/auth/auth_vpopmaild.js +84 -85
- package/plugins/auth/flat_file.js +18 -17
- package/plugins/block_me.js +31 -31
- package/plugins/data.signatures.js +13 -13
- package/plugins/delay_deny.js +65 -61
- package/plugins/prevent_credential_leaks.js +23 -23
- package/plugins/process_title.js +125 -128
- package/plugins/profile.js +5 -5
- package/plugins/queue/deliver.js +3 -3
- package/plugins/queue/discard.js +13 -14
- package/plugins/queue/lmtp.js +16 -17
- package/plugins/queue/qmail-queue.js +54 -55
- package/plugins/queue/quarantine.js +68 -70
- package/plugins/queue/rabbitmq.js +80 -87
- package/plugins/queue/rabbitmq_amqplib.js +75 -54
- package/plugins/queue/smtp_bridge.js +16 -16
- package/plugins/queue/smtp_forward.js +175 -179
- package/plugins/queue/smtp_proxy.js +69 -71
- package/plugins/queue/test.js +9 -9
- package/plugins/rcpt_to.host_list_base.js +30 -34
- package/plugins/rcpt_to.in_host_list.js +19 -19
- package/plugins/record_envelope_addresses.js +4 -4
- package/plugins/reseed_rng.js +4 -4
- package/plugins/status.js +90 -97
- package/plugins/tarpit.js +25 -14
- package/plugins/tls.js +68 -68
- package/plugins/toobusy.js +21 -23
- package/plugins/xclient.js +51 -53
- package/plugins.js +276 -293
- package/rfc1869.js +30 -35
- package/server.js +308 -299
- package/smtp_client.js +244 -228
- package/test/.eslintrc.yaml +0 -1
- package/test/connection.js +127 -134
- package/test/endpoint.js +53 -47
- package/test/fixtures/line_socket.js +12 -12
- package/test/fixtures/util_hmailitem.js +89 -85
- package/test/host_pool.js +90 -92
- package/test/installation/plugins/base_plugin.js +2 -2
- package/test/installation/plugins/folder_plugin/index.js +2 -3
- package/test/installation/plugins/inherits.js +3 -3
- package/test/installation/plugins/load_first.js +2 -3
- package/test/installation/plugins/plugin.js +1 -3
- package/test/installation/plugins/tls.js +2 -4
- package/test/logger.js +135 -116
- package/test/outbound/hmail.js +49 -35
- package/test/outbound/index.js +118 -101
- package/test/outbound/qfile.js +51 -53
- package/test/outbound_bounce_net_errors.js +84 -69
- package/test/outbound_bounce_rfc3464.js +235 -165
- package/test/plugins/auth/auth_base.js +420 -279
- package/test/plugins/auth/auth_vpopmaild.js +38 -39
- package/test/plugins/queue/smtp_forward.js +126 -104
- package/test/plugins/rcpt_to.host_list_base.js +85 -67
- package/test/plugins/rcpt_to.in_host_list.js +159 -112
- package/test/plugins/status.js +71 -64
- package/test/plugins/tls.js +37 -34
- package/test/plugins.js +97 -92
- package/test/rfc1869.js +19 -26
- package/test/server.js +293 -272
- package/test/smtp_client.js +180 -176
- package/test/tls_socket.js +62 -66
- package/test/transaction.js +159 -160
- package/tls_socket.js +331 -333
- package/transaction.js +129 -137
|
@@ -2,163 +2,161 @@
|
|
|
2
2
|
|
|
3
3
|
const net = require('node:net')
|
|
4
4
|
|
|
5
|
-
const utils = require('haraka-utils')
|
|
5
|
+
const utils = require('haraka-utils')
|
|
6
6
|
const net_utils = require('haraka-net-utils')
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const tls_socket = require('./tls_socket')
|
|
9
|
+
|
|
10
|
+
const smtp_regexp = /^(\d{3})([ -])(.*)/
|
|
9
11
|
|
|
10
12
|
exports.register = function () {
|
|
11
|
-
this.inherits('auth/auth_base')
|
|
12
|
-
this.load_tls_ini()
|
|
13
|
+
this.inherits('auth/auth_base')
|
|
14
|
+
this.load_tls_ini()
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
exports.load_tls_ini = function () {
|
|
16
18
|
this.tls_cfg = this.config.get('tls.ini', () => {
|
|
17
|
-
this.load_tls_ini()
|
|
18
|
-
})
|
|
19
|
+
this.load_tls_ini()
|
|
20
|
+
})
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
exports.hook_capabilities = (next, connection) => {
|
|
22
24
|
if (connection.tls.enabled) {
|
|
23
|
-
const methods = [
|
|
24
|
-
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
25
|
-
connection.notes.allowed_auth_methods = methods
|
|
25
|
+
const methods = ['PLAIN', 'LOGIN']
|
|
26
|
+
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
27
|
+
connection.notes.allowed_auth_methods = methods
|
|
26
28
|
}
|
|
27
|
-
next()
|
|
29
|
+
next()
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
31
|
-
let domain = /@([^@]+)$/.exec(user)
|
|
33
|
+
let domain = /@([^@]+)$/.exec(user)
|
|
32
34
|
if (domain) {
|
|
33
|
-
domain = domain[1].toLowerCase()
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
35
|
+
domain = domain[1].toLowerCase()
|
|
36
|
+
} else {
|
|
36
37
|
// AUTH user not in user@domain.com format
|
|
37
|
-
connection.logerror(this, `AUTH user="${user}" error="not in required format"`)
|
|
38
|
-
return cb(false)
|
|
38
|
+
connection.logerror(this, `AUTH user="${user}" error="not in required format"`)
|
|
39
|
+
return cb(false)
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// Check if domain exists in configuration file
|
|
42
|
-
const config = this.config.get('auth_proxy.ini')
|
|
43
|
+
const config = this.config.get('auth_proxy.ini')
|
|
43
44
|
if (!config.domains[domain]) {
|
|
44
|
-
connection.logerror(this, `AUTH user="${user}" error="domain '${domain}' is not defined"`)
|
|
45
|
-
return cb(false)
|
|
45
|
+
connection.logerror(this, `AUTH user="${user}" error="domain '${domain}' is not defined"`)
|
|
46
|
+
return cb(false)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
this.try_auth_proxy(connection, config.domains[domain].split(/[,; ]/), user, passwd, cb)
|
|
49
|
+
this.try_auth_proxy(connection, config.domains[domain].split(/[,; ]/), user, passwd, cb)
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
52
|
-
if (!hosts || (hosts && !hosts.length)) return cb(false)
|
|
53
|
+
if (!hosts || (hosts && !hosts.length)) return cb(false)
|
|
53
54
|
if (typeof hosts !== 'object') {
|
|
54
|
-
hosts = [
|
|
55
|
+
hosts = [hosts]
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
const self = this
|
|
58
|
-
let [
|
|
58
|
+
const self = this
|
|
59
|
+
let [host, port] = hosts.shift().split(':') /* eslint prefer-const: 0 */
|
|
59
60
|
if (!port) port = 25
|
|
60
|
-
let methods = []
|
|
61
|
-
let auth_complete = false
|
|
62
|
-
let auth_success = false
|
|
63
|
-
let command = 'connect'
|
|
64
|
-
let response = []
|
|
65
|
-
let secure = false
|
|
66
|
-
|
|
67
|
-
const socket =
|
|
61
|
+
let methods = []
|
|
62
|
+
let auth_complete = false
|
|
63
|
+
let auth_success = false
|
|
64
|
+
let command = 'connect'
|
|
65
|
+
let response = []
|
|
66
|
+
let secure = false
|
|
67
|
+
|
|
68
|
+
const socket = tls_socket.connect({ host, port })
|
|
68
69
|
net_utils.add_line_processor(socket)
|
|
69
|
-
connection.logdebug(this, `attempting connection to host=${host} port=${port}`)
|
|
70
|
-
socket.setTimeout(30 * 1000)
|
|
71
|
-
socket.on('connect', () => {
|
|
70
|
+
connection.logdebug(this, `attempting connection to host=${host} port=${port}`)
|
|
71
|
+
socket.setTimeout(30 * 1000)
|
|
72
|
+
socket.on('connect', () => {})
|
|
72
73
|
socket.on('close', () => {
|
|
73
74
|
if (!auth_complete) {
|
|
74
75
|
// Try next host
|
|
75
|
-
return this.try_auth_proxy(connection, hosts, user, passwd, cb)
|
|
76
|
+
return this.try_auth_proxy(connection, hosts, user, passwd, cb)
|
|
76
77
|
}
|
|
77
|
-
connection.loginfo(this, `AUTH user="${user}" host="${host}" success=${auth_success}`)
|
|
78
|
-
cb(auth_success)
|
|
79
|
-
})
|
|
78
|
+
connection.loginfo(this, `AUTH user="${user}" host="${host}" success=${auth_success}`)
|
|
79
|
+
cb(auth_success)
|
|
80
|
+
})
|
|
80
81
|
socket.on('timeout', () => {
|
|
81
|
-
connection.logerror(this,
|
|
82
|
-
socket.end()
|
|
82
|
+
connection.logerror(this, 'connection timed out')
|
|
83
|
+
socket.end()
|
|
83
84
|
// Try next host
|
|
84
|
-
this.try_auth_proxy(connection, hosts, user, passwd, cb)
|
|
85
|
-
})
|
|
86
|
-
socket.on('error', err => {
|
|
87
|
-
connection.logerror(this, `connection failed to host ${host}: ${err}`)
|
|
88
|
-
socket.end()
|
|
89
|
-
})
|
|
85
|
+
this.try_auth_proxy(connection, hosts, user, passwd, cb)
|
|
86
|
+
})
|
|
87
|
+
socket.on('error', (err) => {
|
|
88
|
+
connection.logerror(this, `connection failed to host ${host}: ${err}`)
|
|
89
|
+
socket.end()
|
|
90
|
+
})
|
|
90
91
|
socket.send_command = function (cmd, data) {
|
|
91
|
-
let line = cmd + (data ?
|
|
92
|
+
let line = cmd + (data ? ` ${data}` : '')
|
|
92
93
|
if (cmd === 'dot') {
|
|
93
|
-
line = '.'
|
|
94
|
+
line = '.'
|
|
94
95
|
}
|
|
95
|
-
connection.logprotocol(self, `C: ${line}`)
|
|
96
|
-
command = cmd.toLowerCase()
|
|
97
|
-
this.write(`${line}\r\n`)
|
|
96
|
+
connection.logprotocol(self, `C: ${line}`)
|
|
97
|
+
command = cmd.toLowerCase()
|
|
98
|
+
this.write(`${line}\r\n`)
|
|
98
99
|
// Clear response buffer from previous command
|
|
99
|
-
response = []
|
|
100
|
-
}
|
|
100
|
+
response = []
|
|
101
|
+
}
|
|
101
102
|
socket.on('line', function (line) {
|
|
102
|
-
connection.logprotocol(self, `S: ${line}`)
|
|
103
|
-
const matches = smtp_regexp.exec(line)
|
|
103
|
+
connection.logprotocol(self, `S: ${line}`)
|
|
104
|
+
const matches = smtp_regexp.exec(line)
|
|
104
105
|
if (!matches) {
|
|
105
|
-
connection.logerror(self, `unrecognised response: ${line}`)
|
|
106
|
-
socket.end()
|
|
107
|
-
return
|
|
106
|
+
connection.logerror(self, `unrecognised response: ${line}`)
|
|
107
|
+
socket.end()
|
|
108
|
+
return
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
const code = matches[1]
|
|
111
|
-
const cont = matches[2]
|
|
112
|
-
const rest = matches[3]
|
|
113
|
-
response.push(rest)
|
|
114
|
-
if (cont !== ' ') return
|
|
111
|
+
const code = matches[1]
|
|
112
|
+
const cont = matches[2]
|
|
113
|
+
const rest = matches[3]
|
|
114
|
+
response.push(rest)
|
|
115
|
+
if (cont !== ' ') return
|
|
115
116
|
|
|
116
|
-
let key
|
|
117
|
-
let cert
|
|
117
|
+
let key
|
|
118
|
+
let cert
|
|
118
119
|
|
|
119
|
-
connection.logdebug(self, `command state: ${command}`)
|
|
120
|
+
connection.logdebug(self, `command state: ${command}`)
|
|
120
121
|
if (command === 'ehlo') {
|
|
121
122
|
if (code.startsWith('5')) {
|
|
122
123
|
// EHLO command rejected; abort
|
|
123
|
-
socket.send_command('QUIT')
|
|
124
|
-
return
|
|
124
|
+
socket.send_command('QUIT')
|
|
125
|
+
return
|
|
125
126
|
}
|
|
126
127
|
// Parse CAPABILITIES
|
|
127
128
|
for (const i in response) {
|
|
128
129
|
if (/^STARTTLS/.test(response[i])) {
|
|
129
|
-
if (secure) continue
|
|
130
|
+
if (secure) continue // silly remote, we've already upgraded
|
|
130
131
|
// Use TLS opportunistically if we found the key and certificate
|
|
131
|
-
key = self.config.get(self.tls_cfg.main.key || 'tls_key.pem', 'binary')
|
|
132
|
-
cert = self.config.get(self.tls_cfg.main.cert || 'tls_cert.pem', 'binary')
|
|
132
|
+
key = self.config.get(self.tls_cfg.main.key || 'tls_key.pem', 'binary')
|
|
133
|
+
cert = self.config.get(self.tls_cfg.main.cert || 'tls_cert.pem', 'binary')
|
|
133
134
|
if (key && cert) {
|
|
134
135
|
this.on('secure', () => {
|
|
135
|
-
if (secure) return
|
|
136
|
-
secure = true
|
|
137
|
-
socket.send_command('EHLO', connection.local.host)
|
|
138
|
-
})
|
|
139
|
-
socket.send_command('STARTTLS')
|
|
140
|
-
return
|
|
136
|
+
if (secure) return
|
|
137
|
+
secure = true
|
|
138
|
+
socket.send_command('EHLO', connection.local.host)
|
|
139
|
+
})
|
|
140
|
+
socket.send_command('STARTTLS')
|
|
141
|
+
return
|
|
141
142
|
}
|
|
142
|
-
}
|
|
143
|
-
else if (/^AUTH /.test(response[i])) {
|
|
143
|
+
} else if (/^AUTH /.test(response[i])) {
|
|
144
144
|
// Parse supported AUTH methods
|
|
145
|
-
const parse = /^AUTH (.+)$/.exec(response[i])
|
|
146
|
-
methods = parse[1].split(/\s+/)
|
|
147
|
-
connection.logdebug(self, `found supported AUTH methods: ${methods}`)
|
|
145
|
+
const parse = /^AUTH (.+)$/.exec(response[i])
|
|
146
|
+
methods = parse[1].split(/\s+/)
|
|
147
|
+
connection.logdebug(self, `found supported AUTH methods: ${methods}`)
|
|
148
148
|
// Prefer PLAIN as it's easiest
|
|
149
149
|
if (methods.includes('PLAIN')) {
|
|
150
|
-
socket.send_command('AUTH'
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
150
|
+
socket.send_command('AUTH', `PLAIN ${utils.base64(`\0${user}\0${passwd}`)}`)
|
|
151
|
+
return
|
|
152
|
+
} else if (methods.includes('LOGIN')) {
|
|
153
|
+
socket.send_command('AUTH', 'LOGIN')
|
|
154
|
+
return
|
|
155
|
+
} else {
|
|
158
156
|
// No compatible methods; abort...
|
|
159
|
-
connection.logdebug(self, 'no compatible AUTH methods')
|
|
160
|
-
socket.send_command('QUIT')
|
|
161
|
-
return
|
|
157
|
+
connection.logdebug(self, 'no compatible AUTH methods')
|
|
158
|
+
socket.send_command('QUIT')
|
|
159
|
+
return
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
162
|
}
|
|
@@ -167,60 +165,57 @@ exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
|
167
165
|
// Handle LOGIN
|
|
168
166
|
if (code.startsWith('3') && response[0] === 'VXNlcm5hbWU6') {
|
|
169
167
|
// Write to the socket directly to keep the state at 'auth'
|
|
170
|
-
this.write(`${utils.base64(user)}\r\n`)
|
|
171
|
-
response = []
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return;
|
|
168
|
+
this.write(`${utils.base64(user)}\r\n`)
|
|
169
|
+
response = []
|
|
170
|
+
return
|
|
171
|
+
} else if (code.startsWith('3') && response[0] === 'UGFzc3dvcmQ6') {
|
|
172
|
+
this.write(`${utils.base64(passwd)}\r\n`)
|
|
173
|
+
response = []
|
|
174
|
+
return
|
|
178
175
|
}
|
|
179
176
|
if (code.startsWith('5')) {
|
|
180
177
|
// Initial attempt failed; strip domain and retry.
|
|
181
178
|
const u = /^([^@]+)@.+$/.exec(user)
|
|
182
179
|
if (u) {
|
|
183
|
-
user = u[1]
|
|
180
|
+
user = u[1]
|
|
184
181
|
if (methods.includes('PLAIN')) {
|
|
185
|
-
socket.send_command('AUTH', `PLAIN ${utils.base64(`\0${user}\0${passwd}`)}`)
|
|
182
|
+
socket.send_command('AUTH', `PLAIN ${utils.base64(`\0${user}\0${passwd}`)}`)
|
|
183
|
+
} else if (methods.includes('LOGIN')) {
|
|
184
|
+
socket.send_command('AUTH', 'LOGIN')
|
|
186
185
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
186
|
+
return
|
|
187
|
+
} else {
|
|
193
188
|
// Don't attempt any other hosts
|
|
194
|
-
auth_complete = true
|
|
189
|
+
auth_complete = true
|
|
195
190
|
}
|
|
196
191
|
}
|
|
197
192
|
}
|
|
198
193
|
if (/^[345]/.test(code)) {
|
|
199
194
|
// Got an unhandled error
|
|
200
|
-
connection.logdebug(self, `error: ${line}`)
|
|
201
|
-
socket.send_command('QUIT')
|
|
202
|
-
return
|
|
195
|
+
connection.logdebug(self, `error: ${line}`)
|
|
196
|
+
socket.send_command('QUIT')
|
|
197
|
+
return
|
|
203
198
|
}
|
|
204
199
|
switch (command) {
|
|
205
200
|
case 'starttls':
|
|
206
|
-
this.upgrade({ key, cert })
|
|
207
|
-
break
|
|
201
|
+
this.upgrade({ key, cert })
|
|
202
|
+
break
|
|
208
203
|
case 'connect':
|
|
209
|
-
socket.send_command('EHLO', connection.local.host)
|
|
210
|
-
break
|
|
204
|
+
socket.send_command('EHLO', connection.local.host)
|
|
205
|
+
break
|
|
211
206
|
case 'auth':
|
|
212
207
|
// AUTH was successful
|
|
213
|
-
auth_complete = true
|
|
214
|
-
auth_success = true
|
|
215
|
-
socket.send_command('QUIT')
|
|
216
|
-
break
|
|
208
|
+
auth_complete = true
|
|
209
|
+
auth_success = true
|
|
210
|
+
socket.send_command('QUIT')
|
|
211
|
+
break
|
|
217
212
|
case 'ehlo':
|
|
218
213
|
case 'helo':
|
|
219
214
|
case 'quit':
|
|
220
|
-
socket.end()
|
|
221
|
-
break
|
|
215
|
+
socket.end()
|
|
216
|
+
break
|
|
222
217
|
default:
|
|
223
|
-
throw new Error(`[auth/auth_proxy] unknown command: ${command}`)
|
|
218
|
+
throw new Error(`[auth/auth_proxy] unknown command: ${command}`)
|
|
224
219
|
}
|
|
225
|
-
})
|
|
220
|
+
})
|
|
226
221
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Auth against vpopmaild
|
|
2
2
|
|
|
3
|
-
const net = require('node:net')
|
|
3
|
+
const net = require('node:net')
|
|
4
4
|
|
|
5
5
|
exports.register = function () {
|
|
6
|
-
this.inherits('auth/auth_base')
|
|
7
|
-
this.blankout_password=true
|
|
6
|
+
this.inherits('auth/auth_base')
|
|
7
|
+
this.blankout_password = true
|
|
8
8
|
|
|
9
|
-
this.load_vpopmaild_ini()
|
|
9
|
+
this.load_vpopmaild_ini()
|
|
10
10
|
|
|
11
11
|
if (this.cfg.main.constrain_sender) {
|
|
12
12
|
this.register_hook('mail', 'constrain_sender')
|
|
@@ -14,150 +14,149 @@ exports.register = function () {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
exports.load_vpopmaild_ini = function () {
|
|
17
|
-
this.cfg = this.config.get(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
+
)
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
exports.hook_capabilities = function (next, connection) {
|
|
28
|
-
if (!connection.tls.enabled) return next()
|
|
29
|
+
if (!connection.tls.enabled) return next()
|
|
29
30
|
|
|
30
|
-
const methods = [
|
|
31
|
-
if (this.cfg.main.sysadmin) methods.push('CRAM-MD5')
|
|
31
|
+
const methods = ['PLAIN', 'LOGIN']
|
|
32
|
+
if (this.cfg.main.sysadmin) methods.push('CRAM-MD5')
|
|
32
33
|
|
|
33
|
-
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
34
|
-
connection.notes.allowed_auth_methods = methods
|
|
34
|
+
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
35
|
+
connection.notes.allowed_auth_methods = methods
|
|
35
36
|
|
|
36
|
-
next()
|
|
37
|
+
next()
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
41
|
+
let chunk_count = 0
|
|
42
|
+
let auth_success = false
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
const socket = this.get_vpopmaild_socket(user)
|
|
45
|
+
socket.setEncoding('utf8')
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
socket.on('data', chunk => {
|
|
48
|
-
chunk_count++;
|
|
47
|
+
socket.on('data', (chunk) => {
|
|
48
|
+
chunk_count++
|
|
49
49
|
if (chunk_count === 1) {
|
|
50
50
|
if (/^\+OK/.test(chunk)) {
|
|
51
|
-
socket.write(`slogin ${user} ${passwd}\n\r`)
|
|
52
|
-
return
|
|
51
|
+
socket.write(`slogin ${user} ${passwd}\n\r`)
|
|
52
|
+
return
|
|
53
53
|
}
|
|
54
|
-
socket.end()
|
|
54
|
+
socket.end()
|
|
55
55
|
}
|
|
56
56
|
if (chunk_count === 2) {
|
|
57
|
-
if (/^\+OK/.test(chunk)) {
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
if (/^\+OK/.test(chunk)) {
|
|
58
|
+
// slogin reply
|
|
59
|
+
auth_success = true
|
|
60
|
+
socket.write('quit\n\r')
|
|
60
61
|
}
|
|
61
|
-
socket.end()
|
|
62
|
+
socket.end() // disconnect
|
|
62
63
|
}
|
|
63
64
|
})
|
|
64
65
|
|
|
65
66
|
socket.on('end', () => {
|
|
66
|
-
connection.loginfo(this, `AUTH user="${user}" success=${auth_success}`)
|
|
67
|
-
cb(auth_success)
|
|
67
|
+
connection.loginfo(this, `AUTH user="${user}" success=${auth_success}`)
|
|
68
|
+
cb(auth_success)
|
|
68
69
|
})
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
exports.get_sock_opts = function (user) {
|
|
72
|
-
|
|
73
73
|
this.sock_opts = {
|
|
74
74
|
port: 89,
|
|
75
75
|
host: '127.0.0.1',
|
|
76
76
|
sysadmin: undefined,
|
|
77
|
-
}
|
|
77
|
+
}
|
|
78
78
|
|
|
79
|
-
const domain =
|
|
80
|
-
let sect = this.cfg.main
|
|
81
|
-
if (domain && this.cfg[domain]) sect = this.cfg[domain]
|
|
79
|
+
const domain = user.split('@')[1]
|
|
80
|
+
let sect = this.cfg.main
|
|
81
|
+
if (domain && this.cfg[domain]) sect = this.cfg[domain]
|
|
82
82
|
|
|
83
|
-
if (sect.port)
|
|
84
|
-
if (sect.host)
|
|
85
|
-
if (sect.sysadmin) this.sock_opts.sysadmin = sect.sysadmin
|
|
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
86
|
|
|
87
|
-
this.logdebug(`sock: ${this.sock_opts.host}:${this.sock_opts.port}`)
|
|
88
|
-
return this.sock_opts
|
|
87
|
+
this.logdebug(`sock: ${this.sock_opts.host}:${this.sock_opts.port}`)
|
|
88
|
+
return this.sock_opts
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
exports.get_vpopmaild_socket = function (user) {
|
|
92
|
-
this.get_sock_opts(user)
|
|
92
|
+
this.get_sock_opts(user)
|
|
93
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')
|
|
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
98
|
|
|
99
99
|
socket.on('timeout', () => {
|
|
100
|
-
this.logerror(
|
|
101
|
-
socket.end()
|
|
100
|
+
this.logerror('vpopmaild connection timed out')
|
|
101
|
+
socket.end()
|
|
102
102
|
})
|
|
103
|
-
socket.on('error', err => {
|
|
104
|
-
this.logerror(`vpopmaild connection failed: ${err}`)
|
|
105
|
-
socket.end()
|
|
103
|
+
socket.on('error', (err) => {
|
|
104
|
+
this.logerror(`vpopmaild connection failed: ${err}`)
|
|
105
|
+
socket.end()
|
|
106
106
|
})
|
|
107
107
|
socket.on('connect', () => {
|
|
108
|
-
this.logdebug('vpopmail connected')
|
|
108
|
+
this.logdebug('vpopmail connected')
|
|
109
109
|
})
|
|
110
|
-
return socket
|
|
110
|
+
return socket
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
exports.get_plain_passwd = function (user, connection, cb) {
|
|
114
|
-
|
|
115
|
-
const socket = this.get_vpopmaild_socket(user);
|
|
114
|
+
const socket = this.get_vpopmaild_socket(user)
|
|
116
115
|
if (!this.sock_opts.sysadmin) {
|
|
117
|
-
this.logerror(
|
|
118
|
-
return cb(null)
|
|
116
|
+
this.logerror('missing sysadmin credentials')
|
|
117
|
+
return cb(null)
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
const sys = this.sock_opts.sysadmin.split(':')
|
|
122
|
-
let plain_pass = null
|
|
123
|
-
let chunk_count = 0
|
|
120
|
+
const sys = this.sock_opts.sysadmin.split(':')
|
|
121
|
+
let plain_pass = null
|
|
122
|
+
let chunk_count = 0
|
|
124
123
|
|
|
125
|
-
socket.on('data', chunk => {
|
|
126
|
-
chunk_count
|
|
127
|
-
this.logdebug(`${chunk_count}\t${chunk}`)
|
|
124
|
+
socket.on('data', (chunk) => {
|
|
125
|
+
chunk_count++
|
|
126
|
+
this.logdebug(`${chunk_count}\t${chunk}`)
|
|
128
127
|
if (chunk_count === 1) {
|
|
129
128
|
if (/^\+OK/.test(chunk)) {
|
|
130
|
-
socket.write(`slogin ${sys[0]} ${sys[1]}\n\r`)
|
|
131
|
-
return
|
|
129
|
+
socket.write(`slogin ${sys[0]} ${sys[1]}\n\r`)
|
|
130
|
+
return
|
|
132
131
|
}
|
|
133
|
-
this.logerror(
|
|
134
|
-
socket.end()
|
|
132
|
+
this.logerror('no ok to start')
|
|
133
|
+
socket.end() // disconnect
|
|
135
134
|
}
|
|
136
135
|
// slogin reply
|
|
137
136
|
if (chunk_count === 2) {
|
|
138
137
|
if (/^\+OK/.test(chunk)) {
|
|
139
|
-
this.logdebug('login success, getting user info')
|
|
140
|
-
socket.write(`user_info ${user}\n\r`)
|
|
141
|
-
return
|
|
138
|
+
this.logdebug('login success, getting user info')
|
|
139
|
+
socket.write(`user_info ${user}\n\r`)
|
|
140
|
+
return
|
|
142
141
|
}
|
|
143
|
-
this.logerror(
|
|
144
|
-
socket.end()
|
|
142
|
+
this.logerror('syadmin login failed')
|
|
143
|
+
socket.end() // disconnect
|
|
145
144
|
}
|
|
146
145
|
if (chunk_count > 2) {
|
|
147
146
|
if (/^-ERR/.test(chunk)) {
|
|
148
|
-
this.lognotice(`get_plain failed: ${chunk}`)
|
|
149
|
-
socket.end()
|
|
150
|
-
return
|
|
147
|
+
this.lognotice(`get_plain failed: ${chunk}`)
|
|
148
|
+
socket.end() // disconnect
|
|
149
|
+
return
|
|
151
150
|
}
|
|
152
151
|
if (!/clear_text_password/.test(chunk)) {
|
|
153
|
-
return
|
|
152
|
+
return // pass might be in the next chunk
|
|
154
153
|
}
|
|
155
|
-
const pass = chunk.match(/clear_text_password\s(\S+)\s/)
|
|
156
|
-
plain_pass = pass[1]
|
|
157
|
-
socket.write(
|
|
154
|
+
const pass = chunk.match(/clear_text_password\s(\S+)\s/)
|
|
155
|
+
plain_pass = pass[1]
|
|
156
|
+
socket.write('quit\n\r')
|
|
158
157
|
}
|
|
159
|
-
})
|
|
158
|
+
})
|
|
160
159
|
socket.on('end', () => {
|
|
161
|
-
cb(plain_pass ? plain_pass.toString() : plain_pass)
|
|
162
|
-
})
|
|
160
|
+
cb(plain_pass ? plain_pass.toString() : plain_pass)
|
|
161
|
+
})
|
|
163
162
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Auth against a flat file
|
|
2
2
|
|
|
3
3
|
exports.register = function () {
|
|
4
|
-
this.inherits('auth/auth_base')
|
|
5
|
-
this.load_flat_ini()
|
|
4
|
+
this.inherits('auth/auth_base')
|
|
5
|
+
this.load_flat_ini()
|
|
6
6
|
|
|
7
7
|
if (this.cfg.core.constrain_sender) {
|
|
8
8
|
this.register_hook('mail', 'constrain_sender')
|
|
@@ -10,34 +10,35 @@ exports.register = function () {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
exports.load_flat_ini = function () {
|
|
13
|
-
this.cfg = this.config.get(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
this.cfg = this.config.get(
|
|
14
|
+
'auth_flat_file.ini',
|
|
15
|
+
{
|
|
16
|
+
booleans: ['+core.constrain_sender'],
|
|
17
|
+
},
|
|
18
|
+
() => {
|
|
19
|
+
this.load_flat_ini()
|
|
20
|
+
},
|
|
21
|
+
)
|
|
21
22
|
|
|
22
23
|
if (this.cfg.users === undefined) this.cfg.users = {}
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
exports.hook_capabilities = function (next, connection) {
|
|
26
27
|
if (!connection.remote.is_private && !connection.tls.enabled) {
|
|
27
|
-
connection.logdebug(this,
|
|
28
|
-
return next()
|
|
28
|
+
connection.logdebug(this, 'Auth disabled for insecure public connection')
|
|
29
|
+
return next()
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
const methods = this.cfg.core?.methods ? this.cfg.core.methods.split(',') : null
|
|
32
33
|
if (methods && methods.length > 0) {
|
|
33
|
-
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
34
|
-
connection.notes.allowed_auth_methods = methods
|
|
34
|
+
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
35
|
+
connection.notes.allowed_auth_methods = methods
|
|
35
36
|
}
|
|
36
|
-
next()
|
|
37
|
+
next()
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
exports.get_plain_passwd = function (user, connection, cb) {
|
|
40
|
-
if (this.cfg.users[user]) return cb(this.cfg.users[user].toString())
|
|
41
|
+
if (this.cfg.users[user]) return cb(this.cfg.users[user].toString())
|
|
41
42
|
|
|
42
|
-
cb()
|
|
43
|
+
cb()
|
|
43
44
|
}
|