Haraka 3.1.0 → 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 +69 -50
- package/Plugins.md +3 -1
- package/README.md +1 -1
- package/bin/haraka +475 -478
- 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 +34 -27
- 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 +38 -33
- 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
package/connection.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
'use strict'
|
|
1
|
+
'use strict'
|
|
2
2
|
// a single connection
|
|
3
3
|
|
|
4
|
-
const dns
|
|
5
|
-
const net
|
|
6
|
-
const os
|
|
4
|
+
const dns = require('node:dns')
|
|
5
|
+
const net = require('node:net')
|
|
6
|
+
const os = require('node:os')
|
|
7
7
|
|
|
8
8
|
// npm libs
|
|
9
|
-
const ipaddr
|
|
10
|
-
const config
|
|
11
|
-
const constants
|
|
12
|
-
const net_utils
|
|
13
|
-
const Notes
|
|
14
|
-
const utils
|
|
15
|
-
const { Address } = require('address-rfc2821')
|
|
16
|
-
const ResultStore = require('haraka-results')
|
|
9
|
+
const ipaddr = require('ipaddr.js')
|
|
10
|
+
const config = require('haraka-config')
|
|
11
|
+
const constants = require('haraka-constants')
|
|
12
|
+
const net_utils = require('haraka-net-utils')
|
|
13
|
+
const Notes = require('haraka-notes')
|
|
14
|
+
const utils = require('haraka-utils')
|
|
15
|
+
const { Address } = require('address-rfc2821')
|
|
16
|
+
const ResultStore = require('haraka-results')
|
|
17
17
|
|
|
18
18
|
// Haraka libs
|
|
19
|
-
const logger
|
|
20
|
-
const trans
|
|
21
|
-
const plugins
|
|
22
|
-
const rfc1869
|
|
23
|
-
const outbound
|
|
19
|
+
const logger = require('./logger')
|
|
20
|
+
const trans = require('./transaction')
|
|
21
|
+
const plugins = require('./plugins')
|
|
22
|
+
const rfc1869 = require('./rfc1869')
|
|
23
|
+
const outbound = require('./outbound')
|
|
24
24
|
|
|
25
|
-
const states
|
|
25
|
+
const states = constants.connection.state
|
|
26
26
|
|
|
27
27
|
const cfg = config.get('connection.ini', {
|
|
28
28
|
booleans: [
|
|
@@ -31,402 +31,395 @@ const cfg = config.get('connection.ini', {
|
|
|
31
31
|
'+headers.add_received',
|
|
32
32
|
'+headers.show_version',
|
|
33
33
|
'+headers.clean_auth_results',
|
|
34
|
-
]
|
|
35
|
-
})
|
|
34
|
+
],
|
|
35
|
+
})
|
|
36
36
|
|
|
37
|
-
const haproxy_hosts_ipv4 = []
|
|
38
|
-
const haproxy_hosts_ipv6 = []
|
|
37
|
+
const haproxy_hosts_ipv4 = []
|
|
38
|
+
const haproxy_hosts_ipv6 = []
|
|
39
39
|
|
|
40
40
|
for (const ip of cfg.haproxy.hosts) {
|
|
41
|
-
if (!ip) continue
|
|
41
|
+
if (!ip) continue
|
|
42
42
|
if (net.isIPv6(ip.split('/')[0])) {
|
|
43
|
-
haproxy_hosts_ipv6.push([ipaddr.IPv6.parse(ip.split('/')[0]), parseInt(ip.split('/')[1] || 64)])
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
haproxy_hosts_ipv4.push([ipaddr.IPv4.parse(ip.split('/')[0]), parseInt(ip.split('/')[1] || 32)]);
|
|
43
|
+
haproxy_hosts_ipv6.push([ipaddr.IPv6.parse(ip.split('/')[0]), parseInt(ip.split('/')[1] || 64)])
|
|
44
|
+
} else {
|
|
45
|
+
haproxy_hosts_ipv4.push([ipaddr.IPv4.parse(ip.split('/')[0]), parseInt(ip.split('/')[1] || 32)])
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
class Connection {
|
|
51
|
-
constructor
|
|
52
|
-
this.client = client
|
|
53
|
-
this.server = server
|
|
50
|
+
constructor(client, server, smtp_cfg) {
|
|
51
|
+
this.client = client
|
|
52
|
+
this.server = server
|
|
54
53
|
|
|
55
54
|
this.local = {
|
|
56
55
|
ip: null,
|
|
57
56
|
port: null,
|
|
58
57
|
host: net_utils.get_primary_host_name(),
|
|
59
58
|
info: 'Haraka',
|
|
60
|
-
}
|
|
59
|
+
}
|
|
61
60
|
this.remote = {
|
|
62
|
-
ip:
|
|
61
|
+
ip: null,
|
|
63
62
|
port: null,
|
|
64
63
|
host: null,
|
|
65
64
|
info: null,
|
|
66
65
|
closed: false,
|
|
67
66
|
is_private: false,
|
|
68
67
|
is_local: false,
|
|
69
|
-
}
|
|
68
|
+
}
|
|
70
69
|
this.hello = {
|
|
71
70
|
host: null,
|
|
72
71
|
verb: null,
|
|
73
|
-
}
|
|
72
|
+
}
|
|
74
73
|
this.tls = {
|
|
75
74
|
enabled: false,
|
|
76
75
|
advertised: false,
|
|
77
76
|
verified: false,
|
|
78
77
|
cipher: {},
|
|
79
|
-
}
|
|
78
|
+
}
|
|
80
79
|
this.proxy = {
|
|
81
80
|
allowed: false,
|
|
82
81
|
ip: null,
|
|
83
82
|
type: null,
|
|
84
83
|
timer: null,
|
|
85
|
-
}
|
|
86
|
-
this.set('tls', 'enabled',
|
|
87
|
-
|
|
88
|
-
this.current_data = null
|
|
89
|
-
this.current_line = null
|
|
90
|
-
this.state = states.PAUSE
|
|
91
|
-
this.encoding = 'utf8'
|
|
92
|
-
this.prev_state = null
|
|
93
|
-
this.loop_code = null
|
|
94
|
-
this.loop_msg = null
|
|
95
|
-
this.uuid = utils.uuid()
|
|
96
|
-
this.notes = new Notes()
|
|
97
|
-
this.transaction = null
|
|
98
|
-
this.tran_count = 0
|
|
99
|
-
this.capabilities = null
|
|
100
|
-
this.early_talker = false
|
|
101
|
-
this.pipelining = false
|
|
102
|
-
this._relaying = false
|
|
103
|
-
this.esmtp = false
|
|
104
|
-
this.last_response = null
|
|
105
|
-
this.hooks_to_run = []
|
|
106
|
-
this.start_time = Date.now()
|
|
107
|
-
this.last_reject = ''
|
|
108
|
-
this.totalbytes = 0
|
|
84
|
+
}
|
|
85
|
+
this.set('tls', 'enabled', !!server.has_tls)
|
|
86
|
+
|
|
87
|
+
this.current_data = null
|
|
88
|
+
this.current_line = null
|
|
89
|
+
this.state = states.PAUSE
|
|
90
|
+
this.encoding = 'utf8'
|
|
91
|
+
this.prev_state = null
|
|
92
|
+
this.loop_code = null
|
|
93
|
+
this.loop_msg = null
|
|
94
|
+
this.uuid = utils.uuid()
|
|
95
|
+
this.notes = new Notes()
|
|
96
|
+
this.transaction = null
|
|
97
|
+
this.tran_count = 0
|
|
98
|
+
this.capabilities = null
|
|
99
|
+
this.early_talker = false
|
|
100
|
+
this.pipelining = false
|
|
101
|
+
this._relaying = false
|
|
102
|
+
this.esmtp = false
|
|
103
|
+
this.last_response = null
|
|
104
|
+
this.hooks_to_run = []
|
|
105
|
+
this.start_time = Date.now()
|
|
106
|
+
this.last_reject = ''
|
|
107
|
+
this.totalbytes = 0
|
|
109
108
|
this.rcpt_count = {
|
|
110
|
-
accept:
|
|
109
|
+
accept: 0,
|
|
111
110
|
tempfail: 0,
|
|
112
|
-
reject:
|
|
113
|
-
}
|
|
111
|
+
reject: 0,
|
|
112
|
+
}
|
|
114
113
|
this.msg_count = {
|
|
115
|
-
accept:
|
|
114
|
+
accept: 0,
|
|
116
115
|
tempfail: 0,
|
|
117
|
-
reject:
|
|
118
|
-
}
|
|
119
|
-
this.results = new ResultStore(this)
|
|
120
|
-
this.errors = 0
|
|
121
|
-
this.last_rcpt_msg = null
|
|
122
|
-
this.hook = null
|
|
116
|
+
reject: 0,
|
|
117
|
+
}
|
|
118
|
+
this.results = new ResultStore(this)
|
|
119
|
+
this.errors = 0
|
|
120
|
+
this.last_rcpt_msg = null
|
|
121
|
+
this.hook = null
|
|
123
122
|
if (cfg.headers.show_version) {
|
|
124
|
-
this.local.info += `/${utils.getVersion(__dirname)}
|
|
123
|
+
this.local.info += `/${utils.getVersion(__dirname)}`
|
|
125
124
|
}
|
|
126
|
-
Connection.setupClient(this)
|
|
125
|
+
Connection.setupClient(this)
|
|
127
126
|
}
|
|
128
|
-
static setupClient
|
|
129
|
-
const ip = self.client.remoteAddress
|
|
127
|
+
static setupClient(self) {
|
|
128
|
+
const ip = self.client.remoteAddress
|
|
130
129
|
if (!ip) {
|
|
131
|
-
self.logdebug('setupClient got no IP address for this connection!')
|
|
132
|
-
self.client.destroy()
|
|
133
|
-
return
|
|
130
|
+
self.logdebug('setupClient got no IP address for this connection!')
|
|
131
|
+
self.client.destroy()
|
|
132
|
+
return
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
const local_addr = self.server.address()
|
|
137
|
-
self.set('local', 'ip', ipaddr.process(self.client.localAddress || local_addr.address).toString())
|
|
138
|
-
self.set('local', 'port',
|
|
139
|
-
self.results.add({name: 'local'}, self.local)
|
|
135
|
+
const local_addr = self.server.address()
|
|
136
|
+
self.set('local', 'ip', ipaddr.process(self.client.localAddress || local_addr.address).toString())
|
|
137
|
+
self.set('local', 'port', self.client.localPort || local_addr.port)
|
|
138
|
+
self.results.add({ name: 'local' }, self.local)
|
|
140
139
|
|
|
141
|
-
self.set('remote', 'ip', ipaddr.process(ip).toString())
|
|
142
|
-
self.set('remote', 'port', self.client.remotePort)
|
|
143
|
-
self.results.add({name: 'remote'}, self.remote)
|
|
140
|
+
self.set('remote', 'ip', ipaddr.process(ip).toString())
|
|
141
|
+
self.set('remote', 'port', self.client.remotePort)
|
|
142
|
+
self.results.add({ name: 'remote' }, self.remote)
|
|
144
143
|
|
|
145
|
-
self.lognotice(
|
|
144
|
+
self.lognotice('connect', {
|
|
146
145
|
ip: self.remote.ip,
|
|
147
146
|
port: self.remote.port,
|
|
148
147
|
local_ip: self.local.ip,
|
|
149
|
-
local_port: self.local.port
|
|
150
|
-
})
|
|
148
|
+
local_port: self.local.port,
|
|
149
|
+
})
|
|
151
150
|
|
|
152
|
-
if (!self.client.on) return
|
|
151
|
+
if (!self.client.on) return
|
|
153
152
|
|
|
154
|
-
const log_data = {ip: self.remote.ip}
|
|
153
|
+
const log_data = { ip: self.remote.ip }
|
|
155
154
|
if (self.remote.host) log_data.host = self.remote.host
|
|
156
155
|
|
|
157
156
|
self.client.on('end', () => {
|
|
158
|
-
if (self.state >= states.DISCONNECTING) return
|
|
159
|
-
self.remote.closed = true
|
|
160
|
-
self.loginfo('client half closed connection', log_data)
|
|
161
|
-
self.fail()
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
self.client.on('close', has_error => {
|
|
165
|
-
if (self.state >= states.DISCONNECTING) return
|
|
166
|
-
self.remote.closed = true
|
|
167
|
-
self.loginfo('client dropped connection', log_data)
|
|
168
|
-
self.fail()
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
self.client.on('error', err => {
|
|
172
|
-
if (self.state >= states.DISCONNECTING) return
|
|
173
|
-
self.loginfo(`client connection error: ${err}`, log_data)
|
|
174
|
-
self.fail()
|
|
175
|
-
})
|
|
157
|
+
if (self.state >= states.DISCONNECTING) return
|
|
158
|
+
self.remote.closed = true
|
|
159
|
+
self.loginfo('client half closed connection', log_data)
|
|
160
|
+
self.fail()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
self.client.on('close', (has_error) => {
|
|
164
|
+
if (self.state >= states.DISCONNECTING) return
|
|
165
|
+
self.remote.closed = true
|
|
166
|
+
self.loginfo('client dropped connection', log_data)
|
|
167
|
+
self.fail()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
self.client.on('error', (err) => {
|
|
171
|
+
if (self.state >= states.DISCONNECTING) return
|
|
172
|
+
self.loginfo(`client connection error: ${err}`, log_data)
|
|
173
|
+
self.fail()
|
|
174
|
+
})
|
|
176
175
|
|
|
177
176
|
self.client.on('timeout', () => {
|
|
178
177
|
// FIN has sent, when timeout just destroy socket
|
|
179
178
|
if (self.state >= states.DISCONNECTED) {
|
|
180
|
-
self.client.destroy()
|
|
179
|
+
self.client.destroy()
|
|
181
180
|
self.loginfo(`timeout, destroy socket (state:${self.state})`)
|
|
182
|
-
return
|
|
181
|
+
return
|
|
183
182
|
}
|
|
184
|
-
if (self.state >= states.DISCONNECTING) return
|
|
183
|
+
if (self.state >= states.DISCONNECTING) return
|
|
185
184
|
self.respond(421, 'timeout', () => {
|
|
186
|
-
self.fail('client connection timed out', log_data)
|
|
187
|
-
})
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
self.client.on('data', data => {
|
|
191
|
-
self.process_data(data)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
const ha_list = net.isIPv6(self.remote.ip) ? haproxy_hosts_ipv6 : haproxy_hosts_ipv4
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
185
|
+
self.fail('client connection timed out', log_data)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
self.client.on('data', (data) => {
|
|
190
|
+
self.process_data(data)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const ha_list = net.isIPv6(self.remote.ip) ? haproxy_hosts_ipv6 : haproxy_hosts_ipv4
|
|
194
|
+
if (
|
|
195
|
+
ha_list.some((element, index, array) => {
|
|
196
|
+
return ipaddr.parse(self.remote.ip).match(element[0], element[1])
|
|
197
|
+
})
|
|
198
|
+
) {
|
|
199
|
+
self.proxy.allowed = true
|
|
199
200
|
// Wait for PROXY command
|
|
200
201
|
self.proxy.timer = setTimeout(() => {
|
|
201
|
-
self.respond(421, 'PROXY timeout',() => {
|
|
202
|
-
self.disconnect()
|
|
203
|
-
})
|
|
204
|
-
}, 30 * 1000)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
plugins.run_hooks('connect_init', self);
|
|
202
|
+
self.respond(421, 'PROXY timeout', () => {
|
|
203
|
+
self.disconnect()
|
|
204
|
+
})
|
|
205
|
+
}, 30 * 1000)
|
|
206
|
+
} else {
|
|
207
|
+
plugins.run_hooks('connect_init', self)
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
-
setTLS
|
|
211
|
-
this.set('hello', 'host', undefined)
|
|
212
|
-
this.set('tls',
|
|
213
|
-
for (const t of ['cipher','verified','verifyError','peerCertificate']) {
|
|
214
|
-
if (obj[t] === undefined) continue
|
|
215
|
-
this.set('tls', t, obj[t])
|
|
210
|
+
setTLS(obj) {
|
|
211
|
+
this.set('hello', 'host', undefined)
|
|
212
|
+
this.set('tls', 'enabled', true)
|
|
213
|
+
for (const t of ['cipher', 'verified', 'verifyError', 'peerCertificate']) {
|
|
214
|
+
if (obj[t] === undefined) continue
|
|
215
|
+
this.set('tls', t, obj[t])
|
|
216
216
|
}
|
|
217
217
|
// prior to 2017-07, authorized and verified were both used. Verified
|
|
218
218
|
// seems to be the more common and has the property updated in the
|
|
219
219
|
// tls object. However, authorized has been up-to-date in the notes. Store
|
|
220
220
|
// in both, for backwards compatibility.
|
|
221
221
|
this.notes.tls = {
|
|
222
|
-
authorized: obj.verified,
|
|
222
|
+
authorized: obj.verified, // legacy name
|
|
223
223
|
authorizationError: obj.verifyError,
|
|
224
224
|
cipher: obj.cipher,
|
|
225
225
|
peerCertificate: obj.peerCertificate,
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
set
|
|
228
|
+
set(prop_str, val) {
|
|
229
229
|
if (arguments.length === 3) {
|
|
230
|
-
prop_str = `${arguments[0]}.${arguments[1]}
|
|
231
|
-
val = arguments[2]
|
|
230
|
+
prop_str = `${arguments[0]}.${arguments[1]}`
|
|
231
|
+
val = arguments[2]
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
const path_parts = prop_str.split('.')
|
|
235
|
-
let loc = this
|
|
236
|
-
for (let i=0; i < path_parts.length; i++) {
|
|
237
|
-
const part = path_parts[i]
|
|
238
|
-
if (part ===
|
|
234
|
+
const path_parts = prop_str.split('.')
|
|
235
|
+
let loc = this
|
|
236
|
+
for (let i = 0; i < path_parts.length; i++) {
|
|
237
|
+
const part = path_parts[i]
|
|
238
|
+
if (part === '__proto__' || part === 'constructor') continue
|
|
239
239
|
|
|
240
240
|
// while another part remains
|
|
241
|
-
if (i <
|
|
242
|
-
if (loc[part] === undefined) loc[part] = {}
|
|
243
|
-
loc = loc[part]
|
|
244
|
-
continue
|
|
241
|
+
if (i < path_parts.length - 1) {
|
|
242
|
+
if (loc[part] === undefined) loc[part] = {} // initialize
|
|
243
|
+
loc = loc[part] // descend
|
|
244
|
+
continue
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
// last part, so assign the value
|
|
248
|
-
loc[part] = val
|
|
248
|
+
loc[part] = val
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
// Set is_private, is_local automatically when remote.ip is set
|
|
252
252
|
if (prop_str === 'remote.ip') {
|
|
253
|
-
this.set('remote.is_local', net_utils.is_local_ip(this.remote.ip))
|
|
253
|
+
this.set('remote.is_local', net_utils.is_local_ip(this.remote.ip))
|
|
254
254
|
if (this.remote.is_local) {
|
|
255
|
-
this.set('remote.is_private', true)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
this.set('remote.is_private', net_utils.is_private_ip(this.remote.ip));
|
|
255
|
+
this.set('remote.is_private', true)
|
|
256
|
+
} else {
|
|
257
|
+
this.set('remote.is_private', net_utils.is_private_ip(this.remote.ip))
|
|
259
258
|
}
|
|
260
259
|
}
|
|
261
260
|
}
|
|
262
|
-
get
|
|
261
|
+
get(prop_str) {
|
|
263
262
|
return prop_str.split('.').reduce((prev, curr) => {
|
|
264
263
|
return prev ? prev[curr] : undefined
|
|
265
264
|
}, this)
|
|
266
265
|
}
|
|
267
|
-
set relaying
|
|
266
|
+
set relaying(val) {
|
|
268
267
|
if (this.transaction) {
|
|
269
|
-
this.transaction._relaying = val
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
this._relaying = val;
|
|
268
|
+
this.transaction._relaying = val
|
|
269
|
+
} else {
|
|
270
|
+
this._relaying = val
|
|
273
271
|
}
|
|
274
272
|
}
|
|
275
|
-
get relaying
|
|
276
|
-
if (this.transaction && '_relaying' in this.transaction) return this.transaction._relaying
|
|
277
|
-
return this._relaying
|
|
273
|
+
get relaying() {
|
|
274
|
+
if (this.transaction && '_relaying' in this.transaction) return this.transaction._relaying
|
|
275
|
+
return this._relaying
|
|
278
276
|
}
|
|
279
|
-
process_line
|
|
280
|
-
|
|
277
|
+
process_line(line) {
|
|
281
278
|
if (this.state >= states.DISCONNECTING) {
|
|
282
279
|
if (logger.would_log(logger.LOGPROTOCOL)) {
|
|
283
|
-
this.logprotocol(`C: (after-disconnect): ${this.current_line}`, {
|
|
280
|
+
this.logprotocol(`C: (after-disconnect): ${this.current_line}`, {
|
|
281
|
+
state: this.state,
|
|
282
|
+
})
|
|
284
283
|
}
|
|
285
|
-
this.loginfo(`data after disconnect from ${this.remote.ip}`)
|
|
286
|
-
return
|
|
284
|
+
this.loginfo(`data after disconnect from ${this.remote.ip}`)
|
|
285
|
+
return
|
|
287
286
|
}
|
|
288
287
|
|
|
289
288
|
if (this.state === states.DATA) {
|
|
290
289
|
if (logger.would_log(logger.LOGDATA)) {
|
|
291
|
-
this.logdata(`C: ${line}`)
|
|
290
|
+
this.logdata(`C: ${line}`)
|
|
292
291
|
}
|
|
293
|
-
this.accumulate_data(line)
|
|
294
|
-
return
|
|
292
|
+
this.accumulate_data(line)
|
|
293
|
+
return
|
|
295
294
|
}
|
|
296
295
|
|
|
297
|
-
this.current_line = line.toString(this.encoding).replace(/\r?\n/, '')
|
|
296
|
+
this.current_line = line.toString(this.encoding).replace(/\r?\n/, '')
|
|
298
297
|
if (logger.would_log(logger.LOGPROTOCOL)) {
|
|
299
|
-
this.logprotocol(`C: ${this.current_line}`, {
|
|
298
|
+
this.logprotocol(`C: ${this.current_line}`, { state: this.state })
|
|
300
299
|
}
|
|
301
300
|
|
|
302
301
|
// Check for non-ASCII characters
|
|
303
302
|
/* eslint no-control-regex: 0 */
|
|
304
303
|
if (/[^\x00-\x7F]/.test(this.current_line)) {
|
|
305
304
|
// See if this is a TLS handshake
|
|
306
|
-
const buf = Buffer.from(this.current_line.substr(0,3), 'binary')
|
|
307
|
-
if (
|
|
308
|
-
|
|
305
|
+
const buf = Buffer.from(this.current_line.substr(0, 3), 'binary')
|
|
306
|
+
if (
|
|
307
|
+
buf[0] === 0x16 &&
|
|
308
|
+
buf[1] === 0x03 &&
|
|
309
|
+
(buf[2] === 0x00 || buf[2] === 0x01) // SSLv3/TLS1.x format
|
|
309
310
|
) {
|
|
310
311
|
// Nuke the current input buffer to prevent processing further input
|
|
311
|
-
this.current_data = null
|
|
312
|
-
this.respond(501, 'SSL attempted over a non-SSL socket')
|
|
313
|
-
this.disconnect()
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return this.respond(501, 'Syntax error (8-bit characters not allowed)');
|
|
312
|
+
this.current_data = null
|
|
313
|
+
this.respond(501, 'SSL attempted over a non-SSL socket')
|
|
314
|
+
this.disconnect()
|
|
315
|
+
return
|
|
316
|
+
} else if (this.hello.verb == 'HELO') {
|
|
317
|
+
return this.respond(501, 'Syntax error (8-bit characters not allowed)')
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
if (this.state === states.CMD) {
|
|
322
|
-
this.state = states.PAUSE_SMTP
|
|
323
|
-
const matches = /^([^ ]*)( +(.*))?$/.exec(this.current_line)
|
|
322
|
+
this.state = states.PAUSE_SMTP
|
|
323
|
+
const matches = /^([^ ]*)( +(.*))?$/.exec(this.current_line)
|
|
324
324
|
if (!matches) {
|
|
325
|
-
return plugins.run_hooks('unrecognized_command',
|
|
326
|
-
this, [this.current_line]);
|
|
325
|
+
return plugins.run_hooks('unrecognized_command', this, [this.current_line])
|
|
327
326
|
}
|
|
328
|
-
const cmd = matches[1]
|
|
329
|
-
const method = `cmd_${cmd.toLowerCase()}
|
|
330
|
-
const remaining = matches[3] || ''
|
|
327
|
+
const cmd = matches[1]
|
|
328
|
+
const method = `cmd_${cmd.toLowerCase()}`
|
|
329
|
+
const remaining = matches[3] || ''
|
|
331
330
|
if (this[method]) {
|
|
332
331
|
try {
|
|
333
|
-
this[method](remaining)
|
|
334
|
-
}
|
|
335
|
-
catch (err) {
|
|
332
|
+
this[method](remaining)
|
|
333
|
+
} catch (err) {
|
|
336
334
|
if (err.stack) {
|
|
337
|
-
this.logerror(`${method} failed: ${err}`)
|
|
338
|
-
err.stack.split(
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
this.logerror(`${method} failed: ${err}`);
|
|
335
|
+
this.logerror(`${method} failed: ${err}`)
|
|
336
|
+
err.stack.split('\n').forEach(this.logerror)
|
|
337
|
+
} else {
|
|
338
|
+
this.logerror(`${method} failed: ${err}`)
|
|
342
339
|
}
|
|
343
|
-
this.respond(421,
|
|
344
|
-
this.disconnect()
|
|
345
|
-
})
|
|
340
|
+
this.respond(421, 'Internal Server Error', () => {
|
|
341
|
+
this.disconnect()
|
|
342
|
+
})
|
|
346
343
|
}
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
344
|
+
} else {
|
|
349
345
|
// unrecognized command
|
|
350
|
-
plugins.run_hooks('unrecognized_command', this, [
|
|
346
|
+
plugins.run_hooks('unrecognized_command', this, [cmd, remaining])
|
|
351
347
|
}
|
|
352
|
-
}
|
|
353
|
-
else if (this.state === states.LOOP) {
|
|
348
|
+
} else if (this.state === states.LOOP) {
|
|
354
349
|
// Allow QUIT
|
|
355
350
|
if (this.current_line.toUpperCase() === 'QUIT') {
|
|
356
|
-
this.cmd_quit()
|
|
351
|
+
this.cmd_quit()
|
|
352
|
+
} else {
|
|
353
|
+
this.respond(this.loop_code, this.loop_msg)
|
|
357
354
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
throw new Error(`unknown state ${this.state}`);
|
|
355
|
+
} else {
|
|
356
|
+
throw new Error(`unknown state ${this.state}`)
|
|
364
357
|
}
|
|
365
358
|
}
|
|
366
|
-
process_data
|
|
359
|
+
process_data(data) {
|
|
367
360
|
if (this.state >= states.DISCONNECTING) {
|
|
368
|
-
this.loginfo(`data after disconnect from ${this.remote.ip}`)
|
|
369
|
-
return
|
|
361
|
+
this.loginfo(`data after disconnect from ${this.remote.ip}`)
|
|
362
|
+
return
|
|
370
363
|
}
|
|
371
364
|
|
|
372
365
|
if (!this.current_data || !this.current_data.length) {
|
|
373
|
-
this.current_data = data
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
366
|
+
this.current_data = data
|
|
367
|
+
} else {
|
|
376
368
|
// Data left over in buffer
|
|
377
|
-
const buf = Buffer.concat(
|
|
378
|
-
|
|
379
|
-
(this.current_data.length + data.length)
|
|
380
|
-
);
|
|
381
|
-
this.current_data = buf;
|
|
369
|
+
const buf = Buffer.concat([this.current_data, data], this.current_data.length + data.length)
|
|
370
|
+
this.current_data = buf
|
|
382
371
|
}
|
|
383
372
|
|
|
384
|
-
this._process_data()
|
|
373
|
+
this._process_data()
|
|
385
374
|
}
|
|
386
|
-
_process_data
|
|
375
|
+
_process_data() {
|
|
387
376
|
// We *must* detect disconnected connections here as the state
|
|
388
377
|
// only transitions to states.CMD in the respond function below.
|
|
389
378
|
// Otherwise if multiple commands are pipelined and then the
|
|
390
379
|
// connection is dropped; we'll end up in the function forever.
|
|
391
|
-
if (this.state >= states.DISCONNECTING) return
|
|
380
|
+
if (this.state >= states.DISCONNECTING) return
|
|
392
381
|
|
|
393
|
-
let maxlength
|
|
382
|
+
let maxlength
|
|
394
383
|
if (this.state === states.PAUSE_DATA || this.state === states.DATA) {
|
|
395
|
-
maxlength = cfg.max.data_line_length
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
maxlength = cfg.max.line_length;
|
|
384
|
+
maxlength = cfg.max.data_line_length
|
|
385
|
+
} else {
|
|
386
|
+
maxlength = cfg.max.line_length
|
|
399
387
|
}
|
|
400
388
|
|
|
401
|
-
let offset
|
|
402
|
-
while (this.current_data && (
|
|
389
|
+
let offset
|
|
390
|
+
while (this.current_data && (offset = utils.indexOfLF(this.current_data, maxlength)) !== -1) {
|
|
403
391
|
if (this.state === states.PAUSE_DATA) {
|
|
404
|
-
return
|
|
392
|
+
return
|
|
405
393
|
}
|
|
406
|
-
let this_line = this.current_data.slice(0, offset+1)
|
|
394
|
+
let this_line = this.current_data.slice(0, offset + 1)
|
|
407
395
|
// Hack: bypass this code to allow HAProxy's PROXY extension
|
|
408
|
-
const proxyStart = this.proxy.allowed && /^PROXY /.test(this_line)
|
|
396
|
+
const proxyStart = this.proxy.allowed && /^PROXY /.test(this_line)
|
|
409
397
|
if (this.state === states.PAUSE && proxyStart) {
|
|
410
|
-
if (this.proxy.timer) clearTimeout(this.proxy.timer)
|
|
411
|
-
this.state = states.CMD
|
|
412
|
-
this.current_data = this.current_data.slice(this_line.length)
|
|
413
|
-
this.process_line(this_line)
|
|
398
|
+
if (this.proxy.timer) clearTimeout(this.proxy.timer)
|
|
399
|
+
this.state = states.CMD
|
|
400
|
+
this.current_data = this.current_data.slice(this_line.length)
|
|
401
|
+
this.process_line(this_line)
|
|
414
402
|
}
|
|
415
403
|
// Detect early_talker but allow PIPELINING extension (ESMTP)
|
|
416
404
|
else if ((this.state === states.PAUSE || this.state === states.PAUSE_SMTP) && !this.esmtp) {
|
|
417
405
|
// Allow EHLO/HELO to be pipelined with PROXY
|
|
418
|
-
if (this.proxy.allowed && /^(?:EH|HE)LO /i.test(this_line)) return
|
|
406
|
+
if (this.proxy.allowed && /^(?:EH|HE)LO /i.test(this_line)) return
|
|
419
407
|
if (!this.early_talker) {
|
|
420
|
-
this_line = this_line.toString().replace(/\r?\n/,'')
|
|
421
|
-
this.logdebug('[early_talker]', {
|
|
408
|
+
this_line = this_line.toString().replace(/\r?\n/, '')
|
|
409
|
+
this.logdebug('[early_talker]', {
|
|
410
|
+
state: this.state,
|
|
411
|
+
esmtp: this.esmtp,
|
|
412
|
+
line: this_line,
|
|
413
|
+
})
|
|
422
414
|
}
|
|
423
|
-
this.early_talker = true
|
|
424
|
-
setImmediate(() => {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
415
|
+
this.early_talker = true
|
|
416
|
+
setImmediate(() => {
|
|
417
|
+
this._process_data()
|
|
418
|
+
})
|
|
419
|
+
break
|
|
420
|
+
} else if ((this.state === states.PAUSE || this.state === states.PAUSE_SMTP) && this.esmtp) {
|
|
421
|
+
let valid = true
|
|
422
|
+
const cmd = this_line.toString('ascii').slice(0, 4).toUpperCase()
|
|
430
423
|
switch (cmd) {
|
|
431
424
|
case 'RSET':
|
|
432
425
|
case 'MAIL':
|
|
@@ -435,13 +428,13 @@ class Connection {
|
|
|
435
428
|
case 'SAML':
|
|
436
429
|
case 'RCPT':
|
|
437
430
|
// These can be anywhere in the group
|
|
438
|
-
break
|
|
431
|
+
break
|
|
439
432
|
default:
|
|
440
433
|
// Anything else *MUST* be the last command in the group
|
|
441
434
|
if (this_line.length !== this.current_data.length) {
|
|
442
|
-
valid = false
|
|
435
|
+
valid = false
|
|
443
436
|
}
|
|
444
|
-
break
|
|
437
|
+
break
|
|
445
438
|
}
|
|
446
439
|
if (valid) {
|
|
447
440
|
// Valid PIPELINING
|
|
@@ -451,740 +444,742 @@ class Connection {
|
|
|
451
444
|
// has reset the state back to states.CMD and this
|
|
452
445
|
// ensures that we only process one command at a
|
|
453
446
|
// time.
|
|
454
|
-
this.pipelining = true
|
|
455
|
-
this.logdebug(`pipeline: ${this_line}`)
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
447
|
+
this.pipelining = true
|
|
448
|
+
this.logdebug(`pipeline: ${this_line}`)
|
|
449
|
+
} else {
|
|
458
450
|
// Invalid pipeline sequence
|
|
459
451
|
// Treat this as early talker
|
|
460
452
|
if (!this.early_talker) {
|
|
461
|
-
this.logdebug('[early_talker]', {
|
|
453
|
+
this.logdebug('[early_talker]', {
|
|
454
|
+
state: this.state,
|
|
455
|
+
esmtp: this.esmtp,
|
|
456
|
+
line: this_line,
|
|
457
|
+
})
|
|
462
458
|
}
|
|
463
|
-
this.early_talker = true
|
|
464
|
-
setImmediate(() => {
|
|
459
|
+
this.early_talker = true
|
|
460
|
+
setImmediate(() => {
|
|
461
|
+
this._process_data()
|
|
462
|
+
})
|
|
465
463
|
}
|
|
466
|
-
break
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
this.
|
|
470
|
-
this.process_line(this_line);
|
|
464
|
+
break
|
|
465
|
+
} else {
|
|
466
|
+
this.current_data = this.current_data.slice(this_line.length)
|
|
467
|
+
this.process_line(this_line)
|
|
471
468
|
}
|
|
472
469
|
}
|
|
473
470
|
|
|
474
|
-
if (
|
|
475
|
-
|
|
471
|
+
if (
|
|
472
|
+
this.current_data &&
|
|
473
|
+
this.current_data.length > maxlength &&
|
|
474
|
+
utils.indexOfLF(this.current_data, maxlength) === -1
|
|
475
|
+
) {
|
|
476
476
|
if (this.state !== states.DATA && this.state !== states.PAUSE_DATA) {
|
|
477
477
|
// In command mode, reject:
|
|
478
|
-
this.client.pause()
|
|
479
|
-
this.current_data = null
|
|
480
|
-
return this.respond(521,
|
|
481
|
-
this.disconnect()
|
|
482
|
-
})
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
this.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
478
|
+
this.client.pause()
|
|
479
|
+
this.current_data = null
|
|
480
|
+
return this.respond(521, 'Command line too long', () => {
|
|
481
|
+
this.disconnect()
|
|
482
|
+
})
|
|
483
|
+
} else {
|
|
484
|
+
this.loginfo(`DATA line length (${this.current_data.length}) exceeds limit of ${maxlength} bytes`)
|
|
485
|
+
this.transaction.notes.data_line_length_exceeded = true
|
|
486
|
+
const b = Buffer.concat(
|
|
487
|
+
[
|
|
488
|
+
this.current_data.slice(0, maxlength - 2),
|
|
489
|
+
Buffer.from('\r\n ', 'utf8'),
|
|
490
|
+
this.current_data.slice(maxlength - 2),
|
|
491
|
+
],
|
|
492
|
+
this.current_data.length + 3,
|
|
493
|
+
)
|
|
494
|
+
this.current_data = b
|
|
495
|
+
return this._process_data()
|
|
494
496
|
}
|
|
495
497
|
}
|
|
496
498
|
}
|
|
497
|
-
respond
|
|
498
|
-
let uuid = ''
|
|
499
|
-
let messages
|
|
499
|
+
respond(code, msg, func) {
|
|
500
|
+
let uuid = ''
|
|
501
|
+
let messages
|
|
500
502
|
|
|
501
503
|
if (this.state === states.DISCONNECTED) {
|
|
502
|
-
if (func) func()
|
|
503
|
-
return
|
|
504
|
+
if (func) func()
|
|
505
|
+
return
|
|
504
506
|
}
|
|
505
507
|
// Check to see if DSN object was passed in
|
|
506
508
|
if (typeof msg === 'object' && msg.constructor.name === 'DSN') {
|
|
507
509
|
// Override
|
|
508
|
-
code = msg.code
|
|
509
|
-
msg = msg.reply
|
|
510
|
+
code = msg.code
|
|
511
|
+
msg = msg.reply
|
|
510
512
|
}
|
|
511
513
|
|
|
512
514
|
if (!Array.isArray(msg)) {
|
|
513
|
-
messages = msg.toString().split(/\n/)
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
messages = msg.slice();
|
|
515
|
+
messages = msg.toString().split(/\n/)
|
|
516
|
+
} else {
|
|
517
|
+
messages = msg.slice()
|
|
517
518
|
}
|
|
518
519
|
messages = messages.filter((msg2) => {
|
|
519
|
-
return /\S/.test(msg2)
|
|
520
|
-
})
|
|
520
|
+
return /\S/.test(msg2)
|
|
521
|
+
})
|
|
521
522
|
|
|
522
523
|
// Multiline AUTH PLAIN as in RFC-4954 page 8.
|
|
523
524
|
if (code === 334 && !messages.length) {
|
|
524
|
-
messages = [' ']
|
|
525
|
+
messages = [' ']
|
|
525
526
|
}
|
|
526
527
|
|
|
527
528
|
if (code >= 400) {
|
|
528
|
-
this.last_reject = `${code} ${messages.join(' ')}
|
|
529
|
+
this.last_reject = `${code} ${messages.join(' ')}`
|
|
529
530
|
if (cfg.uuid.deny_chars) {
|
|
530
|
-
uuid = (this.transaction || this).uuid
|
|
531
|
+
uuid = (this.transaction || this).uuid
|
|
531
532
|
if (cfg.uuid.deny_chars > 1) {
|
|
532
|
-
uuid = uuid.substr(0, cfg.uuid.deny_chars)
|
|
533
|
+
uuid = uuid.substr(0, cfg.uuid.deny_chars)
|
|
533
534
|
}
|
|
534
535
|
}
|
|
535
536
|
}
|
|
536
537
|
|
|
537
|
-
let mess
|
|
538
|
-
let buf = ''
|
|
539
|
-
const hostname = os.hostname().split('.').shift()
|
|
540
|
-
const _uuid = uuid ? `[${uuid}@${hostname}] ` : ''
|
|
538
|
+
let mess
|
|
539
|
+
let buf = ''
|
|
540
|
+
const hostname = os.hostname().split('.').shift()
|
|
541
|
+
const _uuid = uuid ? `[${uuid}@${hostname}] ` : ''
|
|
541
542
|
|
|
542
543
|
while ((mess = messages.shift())) {
|
|
543
|
-
const line = `${code}${
|
|
544
|
-
this.logprotocol(`S: ${line}`)
|
|
545
|
-
buf = `${buf}${line}\r\n
|
|
544
|
+
const line = `${code}${messages.length ? '-' : ' '}${_uuid}${mess}`
|
|
545
|
+
this.logprotocol(`S: ${line}`)
|
|
546
|
+
buf = `${buf}${line}\r\n`
|
|
546
547
|
}
|
|
547
548
|
|
|
548
|
-
if (this.client.write === undefined) return buf
|
|
549
|
+
if (this.client.write === undefined) return buf // testing
|
|
549
550
|
|
|
550
551
|
try {
|
|
551
|
-
this.client.write(buf)
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
return this.fail(`Writing response: ${buf} failed: ${err}`);
|
|
552
|
+
this.client.write(buf)
|
|
553
|
+
} catch (err) {
|
|
554
|
+
return this.fail(`Writing response: ${buf} failed: ${err}`)
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
// Store the last response
|
|
558
|
-
this.last_response = buf
|
|
558
|
+
this.last_response = buf
|
|
559
559
|
|
|
560
560
|
// Don't change loop state
|
|
561
561
|
if (this.state !== states.LOOP) {
|
|
562
|
-
this.state = states.CMD
|
|
562
|
+
this.state = states.CMD
|
|
563
563
|
}
|
|
564
564
|
|
|
565
565
|
// Run optional closure before handling and further commands
|
|
566
|
-
if (func) func()
|
|
566
|
+
if (func) func()
|
|
567
567
|
|
|
568
568
|
// Process any buffered commands (PIPELINING)
|
|
569
|
-
this._process_data()
|
|
569
|
+
this._process_data()
|
|
570
570
|
}
|
|
571
|
-
fail
|
|
572
|
-
if (err) this.logwarn(err, err_data)
|
|
573
|
-
this.hooks_to_run = []
|
|
574
|
-
this.disconnect()
|
|
571
|
+
fail(err, err_data) {
|
|
572
|
+
if (err) this.logwarn(err, err_data)
|
|
573
|
+
this.hooks_to_run = []
|
|
574
|
+
this.disconnect()
|
|
575
575
|
}
|
|
576
|
-
disconnect
|
|
577
|
-
if (this.state >= states.DISCONNECTING) return
|
|
578
|
-
this.state = states.DISCONNECTING
|
|
579
|
-
this.current_data = null
|
|
576
|
+
disconnect() {
|
|
577
|
+
if (this.state >= states.DISCONNECTING) return
|
|
578
|
+
this.state = states.DISCONNECTING
|
|
579
|
+
this.current_data = null // don't process any more data we have already received
|
|
580
580
|
this.reset_transaction(() => {
|
|
581
|
-
plugins.run_hooks('disconnect', this)
|
|
582
|
-
})
|
|
581
|
+
plugins.run_hooks('disconnect', this)
|
|
582
|
+
})
|
|
583
583
|
}
|
|
584
|
-
disconnect_respond
|
|
584
|
+
disconnect_respond() {
|
|
585
585
|
const logdetail = {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
this.results.add(
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
this.
|
|
619
|
-
|
|
620
|
-
|
|
586
|
+
ip: this.remote.ip,
|
|
587
|
+
rdns: this.remote.host ? this.remote.host : '',
|
|
588
|
+
helo: this.hello.host ? this.hello.host : '',
|
|
589
|
+
relay: this.relaying ? 'Y' : 'N',
|
|
590
|
+
early: this.early_talker ? 'Y' : 'N',
|
|
591
|
+
esmtp: this.esmtp ? 'Y' : 'N',
|
|
592
|
+
tls: this.tls.enabled ? 'Y' : 'N',
|
|
593
|
+
pipe: this.pipelining ? 'Y' : 'N',
|
|
594
|
+
errors: this.errors,
|
|
595
|
+
txns: this.tran_count,
|
|
596
|
+
rcpts: `${this.rcpt_count.accept}/${this.rcpt_count.tempfail}/${this.rcpt_count.reject}`,
|
|
597
|
+
msgs: `${this.msg_count.accept}/${this.msg_count.tempfail}/${this.msg_count.reject}`,
|
|
598
|
+
bytes: this.totalbytes,
|
|
599
|
+
lr: this.last_reject ? this.last_reject : '',
|
|
600
|
+
time: (Date.now() - this.start_time) / 1000,
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
this.results.add(
|
|
604
|
+
{ name: 'disconnect' },
|
|
605
|
+
{
|
|
606
|
+
duration: (Date.now() - this.start_time) / 1000,
|
|
607
|
+
},
|
|
608
|
+
)
|
|
609
|
+
this.lognotice('disconnect', logdetail)
|
|
610
|
+
this.state = states.DISCONNECTED
|
|
611
|
+
this.client.end()
|
|
612
|
+
}
|
|
613
|
+
get_capabilities() {
|
|
614
|
+
return []
|
|
615
|
+
}
|
|
616
|
+
tran_uuid() {
|
|
617
|
+
this.tran_count++
|
|
618
|
+
return `${this.uuid}.${this.tran_count}`
|
|
619
|
+
}
|
|
620
|
+
reset_transaction(cb) {
|
|
621
|
+
this.results.add(
|
|
622
|
+
{ name: 'reset' },
|
|
623
|
+
{
|
|
624
|
+
duration: (Date.now() - this.start_time) / 1000,
|
|
625
|
+
},
|
|
626
|
+
)
|
|
621
627
|
if (this.transaction && this.transaction.resetting === false) {
|
|
622
628
|
// Pause connection to allow the hook to complete
|
|
623
|
-
this.pause()
|
|
624
|
-
this.transaction.resetting = true
|
|
625
|
-
plugins.run_hooks('reset_transaction', this, cb)
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (cb) cb();
|
|
629
|
+
this.pause()
|
|
630
|
+
this.transaction.resetting = true
|
|
631
|
+
plugins.run_hooks('reset_transaction', this, cb)
|
|
632
|
+
} else {
|
|
633
|
+
this.transaction = null
|
|
634
|
+
if (cb) cb()
|
|
630
635
|
}
|
|
631
636
|
}
|
|
632
|
-
reset_transaction_respond
|
|
637
|
+
reset_transaction_respond(retval, msg, cb) {
|
|
633
638
|
if (this.transaction) {
|
|
634
|
-
this.transaction.message_stream.destroy()
|
|
635
|
-
this.transaction = null
|
|
639
|
+
this.transaction.message_stream.destroy()
|
|
640
|
+
this.transaction = null
|
|
636
641
|
}
|
|
637
|
-
if (cb) cb()
|
|
642
|
+
if (cb) cb()
|
|
638
643
|
// Allow the connection to continue
|
|
639
|
-
this.resume()
|
|
644
|
+
this.resume()
|
|
640
645
|
}
|
|
641
|
-
init_transaction
|
|
646
|
+
init_transaction(cb) {
|
|
642
647
|
this.reset_transaction(() => {
|
|
643
|
-
this.transaction = trans.createTransaction(this.tran_uuid(), cfg)
|
|
648
|
+
this.transaction = trans.createTransaction(this.tran_uuid(), cfg)
|
|
644
649
|
// Catch any errors from the message_stream
|
|
645
650
|
this.transaction.message_stream.on('error', (err) => {
|
|
646
|
-
this.logcrit(`message_stream error: ${err.message}`)
|
|
651
|
+
this.logcrit(`message_stream error: ${err.message}`)
|
|
647
652
|
this.respond('421', 'Internal Server Error', () => {
|
|
648
|
-
this.disconnect()
|
|
649
|
-
})
|
|
650
|
-
})
|
|
651
|
-
this.transaction.results = new ResultStore(this)
|
|
652
|
-
if (cb) cb()
|
|
653
|
-
})
|
|
654
|
-
}
|
|
655
|
-
loop_respond
|
|
656
|
-
if (this.state >= states.DISCONNECTING) return
|
|
657
|
-
this.state = states.LOOP
|
|
658
|
-
this.loop_code = code
|
|
659
|
-
this.loop_msg = msg
|
|
660
|
-
this.respond(code, msg)
|
|
661
|
-
}
|
|
662
|
-
pause
|
|
663
|
-
if (this.state >= states.DISCONNECTING) return
|
|
664
|
-
this.client.pause()
|
|
665
|
-
if (this.state !== states.PAUSE_DATA) this.prev_state = this.state
|
|
666
|
-
this.state = states.PAUSE_DATA
|
|
667
|
-
}
|
|
668
|
-
resume
|
|
669
|
-
if (this.state >= states.DISCONNECTING) return
|
|
670
|
-
this.client.resume()
|
|
653
|
+
this.disconnect()
|
|
654
|
+
})
|
|
655
|
+
})
|
|
656
|
+
this.transaction.results = new ResultStore(this)
|
|
657
|
+
if (cb) cb()
|
|
658
|
+
})
|
|
659
|
+
}
|
|
660
|
+
loop_respond(code, msg) {
|
|
661
|
+
if (this.state >= states.DISCONNECTING) return
|
|
662
|
+
this.state = states.LOOP
|
|
663
|
+
this.loop_code = code
|
|
664
|
+
this.loop_msg = msg
|
|
665
|
+
this.respond(code, msg)
|
|
666
|
+
}
|
|
667
|
+
pause() {
|
|
668
|
+
if (this.state >= states.DISCONNECTING) return
|
|
669
|
+
this.client.pause()
|
|
670
|
+
if (this.state !== states.PAUSE_DATA) this.prev_state = this.state
|
|
671
|
+
this.state = states.PAUSE_DATA
|
|
672
|
+
}
|
|
673
|
+
resume() {
|
|
674
|
+
if (this.state >= states.DISCONNECTING) return
|
|
675
|
+
this.client.resume()
|
|
671
676
|
if (this.prev_state) {
|
|
672
|
-
this.state = this.prev_state
|
|
673
|
-
this.prev_state = null
|
|
677
|
+
this.state = this.prev_state
|
|
678
|
+
this.prev_state = null
|
|
674
679
|
}
|
|
675
|
-
setImmediate(() => this._process_data())
|
|
680
|
+
setImmediate(() => this._process_data())
|
|
676
681
|
}
|
|
677
682
|
/////////////////////////////////////////////////////////////////////////////
|
|
678
683
|
// SMTP Responses
|
|
679
|
-
connect_init_respond
|
|
684
|
+
connect_init_respond(retval, msg) {
|
|
680
685
|
// retval and message are ignored
|
|
681
|
-
this.logdebug('running connect_init_respond')
|
|
682
|
-
plugins.run_hooks('lookup_rdns', this)
|
|
686
|
+
this.logdebug('running connect_init_respond')
|
|
687
|
+
plugins.run_hooks('lookup_rdns', this)
|
|
683
688
|
}
|
|
684
|
-
lookup_rdns_respond
|
|
689
|
+
lookup_rdns_respond(retval, msg) {
|
|
685
690
|
switch (retval) {
|
|
686
691
|
case constants.ok:
|
|
687
|
-
this.set('remote', 'host',
|
|
688
|
-
this.set('remote', 'info',
|
|
689
|
-
plugins.run_hooks('connect', this)
|
|
690
|
-
break
|
|
692
|
+
this.set('remote', 'host', msg || 'Unknown')
|
|
693
|
+
this.set('remote', 'info', this.remote.info || this.remote.host)
|
|
694
|
+
plugins.run_hooks('connect', this)
|
|
695
|
+
break
|
|
691
696
|
case constants.deny:
|
|
692
|
-
this.loop_respond(554, msg ||
|
|
693
|
-
break
|
|
697
|
+
this.loop_respond(554, msg || 'rDNS Lookup Failed')
|
|
698
|
+
break
|
|
694
699
|
case constants.denydisconnect:
|
|
695
700
|
case constants.disconnect:
|
|
696
|
-
this.respond(554, msg ||
|
|
697
|
-
this.disconnect()
|
|
698
|
-
})
|
|
699
|
-
break
|
|
701
|
+
this.respond(554, msg || 'rDNS Lookup Failed', () => {
|
|
702
|
+
this.disconnect()
|
|
703
|
+
})
|
|
704
|
+
break
|
|
700
705
|
case constants.denysoft:
|
|
701
|
-
this.loop_respond(421, msg ||
|
|
702
|
-
break
|
|
706
|
+
this.loop_respond(421, msg || 'rDNS Temporary Failure')
|
|
707
|
+
break
|
|
703
708
|
case constants.denysoftdisconnect:
|
|
704
|
-
this.respond(421, msg ||
|
|
705
|
-
this.disconnect()
|
|
706
|
-
})
|
|
707
|
-
break
|
|
709
|
+
this.respond(421, msg || 'rDNS Temporary Failure', () => {
|
|
710
|
+
this.disconnect()
|
|
711
|
+
})
|
|
712
|
+
break
|
|
708
713
|
default:
|
|
709
714
|
// BUG: dns.reverse throws on invalid input (and sometimes valid
|
|
710
715
|
// input nodejs/node#47847). Also throws when empty results
|
|
711
716
|
try {
|
|
712
717
|
dns.reverse(this.remote.ip, (err, domains) => {
|
|
713
|
-
this.rdns_response(err, domains)
|
|
718
|
+
this.rdns_response(err, domains)
|
|
714
719
|
})
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
this.rdns_response(err, []);
|
|
720
|
+
} catch (err) {
|
|
721
|
+
this.rdns_response(err, [])
|
|
718
722
|
}
|
|
719
723
|
}
|
|
720
724
|
}
|
|
721
|
-
rdns_response
|
|
725
|
+
rdns_response(err, domains) {
|
|
722
726
|
if (err) {
|
|
723
727
|
switch (err.code) {
|
|
724
728
|
case dns.NXDOMAIN:
|
|
725
729
|
case dns.NOTFOUND:
|
|
726
|
-
this.set('remote', 'host', 'NXDOMAIN')
|
|
727
|
-
break
|
|
730
|
+
this.set('remote', 'host', 'NXDOMAIN')
|
|
731
|
+
break
|
|
728
732
|
default:
|
|
729
|
-
this.set('remote', 'host', 'DNSERROR')
|
|
730
|
-
break
|
|
733
|
+
this.set('remote', 'host', 'DNSERROR')
|
|
734
|
+
break
|
|
731
735
|
}
|
|
736
|
+
} else {
|
|
737
|
+
this.set('remote', 'host', domains[0] || 'Unknown')
|
|
738
|
+
this.results.add({ name: 'remote' }, this.remote)
|
|
732
739
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
this.results.add({name: 'remote'}, this.remote);
|
|
736
|
-
}
|
|
737
|
-
this.set('remote', 'info', this.remote.info || this.remote.host);
|
|
738
|
-
plugins.run_hooks('connect', this);
|
|
740
|
+
this.set('remote', 'info', this.remote.info || this.remote.host)
|
|
741
|
+
plugins.run_hooks('connect', this)
|
|
739
742
|
}
|
|
740
|
-
unrecognized_command_respond
|
|
743
|
+
unrecognized_command_respond(retval, msg) {
|
|
741
744
|
switch (retval) {
|
|
742
745
|
case constants.ok:
|
|
743
746
|
// response already sent, cool...
|
|
744
|
-
break
|
|
747
|
+
break
|
|
745
748
|
case constants.next_hook:
|
|
746
|
-
plugins.run_hooks(msg, this)
|
|
747
|
-
break
|
|
749
|
+
plugins.run_hooks(msg, this)
|
|
750
|
+
break
|
|
748
751
|
case constants.deny:
|
|
749
|
-
this.respond(500, msg ||
|
|
750
|
-
break
|
|
752
|
+
this.respond(500, msg || 'Unrecognized command')
|
|
753
|
+
break
|
|
751
754
|
case constants.denydisconnect:
|
|
752
755
|
case constants.denysoftdisconnect:
|
|
753
|
-
this.respond(retval === constants.denydisconnect ? 521 : 421, msg ||
|
|
754
|
-
this.disconnect()
|
|
755
|
-
})
|
|
756
|
-
break
|
|
756
|
+
this.respond(retval === constants.denydisconnect ? 521 : 421, msg || 'Unrecognized command', () => {
|
|
757
|
+
this.disconnect()
|
|
758
|
+
})
|
|
759
|
+
break
|
|
757
760
|
default:
|
|
758
|
-
this.errors
|
|
759
|
-
this.respond(500, msg ||
|
|
761
|
+
this.errors++
|
|
762
|
+
this.respond(500, msg || 'Unrecognized command')
|
|
760
763
|
}
|
|
761
764
|
}
|
|
762
|
-
connect_respond
|
|
765
|
+
connect_respond(retval, msg) {
|
|
763
766
|
// RFC 5321 Section 4.3.2 states that the only valid SMTP codes here are:
|
|
764
767
|
// 220 = Service ready
|
|
765
768
|
// 554 = Transaction failed (no SMTP service here)
|
|
766
769
|
// 421 = Service shutting down and closing transmission channel
|
|
767
770
|
switch (retval) {
|
|
768
771
|
case constants.deny:
|
|
769
|
-
this.loop_respond(554, msg ||
|
|
770
|
-
break
|
|
772
|
+
this.loop_respond(554, msg || 'Your mail is not welcome here')
|
|
773
|
+
break
|
|
771
774
|
case constants.denydisconnect:
|
|
772
775
|
case constants.disconnect:
|
|
773
|
-
this.respond(554, msg ||
|
|
774
|
-
this.disconnect()
|
|
775
|
-
})
|
|
776
|
-
break
|
|
776
|
+
this.respond(554, msg || 'Your mail is not welcome here', () => {
|
|
777
|
+
this.disconnect()
|
|
778
|
+
})
|
|
779
|
+
break
|
|
777
780
|
case constants.denysoft:
|
|
778
|
-
this.loop_respond(421, msg ||
|
|
779
|
-
break
|
|
781
|
+
this.loop_respond(421, msg || 'Come back later')
|
|
782
|
+
break
|
|
780
783
|
case constants.denysoftdisconnect:
|
|
781
|
-
this.respond(421, msg ||
|
|
782
|
-
this.disconnect()
|
|
783
|
-
})
|
|
784
|
-
break
|
|
784
|
+
this.respond(421, msg || 'Come back later', () => {
|
|
785
|
+
this.disconnect()
|
|
786
|
+
})
|
|
787
|
+
break
|
|
785
788
|
default: {
|
|
786
|
-
let greeting
|
|
789
|
+
let greeting
|
|
787
790
|
if (cfg.message.greeting?.length) {
|
|
788
791
|
// RFC5321 section 4.2
|
|
789
792
|
// Hostname/domain should appear after the 220
|
|
790
|
-
greeting = [...cfg.message.greeting]
|
|
791
|
-
greeting[0] = `${this.local.host} ESMTP ${greeting[0]}
|
|
793
|
+
greeting = [...cfg.message.greeting]
|
|
794
|
+
greeting[0] = `${this.local.host} ESMTP ${greeting[0]}`
|
|
792
795
|
if (cfg.uuid.banner_chars) {
|
|
793
|
-
greeting[0] += ` (${this.uuid.substr(0, cfg.uuid.banner_chars)})
|
|
796
|
+
greeting[0] += ` (${this.uuid.substr(0, cfg.uuid.banner_chars)})`
|
|
794
797
|
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
greeting = `${this.local.host} ESMTP ${this.local.info} ready`;
|
|
798
|
+
} else {
|
|
799
|
+
greeting = `${this.local.host} ESMTP ${this.local.info} ready`
|
|
798
800
|
if (cfg.uuid.banner_chars) {
|
|
799
|
-
greeting += ` (${this.uuid.substr(0, cfg.uuid.banner_chars)})
|
|
801
|
+
greeting += ` (${this.uuid.substr(0, cfg.uuid.banner_chars)})`
|
|
800
802
|
}
|
|
801
803
|
}
|
|
802
|
-
this.respond(220, msg || greeting)
|
|
804
|
+
this.respond(220, msg || greeting)
|
|
803
805
|
}
|
|
804
806
|
}
|
|
805
807
|
}
|
|
806
|
-
get_remote
|
|
808
|
+
get_remote(prop) {
|
|
807
809
|
switch (this.remote[prop]) {
|
|
808
810
|
case 'NXDOMAIN':
|
|
809
811
|
case 'DNSERROR':
|
|
810
812
|
case '':
|
|
811
813
|
case undefined:
|
|
812
814
|
case null:
|
|
813
|
-
return `[${this.remote.ip}]
|
|
815
|
+
return `[${this.remote.ip}]`
|
|
814
816
|
default:
|
|
815
|
-
return `${this.remote[prop]} [${this.remote.ip}]
|
|
817
|
+
return `${this.remote[prop]} [${this.remote.ip}]`
|
|
816
818
|
}
|
|
817
819
|
}
|
|
818
|
-
helo_respond
|
|
820
|
+
helo_respond(retval, msg) {
|
|
819
821
|
switch (retval) {
|
|
820
822
|
case constants.deny:
|
|
821
|
-
this.respond(550, msg ||
|
|
822
|
-
this.set('hello', 'verb', null)
|
|
823
|
-
this.set('hello', 'host', null)
|
|
824
|
-
})
|
|
825
|
-
break
|
|
823
|
+
this.respond(550, msg || 'HELO denied', () => {
|
|
824
|
+
this.set('hello', 'verb', null)
|
|
825
|
+
this.set('hello', 'host', null)
|
|
826
|
+
})
|
|
827
|
+
break
|
|
826
828
|
case constants.denydisconnect:
|
|
827
|
-
this.respond(550, msg ||
|
|
828
|
-
this.disconnect()
|
|
829
|
-
})
|
|
830
|
-
break
|
|
829
|
+
this.respond(550, msg || 'HELO denied', () => {
|
|
830
|
+
this.disconnect()
|
|
831
|
+
})
|
|
832
|
+
break
|
|
831
833
|
case constants.denysoft:
|
|
832
|
-
this.respond(450, msg ||
|
|
833
|
-
this.set('hello', 'verb', null)
|
|
834
|
-
this.set('hello', 'host', null)
|
|
835
|
-
})
|
|
836
|
-
break
|
|
834
|
+
this.respond(450, msg || 'HELO denied', () => {
|
|
835
|
+
this.set('hello', 'verb', null)
|
|
836
|
+
this.set('hello', 'host', null)
|
|
837
|
+
})
|
|
838
|
+
break
|
|
837
839
|
case constants.denysoftdisconnect:
|
|
838
|
-
this.respond(450, msg ||
|
|
839
|
-
this.disconnect()
|
|
840
|
-
})
|
|
841
|
-
break
|
|
840
|
+
this.respond(450, msg || 'HELO denied', () => {
|
|
841
|
+
this.disconnect()
|
|
842
|
+
})
|
|
843
|
+
break
|
|
842
844
|
default:
|
|
843
845
|
// RFC5321 section 4.1.1.1
|
|
844
846
|
// Hostname/domain should appear after 250
|
|
845
|
-
this.respond(250, `${this.local.host} Hello ${this.get_remote('host')}, ${cfg.message.helo}`)
|
|
847
|
+
this.respond(250, `${this.local.host} Hello ${this.get_remote('host')}, ${cfg.message.helo}`)
|
|
846
848
|
}
|
|
847
849
|
}
|
|
848
|
-
ehlo_respond
|
|
849
|
-
|
|
850
|
+
ehlo_respond(retval, msg) {
|
|
850
851
|
switch (retval) {
|
|
851
852
|
case constants.deny:
|
|
852
|
-
this.respond(550, msg ||
|
|
853
|
-
this.set('hello', 'verb', null)
|
|
854
|
-
this.set('hello', 'host', null)
|
|
855
|
-
})
|
|
856
|
-
break
|
|
853
|
+
this.respond(550, msg || 'EHLO denied', () => {
|
|
854
|
+
this.set('hello', 'verb', null)
|
|
855
|
+
this.set('hello', 'host', null)
|
|
856
|
+
})
|
|
857
|
+
break
|
|
857
858
|
case constants.denydisconnect:
|
|
858
|
-
this.respond(550, msg ||
|
|
859
|
-
this.disconnect()
|
|
860
|
-
})
|
|
861
|
-
break
|
|
859
|
+
this.respond(550, msg || 'EHLO denied', () => {
|
|
860
|
+
this.disconnect()
|
|
861
|
+
})
|
|
862
|
+
break
|
|
862
863
|
case constants.denysoft:
|
|
863
|
-
this.respond(450, msg ||
|
|
864
|
-
this.set('hello', 'verb', null)
|
|
865
|
-
this.set('hello', 'host', null)
|
|
866
|
-
})
|
|
867
|
-
break
|
|
864
|
+
this.respond(450, msg || 'EHLO denied', () => {
|
|
865
|
+
this.set('hello', 'verb', null)
|
|
866
|
+
this.set('hello', 'host', null)
|
|
867
|
+
})
|
|
868
|
+
break
|
|
868
869
|
case constants.denysoftdisconnect:
|
|
869
|
-
this.respond(450, msg ||
|
|
870
|
-
this.disconnect()
|
|
871
|
-
})
|
|
872
|
-
break
|
|
870
|
+
this.respond(450, msg || 'EHLO denied', () => {
|
|
871
|
+
this.disconnect()
|
|
872
|
+
})
|
|
873
|
+
break
|
|
873
874
|
default: {
|
|
874
875
|
// RFC5321 section 4.1.1.1
|
|
875
876
|
// Hostname/domain should appear after 250
|
|
876
877
|
|
|
877
878
|
const response = [
|
|
878
879
|
`${this.local.host} Hello ${this.get_remote('host')}, ${cfg.message.helo}`,
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
]
|
|
880
|
+
'PIPELINING',
|
|
881
|
+
'8BITMIME',
|
|
882
|
+
]
|
|
882
883
|
|
|
883
|
-
if (cfg.main.smtputf8) response.push(
|
|
884
|
+
if (cfg.main.smtputf8) response.push('SMTPUTF8')
|
|
884
885
|
|
|
885
|
-
response.push(`SIZE ${cfg.max.bytes}`)
|
|
886
|
+
response.push(`SIZE ${cfg.max.bytes}`)
|
|
886
887
|
|
|
887
|
-
this.capabilities = response
|
|
888
|
+
this.capabilities = response
|
|
888
889
|
|
|
889
|
-
plugins.run_hooks('capabilities', this)
|
|
890
|
-
this.esmtp = true
|
|
890
|
+
plugins.run_hooks('capabilities', this)
|
|
891
|
+
this.esmtp = true
|
|
891
892
|
}
|
|
892
893
|
}
|
|
893
894
|
}
|
|
894
|
-
capabilities_respond
|
|
895
|
-
this.respond(250, this.capabilities)
|
|
895
|
+
capabilities_respond(retval, msg) {
|
|
896
|
+
this.respond(250, this.capabilities)
|
|
896
897
|
}
|
|
897
|
-
quit_respond
|
|
898
|
+
quit_respond(retval, msg) {
|
|
898
899
|
this.respond(221, msg || `${this.local.host} ${cfg.message.close}`, () => {
|
|
899
|
-
this.disconnect()
|
|
900
|
-
})
|
|
900
|
+
this.disconnect()
|
|
901
|
+
})
|
|
901
902
|
}
|
|
902
|
-
vrfy_respond
|
|
903
|
+
vrfy_respond(retval, msg) {
|
|
903
904
|
switch (retval) {
|
|
904
905
|
case constants.deny:
|
|
905
|
-
this.respond(550, msg ||
|
|
906
|
-
this.reset_transaction()
|
|
907
|
-
})
|
|
908
|
-
break
|
|
906
|
+
this.respond(550, msg || 'Access Denied', () => {
|
|
907
|
+
this.reset_transaction()
|
|
908
|
+
})
|
|
909
|
+
break
|
|
909
910
|
case constants.denydisconnect:
|
|
910
|
-
this.respond(550, msg ||
|
|
911
|
-
this.disconnect()
|
|
912
|
-
})
|
|
913
|
-
break
|
|
911
|
+
this.respond(550, msg || 'Access Denied', () => {
|
|
912
|
+
this.disconnect()
|
|
913
|
+
})
|
|
914
|
+
break
|
|
914
915
|
case constants.denysoft:
|
|
915
|
-
this.respond(450, msg ||
|
|
916
|
-
this.reset_transaction()
|
|
917
|
-
})
|
|
918
|
-
break
|
|
916
|
+
this.respond(450, msg || 'Lookup Failed', () => {
|
|
917
|
+
this.reset_transaction()
|
|
918
|
+
})
|
|
919
|
+
break
|
|
919
920
|
case constants.denysoftdisconnect:
|
|
920
|
-
this.respond(450, msg ||
|
|
921
|
-
this.disconnect()
|
|
922
|
-
})
|
|
923
|
-
break
|
|
921
|
+
this.respond(450, msg || 'Lookup Failed', () => {
|
|
922
|
+
this.disconnect()
|
|
923
|
+
})
|
|
924
|
+
break
|
|
924
925
|
case constants.ok:
|
|
925
|
-
this.respond(250, msg ||
|
|
926
|
-
break
|
|
926
|
+
this.respond(250, msg || 'User OK')
|
|
927
|
+
break
|
|
927
928
|
default:
|
|
928
|
-
this.respond(252, "Just try sending a mail and we'll see how it turns out...")
|
|
929
|
+
this.respond(252, "Just try sending a mail and we'll see how it turns out...")
|
|
929
930
|
}
|
|
930
931
|
}
|
|
931
|
-
noop_respond
|
|
932
|
+
noop_respond(retval, msg) {
|
|
932
933
|
switch (retval) {
|
|
933
934
|
case constants.deny:
|
|
934
|
-
this.respond(500, msg ||
|
|
935
|
-
break
|
|
935
|
+
this.respond(500, msg || 'Stop wasting my time')
|
|
936
|
+
break
|
|
936
937
|
case constants.denydisconnect:
|
|
937
|
-
this.respond(500, msg ||
|
|
938
|
-
this.disconnect()
|
|
939
|
-
})
|
|
940
|
-
break
|
|
938
|
+
this.respond(500, msg || 'Stop wasting my time', () => {
|
|
939
|
+
this.disconnect()
|
|
940
|
+
})
|
|
941
|
+
break
|
|
941
942
|
default:
|
|
942
|
-
this.respond(250,
|
|
943
|
+
this.respond(250, 'OK')
|
|
943
944
|
}
|
|
944
945
|
}
|
|
945
|
-
rset_respond
|
|
946
|
-
this.respond(250,
|
|
947
|
-
this.reset_transaction()
|
|
946
|
+
rset_respond(retval, msg) {
|
|
947
|
+
this.respond(250, 'OK', () => {
|
|
948
|
+
this.reset_transaction()
|
|
948
949
|
})
|
|
949
950
|
}
|
|
950
|
-
mail_respond
|
|
951
|
+
mail_respond(retval, msg) {
|
|
951
952
|
if (!this.transaction) {
|
|
952
|
-
this.logerror(
|
|
953
|
-
return
|
|
954
|
-
}
|
|
955
|
-
const sender = this.transaction.mail_from
|
|
956
|
-
const dmsg
|
|
957
|
-
this.lognotice(
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
'msg': (msg || ''),
|
|
962
|
-
}
|
|
963
|
-
);
|
|
953
|
+
this.logerror('mail_respond found no transaction!')
|
|
954
|
+
return
|
|
955
|
+
}
|
|
956
|
+
const sender = this.transaction.mail_from
|
|
957
|
+
const dmsg = `sender ${sender.format()}`
|
|
958
|
+
this.lognotice(dmsg, {
|
|
959
|
+
code: constants.translate(retval),
|
|
960
|
+
msg: msg || '',
|
|
961
|
+
})
|
|
964
962
|
|
|
965
963
|
const store_results = (action) => {
|
|
966
|
-
let addr = sender.format()
|
|
967
|
-
if (addr.length > 2) {
|
|
968
|
-
|
|
964
|
+
let addr = sender.format()
|
|
965
|
+
if (addr.length > 2) {
|
|
966
|
+
// all but null sender
|
|
967
|
+
addr = addr.substr(1, addr.length - 2) // trim off < >
|
|
969
968
|
}
|
|
970
|
-
this.transaction.results.add(
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
969
|
+
this.transaction.results.add(
|
|
970
|
+
{ name: 'mail_from' },
|
|
971
|
+
{
|
|
972
|
+
action,
|
|
973
|
+
code: constants.translate(retval),
|
|
974
|
+
address: addr,
|
|
975
|
+
},
|
|
976
|
+
)
|
|
975
977
|
}
|
|
976
978
|
|
|
977
979
|
switch (retval) {
|
|
978
980
|
case constants.deny:
|
|
979
981
|
this.respond(550, msg || `${dmsg} denied`, () => {
|
|
980
|
-
store_results('reject')
|
|
981
|
-
this.reset_transaction()
|
|
982
|
-
})
|
|
983
|
-
break
|
|
982
|
+
store_results('reject')
|
|
983
|
+
this.reset_transaction()
|
|
984
|
+
})
|
|
985
|
+
break
|
|
984
986
|
case constants.denydisconnect:
|
|
985
|
-
this.respond(550, msg ||
|
|
986
|
-
store_results('reject')
|
|
987
|
-
this.disconnect()
|
|
988
|
-
})
|
|
989
|
-
break
|
|
987
|
+
this.respond(550, msg || `${dmsg} denied`, () => {
|
|
988
|
+
store_results('reject')
|
|
989
|
+
this.disconnect()
|
|
990
|
+
})
|
|
991
|
+
break
|
|
990
992
|
case constants.denysoft:
|
|
991
993
|
this.respond(450, msg || `${dmsg} denied`, () => {
|
|
992
|
-
store_results('tempfail')
|
|
993
|
-
this.reset_transaction()
|
|
994
|
-
})
|
|
995
|
-
break
|
|
994
|
+
store_results('tempfail')
|
|
995
|
+
this.reset_transaction()
|
|
996
|
+
})
|
|
997
|
+
break
|
|
996
998
|
case constants.denysoftdisconnect:
|
|
997
999
|
this.respond(450, msg || `${dmsg} denied`, () => {
|
|
998
|
-
store_results('tempfail')
|
|
999
|
-
this.disconnect()
|
|
1000
|
-
})
|
|
1001
|
-
break
|
|
1000
|
+
store_results('tempfail')
|
|
1001
|
+
this.disconnect()
|
|
1002
|
+
})
|
|
1003
|
+
break
|
|
1002
1004
|
default:
|
|
1003
|
-
store_results('accept')
|
|
1004
|
-
this.respond(250, msg || `${dmsg} OK`)
|
|
1005
|
+
store_results('accept')
|
|
1006
|
+
this.respond(250, msg || `${dmsg} OK`)
|
|
1005
1007
|
}
|
|
1006
1008
|
}
|
|
1007
|
-
rcpt_incr
|
|
1008
|
-
this.transaction.rcpt_count[action]
|
|
1009
|
-
this.rcpt_count[action]
|
|
1009
|
+
rcpt_incr(rcpt, action, msg, retval) {
|
|
1010
|
+
this.transaction.rcpt_count[action]++
|
|
1011
|
+
this.rcpt_count[action]++
|
|
1010
1012
|
|
|
1011
|
-
const addr = rcpt.format()
|
|
1013
|
+
const addr = rcpt.format()
|
|
1012
1014
|
const recipient = {
|
|
1013
|
-
address: addr.substr(1, addr.length -2),
|
|
1014
|
-
action
|
|
1015
|
-
}
|
|
1015
|
+
address: addr.substr(1, addr.length - 2),
|
|
1016
|
+
action,
|
|
1017
|
+
}
|
|
1016
1018
|
|
|
1017
1019
|
if (msg && action !== 'accept') {
|
|
1018
1020
|
if (typeof msg === 'object' && msg.constructor.name === 'DSN') {
|
|
1019
|
-
recipient.msg
|
|
1020
|
-
recipient.code = msg.code
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
recipient.
|
|
1024
|
-
recipient.code = constants.translate(retval);
|
|
1021
|
+
recipient.msg = msg.reply
|
|
1022
|
+
recipient.code = msg.code
|
|
1023
|
+
} else {
|
|
1024
|
+
recipient.msg = msg
|
|
1025
|
+
recipient.code = constants.translate(retval)
|
|
1025
1026
|
}
|
|
1026
1027
|
}
|
|
1027
1028
|
|
|
1028
|
-
this.transaction.results.push({name: 'rcpt_to'}, { recipient })
|
|
1029
|
+
this.transaction.results.push({ name: 'rcpt_to' }, { recipient })
|
|
1029
1030
|
}
|
|
1030
|
-
rcpt_ok_respond
|
|
1031
|
+
rcpt_ok_respond(retval, msg) {
|
|
1031
1032
|
if (!this.transaction) {
|
|
1032
|
-
this.results.add(this, {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1033
|
+
this.results.add(this, {
|
|
1034
|
+
err: 'rcpt_ok_respond found no transaction',
|
|
1035
|
+
})
|
|
1036
|
+
return
|
|
1037
|
+
}
|
|
1038
|
+
if (!msg) msg = this.last_rcpt_msg
|
|
1039
|
+
const rcpt = this.transaction.rcpt_to[this.transaction.rcpt_to.length - 1]
|
|
1040
|
+
const dmsg = `recipient ${rcpt.format()}`
|
|
1038
1041
|
// Log OK instead of CONT as this hook only runs if hook_rcpt returns OK
|
|
1039
|
-
this.lognotice(
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
'sender': this.transaction.mail_from.address(),
|
|
1045
|
-
}
|
|
1046
|
-
);
|
|
1042
|
+
this.lognotice(dmsg, {
|
|
1043
|
+
code: constants.translate(retval === constants.cont ? constants.ok : retval),
|
|
1044
|
+
msg: msg || '',
|
|
1045
|
+
sender: this.transaction.mail_from.address(),
|
|
1046
|
+
})
|
|
1047
1047
|
switch (retval) {
|
|
1048
1048
|
case constants.deny:
|
|
1049
1049
|
this.respond(550, msg || `${dmsg} denied`, () => {
|
|
1050
|
-
this.rcpt_incr(rcpt, 'reject', msg, retval)
|
|
1051
|
-
this.transaction.rcpt_to.pop()
|
|
1052
|
-
})
|
|
1053
|
-
break
|
|
1050
|
+
this.rcpt_incr(rcpt, 'reject', msg, retval)
|
|
1051
|
+
this.transaction.rcpt_to.pop()
|
|
1052
|
+
})
|
|
1053
|
+
break
|
|
1054
1054
|
case constants.denydisconnect:
|
|
1055
1055
|
this.respond(550, msg || `${dmsg} denied`, () => {
|
|
1056
|
-
this.rcpt_incr(rcpt, 'reject', msg, retval)
|
|
1057
|
-
this.disconnect()
|
|
1058
|
-
})
|
|
1059
|
-
break
|
|
1056
|
+
this.rcpt_incr(rcpt, 'reject', msg, retval)
|
|
1057
|
+
this.disconnect()
|
|
1058
|
+
})
|
|
1059
|
+
break
|
|
1060
1060
|
case constants.denysoft:
|
|
1061
1061
|
this.respond(450, msg || `${dmsg} denied`, () => {
|
|
1062
|
-
this.rcpt_incr(rcpt, 'tempfail', msg, retval)
|
|
1063
|
-
this.transaction.rcpt_to.pop()
|
|
1064
|
-
})
|
|
1065
|
-
break
|
|
1062
|
+
this.rcpt_incr(rcpt, 'tempfail', msg, retval)
|
|
1063
|
+
this.transaction.rcpt_to.pop()
|
|
1064
|
+
})
|
|
1065
|
+
break
|
|
1066
1066
|
case constants.denysoftdisconnect:
|
|
1067
1067
|
this.respond(450, msg || `${dmsg} denied`, () => {
|
|
1068
|
-
this.rcpt_incr(rcpt, 'tempfail', msg, retval)
|
|
1069
|
-
this.disconnect()
|
|
1070
|
-
})
|
|
1071
|
-
break
|
|
1068
|
+
this.rcpt_incr(rcpt, 'tempfail', msg, retval)
|
|
1069
|
+
this.disconnect()
|
|
1070
|
+
})
|
|
1071
|
+
break
|
|
1072
1072
|
default:
|
|
1073
1073
|
this.respond(250, msg || `${dmsg} OK`, () => {
|
|
1074
|
-
this.rcpt_incr(rcpt, 'accept', msg, retval)
|
|
1075
|
-
})
|
|
1074
|
+
this.rcpt_incr(rcpt, 'accept', msg, retval)
|
|
1075
|
+
})
|
|
1076
1076
|
}
|
|
1077
1077
|
}
|
|
1078
|
-
rcpt_respond
|
|
1078
|
+
rcpt_respond(retval, msg) {
|
|
1079
1079
|
if (retval === constants.cont && this.relaying) {
|
|
1080
|
-
retval = constants.ok
|
|
1080
|
+
retval = constants.ok
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
1083
|
if (!this.transaction) {
|
|
1084
|
-
this.results.add(this, {
|
|
1085
|
-
|
|
1084
|
+
this.results.add(this, {
|
|
1085
|
+
err: 'rcpt_respond found no transaction',
|
|
1086
|
+
})
|
|
1087
|
+
return
|
|
1086
1088
|
}
|
|
1087
|
-
const rcpt = this.transaction.rcpt_to[this.transaction.rcpt_to.length - 1]
|
|
1088
|
-
const dmsg = `recipient ${rcpt.format()}
|
|
1089
|
+
const rcpt = this.transaction.rcpt_to[this.transaction.rcpt_to.length - 1]
|
|
1090
|
+
const dmsg = `recipient ${rcpt.format()}`
|
|
1089
1091
|
if (retval !== constants.ok) {
|
|
1090
|
-
this.lognotice(
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
'sender': this.transaction.mail_from.address(),
|
|
1096
|
-
}
|
|
1097
|
-
);
|
|
1092
|
+
this.lognotice(dmsg, {
|
|
1093
|
+
code: constants.translate(retval === constants.cont ? constants.ok : retval),
|
|
1094
|
+
msg: msg || '',
|
|
1095
|
+
sender: this.transaction.mail_from.address(),
|
|
1096
|
+
})
|
|
1098
1097
|
}
|
|
1099
1098
|
switch (retval) {
|
|
1100
1099
|
case constants.deny:
|
|
1101
1100
|
this.respond(550, msg || `${dmsg} denied`, () => {
|
|
1102
|
-
this.rcpt_incr(rcpt, 'reject', msg, retval)
|
|
1103
|
-
this.transaction.rcpt_to.pop()
|
|
1104
|
-
})
|
|
1105
|
-
break
|
|
1101
|
+
this.rcpt_incr(rcpt, 'reject', msg, retval)
|
|
1102
|
+
this.transaction.rcpt_to.pop()
|
|
1103
|
+
})
|
|
1104
|
+
break
|
|
1106
1105
|
case constants.denydisconnect:
|
|
1107
1106
|
this.respond(550, msg || `${dmsg} denied`, () => {
|
|
1108
|
-
this.rcpt_incr(rcpt, 'reject', msg, retval)
|
|
1109
|
-
this.disconnect()
|
|
1110
|
-
})
|
|
1111
|
-
break
|
|
1107
|
+
this.rcpt_incr(rcpt, 'reject', msg, retval)
|
|
1108
|
+
this.disconnect()
|
|
1109
|
+
})
|
|
1110
|
+
break
|
|
1112
1111
|
case constants.denysoft:
|
|
1113
1112
|
this.respond(450, msg || `${dmsg} denied`, () => {
|
|
1114
|
-
this.rcpt_incr(rcpt, 'tempfail', msg, retval)
|
|
1115
|
-
this.transaction.rcpt_to.pop()
|
|
1116
|
-
})
|
|
1117
|
-
break
|
|
1113
|
+
this.rcpt_incr(rcpt, 'tempfail', msg, retval)
|
|
1114
|
+
this.transaction.rcpt_to.pop()
|
|
1115
|
+
})
|
|
1116
|
+
break
|
|
1118
1117
|
case constants.denysoftdisconnect:
|
|
1119
1118
|
this.respond(450, msg || `${dmsg} denied`, () => {
|
|
1120
|
-
this.rcpt_incr(rcpt, 'tempfail', msg, retval)
|
|
1121
|
-
this.disconnect()
|
|
1122
|
-
})
|
|
1123
|
-
break
|
|
1119
|
+
this.rcpt_incr(rcpt, 'tempfail', msg, retval)
|
|
1120
|
+
this.disconnect()
|
|
1121
|
+
})
|
|
1122
|
+
break
|
|
1124
1123
|
case constants.ok:
|
|
1125
1124
|
// Store any msg for rcpt_ok
|
|
1126
|
-
this.last_rcpt_msg = msg
|
|
1127
|
-
plugins.run_hooks('rcpt_ok', this, rcpt)
|
|
1128
|
-
break
|
|
1125
|
+
this.last_rcpt_msg = msg
|
|
1126
|
+
plugins.run_hooks('rcpt_ok', this, rcpt)
|
|
1127
|
+
break
|
|
1129
1128
|
default: {
|
|
1130
1129
|
if (retval !== constants.cont) {
|
|
1131
|
-
this.logalert(
|
|
1130
|
+
this.logalert('No plugin determined if relaying was allowed')
|
|
1132
1131
|
}
|
|
1133
|
-
const rej_msg = `I cannot deliver mail for ${rcpt.format()}
|
|
1132
|
+
const rej_msg = `I cannot deliver mail for ${rcpt.format()}`
|
|
1134
1133
|
this.respond(550, rej_msg, () => {
|
|
1135
|
-
this.rcpt_incr(rcpt, 'reject', rej_msg, retval)
|
|
1136
|
-
this.transaction.rcpt_to.pop()
|
|
1137
|
-
})
|
|
1134
|
+
this.rcpt_incr(rcpt, 'reject', rej_msg, retval)
|
|
1135
|
+
this.transaction.rcpt_to.pop()
|
|
1136
|
+
})
|
|
1138
1137
|
}
|
|
1139
1138
|
}
|
|
1140
1139
|
}
|
|
1141
1140
|
/////////////////////////////////////////////////////////////////////////////
|
|
1142
1141
|
// HAProxy support
|
|
1143
1142
|
|
|
1144
|
-
cmd_proxy
|
|
1145
|
-
|
|
1143
|
+
cmd_proxy(line) {
|
|
1146
1144
|
if (!this.proxy.allowed) {
|
|
1147
|
-
this.respond(421, `PROXY not allowed from ${this.remote.ip}`)
|
|
1148
|
-
return this.disconnect()
|
|
1145
|
+
this.respond(421, `PROXY not allowed from ${this.remote.ip}`)
|
|
1146
|
+
return this.disconnect()
|
|
1149
1147
|
}
|
|
1150
1148
|
|
|
1151
|
-
const match = /(TCP4|TCP6|UNKNOWN) (\S+) (\S+) (\d+) (\d+)$/.exec(line)
|
|
1149
|
+
const match = /(TCP4|TCP6|UNKNOWN) (\S+) (\S+) (\d+) (\d+)$/.exec(line)
|
|
1152
1150
|
if (!match) {
|
|
1153
|
-
this.respond(421, 'Invalid PROXY format')
|
|
1154
|
-
return this.disconnect()
|
|
1151
|
+
this.respond(421, 'Invalid PROXY format')
|
|
1152
|
+
return this.disconnect()
|
|
1155
1153
|
}
|
|
1156
|
-
const proto = match[1]
|
|
1157
|
-
const src_ip = match[2]
|
|
1158
|
-
const dst_ip = match[3]
|
|
1159
|
-
const src_port = match[4]
|
|
1160
|
-
const dst_port = match[5]
|
|
1154
|
+
const proto = match[1]
|
|
1155
|
+
const src_ip = match[2]
|
|
1156
|
+
const dst_ip = match[3]
|
|
1157
|
+
const src_port = match[4]
|
|
1158
|
+
const dst_port = match[5]
|
|
1161
1159
|
|
|
1162
1160
|
// Validate source/destination IP
|
|
1163
1161
|
/*eslint no-fallthrough: 0 */
|
|
1164
1162
|
switch (proto) {
|
|
1165
1163
|
case 'TCP4':
|
|
1166
1164
|
if (ipaddr.IPv4.isValid(src_ip) && ipaddr.IPv4.isValid(dst_ip)) {
|
|
1167
|
-
break
|
|
1165
|
+
break
|
|
1168
1166
|
}
|
|
1169
1167
|
case 'TCP6':
|
|
1170
1168
|
if (ipaddr.IPv6.isValid(src_ip) && ipaddr.IPv6.isValid(dst_ip)) {
|
|
1171
|
-
break
|
|
1169
|
+
break
|
|
1172
1170
|
}
|
|
1173
1171
|
// case 'UNKNOWN':
|
|
1174
1172
|
default:
|
|
1175
|
-
this.respond(421, 'Invalid PROXY format')
|
|
1176
|
-
return this.disconnect()
|
|
1173
|
+
this.respond(421, 'Invalid PROXY format')
|
|
1174
|
+
return this.disconnect()
|
|
1177
1175
|
}
|
|
1178
1176
|
|
|
1179
1177
|
// Apply changes
|
|
1180
|
-
this.loginfo(
|
|
1181
|
-
|
|
1182
|
-
{
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
dst_ip: `${dst_ip}:${dst_port}`,
|
|
1186
|
-
}
|
|
1187
|
-
);
|
|
1178
|
+
this.loginfo('HAProxy', {
|
|
1179
|
+
proto,
|
|
1180
|
+
src_ip: `${src_ip}:${src_port}`,
|
|
1181
|
+
dst_ip: `${dst_ip}:${dst_port}`,
|
|
1182
|
+
})
|
|
1188
1183
|
|
|
1189
1184
|
this.notes.proxy = {
|
|
1190
1185
|
type: 'haproxy',
|
|
@@ -1193,697 +1188,666 @@ class Connection {
|
|
|
1193
1188
|
src_port,
|
|
1194
1189
|
dst_ip,
|
|
1195
1190
|
dst_port,
|
|
1196
|
-
proxy_ip: this.remote.ip
|
|
1197
|
-
}
|
|
1191
|
+
proxy_ip: this.remote.ip,
|
|
1192
|
+
}
|
|
1198
1193
|
|
|
1199
1194
|
this.reset_transaction(() => {
|
|
1200
|
-
this.set('proxy.ip', this.remote.ip)
|
|
1201
|
-
this.set('proxy.type', 'haproxy')
|
|
1202
|
-
this.relaying = false
|
|
1203
|
-
this.set('local.ip', dst_ip)
|
|
1204
|
-
this.set('local.port', parseInt(dst_port, 10))
|
|
1205
|
-
this.set('remote.ip', src_ip)
|
|
1206
|
-
this.set('remote.port', parseInt(src_port, 10))
|
|
1207
|
-
this.set('remote.host', null)
|
|
1208
|
-
this.set('hello.host', null)
|
|
1209
|
-
plugins.run_hooks('connect_init', this)
|
|
1210
|
-
})
|
|
1195
|
+
this.set('proxy.ip', this.remote.ip)
|
|
1196
|
+
this.set('proxy.type', 'haproxy')
|
|
1197
|
+
this.relaying = false
|
|
1198
|
+
this.set('local.ip', dst_ip)
|
|
1199
|
+
this.set('local.port', parseInt(dst_port, 10))
|
|
1200
|
+
this.set('remote.ip', src_ip)
|
|
1201
|
+
this.set('remote.port', parseInt(src_port, 10))
|
|
1202
|
+
this.set('remote.host', null)
|
|
1203
|
+
this.set('hello.host', null)
|
|
1204
|
+
plugins.run_hooks('connect_init', this)
|
|
1205
|
+
})
|
|
1211
1206
|
}
|
|
1212
1207
|
/////////////////////////////////////////////////////////////////////////////
|
|
1213
1208
|
// SMTP Commands
|
|
1214
1209
|
|
|
1215
|
-
cmd_internalcmd
|
|
1210
|
+
cmd_internalcmd(line) {
|
|
1216
1211
|
if (!this.remote.is_local) {
|
|
1217
|
-
return this.respond(501,
|
|
1212
|
+
return this.respond(501, 'INTERNALCMD not allowed remotely')
|
|
1218
1213
|
}
|
|
1219
|
-
const results =
|
|
1214
|
+
const results = String(line).split(/ +/)
|
|
1220
1215
|
if (/key:/.test(results[0])) {
|
|
1221
|
-
const internal_key = config.get('internalcmd_key')
|
|
1216
|
+
const internal_key = config.get('internalcmd_key')
|
|
1222
1217
|
if (results[0] != `key:${internal_key}`) {
|
|
1223
|
-
return this.respond(501,
|
|
1218
|
+
return this.respond(501, 'Invalid internalcmd_key - check config')
|
|
1224
1219
|
}
|
|
1225
|
-
results.shift()
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
return this.respond(501, "Missing internalcmd_key - check config");
|
|
1220
|
+
results.shift()
|
|
1221
|
+
} else if (config.get('internalcmd_key')) {
|
|
1222
|
+
return this.respond(501, 'Missing internalcmd_key - check config')
|
|
1229
1223
|
}
|
|
1230
1224
|
|
|
1231
1225
|
// Now send the internal command to the master process
|
|
1232
|
-
const command = results.shift()
|
|
1226
|
+
const command = results.shift()
|
|
1233
1227
|
if (!command) {
|
|
1234
|
-
return this.respond(501,
|
|
1228
|
+
return this.respond(501, 'No command given')
|
|
1235
1229
|
}
|
|
1236
1230
|
|
|
1237
|
-
require('./server').sendToMaster(command, results)
|
|
1238
|
-
return this.respond(250,
|
|
1231
|
+
require('./server').sendToMaster(command, results)
|
|
1232
|
+
return this.respond(250, 'Command sent for execution. Check Haraka logs for results.')
|
|
1239
1233
|
}
|
|
1240
|
-
cmd_helo
|
|
1241
|
-
const results =
|
|
1242
|
-
const host = results[0]
|
|
1234
|
+
cmd_helo(line) {
|
|
1235
|
+
const results = String(line).split(/ +/)
|
|
1236
|
+
const host = results[0]
|
|
1243
1237
|
if (!host) {
|
|
1244
|
-
return this.respond(501,
|
|
1238
|
+
return this.respond(501, 'HELO requires domain/address - see RFC-2821 4.1.1.1')
|
|
1245
1239
|
}
|
|
1246
1240
|
|
|
1247
1241
|
this.reset_transaction(() => {
|
|
1248
|
-
this.set('hello', 'verb', 'HELO')
|
|
1249
|
-
this.set('hello', 'host', host)
|
|
1250
|
-
this.results.add({ name: 'helo' }, this.hello)
|
|
1251
|
-
plugins.run_hooks('helo', this, host)
|
|
1252
|
-
})
|
|
1253
|
-
}
|
|
1254
|
-
cmd_ehlo
|
|
1255
|
-
const results =
|
|
1256
|
-
const host = results[0]
|
|
1242
|
+
this.set('hello', 'verb', 'HELO')
|
|
1243
|
+
this.set('hello', 'host', host)
|
|
1244
|
+
this.results.add({ name: 'helo' }, this.hello)
|
|
1245
|
+
plugins.run_hooks('helo', this, host)
|
|
1246
|
+
})
|
|
1247
|
+
}
|
|
1248
|
+
cmd_ehlo(line) {
|
|
1249
|
+
const results = String(line).split(/ +/)
|
|
1250
|
+
const host = results[0]
|
|
1257
1251
|
if (!host) {
|
|
1258
|
-
return this.respond(501,
|
|
1252
|
+
return this.respond(501, 'EHLO requires domain/address - see RFC-2821 4.1.1.1')
|
|
1259
1253
|
}
|
|
1260
1254
|
|
|
1261
1255
|
this.reset_transaction(() => {
|
|
1262
|
-
this.set('hello', 'verb', 'EHLO')
|
|
1263
|
-
this.set('hello', 'host', host)
|
|
1264
|
-
this.results.add({ name: 'helo' }, this.hello)
|
|
1265
|
-
plugins.run_hooks('ehlo', this, host)
|
|
1266
|
-
})
|
|
1256
|
+
this.set('hello', 'verb', 'EHLO')
|
|
1257
|
+
this.set('hello', 'host', host)
|
|
1258
|
+
this.results.add({ name: 'helo' }, this.hello)
|
|
1259
|
+
plugins.run_hooks('ehlo', this, host)
|
|
1260
|
+
})
|
|
1267
1261
|
}
|
|
1268
|
-
cmd_quit
|
|
1262
|
+
cmd_quit(args) {
|
|
1269
1263
|
// RFC 5321 Section 4.3.2
|
|
1270
1264
|
// QUIT does not accept arguments
|
|
1271
1265
|
if (args) {
|
|
1272
|
-
return this.respond(501,
|
|
1266
|
+
return this.respond(501, 'Syntax error')
|
|
1273
1267
|
}
|
|
1274
|
-
plugins.run_hooks('quit', this)
|
|
1268
|
+
plugins.run_hooks('quit', this)
|
|
1275
1269
|
}
|
|
1276
|
-
cmd_rset
|
|
1270
|
+
cmd_rset(args) {
|
|
1277
1271
|
// RFC 5321 Section 4.3.2
|
|
1278
1272
|
// RSET does not accept arguments
|
|
1279
1273
|
if (args) {
|
|
1280
|
-
return this.respond(501,
|
|
1274
|
+
return this.respond(501, 'Syntax error')
|
|
1281
1275
|
}
|
|
1282
|
-
plugins.run_hooks('rset', this)
|
|
1276
|
+
plugins.run_hooks('rset', this)
|
|
1283
1277
|
}
|
|
1284
|
-
cmd_vrfy
|
|
1278
|
+
cmd_vrfy(line) {
|
|
1285
1279
|
// only supported via plugins
|
|
1286
|
-
plugins.run_hooks('vrfy', this)
|
|
1280
|
+
plugins.run_hooks('vrfy', this)
|
|
1287
1281
|
}
|
|
1288
|
-
cmd_noop
|
|
1289
|
-
plugins.run_hooks('noop', this)
|
|
1282
|
+
cmd_noop() {
|
|
1283
|
+
plugins.run_hooks('noop', this)
|
|
1290
1284
|
}
|
|
1291
|
-
cmd_help
|
|
1292
|
-
this.respond(250,
|
|
1285
|
+
cmd_help() {
|
|
1286
|
+
this.respond(250, 'Not implemented')
|
|
1293
1287
|
}
|
|
1294
|
-
cmd_mail
|
|
1288
|
+
cmd_mail(line) {
|
|
1295
1289
|
if (!this.hello.host) {
|
|
1296
|
-
this.errors
|
|
1297
|
-
return this.respond(503, 'Use EHLO/HELO before MAIL')
|
|
1290
|
+
this.errors++
|
|
1291
|
+
return this.respond(503, 'Use EHLO/HELO before MAIL')
|
|
1298
1292
|
}
|
|
1299
1293
|
// Require authentication on ports 587 & 465
|
|
1300
|
-
if (!this.relaying && [587,465].includes(this.local.port)) {
|
|
1301
|
-
this.errors
|
|
1302
|
-
return this.respond(550, 'Authentication required')
|
|
1294
|
+
if (!this.relaying && [587, 465].includes(this.local.port)) {
|
|
1295
|
+
this.errors++
|
|
1296
|
+
return this.respond(550, 'Authentication required')
|
|
1303
1297
|
}
|
|
1304
1298
|
|
|
1305
|
-
let results
|
|
1299
|
+
let results
|
|
1306
1300
|
try {
|
|
1307
|
-
results = rfc1869.parse('mail', line,
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
this.errors++;
|
|
1301
|
+
results = rfc1869.parse('mail', line, !this.relaying && cfg.main.strict_rfc1869)
|
|
1302
|
+
} catch (err) {
|
|
1303
|
+
this.errors++
|
|
1311
1304
|
if (err.stack) {
|
|
1312
|
-
this.lognotice(err.stack.split(/\n/)[0])
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
this.logerror(err);
|
|
1305
|
+
this.lognotice(err.stack.split(/\n/)[0])
|
|
1306
|
+
} else {
|
|
1307
|
+
this.logerror(err)
|
|
1316
1308
|
}
|
|
1317
1309
|
// Explicitly handle out-of-disk space errors
|
|
1318
1310
|
if (err.code === 'ENOSPC') {
|
|
1319
|
-
return this.respond(452, 'Internal Server Error')
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
return this.respond(501, ['Command parsing failed', err]);
|
|
1311
|
+
return this.respond(452, 'Internal Server Error')
|
|
1312
|
+
} else {
|
|
1313
|
+
return this.respond(501, ['Command parsing failed', err])
|
|
1323
1314
|
}
|
|
1324
1315
|
}
|
|
1325
1316
|
|
|
1326
|
-
let from
|
|
1317
|
+
let from
|
|
1327
1318
|
try {
|
|
1328
|
-
from = new Address(results.shift())
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
return this.respond(501, `Invalid MAIL FROM address`);
|
|
1319
|
+
from = new Address(results.shift())
|
|
1320
|
+
} catch (err) {
|
|
1321
|
+
return this.respond(501, `Invalid MAIL FROM address`)
|
|
1332
1322
|
}
|
|
1333
1323
|
|
|
1334
1324
|
// Get rest of key=value pairs
|
|
1335
|
-
const params = {}
|
|
1336
|
-
results.forEach(param => {
|
|
1337
|
-
const kv = param.match(/^([^=]+)(?:=(.+))?$/)
|
|
1338
|
-
if (kv)
|
|
1339
|
-
|
|
1340
|
-
});
|
|
1325
|
+
const params = {}
|
|
1326
|
+
results.forEach((param) => {
|
|
1327
|
+
const kv = param.match(/^([^=]+)(?:=(.+))?$/)
|
|
1328
|
+
if (kv) params[kv[1].toUpperCase()] = kv[2] || null
|
|
1329
|
+
})
|
|
1341
1330
|
|
|
1342
1331
|
// Parameters are only valid if EHLO was sent
|
|
1343
1332
|
if (!this.esmtp && Object.keys(params).length > 0) {
|
|
1344
|
-
return this.respond(555, 'Invalid command parameters')
|
|
1333
|
+
return this.respond(555, 'Invalid command parameters')
|
|
1345
1334
|
}
|
|
1346
1335
|
|
|
1347
1336
|
// Handle SIZE extension
|
|
1348
1337
|
if (params?.SIZE && params.SIZE > 0) {
|
|
1349
1338
|
if (cfg.max.bytes > 0 && params.SIZE > cfg.max.bytes) {
|
|
1350
|
-
return this.respond(550, 'Message too big!')
|
|
1339
|
+
return this.respond(550, 'Message too big!')
|
|
1351
1340
|
}
|
|
1352
1341
|
}
|
|
1353
1342
|
|
|
1354
1343
|
this.init_transaction(() => {
|
|
1355
|
-
this.transaction.mail_from = from
|
|
1344
|
+
this.transaction.mail_from = from
|
|
1356
1345
|
if (this.hello.verb == 'HELO') {
|
|
1357
|
-
this.transaction.encoding = 'binary'
|
|
1358
|
-
this.encoding = 'binary'
|
|
1346
|
+
this.transaction.encoding = 'binary'
|
|
1347
|
+
this.encoding = 'binary'
|
|
1359
1348
|
}
|
|
1360
|
-
plugins.run_hooks('mail', this, [from, params])
|
|
1361
|
-
})
|
|
1349
|
+
plugins.run_hooks('mail', this, [from, params])
|
|
1350
|
+
})
|
|
1362
1351
|
}
|
|
1363
|
-
cmd_rcpt
|
|
1352
|
+
cmd_rcpt(line) {
|
|
1364
1353
|
if (!this.transaction || !this.transaction.mail_from) {
|
|
1365
|
-
this.errors
|
|
1366
|
-
return this.respond(503,
|
|
1354
|
+
this.errors++
|
|
1355
|
+
return this.respond(503, 'Use MAIL before RCPT')
|
|
1367
1356
|
}
|
|
1368
1357
|
|
|
1369
|
-
let results
|
|
1358
|
+
let results
|
|
1370
1359
|
try {
|
|
1371
|
-
results = rfc1869.parse('rcpt', line, cfg.main.strict_rfc1869 && !this.relaying)
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
this.errors++;
|
|
1360
|
+
results = rfc1869.parse('rcpt', line, cfg.main.strict_rfc1869 && !this.relaying)
|
|
1361
|
+
} catch (err) {
|
|
1362
|
+
this.errors++
|
|
1375
1363
|
if (err.stack) {
|
|
1376
|
-
this.lognotice(err.stack.split(/\n/)[0])
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
this.logerror(err);
|
|
1364
|
+
this.lognotice(err.stack.split(/\n/)[0])
|
|
1365
|
+
} else {
|
|
1366
|
+
this.logerror(err)
|
|
1380
1367
|
}
|
|
1381
1368
|
// Explicitly handle out-of-disk space errors
|
|
1382
1369
|
if (err.code === 'ENOSPC') {
|
|
1383
|
-
return this.respond(452, 'Internal Server Error')
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
return this.respond(501, ["Command parsing failed", err]);
|
|
1370
|
+
return this.respond(452, 'Internal Server Error')
|
|
1371
|
+
} else {
|
|
1372
|
+
return this.respond(501, ['Command parsing failed', err])
|
|
1387
1373
|
}
|
|
1388
1374
|
}
|
|
1389
1375
|
|
|
1390
|
-
let recip
|
|
1376
|
+
let recip
|
|
1391
1377
|
try {
|
|
1392
|
-
recip = new Address(results.shift())
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
return this.respond(501, `Invalid RCPT TO address`);
|
|
1378
|
+
recip = new Address(results.shift())
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
return this.respond(501, `Invalid RCPT TO address`)
|
|
1396
1381
|
}
|
|
1397
1382
|
|
|
1398
1383
|
// Get rest of key=value pairs
|
|
1399
|
-
const params = {}
|
|
1384
|
+
const params = {}
|
|
1400
1385
|
results.forEach((param) => {
|
|
1401
|
-
const kv = param.match(/^([^=]+)(?:=(.+))?$/)
|
|
1402
|
-
if (kv)
|
|
1403
|
-
|
|
1404
|
-
});
|
|
1386
|
+
const kv = param.match(/^([^=]+)(?:=(.+))?$/)
|
|
1387
|
+
if (kv) params[kv[1].toUpperCase()] = kv[2] || null
|
|
1388
|
+
})
|
|
1405
1389
|
|
|
1406
1390
|
// Parameters are only valid if EHLO was sent
|
|
1407
1391
|
if (!this.esmtp && Object.keys(params).length > 0) {
|
|
1408
|
-
return this.respond(555, 'Invalid command parameters')
|
|
1392
|
+
return this.respond(555, 'Invalid command parameters')
|
|
1409
1393
|
}
|
|
1410
1394
|
|
|
1411
|
-
this.transaction.rcpt_to.push(recip)
|
|
1412
|
-
plugins.run_hooks('rcpt', this, [recip, params])
|
|
1395
|
+
this.transaction.rcpt_to.push(recip)
|
|
1396
|
+
plugins.run_hooks('rcpt', this, [recip, params])
|
|
1413
1397
|
}
|
|
1414
|
-
received_line
|
|
1415
|
-
let smtp = this.hello.verb === 'EHLO' ? 'ESMTP' : 'SMTP'
|
|
1398
|
+
received_line() {
|
|
1399
|
+
let smtp = this.hello.verb === 'EHLO' ? 'ESMTP' : 'SMTP'
|
|
1416
1400
|
// Implement RFC3848
|
|
1417
|
-
if (this.tls.enabled) smtp += 'S'
|
|
1418
|
-
if (this.authheader) smtp += 'A'
|
|
1401
|
+
if (this.tls.enabled) smtp += 'S'
|
|
1402
|
+
if (this.authheader) smtp += 'A'
|
|
1419
1403
|
|
|
1420
|
-
let sslheader
|
|
1404
|
+
let sslheader
|
|
1421
1405
|
|
|
1422
1406
|
if (this.get('tls.cipher.version')) {
|
|
1423
1407
|
// standardName appeared in Node.js v12.16 and v13.4
|
|
1424
1408
|
// RFC 8314
|
|
1425
|
-
sslheader = `tls ${this.tls.cipher.standardName || this.tls.cipher.name}
|
|
1409
|
+
sslheader = `tls ${this.tls.cipher.standardName || this.tls.cipher.name}`
|
|
1426
1410
|
}
|
|
1427
1411
|
|
|
1428
1412
|
let received_header = `from ${this.hello.host} (${this.get_remote('info')})\r
|
|
1429
1413
|
\tby ${this.local.host} (${this.local.info}) with ${smtp} id ${this.transaction.uuid}\r
|
|
1430
|
-
\tenvelope-from ${this.transaction.mail_from.format()}
|
|
1414
|
+
\tenvelope-from ${this.transaction.mail_from.format()}`
|
|
1431
1415
|
|
|
1432
|
-
if (sslheader)
|
|
1416
|
+
if (sslheader) received_header += `\r\n\t${sslheader.replace(/\r?\n\t?$/, '')}`
|
|
1433
1417
|
|
|
1434
1418
|
// Does not follow RFC 5321 section 4.4 grammar
|
|
1435
1419
|
if (this.authheader) received_header += ` ${this.authheader.replace(/\r?\n\t?$/, '')}`
|
|
1436
1420
|
|
|
1437
1421
|
received_header += `;\r\n\t${utils.date_to_str(new Date())}`
|
|
1438
1422
|
|
|
1439
|
-
return received_header
|
|
1423
|
+
return received_header
|
|
1440
1424
|
}
|
|
1441
|
-
auth_results
|
|
1425
|
+
auth_results(message) {
|
|
1442
1426
|
// https://datatracker.ietf.org/doc/rfc7001/
|
|
1443
|
-
const has_tran = !!
|
|
1427
|
+
const has_tran = !!this.transaction?.notes
|
|
1444
1428
|
|
|
1445
1429
|
// initialize connection note
|
|
1446
1430
|
if (!this.notes.authentication_results) {
|
|
1447
|
-
this.notes.authentication_results = []
|
|
1431
|
+
this.notes.authentication_results = []
|
|
1448
1432
|
}
|
|
1449
1433
|
|
|
1450
1434
|
// initialize transaction note, if possible
|
|
1451
1435
|
if (has_tran === true && !this.transaction.notes.authentication_results) {
|
|
1452
|
-
this.transaction.notes.authentication_results = []
|
|
1436
|
+
this.transaction.notes.authentication_results = []
|
|
1453
1437
|
}
|
|
1454
1438
|
|
|
1455
1439
|
// if message, store it in the appropriate note
|
|
1456
1440
|
if (message) {
|
|
1457
1441
|
if (has_tran === true) {
|
|
1458
|
-
this.transaction.notes.authentication_results.push(message)
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
this.notes.authentication_results.push(message);
|
|
1442
|
+
this.transaction.notes.authentication_results.push(message)
|
|
1443
|
+
} else {
|
|
1444
|
+
this.notes.authentication_results.push(message)
|
|
1462
1445
|
}
|
|
1463
1446
|
}
|
|
1464
1447
|
|
|
1465
1448
|
// assemble the new header
|
|
1466
|
-
let header = [
|
|
1467
|
-
header = header.concat(this.notes.authentication_results)
|
|
1449
|
+
let header = [this.local.host]
|
|
1450
|
+
header = header.concat(this.notes.authentication_results)
|
|
1468
1451
|
if (has_tran === true) {
|
|
1469
|
-
header = header.concat(this.transaction.notes.authentication_results)
|
|
1452
|
+
header = header.concat(this.transaction.notes.authentication_results)
|
|
1470
1453
|
}
|
|
1471
|
-
if (header.length === 1) return ''
|
|
1472
|
-
return header.join(
|
|
1454
|
+
if (header.length === 1) return '' // no results
|
|
1455
|
+
return header.join(';\r\n\t')
|
|
1473
1456
|
}
|
|
1474
|
-
auth_results_clean
|
|
1457
|
+
auth_results_clean() {
|
|
1475
1458
|
// move any existing Auth-Res headers to Original-Auth-Res headers
|
|
1476
1459
|
// http://tools.ietf.org/html/draft-kucherawy-original-authres-00.html
|
|
1477
|
-
const ars = this.transaction.header.get_all('Authentication-Results')
|
|
1478
|
-
if (ars.length === 0) return
|
|
1460
|
+
const ars = this.transaction.header.get_all('Authentication-Results')
|
|
1461
|
+
if (ars.length === 0) return
|
|
1479
1462
|
|
|
1480
1463
|
for (const element of ars) {
|
|
1481
|
-
this.transaction.add_header('Original-Authentication-Results', element)
|
|
1464
|
+
this.transaction.add_header('Original-Authentication-Results', element)
|
|
1482
1465
|
}
|
|
1483
|
-
this.transaction.remove_header('Authentication-Results')
|
|
1484
|
-
this.logdebug(
|
|
1466
|
+
this.transaction.remove_header('Authentication-Results')
|
|
1467
|
+
this.logdebug('Authentication-Results moved to Original-Authentication-Results')
|
|
1485
1468
|
}
|
|
1486
|
-
cmd_data
|
|
1469
|
+
cmd_data(args) {
|
|
1487
1470
|
// RFC 5321 Section 4.3.2
|
|
1488
1471
|
// DATA does not accept arguments
|
|
1489
1472
|
if (args) {
|
|
1490
|
-
this.errors
|
|
1491
|
-
return this.respond(501,
|
|
1473
|
+
this.errors++
|
|
1474
|
+
return this.respond(501, 'Syntax error')
|
|
1492
1475
|
}
|
|
1493
1476
|
if (!this.transaction) {
|
|
1494
|
-
this.errors
|
|
1495
|
-
return this.respond(503,
|
|
1477
|
+
this.errors++
|
|
1478
|
+
return this.respond(503, 'MAIL required first')
|
|
1496
1479
|
}
|
|
1497
1480
|
if (!this.transaction.rcpt_to.length) {
|
|
1498
1481
|
if (this.pipelining) {
|
|
1499
|
-
return this.respond(554,
|
|
1482
|
+
return this.respond(554, 'No valid recipients')
|
|
1500
1483
|
}
|
|
1501
|
-
this.errors
|
|
1502
|
-
return this.respond(503,
|
|
1484
|
+
this.errors++
|
|
1485
|
+
return this.respond(503, 'RCPT required first')
|
|
1503
1486
|
}
|
|
1504
1487
|
|
|
1505
1488
|
if (cfg.headers.add_received) {
|
|
1506
|
-
this.accumulate_data(`Received: ${this.received_line()}\r\n`)
|
|
1489
|
+
this.accumulate_data(`Received: ${this.received_line()}\r\n`)
|
|
1507
1490
|
}
|
|
1508
|
-
plugins.run_hooks('data', this)
|
|
1491
|
+
plugins.run_hooks('data', this)
|
|
1509
1492
|
}
|
|
1510
|
-
data_respond
|
|
1511
|
-
let cont = 0
|
|
1493
|
+
data_respond(retval, msg) {
|
|
1494
|
+
let cont = 0
|
|
1512
1495
|
switch (retval) {
|
|
1513
1496
|
case constants.deny:
|
|
1514
|
-
this.respond(554, msg ||
|
|
1515
|
-
this.reset_transaction()
|
|
1516
|
-
})
|
|
1517
|
-
break
|
|
1497
|
+
this.respond(554, msg || 'Message denied', () => {
|
|
1498
|
+
this.reset_transaction()
|
|
1499
|
+
})
|
|
1500
|
+
break
|
|
1518
1501
|
case constants.denydisconnect:
|
|
1519
|
-
this.respond(554, msg ||
|
|
1520
|
-
this.disconnect()
|
|
1521
|
-
})
|
|
1522
|
-
break
|
|
1502
|
+
this.respond(554, msg || 'Message denied', () => {
|
|
1503
|
+
this.disconnect()
|
|
1504
|
+
})
|
|
1505
|
+
break
|
|
1523
1506
|
case constants.denysoft:
|
|
1524
|
-
this.respond(451, msg ||
|
|
1525
|
-
this.reset_transaction()
|
|
1526
|
-
})
|
|
1527
|
-
break
|
|
1507
|
+
this.respond(451, msg || 'Message denied', () => {
|
|
1508
|
+
this.reset_transaction()
|
|
1509
|
+
})
|
|
1510
|
+
break
|
|
1528
1511
|
case constants.denysoftdisconnect:
|
|
1529
|
-
this.respond(451, msg ||
|
|
1530
|
-
this.disconnect()
|
|
1531
|
-
})
|
|
1532
|
-
break
|
|
1512
|
+
this.respond(451, msg || 'Message denied', () => {
|
|
1513
|
+
this.disconnect()
|
|
1514
|
+
})
|
|
1515
|
+
break
|
|
1533
1516
|
default:
|
|
1534
|
-
cont = 1
|
|
1517
|
+
cont = 1
|
|
1535
1518
|
}
|
|
1536
|
-
if (!cont) return
|
|
1519
|
+
if (!cont) return
|
|
1537
1520
|
|
|
1538
1521
|
// We already checked for MAIL/RCPT in cmd_data
|
|
1539
|
-
this.respond(354,
|
|
1522
|
+
this.respond(354, 'go ahead, make my day', () => {
|
|
1540
1523
|
// OK... now we get the data
|
|
1541
|
-
this.state = states.DATA
|
|
1542
|
-
this.transaction.data_bytes = 0
|
|
1543
|
-
})
|
|
1524
|
+
this.state = states.DATA
|
|
1525
|
+
this.transaction.data_bytes = 0
|
|
1526
|
+
})
|
|
1544
1527
|
}
|
|
1545
|
-
accumulate_data
|
|
1546
|
-
|
|
1547
|
-
this.transaction.data_bytes += line.length;
|
|
1528
|
+
accumulate_data(line) {
|
|
1529
|
+
this.transaction.data_bytes += line.length
|
|
1548
1530
|
|
|
1549
1531
|
// Look for .\r\n
|
|
1550
|
-
if (line.length === 3 &&
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
line[2] === 0x0a) {
|
|
1554
|
-
this.data_done();
|
|
1555
|
-
return;
|
|
1532
|
+
if (line.length === 3 && line[0] === 0x2e && line[1] === 0x0d && line[2] === 0x0a) {
|
|
1533
|
+
this.data_done()
|
|
1534
|
+
return
|
|
1556
1535
|
}
|
|
1557
1536
|
|
|
1558
1537
|
// Look for .\n
|
|
1559
|
-
if (line.length === 2 &&
|
|
1560
|
-
line
|
|
1561
|
-
line
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
});
|
|
1566
|
-
return;
|
|
1538
|
+
if (line.length === 2 && line[0] === 0x2e && line[1] === 0x0a) {
|
|
1539
|
+
this.lognotice('Client sent bare line-feed - .\\n rather than .\\r\\n')
|
|
1540
|
+
this.respond(451, 'Bare line-feed; see http://haraka.github.io/barelf/', () => {
|
|
1541
|
+
this.reset_transaction()
|
|
1542
|
+
})
|
|
1543
|
+
return
|
|
1567
1544
|
}
|
|
1568
1545
|
|
|
1569
1546
|
// Stop accumulating data as we're going to reject at dot.
|
|
1570
1547
|
if (cfg.max.bytes && this.transaction.data_bytes > cfg.max.bytes) {
|
|
1571
|
-
return
|
|
1548
|
+
return
|
|
1572
1549
|
}
|
|
1573
1550
|
|
|
1574
1551
|
if (this.transaction.mime_part_count >= cfg.max.mime_parts) {
|
|
1575
|
-
this.logcrit(
|
|
1576
|
-
this.respond(554,
|
|
1577
|
-
this.disconnect()
|
|
1578
|
-
})
|
|
1579
|
-
return
|
|
1552
|
+
this.logcrit('Possible DoS attempt - too many MIME parts')
|
|
1553
|
+
this.respond(554, 'Transaction failed due to too many MIME parts', () => {
|
|
1554
|
+
this.disconnect()
|
|
1555
|
+
})
|
|
1556
|
+
return
|
|
1580
1557
|
}
|
|
1581
1558
|
|
|
1582
|
-
this.transaction.add_data(line)
|
|
1559
|
+
this.transaction.add_data(line)
|
|
1583
1560
|
}
|
|
1584
|
-
data_done
|
|
1585
|
-
this.pause()
|
|
1586
|
-
this.totalbytes += this.transaction.data_bytes
|
|
1561
|
+
data_done() {
|
|
1562
|
+
this.pause()
|
|
1563
|
+
this.totalbytes += this.transaction.data_bytes
|
|
1587
1564
|
|
|
1588
1565
|
// Check message size limit
|
|
1589
1566
|
if (cfg.max.bytes && this.transaction.data_bytes > cfg.max.bytes) {
|
|
1590
|
-
this.lognotice(`Incoming message exceeded max size of ${cfg.max.bytes}`)
|
|
1591
|
-
return plugins.run_hooks('max_data_exceeded', this)
|
|
1567
|
+
this.lognotice(`Incoming message exceeded max size of ${cfg.max.bytes}`)
|
|
1568
|
+
return plugins.run_hooks('max_data_exceeded', this)
|
|
1592
1569
|
}
|
|
1593
1570
|
|
|
1594
1571
|
// Check max received headers count
|
|
1595
1572
|
if (this.transaction.header.get_all('received').length > cfg.headers.max_received) {
|
|
1596
|
-
this.logerror(
|
|
1597
|
-
this.respond(550,
|
|
1598
|
-
this.reset_transaction()
|
|
1599
|
-
})
|
|
1600
|
-
return
|
|
1573
|
+
this.logerror('Incoming message had too many Received headers')
|
|
1574
|
+
this.respond(550, 'Too many received headers - possible mail loop', () => {
|
|
1575
|
+
this.reset_transaction()
|
|
1576
|
+
})
|
|
1577
|
+
return
|
|
1601
1578
|
}
|
|
1602
1579
|
|
|
1603
1580
|
// Warn if we hit the maximum parsed header lines limit
|
|
1604
1581
|
if (this.transaction.header_lines.length >= cfg.headers.max_lines) {
|
|
1605
|
-
this.logwarn(`Incoming message reached maximum parsing limit of ${cfg.headers.max_lines} header lines`)
|
|
1582
|
+
this.logwarn(`Incoming message reached maximum parsing limit of ${cfg.headers.max_lines} header lines`)
|
|
1606
1583
|
}
|
|
1607
1584
|
|
|
1608
1585
|
if (cfg.headers.clean_auth_results) {
|
|
1609
|
-
this.auth_results_clean()
|
|
1586
|
+
this.auth_results_clean() // rename old A-R headers
|
|
1610
1587
|
}
|
|
1611
|
-
const ar_field = this.auth_results()
|
|
1588
|
+
const ar_field = this.auth_results() // assemble new one
|
|
1612
1589
|
if (ar_field) {
|
|
1613
|
-
this.transaction.add_header('Authentication-Results', ar_field)
|
|
1590
|
+
this.transaction.add_header('Authentication-Results', ar_field)
|
|
1614
1591
|
}
|
|
1615
1592
|
|
|
1616
1593
|
this.transaction.end_data(() => {
|
|
1617
1594
|
// As this will be called asynchronously,
|
|
1618
1595
|
// make sure we still have a transaction.
|
|
1619
|
-
if (!this.transaction) return
|
|
1596
|
+
if (!this.transaction) return
|
|
1620
1597
|
// Record the start time of this hook as we can't take too long
|
|
1621
1598
|
// as the client will typically hang up after 2 to 3 minutes
|
|
1622
1599
|
// despite the RFC mandating that 10 minutes should be allowed.
|
|
1623
|
-
this.transaction.data_post_start = Date.now()
|
|
1624
|
-
plugins.run_hooks('data_post', this)
|
|
1625
|
-
})
|
|
1626
|
-
}
|
|
1627
|
-
data_post_respond
|
|
1628
|
-
if (!this.transaction) return
|
|
1629
|
-
this.transaction.data_post_delay = (Date.now() - this.transaction.data_post_start)/1000
|
|
1630
|
-
const mid = this.transaction.header.get('Message-ID') || ''
|
|
1631
|
-
this.lognotice(
|
|
1632
|
-
'
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
}
|
|
1641
|
-
);
|
|
1642
|
-
const ar_field = this.auth_results(); // assemble A-R header
|
|
1600
|
+
this.transaction.data_post_start = Date.now()
|
|
1601
|
+
plugins.run_hooks('data_post', this)
|
|
1602
|
+
})
|
|
1603
|
+
}
|
|
1604
|
+
data_post_respond(retval, msg) {
|
|
1605
|
+
if (!this.transaction) return
|
|
1606
|
+
this.transaction.data_post_delay = (Date.now() - this.transaction.data_post_start) / 1000
|
|
1607
|
+
const mid = this.transaction.header.get('Message-ID') || ''
|
|
1608
|
+
this.lognotice('message', {
|
|
1609
|
+
mid: mid.replace(/\r?\n/, ''),
|
|
1610
|
+
size: this.transaction.data_bytes,
|
|
1611
|
+
rcpts: `${this.transaction.rcpt_count.accept}/${this.transaction.rcpt_count.tempfail}/${this.transaction.rcpt_count.reject}`,
|
|
1612
|
+
delay: this.transaction.data_post_delay,
|
|
1613
|
+
code: constants.translate(retval),
|
|
1614
|
+
msg: msg || '',
|
|
1615
|
+
})
|
|
1616
|
+
const ar_field = this.auth_results() // assemble A-R header
|
|
1643
1617
|
if (ar_field) {
|
|
1644
|
-
this.transaction.remove_header('Authentication-Results')
|
|
1645
|
-
this.transaction.add_leading_header('Authentication-Results', ar_field)
|
|
1618
|
+
this.transaction.remove_header('Authentication-Results')
|
|
1619
|
+
this.transaction.add_leading_header('Authentication-Results', ar_field)
|
|
1646
1620
|
}
|
|
1647
1621
|
switch (retval) {
|
|
1648
1622
|
case constants.deny:
|
|
1649
|
-
this.respond(550, msg ||
|
|
1650
|
-
this.msg_count.reject
|
|
1651
|
-
this.transaction.msg_status = 'rejected'
|
|
1652
|
-
this.reset_transaction(() => this.resume())
|
|
1653
|
-
})
|
|
1654
|
-
break
|
|
1623
|
+
this.respond(550, msg || 'Message denied', () => {
|
|
1624
|
+
this.msg_count.reject++
|
|
1625
|
+
this.transaction.msg_status = 'rejected'
|
|
1626
|
+
this.reset_transaction(() => this.resume())
|
|
1627
|
+
})
|
|
1628
|
+
break
|
|
1655
1629
|
case constants.denydisconnect:
|
|
1656
|
-
this.respond(550, msg ||
|
|
1657
|
-
this.msg_count.reject
|
|
1658
|
-
this.transaction.msg_status = 'rejected'
|
|
1659
|
-
this.disconnect()
|
|
1660
|
-
})
|
|
1661
|
-
break
|
|
1630
|
+
this.respond(550, msg || 'Message denied', () => {
|
|
1631
|
+
this.msg_count.reject++
|
|
1632
|
+
this.transaction.msg_status = 'rejected'
|
|
1633
|
+
this.disconnect()
|
|
1634
|
+
})
|
|
1635
|
+
break
|
|
1662
1636
|
case constants.denysoft:
|
|
1663
|
-
this.respond(450, msg ||
|
|
1664
|
-
this.msg_count.tempfail
|
|
1665
|
-
this.transaction.msg_status = 'deferred'
|
|
1666
|
-
this.reset_transaction(() => this.resume())
|
|
1667
|
-
})
|
|
1668
|
-
break
|
|
1637
|
+
this.respond(450, msg || 'Message denied temporarily', () => {
|
|
1638
|
+
this.msg_count.tempfail++
|
|
1639
|
+
this.transaction.msg_status = 'deferred'
|
|
1640
|
+
this.reset_transaction(() => this.resume())
|
|
1641
|
+
})
|
|
1642
|
+
break
|
|
1669
1643
|
case constants.denysoftdisconnect:
|
|
1670
|
-
this.respond(450, msg ||
|
|
1671
|
-
this.msg_count.tempfail
|
|
1672
|
-
this.transaction.msg_status = 'deferred'
|
|
1673
|
-
this.disconnect()
|
|
1674
|
-
})
|
|
1675
|
-
break
|
|
1644
|
+
this.respond(450, msg || 'Message denied temporarily', () => {
|
|
1645
|
+
this.msg_count.tempfail++
|
|
1646
|
+
this.transaction.msg_status = 'deferred'
|
|
1647
|
+
this.disconnect()
|
|
1648
|
+
})
|
|
1649
|
+
break
|
|
1676
1650
|
default:
|
|
1677
1651
|
if (this.relaying) {
|
|
1678
|
-
plugins.run_hooks(
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
plugins.run_hooks("queue", this);
|
|
1652
|
+
plugins.run_hooks('queue_outbound', this)
|
|
1653
|
+
} else {
|
|
1654
|
+
plugins.run_hooks('queue', this)
|
|
1682
1655
|
}
|
|
1683
1656
|
}
|
|
1684
1657
|
}
|
|
1685
|
-
max_data_exceeded_respond
|
|
1658
|
+
max_data_exceeded_respond(retval, msg) {
|
|
1686
1659
|
// TODO: Maybe figure out what to do with other return codes
|
|
1687
|
-
this.respond(retval === constants.denysoft ? 450 : 550,
|
|
1688
|
-
this.reset_transaction()
|
|
1689
|
-
})
|
|
1660
|
+
this.respond(retval === constants.denysoft ? 450 : 550, 'Message too big!', () => {
|
|
1661
|
+
this.reset_transaction()
|
|
1662
|
+
})
|
|
1690
1663
|
}
|
|
1691
|
-
queue_msg
|
|
1664
|
+
queue_msg(retval, msg) {
|
|
1692
1665
|
if (msg) {
|
|
1693
1666
|
if (typeof msg === 'object' && msg.constructor.name === 'DSN') {
|
|
1694
1667
|
return msg.reply
|
|
1695
1668
|
}
|
|
1696
|
-
return msg
|
|
1669
|
+
return msg
|
|
1697
1670
|
}
|
|
1698
1671
|
|
|
1699
1672
|
switch (retval) {
|
|
1700
1673
|
case constants.ok:
|
|
1701
|
-
return 'Message Queued'
|
|
1674
|
+
return 'Message Queued'
|
|
1702
1675
|
case constants.deny:
|
|
1703
1676
|
case constants.denydisconnect:
|
|
1704
|
-
return 'Message denied'
|
|
1677
|
+
return 'Message denied'
|
|
1705
1678
|
case constants.denysoft:
|
|
1706
1679
|
case constants.denysoftdisconnect:
|
|
1707
|
-
return 'Message denied temporarily'
|
|
1680
|
+
return 'Message denied temporarily'
|
|
1708
1681
|
default:
|
|
1709
|
-
return ''
|
|
1682
|
+
return ''
|
|
1710
1683
|
}
|
|
1711
1684
|
}
|
|
1712
|
-
store_queue_result
|
|
1713
|
-
const res_as = {name: 'queue'}
|
|
1685
|
+
store_queue_result(retval, msg) {
|
|
1686
|
+
const res_as = { name: 'queue' }
|
|
1714
1687
|
switch (retval) {
|
|
1715
1688
|
case constants.ok:
|
|
1716
|
-
this.transaction.results.add(res_as, { pass: msg })
|
|
1717
|
-
break
|
|
1689
|
+
this.transaction.results.add(res_as, { pass: msg })
|
|
1690
|
+
break
|
|
1718
1691
|
case constants.deny:
|
|
1719
1692
|
case constants.denydisconnect:
|
|
1720
1693
|
case constants.denysoft:
|
|
1721
1694
|
case constants.denysoftdisconnect:
|
|
1722
|
-
this.transaction.results.add(res_as, { fail: msg })
|
|
1723
|
-
break
|
|
1695
|
+
this.transaction.results.add(res_as, { fail: msg })
|
|
1696
|
+
break
|
|
1724
1697
|
case constants.cont:
|
|
1725
|
-
break
|
|
1698
|
+
break
|
|
1726
1699
|
default:
|
|
1727
|
-
this.transaction.results.add(res_as, { msg })
|
|
1728
|
-
break
|
|
1700
|
+
this.transaction.results.add(res_as, { msg })
|
|
1701
|
+
break
|
|
1729
1702
|
}
|
|
1730
1703
|
}
|
|
1731
|
-
queue_outbound_respond
|
|
1732
|
-
if (this.remote.closed) return
|
|
1733
|
-
msg = this.queue_msg(retval, msg) || 'Message Queued'
|
|
1734
|
-
this.store_queue_result(retval, msg)
|
|
1735
|
-
msg = `${msg} (${this.transaction.uuid})
|
|
1704
|
+
queue_outbound_respond(retval, msg) {
|
|
1705
|
+
if (this.remote.closed) return
|
|
1706
|
+
msg = this.queue_msg(retval, msg) || 'Message Queued'
|
|
1707
|
+
this.store_queue_result(retval, msg)
|
|
1708
|
+
msg = `${msg} (${this.transaction.uuid})`
|
|
1736
1709
|
if (retval !== constants.ok) {
|
|
1737
|
-
this.lognotice(
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
msg
|
|
1742
|
-
}
|
|
1743
|
-
);
|
|
1710
|
+
this.lognotice('queue', {
|
|
1711
|
+
code: constants.translate(retval),
|
|
1712
|
+
msg,
|
|
1713
|
+
})
|
|
1744
1714
|
}
|
|
1745
1715
|
switch (retval) {
|
|
1746
1716
|
case constants.ok:
|
|
1747
|
-
plugins.run_hooks('queue_ok', this, msg)
|
|
1748
|
-
break
|
|
1717
|
+
plugins.run_hooks('queue_ok', this, msg)
|
|
1718
|
+
break
|
|
1749
1719
|
case constants.deny:
|
|
1750
1720
|
this.respond(550, msg, () => {
|
|
1751
|
-
this.msg_count.reject
|
|
1752
|
-
this.transaction.msg_status = 'rejected'
|
|
1753
|
-
this.reset_transaction(() => this.resume())
|
|
1754
|
-
})
|
|
1755
|
-
break
|
|
1721
|
+
this.msg_count.reject++
|
|
1722
|
+
this.transaction.msg_status = 'rejected'
|
|
1723
|
+
this.reset_transaction(() => this.resume())
|
|
1724
|
+
})
|
|
1725
|
+
break
|
|
1756
1726
|
case constants.denydisconnect:
|
|
1757
1727
|
this.respond(550, msg, () => {
|
|
1758
|
-
this.msg_count.reject
|
|
1759
|
-
this.transaction.msg_status = 'rejected'
|
|
1760
|
-
this.disconnect()
|
|
1761
|
-
})
|
|
1762
|
-
break
|
|
1728
|
+
this.msg_count.reject++
|
|
1729
|
+
this.transaction.msg_status = 'rejected'
|
|
1730
|
+
this.disconnect()
|
|
1731
|
+
})
|
|
1732
|
+
break
|
|
1763
1733
|
case constants.denysoft:
|
|
1764
1734
|
this.respond(450, msg, () => {
|
|
1765
|
-
this.msg_count.tempfail
|
|
1766
|
-
this.transaction.msg_status = 'deferred'
|
|
1767
|
-
this.reset_transaction(() => this.resume())
|
|
1768
|
-
})
|
|
1769
|
-
break
|
|
1735
|
+
this.msg_count.tempfail++
|
|
1736
|
+
this.transaction.msg_status = 'deferred'
|
|
1737
|
+
this.reset_transaction(() => this.resume())
|
|
1738
|
+
})
|
|
1739
|
+
break
|
|
1770
1740
|
case constants.denysoftdisconnect:
|
|
1771
1741
|
this.respond(450, msg, () => {
|
|
1772
|
-
this.msg_count.tempfail
|
|
1773
|
-
this.transaction.msg_status = 'deferred'
|
|
1774
|
-
this.disconnect()
|
|
1775
|
-
})
|
|
1776
|
-
break
|
|
1742
|
+
this.msg_count.tempfail++
|
|
1743
|
+
this.transaction.msg_status = 'deferred'
|
|
1744
|
+
this.disconnect()
|
|
1745
|
+
})
|
|
1746
|
+
break
|
|
1777
1747
|
default:
|
|
1778
1748
|
outbound.send_trans_email(this.transaction, (retval2, msg2) => {
|
|
1779
|
-
if (!msg2) msg2 = this.queue_msg(retval2, msg)
|
|
1749
|
+
if (!msg2) msg2 = this.queue_msg(retval2, msg)
|
|
1780
1750
|
switch (retval2) {
|
|
1781
1751
|
case constants.ok:
|
|
1782
|
-
if (!msg2) msg2 = this.queue_msg(retval2, msg2)
|
|
1783
|
-
plugins.run_hooks('queue_ok', this, msg2)
|
|
1784
|
-
break
|
|
1752
|
+
if (!msg2) msg2 = this.queue_msg(retval2, msg2)
|
|
1753
|
+
plugins.run_hooks('queue_ok', this, msg2)
|
|
1754
|
+
break
|
|
1785
1755
|
case constants.deny:
|
|
1786
|
-
if (!msg2) msg2 = this.queue_msg(retval2, msg2)
|
|
1756
|
+
if (!msg2) msg2 = this.queue_msg(retval2, msg2)
|
|
1787
1757
|
this.respond(550, msg2, () => {
|
|
1788
|
-
this.msg_count.reject
|
|
1789
|
-
this.transaction.msg_status = 'rejected'
|
|
1758
|
+
this.msg_count.reject++
|
|
1759
|
+
this.transaction.msg_status = 'rejected'
|
|
1790
1760
|
this.reset_transaction(() => {
|
|
1791
|
-
this.resume()
|
|
1792
|
-
})
|
|
1793
|
-
})
|
|
1794
|
-
break
|
|
1761
|
+
this.resume()
|
|
1762
|
+
})
|
|
1763
|
+
})
|
|
1764
|
+
break
|
|
1795
1765
|
default:
|
|
1796
|
-
this.logerror(`Unrecognized response from outbound layer: ${retval2} : ${msg2}`)
|
|
1797
|
-
this.respond(550, msg2 ||
|
|
1798
|
-
this.msg_count.reject
|
|
1799
|
-
this.transaction.msg_status = 'rejected'
|
|
1766
|
+
this.logerror(`Unrecognized response from outbound layer: ${retval2} : ${msg2}`)
|
|
1767
|
+
this.respond(550, msg2 || 'Internal Server Error', () => {
|
|
1768
|
+
this.msg_count.reject++
|
|
1769
|
+
this.transaction.msg_status = 'rejected'
|
|
1800
1770
|
this.reset_transaction(() => {
|
|
1801
|
-
this.resume()
|
|
1802
|
-
})
|
|
1803
|
-
})
|
|
1771
|
+
this.resume()
|
|
1772
|
+
})
|
|
1773
|
+
})
|
|
1804
1774
|
}
|
|
1805
|
-
})
|
|
1775
|
+
})
|
|
1806
1776
|
}
|
|
1807
1777
|
}
|
|
1808
|
-
queue_respond
|
|
1809
|
-
msg = this.queue_msg(retval, msg)
|
|
1810
|
-
this.store_queue_result(retval, msg)
|
|
1811
|
-
msg = `${msg} (${this.transaction.uuid})
|
|
1778
|
+
queue_respond(retval, msg) {
|
|
1779
|
+
msg = this.queue_msg(retval, msg)
|
|
1780
|
+
this.store_queue_result(retval, msg)
|
|
1781
|
+
msg = `${msg} (${this.transaction.uuid})`
|
|
1812
1782
|
|
|
1813
1783
|
if (retval !== constants.ok) {
|
|
1814
|
-
this.lognotice(
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
msg
|
|
1819
|
-
}
|
|
1820
|
-
);
|
|
1784
|
+
this.lognotice('queue', {
|
|
1785
|
+
code: constants.translate(retval),
|
|
1786
|
+
msg,
|
|
1787
|
+
})
|
|
1821
1788
|
}
|
|
1822
1789
|
switch (retval) {
|
|
1823
1790
|
case constants.ok:
|
|
1824
|
-
plugins.run_hooks('queue_ok', this, msg)
|
|
1825
|
-
break
|
|
1791
|
+
plugins.run_hooks('queue_ok', this, msg)
|
|
1792
|
+
break
|
|
1826
1793
|
case constants.deny:
|
|
1827
1794
|
this.respond(550, msg, () => {
|
|
1828
|
-
this.msg_count.reject
|
|
1829
|
-
this.transaction.msg_status = 'rejected'
|
|
1830
|
-
this.reset_transaction(() =>
|
|
1831
|
-
})
|
|
1832
|
-
break
|
|
1795
|
+
this.msg_count.reject++
|
|
1796
|
+
this.transaction.msg_status = 'rejected'
|
|
1797
|
+
this.reset_transaction(() => this.resume())
|
|
1798
|
+
})
|
|
1799
|
+
break
|
|
1833
1800
|
case constants.denydisconnect:
|
|
1834
1801
|
this.respond(550, msg, () => {
|
|
1835
|
-
this.msg_count.reject
|
|
1836
|
-
this.transaction.msg_status = 'rejected'
|
|
1837
|
-
this.disconnect()
|
|
1838
|
-
})
|
|
1839
|
-
break
|
|
1802
|
+
this.msg_count.reject++
|
|
1803
|
+
this.transaction.msg_status = 'rejected'
|
|
1804
|
+
this.disconnect()
|
|
1805
|
+
})
|
|
1806
|
+
break
|
|
1840
1807
|
case constants.denysoft:
|
|
1841
1808
|
this.respond(450, msg, () => {
|
|
1842
|
-
this.msg_count.tempfail
|
|
1843
|
-
this.transaction.msg_status = 'deferred'
|
|
1844
|
-
this.reset_transaction(() => this.resume())
|
|
1845
|
-
})
|
|
1846
|
-
break
|
|
1809
|
+
this.msg_count.tempfail++
|
|
1810
|
+
this.transaction.msg_status = 'deferred'
|
|
1811
|
+
this.reset_transaction(() => this.resume())
|
|
1812
|
+
})
|
|
1813
|
+
break
|
|
1847
1814
|
case constants.denysoftdisconnect:
|
|
1848
1815
|
this.respond(450, msg, () => {
|
|
1849
|
-
this.msg_count.tempfail
|
|
1850
|
-
this.transaction.msg_status = 'deferred'
|
|
1851
|
-
this.disconnect()
|
|
1852
|
-
})
|
|
1853
|
-
break
|
|
1816
|
+
this.msg_count.tempfail++
|
|
1817
|
+
this.transaction.msg_status = 'deferred'
|
|
1818
|
+
this.disconnect()
|
|
1819
|
+
})
|
|
1820
|
+
break
|
|
1854
1821
|
default:
|
|
1855
|
-
if (!msg) msg = 'Queuing declined or disabled, try later'
|
|
1822
|
+
if (!msg) msg = 'Queuing declined or disabled, try later'
|
|
1856
1823
|
this.respond(451, msg, () => {
|
|
1857
|
-
this.msg_count.tempfail
|
|
1858
|
-
this.transaction.msg_status = 'deferred'
|
|
1859
|
-
this.reset_transaction(() => this.resume())
|
|
1860
|
-
})
|
|
1861
|
-
break
|
|
1824
|
+
this.msg_count.tempfail++
|
|
1825
|
+
this.transaction.msg_status = 'deferred'
|
|
1826
|
+
this.reset_transaction(() => this.resume())
|
|
1827
|
+
})
|
|
1828
|
+
break
|
|
1862
1829
|
}
|
|
1863
1830
|
}
|
|
1864
|
-
queue_ok_respond
|
|
1831
|
+
queue_ok_respond(retval, msg, params) {
|
|
1865
1832
|
// This hook is common to both hook_queue and hook_queue_outbound
|
|
1866
1833
|
// retval and msg are ignored in this hook so we always log OK
|
|
1867
|
-
this.lognotice(
|
|
1868
|
-
'
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
msg: (params || '')
|
|
1872
|
-
}
|
|
1873
|
-
);
|
|
1834
|
+
this.lognotice('queue', {
|
|
1835
|
+
code: 'OK',
|
|
1836
|
+
msg: params || '',
|
|
1837
|
+
})
|
|
1874
1838
|
|
|
1875
1839
|
this.respond(250, params, () => {
|
|
1876
|
-
this.msg_count.accept
|
|
1877
|
-
if (this.transaction) this.transaction.msg_status = 'accepted'
|
|
1878
|
-
this.reset_transaction(() => this.resume())
|
|
1879
|
-
})
|
|
1840
|
+
this.msg_count.accept++
|
|
1841
|
+
if (this.transaction) this.transaction.msg_status = 'accepted'
|
|
1842
|
+
this.reset_transaction(() => this.resume())
|
|
1843
|
+
})
|
|
1880
1844
|
}
|
|
1881
1845
|
}
|
|
1882
1846
|
|
|
1883
|
-
exports.Connection = Connection
|
|
1847
|
+
exports.Connection = Connection
|
|
1884
1848
|
|
|
1885
1849
|
exports.createConnection = (client, server, cfg) => {
|
|
1886
|
-
return new Connection(client, server, cfg)
|
|
1850
|
+
return new Connection(client, server, cfg)
|
|
1887
1851
|
}
|
|
1888
1852
|
|
|
1889
1853
|
logger.add_log_methods(Connection)
|