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/outbound/hmail.js
CHANGED
|
@@ -1,151 +1,157 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const events
|
|
4
|
-
const fs
|
|
5
|
-
const dns
|
|
6
|
-
const net
|
|
7
|
-
const path
|
|
8
|
-
|
|
9
|
-
const { Address } = require('address-rfc2821')
|
|
10
|
-
const config
|
|
11
|
-
const constants
|
|
12
|
-
const DSN
|
|
13
|
-
const message
|
|
14
|
-
const net_utils
|
|
15
|
-
const Notes
|
|
16
|
-
const utils
|
|
17
|
-
|
|
18
|
-
const logger
|
|
19
|
-
const plugins
|
|
20
|
-
|
|
21
|
-
const client_pool = require('./client_pool')
|
|
22
|
-
const _qfile
|
|
23
|
-
const outbound
|
|
24
|
-
const obtls
|
|
25
|
-
|
|
26
|
-
const FsyncWriteStream = require('./fsync_writestream')
|
|
27
|
-
|
|
28
|
-
let queue_dir
|
|
29
|
-
let temp_fail_queue
|
|
30
|
-
let delivery_queue
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const events = require('node:events')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const dns = require('node:dns')
|
|
6
|
+
const net = require('node:net')
|
|
7
|
+
const path = require('node:path')
|
|
8
|
+
|
|
9
|
+
const { Address } = require('address-rfc2821')
|
|
10
|
+
const config = require('haraka-config')
|
|
11
|
+
const constants = require('haraka-constants')
|
|
12
|
+
const DSN = require('haraka-dsn')
|
|
13
|
+
const message = require('haraka-email-message')
|
|
14
|
+
const net_utils = require('haraka-net-utils')
|
|
15
|
+
const Notes = require('haraka-notes')
|
|
16
|
+
const utils = require('haraka-utils')
|
|
17
|
+
|
|
18
|
+
const logger = require('../logger')
|
|
19
|
+
const plugins = require('../plugins')
|
|
20
|
+
|
|
21
|
+
const client_pool = require('./client_pool')
|
|
22
|
+
const _qfile = require('./qfile')
|
|
23
|
+
const outbound = require('./index')
|
|
24
|
+
const obtls = require('./tls')
|
|
25
|
+
|
|
26
|
+
const FsyncWriteStream = require('./fsync_writestream')
|
|
27
|
+
|
|
28
|
+
let queue_dir
|
|
29
|
+
let temp_fail_queue
|
|
30
|
+
let delivery_queue
|
|
31
31
|
setImmediate(() => {
|
|
32
|
-
const queuelib = require('./queue')
|
|
33
|
-
queue_dir = queuelib.queue_dir
|
|
34
|
-
temp_fail_queue = queuelib.temp_fail_queue
|
|
35
|
-
delivery_queue = queuelib.delivery_queue
|
|
36
|
-
})
|
|
32
|
+
const queuelib = require('./queue')
|
|
33
|
+
queue_dir = queuelib.queue_dir
|
|
34
|
+
temp_fail_queue = queuelib.temp_fail_queue
|
|
35
|
+
delivery_queue = queuelib.delivery_queue
|
|
36
|
+
})
|
|
37
37
|
|
|
38
|
-
const obc = require('./config')
|
|
38
|
+
const obc = require('./config')
|
|
39
39
|
|
|
40
40
|
/////////////////////////////////////////////////////////////////////////////
|
|
41
41
|
// HMailItem - encapsulates an individual outbound mail item
|
|
42
42
|
|
|
43
|
-
function dummy_func
|
|
43
|
+
function dummy_func() {}
|
|
44
44
|
|
|
45
45
|
class HMailItem extends events.EventEmitter {
|
|
46
|
-
constructor
|
|
47
|
-
super()
|
|
48
|
-
|
|
49
|
-
const parts = _qfile.parts(filename)
|
|
50
|
-
if (!parts) throw new Error(`Bad filename: ${filename}`)
|
|
51
|
-
|
|
52
|
-
this.cfg
|
|
53
|
-
this.obtls
|
|
54
|
-
this.name
|
|
55
|
-
this.path
|
|
56
|
-
this.filename
|
|
57
|
-
this.next_process = parts.next_attempt
|
|
58
|
-
this.num_failures = parts.attempts
|
|
59
|
-
this.pid
|
|
60
|
-
this.notes
|
|
61
|
-
this.refcount
|
|
62
|
-
this.todo
|
|
63
|
-
this.file_size
|
|
64
|
-
this.next_cb
|
|
65
|
-
this.bounce_error = null
|
|
66
|
-
this.hook
|
|
67
|
-
this.size_file()
|
|
46
|
+
constructor(filename, filePath, notes) {
|
|
47
|
+
super()
|
|
48
|
+
|
|
49
|
+
const parts = _qfile.parts(filename)
|
|
50
|
+
if (!parts) throw new Error(`Bad filename: ${filename}`)
|
|
51
|
+
|
|
52
|
+
this.cfg = obc.cfg
|
|
53
|
+
this.obtls = obtls
|
|
54
|
+
this.name = 'outbound'
|
|
55
|
+
this.path = filePath
|
|
56
|
+
this.filename = filename
|
|
57
|
+
this.next_process = parts.next_attempt
|
|
58
|
+
this.num_failures = parts.attempts
|
|
59
|
+
this.pid = parts.pid
|
|
60
|
+
this.notes = notes || new Notes()
|
|
61
|
+
this.refcount = 1
|
|
62
|
+
this.todo = null
|
|
63
|
+
this.file_size = 0
|
|
64
|
+
this.next_cb = dummy_func
|
|
65
|
+
this.bounce_error = null
|
|
66
|
+
this.hook = null
|
|
67
|
+
this.size_file()
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
data_stream
|
|
71
|
-
return fs.createReadStream(this.path, {
|
|
70
|
+
data_stream() {
|
|
71
|
+
return fs.createReadStream(this.path, {
|
|
72
|
+
start: this.data_start,
|
|
73
|
+
end: this.file_size,
|
|
74
|
+
})
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
size_file
|
|
77
|
+
size_file() {
|
|
75
78
|
fs.stat(this.path, (err, stats) => {
|
|
76
79
|
if (err) {
|
|
77
80
|
// we are fucked... guess I need somewhere for this to go
|
|
78
|
-
this.logerror(`Error obtaining file size: ${err}`)
|
|
79
|
-
this.temp_fail(
|
|
81
|
+
this.logerror(`Error obtaining file size: ${err}`)
|
|
82
|
+
this.temp_fail('Error obtaining file size')
|
|
80
83
|
return
|
|
81
84
|
}
|
|
82
85
|
if (stats.size === 0) {
|
|
83
|
-
this.logerror(`Error reading queue file ${this.filename}: zero bytes`)
|
|
84
|
-
this.emit('error', `Error reading queue file ${this.filename}: zero bytes`)
|
|
86
|
+
this.logerror(`Error reading queue file ${this.filename}: zero bytes`)
|
|
87
|
+
this.emit('error', `Error reading queue file ${this.filename}: zero bytes`)
|
|
85
88
|
return
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
this.file_size = stats.size
|
|
89
|
-
this.read_todo()
|
|
90
|
-
})
|
|
91
|
+
this.file_size = stats.size
|
|
92
|
+
this.read_todo()
|
|
93
|
+
})
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
read_todo
|
|
94
|
-
this._stream_bytes_from(this.path, {start: 0, end: 3}, (err, bytes) => {
|
|
96
|
+
read_todo() {
|
|
97
|
+
this._stream_bytes_from(this.path, { start: 0, end: 3 }, (err, bytes) => {
|
|
95
98
|
if (err) {
|
|
96
|
-
const errMsg = `Error reading queue file ${this.filename}: ${err}
|
|
97
|
-
this.logerror(errMsg)
|
|
98
|
-
this.temp_fail(errMsg)
|
|
99
|
+
const errMsg = `Error reading queue file ${this.filename}: ${err}`
|
|
100
|
+
this.logerror(errMsg)
|
|
101
|
+
this.temp_fail(errMsg)
|
|
99
102
|
return
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
const todo_len = bytes.readUInt32BE(0)
|
|
103
|
-
this.logdebug(`todo header length: ${todo_len}`)
|
|
104
|
-
this.data_start = todo_len + 4
|
|
105
|
+
const todo_len = bytes.readUInt32BE(0)
|
|
106
|
+
this.logdebug(`todo header length: ${todo_len}`)
|
|
107
|
+
this.data_start = todo_len + 4
|
|
105
108
|
|
|
106
|
-
this._stream_bytes_from(this.path, {start: 4, end: todo_len + 3}, (err2, todo_bytes) => {
|
|
109
|
+
this._stream_bytes_from(this.path, { start: 4, end: todo_len + 3 }, (err2, todo_bytes) => {
|
|
107
110
|
if (todo_bytes.length !== todo_len) {
|
|
108
|
-
const wrongLength = `Didn't find right amount of data in todo!: ${err2} ${this.path}
|
|
109
|
-
this.logcrit(wrongLength)
|
|
111
|
+
const wrongLength = `Didn't find right amount of data in todo!: ${err2} ${this.path}`
|
|
112
|
+
this.logcrit(wrongLength)
|
|
110
113
|
fs.rename(this.path, path.join(queue_dir, `error.${this.filename}`), (err3) => {
|
|
111
114
|
if (err3) {
|
|
112
|
-
this.logerror(`Error creating (error.${this.filename}): ${err3}`)
|
|
115
|
+
this.logerror(`Error creating (error.${this.filename}): ${err3}`)
|
|
113
116
|
}
|
|
114
|
-
})
|
|
115
|
-
this.emit('error', wrongLength)
|
|
117
|
+
})
|
|
118
|
+
this.emit('error', wrongLength) // Note nothing picks this up yet
|
|
116
119
|
return
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
// we read everything
|
|
120
123
|
const todo_json = todo_bytes.toString().trim()
|
|
121
|
-
const last_char = todo_json.charAt(todo_json.length - 1)
|
|
124
|
+
const last_char = todo_json.charAt(todo_json.length - 1)
|
|
122
125
|
if (last_char !== '}') {
|
|
123
|
-
this.emit(
|
|
126
|
+
this.emit(
|
|
127
|
+
'error',
|
|
128
|
+
`invalid todo header end char: ${last_char} at pos ${todo_len} of ${this.filename}`,
|
|
129
|
+
)
|
|
124
130
|
return
|
|
125
131
|
}
|
|
126
|
-
this.todo = JSON.parse(todo_json)
|
|
127
|
-
this.todo.mail_from = new Address
|
|
128
|
-
this.todo.rcpt_to = this.todo.rcpt_to.map(a => new Address
|
|
129
|
-
this.todo.notes = new Notes(this.todo.notes)
|
|
130
|
-
this.emit('ready')
|
|
131
|
-
})
|
|
132
|
-
})
|
|
132
|
+
this.todo = JSON.parse(todo_json)
|
|
133
|
+
this.todo.mail_from = new Address(this.todo.mail_from)
|
|
134
|
+
this.todo.rcpt_to = this.todo.rcpt_to.map((a) => new Address(a))
|
|
135
|
+
this.todo.notes = new Notes(this.todo.notes)
|
|
136
|
+
this.emit('ready')
|
|
137
|
+
})
|
|
138
|
+
})
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
_stream_bytes_from
|
|
141
|
+
_stream_bytes_from(file_path, opts, done) {
|
|
136
142
|
if (opts.encoding !== undefined) {
|
|
137
143
|
// passing an encoding to fs.createReadStream will change the type of data returned
|
|
138
144
|
// ex: instead of returning a buffer, it may return a String, which will cause
|
|
139
145
|
// Buffer.concat to barf. There's a reason this function has 'bytes' in the name
|
|
140
|
-
done(new Error(
|
|
146
|
+
done(new Error('Thar be dragons here! Encode/decode on the result of this function'))
|
|
141
147
|
return
|
|
142
148
|
}
|
|
143
149
|
|
|
144
|
-
const stream = fs.createReadStream(file_path, opts)
|
|
150
|
+
const stream = fs.createReadStream(file_path, opts)
|
|
145
151
|
|
|
146
152
|
stream.on('error', done)
|
|
147
153
|
|
|
148
|
-
let raw_bytes = Buffer.alloc(0)
|
|
154
|
+
let raw_bytes = Buffer.alloc(0)
|
|
149
155
|
stream.on('data', (data) => {
|
|
150
156
|
raw_bytes = Buffer.concat([raw_bytes, data])
|
|
151
157
|
})
|
|
@@ -155,786 +161,826 @@ class HMailItem extends events.EventEmitter {
|
|
|
155
161
|
})
|
|
156
162
|
}
|
|
157
163
|
|
|
158
|
-
send
|
|
164
|
+
send() {
|
|
159
165
|
if (obc.cfg.disabled) {
|
|
160
166
|
// try again in 1 second if delivery is disabled
|
|
161
|
-
this.logdebug(
|
|
162
|
-
setTimeout(() => {
|
|
163
|
-
|
|
167
|
+
this.logdebug('delivery disabled temporarily. Retrying in 1s.')
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
this.send()
|
|
170
|
+
}, 1000)
|
|
171
|
+
return
|
|
164
172
|
}
|
|
165
173
|
|
|
166
174
|
if (!this.todo) {
|
|
167
|
-
this.once('ready', () => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
this.once('ready', () => {
|
|
176
|
+
this._send()
|
|
177
|
+
})
|
|
178
|
+
} else {
|
|
179
|
+
this._send()
|
|
171
180
|
}
|
|
172
181
|
}
|
|
173
182
|
|
|
174
|
-
_send
|
|
175
|
-
plugins.run_hooks('send_email', this)
|
|
183
|
+
_send() {
|
|
184
|
+
plugins.run_hooks('send_email', this)
|
|
176
185
|
}
|
|
177
186
|
|
|
178
|
-
send_email_respond
|
|
187
|
+
send_email_respond(retval, delay_seconds) {
|
|
179
188
|
if (retval === constants.delay) {
|
|
180
189
|
// Try again in 'delay' seconds.
|
|
181
|
-
this.logdebug(`Delivery of this email delayed for ${delay_seconds} seconds`)
|
|
182
|
-
this.next_cb()
|
|
183
|
-
temp_fail_queue.add(this.filename, delay_seconds * 1000, () => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
this.
|
|
190
|
+
this.logdebug(`Delivery of this email delayed for ${delay_seconds} seconds`)
|
|
191
|
+
this.next_cb()
|
|
192
|
+
temp_fail_queue.add(this.filename, delay_seconds * 1000, () => {
|
|
193
|
+
delivery_queue.push(this)
|
|
194
|
+
})
|
|
195
|
+
} else {
|
|
196
|
+
this.logdebug(`Sending mail: ${this.filename}`)
|
|
197
|
+
this.get_mx()
|
|
188
198
|
}
|
|
189
199
|
}
|
|
190
200
|
|
|
191
|
-
get_mx
|
|
192
|
-
const { domain } = this.todo
|
|
193
|
-
plugins.run_hooks('get_mx', this, domain)
|
|
201
|
+
get_mx() {
|
|
202
|
+
const { domain } = this.todo
|
|
203
|
+
plugins.run_hooks('get_mx', this, domain)
|
|
194
204
|
}
|
|
195
205
|
|
|
196
|
-
async get_mx_respond
|
|
206
|
+
async get_mx_respond(retval, mx) {
|
|
197
207
|
switch (retval) {
|
|
198
208
|
case constants.ok: {
|
|
199
|
-
this.logdebug(`MX from Plugin: ${this.todo.domain} => 0 ${JSON.stringify(mx)}`)
|
|
200
|
-
let mx_list
|
|
209
|
+
this.logdebug(`MX from Plugin: ${this.todo.domain} => 0 ${JSON.stringify(mx)}`)
|
|
210
|
+
let mx_list
|
|
201
211
|
if (Array.isArray(mx)) {
|
|
202
|
-
mx_list = mx.map(m => new net_utils.HarakaMx(m))
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
mx_list = [new net_utils.HarakaMx(mx)];
|
|
212
|
+
mx_list = mx.map((m) => new net_utils.HarakaMx(m))
|
|
213
|
+
} else {
|
|
214
|
+
mx_list = [new net_utils.HarakaMx(mx)]
|
|
206
215
|
}
|
|
207
|
-
return this.found_mx(mx_list)
|
|
216
|
+
return this.found_mx(mx_list)
|
|
208
217
|
}
|
|
209
218
|
case constants.deny:
|
|
210
|
-
this.logwarn(`get_mx plugin returned DENY: ${mx}`)
|
|
219
|
+
this.logwarn(`get_mx plugin returned DENY: ${mx}`)
|
|
211
220
|
for (const rcpt of this.todo.rcpt_to) {
|
|
212
|
-
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No MX for ${this.todo.domain}`))
|
|
221
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No MX for ${this.todo.domain}`))
|
|
213
222
|
}
|
|
214
|
-
return this.bounce(`No MX for ${this.todo.domain}`)
|
|
223
|
+
return this.bounce(`No MX for ${this.todo.domain}`)
|
|
215
224
|
case constants.denysoft:
|
|
216
|
-
this.logwarn(`get_mx plugin returned DENYSOFT: ${mx}`)
|
|
225
|
+
this.logwarn(`get_mx plugin returned DENYSOFT: ${mx}`)
|
|
217
226
|
for (const rcpt of this.todo.rcpt_to) {
|
|
218
|
-
this.extend_rcpt_with_dsn(
|
|
227
|
+
this.extend_rcpt_with_dsn(
|
|
228
|
+
rcpt,
|
|
229
|
+
DSN.addr_bad_dest_system(`Temporary MX lookup error for ${this.todo.domain}`, 450),
|
|
230
|
+
)
|
|
219
231
|
}
|
|
220
|
-
return this.temp_fail(`Temporary MX lookup error for ${this.todo.domain}`)
|
|
232
|
+
return this.temp_fail(`Temporary MX lookup error for ${this.todo.domain}`)
|
|
221
233
|
}
|
|
222
234
|
|
|
223
235
|
// none of the above return codes, drop through to DNS
|
|
224
236
|
try {
|
|
225
|
-
const exchanges = await net_utils.get_mx(this.todo.domain)
|
|
226
|
-
|
|
237
|
+
const exchanges = await net_utils.get_mx(this.todo.domain)
|
|
238
|
+
|
|
227
239
|
if (exchanges.length) {
|
|
228
240
|
this.found_mx(this.sort_mx(exchanges))
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
241
|
+
} else {
|
|
231
242
|
for (const rcpt of this.todo.rcpt_to) {
|
|
232
|
-
this.extend_rcpt_with_dsn(
|
|
243
|
+
this.extend_rcpt_with_dsn(
|
|
244
|
+
rcpt,
|
|
245
|
+
DSN.addr_bad_dest_system(`Nowhere to deliver mail to for domain: ${this.todo.domain}`),
|
|
246
|
+
)
|
|
233
247
|
}
|
|
234
|
-
this.bounce(`Nowhere to deliver mail to for domain: ${this.todo.domain}`)
|
|
248
|
+
this.bounce(`Nowhere to deliver mail to for domain: ${this.todo.domain}`)
|
|
235
249
|
}
|
|
236
250
|
} catch (e) {
|
|
237
|
-
this.get_mx_error(e)
|
|
251
|
+
this.get_mx_error(e)
|
|
238
252
|
}
|
|
239
253
|
}
|
|
240
254
|
|
|
241
|
-
get_mx_error
|
|
242
|
-
this.lognotice(`MX Lookup for ${this.todo.domain} failed: ${err}`)
|
|
255
|
+
get_mx_error(err) {
|
|
256
|
+
this.lognotice(`MX Lookup for ${this.todo.domain} failed: ${err}`)
|
|
243
257
|
|
|
244
258
|
if (err.code === dns.NXDOMAIN || err.code === dns.NOTFOUND) {
|
|
245
259
|
for (const rcpt of this.todo.rcpt_to) {
|
|
246
|
-
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No Such Domain: ${this.todo.domain}`))
|
|
260
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No Such Domain: ${this.todo.domain}`))
|
|
247
261
|
}
|
|
248
|
-
this.bounce(`No Such Domain: ${this.todo.domain}`)
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
262
|
+
this.bounce(`No Such Domain: ${this.todo.domain}`)
|
|
263
|
+
} else {
|
|
251
264
|
// every other error is transient
|
|
252
265
|
for (const rcpt of this.todo.rcpt_to) {
|
|
253
|
-
this.extend_rcpt_with_dsn(rcpt, DSN.addr_unspecified(`DNS lookup failure: ${this.todo.domain}`))
|
|
266
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_unspecified(`DNS lookup failure: ${this.todo.domain}`))
|
|
254
267
|
}
|
|
255
|
-
this.temp_fail(`DNS lookup failure: ${err}`)
|
|
268
|
+
this.temp_fail(`DNS lookup failure: ${err}`)
|
|
256
269
|
}
|
|
257
270
|
}
|
|
258
271
|
|
|
259
|
-
async found_mx
|
|
260
|
-
|
|
272
|
+
async found_mx(mxs) {
|
|
261
273
|
// support draft-delany-nullmx-02
|
|
262
274
|
if (mxs.length === 1 && mxs[0].priority === 0 && mxs[0].exchange === '') {
|
|
263
275
|
for (const rcpt of this.todo.rcpt_to) {
|
|
264
|
-
this.extend_rcpt_with_dsn(
|
|
276
|
+
this.extend_rcpt_with_dsn(
|
|
277
|
+
rcpt,
|
|
278
|
+
DSN.addr_bad_dest_system(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`),
|
|
279
|
+
)
|
|
265
280
|
}
|
|
266
|
-
return this.bounce(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`)
|
|
281
|
+
return this.bounce(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`)
|
|
267
282
|
}
|
|
268
283
|
|
|
269
284
|
// resolves the MX hostnames to IPs
|
|
270
|
-
this.mxlist = await net_utils.resolve_mx_hosts(mxs)
|
|
285
|
+
this.mxlist = await net_utils.resolve_mx_hosts(mxs)
|
|
286
|
+
|
|
287
|
+
switch (obc.cfg.inet_prefer) {
|
|
288
|
+
case 'v4':
|
|
289
|
+
this.mxlist = [
|
|
290
|
+
...this.mxlist.filter((mx) => !net.isIP(mx.exchange) || net.isIPv4(mx.exchange)),
|
|
291
|
+
...this.mxlist.filter((mx) => net.isIPv6(mx.exchange)),
|
|
292
|
+
]
|
|
293
|
+
break
|
|
294
|
+
case 'v6':
|
|
295
|
+
this.mxlist = [
|
|
296
|
+
...this.mxlist.filter((mx) => !net.isIP(mx.exchange) || net.isIPv6(mx.exchange)),
|
|
297
|
+
...this.mxlist.filter((mx) => net.isIPv4(mx.exchange)),
|
|
298
|
+
]
|
|
299
|
+
break
|
|
300
|
+
}
|
|
271
301
|
|
|
272
|
-
this.try_deliver()
|
|
302
|
+
this.try_deliver()
|
|
273
303
|
}
|
|
274
304
|
|
|
275
|
-
async try_deliver
|
|
276
|
-
|
|
305
|
+
async try_deliver() {
|
|
277
306
|
// are any MXs left?
|
|
278
307
|
if (this.mxlist.length === 0) {
|
|
279
308
|
for (const rcpt of this.todo.rcpt_to) {
|
|
280
|
-
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Tried all MXs ${this.todo.domain}`))
|
|
309
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Tried all MXs ${this.todo.domain}`))
|
|
281
310
|
}
|
|
282
|
-
return this.temp_fail(
|
|
311
|
+
return this.temp_fail('Tried all MXs')
|
|
283
312
|
}
|
|
284
313
|
|
|
285
|
-
const mx = this.mxlist.shift()
|
|
314
|
+
const mx = this.mxlist.shift()
|
|
286
315
|
|
|
287
|
-
if (!obc.cfg.local_mx_ok && mx.from_dns && await net_utils.is_local_host(mx.exchange)) {
|
|
316
|
+
if (!obc.cfg.local_mx_ok && mx.from_dns && (await net_utils.is_local_host(mx.exchange))) {
|
|
288
317
|
this.loginfo(`MX ${mx.exchange} is local, skipping since local_mx_ok=false`)
|
|
289
|
-
return this.try_deliver()
|
|
318
|
+
return this.try_deliver() // try next MX
|
|
290
319
|
}
|
|
291
320
|
|
|
292
321
|
this.force_tls = this.get_force_tls(mx)
|
|
293
322
|
|
|
294
323
|
if (this.todo.notes.outbound_ip) {
|
|
295
|
-
this.logerror(`notes.outbound_ip is deprecated. Use get_mx.bind instead!`)
|
|
296
|
-
if (!mx.bind) mx.bind = this.todo.notes.outbound_ip
|
|
324
|
+
this.logerror(`notes.outbound_ip is deprecated. Use get_mx.bind instead!`)
|
|
325
|
+
if (!mx.bind) mx.bind = this.todo.notes.outbound_ip
|
|
297
326
|
}
|
|
298
327
|
|
|
299
328
|
// Allow transaction notes to set outbound IP helo
|
|
300
329
|
if (this.todo.notes.outbound_helo) {
|
|
301
|
-
mx.bind_helo = this.todo.notes.outbound_helo
|
|
330
|
+
mx.bind_helo = this.todo.notes.outbound_helo
|
|
302
331
|
}
|
|
303
332
|
|
|
304
|
-
const host = mx.path ? mx.path : mx.exchange
|
|
333
|
+
const host = mx.path ? mx.path : mx.exchange
|
|
305
334
|
const lmtp = mx.using_lmtp ? ' using LMTP' : ''
|
|
306
335
|
if (!mx.port) mx.port = mx.using_lmtp ? 24 : 25
|
|
307
336
|
const from_dns = mx.from_dns ? ' (via DNS)' : ''
|
|
308
337
|
|
|
309
|
-
this.logdebug(
|
|
338
|
+
this.logdebug(
|
|
339
|
+
`deliver: ${mx.bind_helo} -> ${host}${lmtp}${from_dns} (${delivery_queue.length()}) (${temp_fail_queue.length()})`,
|
|
340
|
+
)
|
|
310
341
|
client_pool.get_client(mx, (err, socket) => {
|
|
311
342
|
if (err) {
|
|
312
343
|
if (/connection timed out|connect ECONNREFUSED/.test(err)) {
|
|
313
|
-
logger.notice(this, `Failed to get socket: ${err}`)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
logger.error(this, `Failed to get socket: ${err}`);
|
|
344
|
+
logger.notice(this, `Failed to get socket: ${err}`)
|
|
345
|
+
} else {
|
|
346
|
+
logger.error(this, `Failed to get socket: ${err}`)
|
|
317
347
|
}
|
|
318
348
|
|
|
319
|
-
return this.try_deliver()
|
|
349
|
+
return this.try_deliver() // try next MX
|
|
320
350
|
}
|
|
321
|
-
this.try_deliver_host_on_socket(mx, host, mx.port, socket)
|
|
322
|
-
})
|
|
351
|
+
this.try_deliver_host_on_socket(mx, host, mx.port, socket)
|
|
352
|
+
})
|
|
323
353
|
}
|
|
324
354
|
|
|
325
|
-
try_deliver_host_on_socket
|
|
326
|
-
const self = this
|
|
327
|
-
let processing_mail = true
|
|
328
|
-
let command = mx.using_lmtp ? 'connect_lmtp' : 'connect'
|
|
355
|
+
try_deliver_host_on_socket(mx, host, port, socket) {
|
|
356
|
+
const self = this
|
|
357
|
+
let processing_mail = true
|
|
358
|
+
let command = mx.using_lmtp ? 'connect_lmtp' : 'connect'
|
|
329
359
|
|
|
330
360
|
for (const l of ['error', 'timeout', 'close', 'end']) {
|
|
331
|
-
socket.removeAllListeners(l)
|
|
361
|
+
socket.removeAllListeners(l)
|
|
332
362
|
}
|
|
333
363
|
|
|
334
364
|
socket.once('timeout', function () {
|
|
335
|
-
socket.emit('error', `socket timeout waiting on ${command}`)
|
|
336
|
-
})
|
|
365
|
+
socket.emit('error', `socket timeout waiting on ${command}`)
|
|
366
|
+
})
|
|
337
367
|
|
|
338
|
-
socket.once('error', err => {
|
|
368
|
+
socket.once('error', (err) => {
|
|
339
369
|
if (!processing_mail) return
|
|
340
370
|
|
|
341
|
-
self.logerror(`Ongoing connection failed to ${host}:${port} : ${err}`)
|
|
342
|
-
processing_mail = false
|
|
343
|
-
client_pool.release_client(socket, mx)
|
|
344
|
-
if (err.source === 'tls')
|
|
345
|
-
|
|
346
|
-
|
|
371
|
+
self.logerror(`Ongoing connection failed to ${host}:${port} : ${err}`)
|
|
372
|
+
processing_mail = false
|
|
373
|
+
client_pool.release_client(socket, mx)
|
|
374
|
+
if (err.source === 'tls')
|
|
375
|
+
// exception thrown from tls_socket during tls upgrade
|
|
376
|
+
return obtls.mark_tls_nogo(host, () => {
|
|
377
|
+
return self.try_deliver()
|
|
378
|
+
})
|
|
379
|
+
self.try_deliver() // try the next MX
|
|
347
380
|
})
|
|
348
381
|
|
|
349
382
|
socket.once('close', () => {
|
|
350
383
|
if (!processing_mail) return
|
|
351
384
|
|
|
352
|
-
self.logerror(`Remote end ${host}:${port} closed connection while we were processing mail. Trying next MX.`)
|
|
353
|
-
processing_mail = false
|
|
354
|
-
client_pool.release_client(socket, mx)
|
|
355
|
-
self.try_deliver()
|
|
356
|
-
})
|
|
385
|
+
self.logerror(`Remote end ${host}:${port} closed connection while we were processing mail. Trying next MX.`)
|
|
386
|
+
processing_mail = false
|
|
387
|
+
client_pool.release_client(socket, mx)
|
|
388
|
+
self.try_deliver()
|
|
389
|
+
})
|
|
357
390
|
|
|
358
391
|
socket.once('end', () => {
|
|
359
|
-
socket.writable = false
|
|
360
|
-
if (!processing_mail) client_pool.release_client(socket, mx)
|
|
392
|
+
socket.writable = false
|
|
393
|
+
if (!processing_mail) client_pool.release_client(socket, mx)
|
|
361
394
|
})
|
|
362
395
|
|
|
363
|
-
let response = []
|
|
396
|
+
let response = []
|
|
364
397
|
|
|
365
|
-
let recip_index = 0
|
|
366
|
-
const recipients = this.todo.rcpt_to
|
|
367
|
-
let lmtp_rcpt_idx = 0
|
|
398
|
+
let recip_index = 0
|
|
399
|
+
const recipients = this.todo.rcpt_to
|
|
400
|
+
let lmtp_rcpt_idx = 0
|
|
368
401
|
|
|
369
|
-
let last_recip = null
|
|
370
|
-
const ok_recips = []
|
|
371
|
-
const fail_recips = []
|
|
372
|
-
const bounce_recips = []
|
|
373
|
-
let secured = false
|
|
374
|
-
let authenticating = false
|
|
375
|
-
let authenticated = false
|
|
402
|
+
let last_recip = null
|
|
403
|
+
const ok_recips = []
|
|
404
|
+
const fail_recips = []
|
|
405
|
+
const bounce_recips = []
|
|
406
|
+
let secured = false
|
|
407
|
+
let authenticating = false
|
|
408
|
+
let authenticated = false
|
|
376
409
|
let smtp_properties = {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const send_command = socket.send_command = (cmd, data) => {
|
|
410
|
+
tls: false,
|
|
411
|
+
max_size: 0,
|
|
412
|
+
eightbitmime: false,
|
|
413
|
+
enh_status_codes: false,
|
|
414
|
+
auth: [],
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const send_command = (socket.send_command = (cmd, data) => {
|
|
385
418
|
if (!socket.writable) {
|
|
386
|
-
self.logerror(
|
|
419
|
+
self.logerror('Socket writability went away')
|
|
387
420
|
if (processing_mail) {
|
|
388
|
-
processing_mail = false
|
|
389
|
-
client_pool.release_client(socket, mx)
|
|
390
|
-
return self.try_deliver()
|
|
421
|
+
processing_mail = false
|
|
422
|
+
client_pool.release_client(socket, mx)
|
|
423
|
+
return self.try_deliver()
|
|
391
424
|
}
|
|
392
|
-
return
|
|
425
|
+
return
|
|
393
426
|
}
|
|
394
427
|
if (self.force_tls && !['EHLO', 'LHLO', 'STARTTLS'].includes(cmd.toUpperCase()) && !socket.isSecure()) {
|
|
395
428
|
// For safety against programming mistakes
|
|
396
|
-
self.logerror(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
429
|
+
self.logerror(
|
|
430
|
+
'Blocking attempt to send unencrypted data to forced TLS socket. This message indicates a programming error in the software.',
|
|
431
|
+
)
|
|
432
|
+
processing_mail = false
|
|
433
|
+
client_pool.release_client(socket, mx)
|
|
434
|
+
return
|
|
400
435
|
}
|
|
401
436
|
|
|
402
|
-
let line = `${cmd}${data ? ` ${data}` : ''}
|
|
437
|
+
let line = `${cmd}${data ? ` ${data}` : ''}`
|
|
403
438
|
if (cmd === 'dot' || cmd === 'dot_lmtp') {
|
|
404
|
-
line = '.'
|
|
439
|
+
line = '.'
|
|
405
440
|
}
|
|
406
|
-
if (authenticating) cmd = 'auth'
|
|
407
|
-
self.logprotocol(`C: ${line}`)
|
|
408
|
-
socket.write(`${line}\r\n`,
|
|
441
|
+
if (authenticating) cmd = 'auth'
|
|
442
|
+
self.logprotocol(`C: ${line}`)
|
|
443
|
+
socket.write(`${line}\r\n`, 'utf8', (err) => {
|
|
409
444
|
if (err) {
|
|
410
|
-
self.logcrit(`Socket write failed unexpectedly: ${err}`)
|
|
445
|
+
self.logcrit(`Socket write failed unexpectedly: ${err}`)
|
|
411
446
|
// We may want to release client here - but I want to get this
|
|
412
447
|
// line of code in before we do that so we might see some logging
|
|
413
448
|
// in case of errors.
|
|
414
449
|
// client_pool.release_client(socket, mx);
|
|
415
450
|
}
|
|
416
|
-
})
|
|
417
|
-
command = cmd.toLowerCase()
|
|
418
|
-
response = []
|
|
419
|
-
}
|
|
451
|
+
})
|
|
452
|
+
command = cmd.toLowerCase()
|
|
453
|
+
response = []
|
|
454
|
+
})
|
|
420
455
|
|
|
421
|
-
function set_ehlo_props
|
|
456
|
+
function set_ehlo_props() {
|
|
422
457
|
for (let i = 0, l = response.length; i < l; i++) {
|
|
423
|
-
const r = response[i]
|
|
458
|
+
const r = response[i]
|
|
424
459
|
if (r.toUpperCase() === '8BITMIME') {
|
|
425
|
-
smtp_properties.eightbitmime = true
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
else if (r.toUpperCase() === '
|
|
431
|
-
smtp_properties.
|
|
432
|
-
}
|
|
433
|
-
else if (r.toUpperCase() === 'SMTPUTF8') {
|
|
434
|
-
smtp_properties.smtp_utf8 = true;
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
460
|
+
smtp_properties.eightbitmime = true
|
|
461
|
+
} else if (r.toUpperCase() === 'STARTTLS') {
|
|
462
|
+
smtp_properties.tls = true
|
|
463
|
+
} else if (r.toUpperCase() === 'ENHANCEDSTATUSCODES') {
|
|
464
|
+
smtp_properties.enh_status_codes = true
|
|
465
|
+
} else if (r.toUpperCase() === 'SMTPUTF8') {
|
|
466
|
+
smtp_properties.smtp_utf8 = true
|
|
467
|
+
} else {
|
|
437
468
|
// Check for SIZE parameter and limit
|
|
438
|
-
let matches = r.match(/^SIZE\s+(\d+)$/)
|
|
469
|
+
let matches = r.match(/^SIZE\s+(\d+)$/)
|
|
439
470
|
if (matches) {
|
|
440
|
-
smtp_properties.max_size = matches[1]
|
|
471
|
+
smtp_properties.max_size = matches[1]
|
|
441
472
|
}
|
|
442
473
|
// Check for AUTH
|
|
443
|
-
matches = r.match(/^AUTH\s+(.+)$/)
|
|
474
|
+
matches = r.match(/^AUTH\s+(.+)$/)
|
|
444
475
|
if (matches) {
|
|
445
|
-
smtp_properties.auth = matches[1].split(/\s+/)
|
|
476
|
+
smtp_properties.auth = matches[1].split(/\s+/)
|
|
446
477
|
}
|
|
447
478
|
}
|
|
448
479
|
}
|
|
449
480
|
}
|
|
450
481
|
|
|
451
|
-
function get_reverse_path_with_params
|
|
482
|
+
function get_reverse_path_with_params() {
|
|
452
483
|
const rp = self.todo.mail_from.format(!smtp_properties.smtp_utf8)
|
|
453
484
|
let rp_params = ''
|
|
454
485
|
if (smtp_properties.smtp_utf8 && has_non_ascii(rp)) rp_params += ' SMTPUTF8'
|
|
455
486
|
return `FROM:${rp}${rp_params}`
|
|
456
487
|
}
|
|
457
488
|
|
|
458
|
-
function has_non_ascii
|
|
459
|
-
return [...string].some(char => char.charCodeAt(0) > 127)
|
|
489
|
+
function has_non_ascii(string) {
|
|
490
|
+
return [...string].some((char) => char.charCodeAt(0) > 127)
|
|
460
491
|
}
|
|
461
492
|
|
|
462
|
-
function auth_and_mail_phase
|
|
463
|
-
if (!authenticated &&
|
|
493
|
+
function auth_and_mail_phase() {
|
|
494
|
+
if (!authenticated && mx.auth_user && mx.auth_pass) {
|
|
464
495
|
// We have AUTH credentials to send for this domain
|
|
465
496
|
|
|
466
497
|
if (!(Array.isArray(smtp_properties.auth) && smtp_properties.auth.length)) {
|
|
467
498
|
// AUTH not offered
|
|
468
|
-
self.logwarn(
|
|
499
|
+
self.logwarn(
|
|
500
|
+
`AUTH configured for domain ${self.todo.domain} but host ${host} did not advertise AUTH capability`,
|
|
501
|
+
)
|
|
469
502
|
// Try and send the message without authentication
|
|
470
|
-
return send_command('MAIL', get_reverse_path_with_params())
|
|
503
|
+
return send_command('MAIL', get_reverse_path_with_params())
|
|
471
504
|
}
|
|
472
505
|
|
|
473
506
|
if (!mx.auth_type) {
|
|
474
507
|
// User hasn't specified an authentication type, so we pick one
|
|
475
508
|
// We'll prefer CRAM-MD5 as it's the most secure that we support.
|
|
476
509
|
if (smtp_properties.auth.includes('CRAM-MD5')) {
|
|
477
|
-
mx.auth_type = 'CRAM-MD5'
|
|
510
|
+
mx.auth_type = 'CRAM-MD5'
|
|
478
511
|
}
|
|
479
512
|
// PLAIN requires less round-trips compared to LOGIN
|
|
480
513
|
else if (smtp_properties.auth.includes('PLAIN')) {
|
|
481
514
|
// PLAIN requires less round trips compared to LOGIN
|
|
482
515
|
// So we'll make this our 2nd pick.
|
|
483
|
-
mx.auth_type = 'PLAIN'
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
mx.auth_type = 'LOGIN';
|
|
516
|
+
mx.auth_type = 'PLAIN'
|
|
517
|
+
} else if (smtp_properties.auth.includes('LOGIN')) {
|
|
518
|
+
mx.auth_type = 'LOGIN'
|
|
487
519
|
}
|
|
488
520
|
}
|
|
489
521
|
|
|
490
522
|
if (!mx.auth_type || (mx.auth_type && !smtp_properties.auth.includes(mx.auth_type.toUpperCase()))) {
|
|
491
523
|
// No compatible authentication types offered by the server
|
|
492
|
-
self.logwarn(
|
|
524
|
+
self.logwarn(
|
|
525
|
+
`AUTH configured for domain ${self.todo.domain} but host ${host}did not offer any compatible types${mx.auth_type ? ` (requested: ${mx.auth_type})` : ''} (offered: ${smtp_properties.auth.join(',')})`,
|
|
526
|
+
)
|
|
493
527
|
// Proceed without authentication
|
|
494
|
-
return send_command('MAIL', get_reverse_path_with_params())
|
|
528
|
+
return send_command('MAIL', get_reverse_path_with_params())
|
|
495
529
|
}
|
|
496
530
|
|
|
497
531
|
switch (mx.auth_type.toUpperCase()) {
|
|
498
532
|
case 'PLAIN':
|
|
499
|
-
return send_command('AUTH', `PLAIN ${utils.base64(`\0${mx.auth_user}\0${mx.auth_pass}`)}`)
|
|
533
|
+
return send_command('AUTH', `PLAIN ${utils.base64(`\0${mx.auth_user}\0${mx.auth_pass}`)}`)
|
|
500
534
|
case 'LOGIN':
|
|
501
|
-
authenticating = true
|
|
502
|
-
return send_command('AUTH', 'LOGIN')
|
|
535
|
+
authenticating = true
|
|
536
|
+
return send_command('AUTH', 'LOGIN')
|
|
503
537
|
case 'CRAM-MD5':
|
|
504
|
-
authenticating = true
|
|
505
|
-
return send_command('AUTH', 'CRAM-MD5')
|
|
538
|
+
authenticating = true
|
|
539
|
+
return send_command('AUTH', 'CRAM-MD5')
|
|
506
540
|
default:
|
|
507
541
|
// Unsupported AUTH type
|
|
508
|
-
self.logwarn(
|
|
509
|
-
|
|
542
|
+
self.logwarn(
|
|
543
|
+
`Unsupported authentication type ${mx.auth_type.toUpperCase()} requested for domain ${self.todo.domain}`,
|
|
544
|
+
)
|
|
545
|
+
return send_command('MAIL', get_reverse_path_with_params())
|
|
510
546
|
}
|
|
511
547
|
}
|
|
512
548
|
|
|
513
|
-
return send_command('MAIL', get_reverse_path_with_params())
|
|
549
|
+
return send_command('MAIL', get_reverse_path_with_params())
|
|
514
550
|
}
|
|
515
551
|
|
|
516
552
|
// IMPORTANT: do STARTTLS before AUTH for security
|
|
517
|
-
function process_ehlo_data
|
|
518
|
-
set_ehlo_props()
|
|
553
|
+
function process_ehlo_data() {
|
|
554
|
+
set_ehlo_props()
|
|
519
555
|
|
|
520
|
-
if (secured) return auth_and_mail_phase()
|
|
556
|
+
if (secured) return auth_and_mail_phase() // TLS already negotiated
|
|
521
557
|
|
|
522
558
|
if (self.force_tls) {
|
|
523
|
-
self.logdebug(`Using TLS for domain: ${self.todo.domain}, host: ${host}`)
|
|
559
|
+
self.logdebug(`Using TLS for domain: ${self.todo.domain}, host: ${host}`)
|
|
524
560
|
|
|
525
561
|
if (!obc.cfg.enable_tls || !smtp_properties.tls) {
|
|
526
562
|
// Prevent further use of the non-securable socket
|
|
527
|
-
processing_mail = false
|
|
528
|
-
socket.write(
|
|
529
|
-
socket.end()
|
|
530
|
-
client_pool.release_client(socket, mx)
|
|
531
|
-
return self.temp_fail(`No TLS available but required by configuration.`)
|
|
563
|
+
processing_mail = false
|
|
564
|
+
socket.write('QUIT\r\n', 'utf8') // courtesy
|
|
565
|
+
socket.end()
|
|
566
|
+
client_pool.release_client(socket, mx)
|
|
567
|
+
return self.temp_fail(`No TLS available but required by configuration.`)
|
|
532
568
|
}
|
|
533
569
|
|
|
534
570
|
socket.once('secure', () => {
|
|
535
571
|
// Set this flag so we don't try STARTTLS again if it
|
|
536
572
|
// is incorrectly offered at EHLO once we are secured.
|
|
537
|
-
secured = true
|
|
538
|
-
send_command(mx.using_lmtp ? 'LHLO' : 'EHLO', mx.bind_helo)
|
|
539
|
-
})
|
|
540
|
-
return send_command('STARTTLS')
|
|
573
|
+
secured = true
|
|
574
|
+
send_command(mx.using_lmtp ? 'LHLO' : 'EHLO', mx.bind_helo)
|
|
575
|
+
})
|
|
576
|
+
return send_command('STARTTLS')
|
|
541
577
|
}
|
|
542
|
-
if (!obc.cfg.enable_tls) return auth_and_mail_phase()
|
|
543
|
-
if (!smtp_properties.tls) return auth_and_mail_phase()
|
|
578
|
+
if (!obc.cfg.enable_tls) return auth_and_mail_phase() // TLS not enabled
|
|
579
|
+
if (!smtp_properties.tls) return auth_and_mail_phase() // TLS not advertised by remote
|
|
544
580
|
|
|
545
581
|
if (obtls.cfg === undefined) {
|
|
546
|
-
self.logerror(`Oops, TLS config not loaded yet!`)
|
|
547
|
-
return auth_and_mail_phase()
|
|
582
|
+
self.logerror(`Oops, TLS config not loaded yet!`)
|
|
583
|
+
return auth_and_mail_phase() // no outbound TLS config
|
|
548
584
|
}
|
|
549
585
|
|
|
550
586
|
// TLS is configured and available
|
|
551
587
|
|
|
552
588
|
// TLS exclude lists checks for MX host or remote domain
|
|
553
|
-
if (net_utils.ip_in_list(obtls.cfg.no_tls_hosts, host)) return auth_and_mail_phase()
|
|
554
|
-
if (net_utils.ip_in_list(obtls.cfg.no_tls_hosts, self.todo.domain)) return auth_and_mail_phase()
|
|
589
|
+
if (net_utils.ip_in_list(obtls.cfg.no_tls_hosts, host)) return auth_and_mail_phase()
|
|
590
|
+
if (net_utils.ip_in_list(obtls.cfg.no_tls_hosts, self.todo.domain)) return auth_and_mail_phase()
|
|
555
591
|
|
|
556
592
|
// Check Redis and skip for hosts that failed past TLS upgrade
|
|
557
|
-
return obtls.check_tls_nogo(
|
|
558
|
-
|
|
559
|
-
|
|
593
|
+
return obtls.check_tls_nogo(
|
|
594
|
+
host,
|
|
595
|
+
() => {
|
|
596
|
+
// Clear to GO
|
|
597
|
+
self.logdebug(`Trying TLS for domain: ${self.todo.domain}, host: ${host}`)
|
|
560
598
|
|
|
561
599
|
socket.once('secure', () => {
|
|
562
600
|
// Set this flag so we don't try STARTTLS again if it
|
|
563
601
|
// is incorrectly offered at EHLO once we are secured.
|
|
564
|
-
secured = true
|
|
565
|
-
send_command(mx.using_lmtp ? 'LHLO' : 'EHLO', mx.bind_helo)
|
|
566
|
-
})
|
|
567
|
-
return send_command('STARTTLS')
|
|
602
|
+
secured = true
|
|
603
|
+
send_command(mx.using_lmtp ? 'LHLO' : 'EHLO', mx.bind_helo)
|
|
604
|
+
})
|
|
605
|
+
return send_command('STARTTLS')
|
|
568
606
|
},
|
|
569
|
-
(when) => {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
607
|
+
(when) => {
|
|
608
|
+
// No GO
|
|
609
|
+
self.loginfo(`TLS disabled for ${host} because it was marked as non-TLS on ${when}`)
|
|
610
|
+
return auth_and_mail_phase()
|
|
611
|
+
},
|
|
612
|
+
)
|
|
574
613
|
}
|
|
575
614
|
|
|
576
|
-
let fp_called = false
|
|
615
|
+
let fp_called = false
|
|
577
616
|
|
|
578
|
-
function finish_processing_mail
|
|
617
|
+
function finish_processing_mail(success) {
|
|
579
618
|
if (fp_called) {
|
|
580
|
-
return self.logerror(`finish_processing_mail called multiple times! Stack: ${
|
|
619
|
+
return self.logerror(`finish_processing_mail called multiple times! Stack: ${new Error().stack}`)
|
|
581
620
|
}
|
|
582
|
-
fp_called = true
|
|
621
|
+
fp_called = true
|
|
583
622
|
if (fail_recips.length) {
|
|
584
|
-
self.refcount
|
|
585
|
-
self.split_to_new_recipients(fail_recips,
|
|
586
|
-
self.discard()
|
|
587
|
-
hmail.temp_fail(`Some recipients temp failed: ${fail_recips.join(', ')}`, {
|
|
588
|
-
|
|
623
|
+
self.refcount++
|
|
624
|
+
self.split_to_new_recipients(fail_recips, 'Some recipients temporarily failed', (hmail) => {
|
|
625
|
+
self.discard()
|
|
626
|
+
hmail.temp_fail(`Some recipients temp failed: ${fail_recips.join(', ')}`, {
|
|
627
|
+
fail_recips,
|
|
628
|
+
mx,
|
|
629
|
+
})
|
|
630
|
+
})
|
|
589
631
|
}
|
|
590
632
|
if (bounce_recips.length) {
|
|
591
|
-
self.refcount
|
|
592
|
-
self.split_to_new_recipients(bounce_recips,
|
|
593
|
-
self.discard()
|
|
594
|
-
hmail.bounce(`Some recipients failed: ${bounce_recips.join(', ')}`, {
|
|
595
|
-
|
|
633
|
+
self.refcount++
|
|
634
|
+
self.split_to_new_recipients(bounce_recips, 'Some recipients rejected', (hmail) => {
|
|
635
|
+
self.discard()
|
|
636
|
+
hmail.bounce(`Some recipients failed: ${bounce_recips.join(', ')}`, {
|
|
637
|
+
bounce_recips,
|
|
638
|
+
mx,
|
|
639
|
+
})
|
|
640
|
+
})
|
|
596
641
|
}
|
|
597
|
-
processing_mail = false
|
|
642
|
+
processing_mail = false
|
|
598
643
|
if (success) {
|
|
599
|
-
const reason = response.join(' ')
|
|
644
|
+
const reason = response.join(' ')
|
|
600
645
|
|
|
601
646
|
let hostname = mx.exchange
|
|
602
647
|
if (net.isIP(hostname) && mx.from_dns && !net.isIP(mx.from_dns)) {
|
|
603
648
|
hostname = mx.from_dns
|
|
604
649
|
}
|
|
605
650
|
|
|
606
|
-
self.delivered(
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
651
|
+
self.delivered(
|
|
652
|
+
host,
|
|
653
|
+
port,
|
|
654
|
+
mx.using_lmtp ? 'LMTP' : 'SMTP',
|
|
655
|
+
hostname,
|
|
656
|
+
reason,
|
|
657
|
+
ok_recips,
|
|
658
|
+
fail_recips,
|
|
659
|
+
bounce_recips,
|
|
660
|
+
secured,
|
|
661
|
+
authenticated,
|
|
662
|
+
)
|
|
663
|
+
} else {
|
|
664
|
+
self.discard()
|
|
611
665
|
}
|
|
612
666
|
|
|
613
|
-
send_command('QUIT')
|
|
667
|
+
send_command('QUIT')
|
|
614
668
|
}
|
|
615
669
|
|
|
616
|
-
socket.on('line', line => {
|
|
670
|
+
socket.on('line', (line) => {
|
|
617
671
|
if (!processing_mail && command !== 'rset') {
|
|
618
672
|
if (command !== 'quit') {
|
|
619
|
-
self.logprotocol(`Received data after stopping processing: ${line}`)
|
|
673
|
+
self.logprotocol(`Received data after stopping processing: ${line}`)
|
|
620
674
|
}
|
|
621
|
-
return
|
|
675
|
+
return
|
|
622
676
|
}
|
|
623
|
-
self.logprotocol(`S: ${line}`)
|
|
624
|
-
const matches = smtp_regexp.exec(line)
|
|
677
|
+
self.logprotocol(`S: ${line}`)
|
|
678
|
+
const matches = smtp_regexp.exec(line)
|
|
625
679
|
if (!matches) {
|
|
626
680
|
// Unrecognized response.
|
|
627
|
-
self.logerror(`Unrecognized response from upstream server: ${line}`)
|
|
628
|
-
processing_mail = false
|
|
681
|
+
self.logerror(`Unrecognized response from upstream server: ${line}`)
|
|
682
|
+
processing_mail = false
|
|
629
683
|
// Release back to the pool and instruct it to terminate this connection
|
|
630
|
-
client_pool.release_client(socket, mx)
|
|
631
|
-
self.todo.rcpt_to.forEach(rcpt => {
|
|
632
|
-
self.extend_rcpt_with_dsn(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
684
|
+
client_pool.release_client(socket, mx)
|
|
685
|
+
self.todo.rcpt_to.forEach((rcpt) => {
|
|
686
|
+
self.extend_rcpt_with_dsn(
|
|
687
|
+
rcpt,
|
|
688
|
+
DSN.proto_invalid_command(`Unrecognized response from upstream server: ${line}`),
|
|
689
|
+
)
|
|
690
|
+
})
|
|
691
|
+
self.bounce(`Unrecognized response from upstream server: ${line}`, { mx })
|
|
692
|
+
return
|
|
636
693
|
}
|
|
637
694
|
|
|
638
|
-
let reason
|
|
639
|
-
const code = matches[1]
|
|
640
|
-
const cont = matches[2]
|
|
641
|
-
const extc = matches[3]
|
|
642
|
-
const rest = matches[4]
|
|
643
|
-
response.push(rest)
|
|
644
|
-
if (cont !== ' ') return
|
|
695
|
+
let reason
|
|
696
|
+
const code = matches[1]
|
|
697
|
+
const cont = matches[2]
|
|
698
|
+
const extc = matches[3]
|
|
699
|
+
const rest = matches[4]
|
|
700
|
+
response.push(rest)
|
|
701
|
+
if (cont !== ' ') return
|
|
645
702
|
|
|
646
703
|
if (code.match(/^2/)) {
|
|
647
704
|
// Successful command, fall through
|
|
648
|
-
}
|
|
649
|
-
else if (code.match(/^3/) && command !== 'data') {
|
|
705
|
+
} else if (code.match(/^3/) && command !== 'data') {
|
|
650
706
|
if (authenticating) {
|
|
651
|
-
const resp = response.join(' ')
|
|
707
|
+
const resp = response.join(' ')
|
|
652
708
|
switch (mx.auth_type.toUpperCase()) {
|
|
653
709
|
case 'LOGIN':
|
|
654
710
|
if (resp === 'VXNlcm5hbWU6') {
|
|
655
711
|
// Username:
|
|
656
|
-
return send_command(utils.base64(mx.auth_user))
|
|
657
|
-
}
|
|
658
|
-
else if (resp === 'UGFzc3dvcmQ6') {
|
|
712
|
+
return send_command(utils.base64(mx.auth_user))
|
|
713
|
+
} else if (resp === 'UGFzc3dvcmQ6') {
|
|
659
714
|
// Password:
|
|
660
|
-
return send_command(utils.base64(mx.auth_pass))
|
|
715
|
+
return send_command(utils.base64(mx.auth_pass))
|
|
661
716
|
}
|
|
662
|
-
break
|
|
717
|
+
break
|
|
663
718
|
case 'CRAM-MD5':
|
|
664
719
|
// The response is our challenge
|
|
665
|
-
return send_command(cram_md5_response(mx.auth_user, mx.auth_pass, resp))
|
|
720
|
+
return send_command(cram_md5_response(mx.auth_user, mx.auth_pass, resp))
|
|
666
721
|
default:
|
|
667
722
|
// This shouldn't happen...
|
|
668
723
|
}
|
|
669
724
|
}
|
|
670
725
|
// Error
|
|
671
|
-
reason = response.join(' ')
|
|
672
|
-
recipients.forEach(rcpt => {
|
|
673
|
-
rcpt.dsn_action = 'delayed'
|
|
674
|
-
rcpt.dsn_smtp_code = code
|
|
675
|
-
rcpt.dsn_smtp_extc = extc
|
|
676
|
-
rcpt.dsn_status = extc
|
|
677
|
-
rcpt.dsn_smtp_response = response.join(' ')
|
|
678
|
-
rcpt.dsn_remote_mta = mx.exchange
|
|
679
|
-
})
|
|
680
|
-
send_command('QUIT')
|
|
681
|
-
processing_mail = false
|
|
682
|
-
return self.temp_fail(`Upstream error: ${code} ${
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
authenticating = false;
|
|
726
|
+
reason = response.join(' ')
|
|
727
|
+
recipients.forEach((rcpt) => {
|
|
728
|
+
rcpt.dsn_action = 'delayed'
|
|
729
|
+
rcpt.dsn_smtp_code = code
|
|
730
|
+
rcpt.dsn_smtp_extc = extc
|
|
731
|
+
rcpt.dsn_status = extc
|
|
732
|
+
rcpt.dsn_smtp_response = response.join(' ')
|
|
733
|
+
rcpt.dsn_remote_mta = mx.exchange
|
|
734
|
+
})
|
|
735
|
+
send_command('QUIT')
|
|
736
|
+
processing_mail = false
|
|
737
|
+
return self.temp_fail(`Upstream error: ${code} ${extc ? `${extc} ` : ''}${reason}`)
|
|
738
|
+
} else if (code.match(/^4/)) {
|
|
739
|
+
authenticating = false
|
|
686
740
|
if (/^rcpt/.test(command) || command === 'dot_lmtp') {
|
|
687
|
-
if (command === 'dot_lmtp') last_recip = ok_recips.shift()
|
|
741
|
+
if (command === 'dot_lmtp') last_recip = ok_recips.shift()
|
|
688
742
|
// this recipient was rejected
|
|
689
|
-
reason = `${code} ${
|
|
690
|
-
self.lognotice(`recipient ${last_recip} deferred: ${reason}`)
|
|
691
|
-
last_recip.reason = reason
|
|
692
|
-
|
|
693
|
-
last_recip.dsn_action = 'delayed'
|
|
694
|
-
last_recip.dsn_smtp_code = code
|
|
695
|
-
last_recip.dsn_smtp_extc = extc
|
|
696
|
-
last_recip.dsn_status = extc
|
|
697
|
-
last_recip.dsn_smtp_response = response.join(' ')
|
|
698
|
-
last_recip.dsn_remote_mta = mx.exchange
|
|
699
|
-
|
|
700
|
-
fail_recips.push(last_recip)
|
|
743
|
+
reason = `${code} ${extc ? `${extc} ` : ''}${response.join(' ')}`
|
|
744
|
+
self.lognotice(`recipient ${last_recip} deferred: ${reason}`)
|
|
745
|
+
last_recip.reason = reason
|
|
746
|
+
|
|
747
|
+
last_recip.dsn_action = 'delayed'
|
|
748
|
+
last_recip.dsn_smtp_code = code
|
|
749
|
+
last_recip.dsn_smtp_extc = extc
|
|
750
|
+
last_recip.dsn_status = extc
|
|
751
|
+
last_recip.dsn_smtp_response = response.join(' ')
|
|
752
|
+
last_recip.dsn_remote_mta = mx.exchange
|
|
753
|
+
|
|
754
|
+
fail_recips.push(last_recip)
|
|
701
755
|
if (command === 'dot_lmtp') {
|
|
702
|
-
response = []
|
|
756
|
+
response = []
|
|
703
757
|
if (ok_recips.length === 0) {
|
|
704
|
-
return finish_processing_mail(false)
|
|
758
|
+
return finish_processing_mail(false)
|
|
705
759
|
}
|
|
706
760
|
}
|
|
761
|
+
} else if (processing_mail) {
|
|
762
|
+
reason = response.join(' ')
|
|
763
|
+
recipients.forEach((rcpt) => {
|
|
764
|
+
rcpt.dsn_action = 'delayed'
|
|
765
|
+
rcpt.dsn_smtp_code = code
|
|
766
|
+
rcpt.dsn_smtp_extc = extc
|
|
767
|
+
rcpt.dsn_status = extc
|
|
768
|
+
rcpt.dsn_smtp_response = response.join(' ')
|
|
769
|
+
rcpt.dsn_remote_mta = mx.exchange
|
|
770
|
+
})
|
|
771
|
+
send_command('QUIT')
|
|
772
|
+
processing_mail = false
|
|
773
|
+
return self.temp_fail(`Upstream error: ${code} ${extc ? `${extc} ` : ''}${reason}`)
|
|
774
|
+
} else {
|
|
775
|
+
reason = response.join(' ')
|
|
776
|
+
self.lognotice(`Error - but not processing mail: ${code} ${extc ? `${extc} ` : ''}${reason}`)
|
|
777
|
+
return client_pool.release_client(socket, mx)
|
|
707
778
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
recipients.forEach(rcpt => {
|
|
711
|
-
rcpt.dsn_action = 'delayed';
|
|
712
|
-
rcpt.dsn_smtp_code = code;
|
|
713
|
-
rcpt.dsn_smtp_extc = extc;
|
|
714
|
-
rcpt.dsn_status = extc;
|
|
715
|
-
rcpt.dsn_smtp_response = response.join(' ');
|
|
716
|
-
rcpt.dsn_remote_mta = mx.exchange;
|
|
717
|
-
});
|
|
718
|
-
send_command('QUIT');
|
|
719
|
-
processing_mail = false;
|
|
720
|
-
return self.temp_fail(`Upstream error: ${code} ${(extc) ? `${extc} ` : ''}${reason}`);
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
reason = response.join(' ');
|
|
724
|
-
self.lognotice(`Error - but not processing mail: ${code} ${((extc) ? `${extc} ` : '')}${reason}`);
|
|
725
|
-
return client_pool.release_client(socket, mx);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
else if (code.match(/^5/)) {
|
|
729
|
-
authenticating = false;
|
|
779
|
+
} else if (code.match(/^5/)) {
|
|
780
|
+
authenticating = false
|
|
730
781
|
if (command === 'ehlo') {
|
|
731
782
|
// EHLO command was rejected; fall-back to HELO
|
|
732
|
-
return send_command('HELO', mx.bind_helo)
|
|
783
|
+
return send_command('HELO', mx.bind_helo)
|
|
733
784
|
}
|
|
734
785
|
if (command === 'rset') {
|
|
735
786
|
// Broken server doesn't accept RSET, terminate the connection
|
|
736
|
-
return client_pool.release_client(socket, mx)
|
|
787
|
+
return client_pool.release_client(socket, mx)
|
|
737
788
|
}
|
|
738
|
-
reason = `${code} ${
|
|
789
|
+
reason = `${code} ${extc ? `${extc} ` : ''}${response.join(' ')}`
|
|
739
790
|
if (/^rcpt/.test(command) || command === 'dot_lmtp') {
|
|
740
|
-
if (command === 'dot_lmtp') last_recip = ok_recips.shift()
|
|
741
|
-
self.lognotice(`recipient ${last_recip} rejected: ${reason}`)
|
|
742
|
-
last_recip.reason = reason
|
|
743
|
-
|
|
744
|
-
last_recip.dsn_action = 'failed'
|
|
745
|
-
last_recip.dsn_smtp_code = code
|
|
746
|
-
last_recip.dsn_smtp_extc = extc
|
|
747
|
-
last_recip.dsn_status = extc
|
|
748
|
-
last_recip.dsn_smtp_response = response.join(' ')
|
|
749
|
-
last_recip.dsn_remote_mta = mx.exchange
|
|
750
|
-
|
|
751
|
-
bounce_recips.push(last_recip)
|
|
791
|
+
if (command === 'dot_lmtp') last_recip = ok_recips.shift()
|
|
792
|
+
self.lognotice(`recipient ${last_recip} rejected: ${reason}`)
|
|
793
|
+
last_recip.reason = reason
|
|
794
|
+
|
|
795
|
+
last_recip.dsn_action = 'failed'
|
|
796
|
+
last_recip.dsn_smtp_code = code
|
|
797
|
+
last_recip.dsn_smtp_extc = extc
|
|
798
|
+
last_recip.dsn_status = extc
|
|
799
|
+
last_recip.dsn_smtp_response = response.join(' ')
|
|
800
|
+
last_recip.dsn_remote_mta = mx.exchange
|
|
801
|
+
|
|
802
|
+
bounce_recips.push(last_recip)
|
|
752
803
|
if (command === 'dot_lmtp') {
|
|
753
|
-
response = []
|
|
804
|
+
response = []
|
|
754
805
|
if (ok_recips.length === 0) {
|
|
755
|
-
return finish_processing_mail(false)
|
|
806
|
+
return finish_processing_mail(false)
|
|
756
807
|
}
|
|
757
808
|
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
rcpt.
|
|
762
|
-
rcpt.
|
|
763
|
-
rcpt.
|
|
764
|
-
rcpt.
|
|
765
|
-
rcpt.
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
return self.bounce(reason, { mx });
|
|
809
|
+
} else {
|
|
810
|
+
recipients.forEach((rcpt) => {
|
|
811
|
+
rcpt.dsn_action = 'failed'
|
|
812
|
+
rcpt.dsn_smtp_code = code
|
|
813
|
+
rcpt.dsn_smtp_extc = extc
|
|
814
|
+
rcpt.dsn_status = extc
|
|
815
|
+
rcpt.dsn_smtp_response = response.join(' ')
|
|
816
|
+
rcpt.dsn_remote_mta = mx.exchange
|
|
817
|
+
})
|
|
818
|
+
send_command('QUIT')
|
|
819
|
+
processing_mail = false
|
|
820
|
+
return self.bounce(reason, { mx })
|
|
771
821
|
}
|
|
772
822
|
}
|
|
773
823
|
|
|
774
824
|
switch (command) {
|
|
775
825
|
case 'connect':
|
|
776
|
-
send_command('EHLO', mx.bind_helo)
|
|
777
|
-
break
|
|
826
|
+
send_command('EHLO', mx.bind_helo)
|
|
827
|
+
break
|
|
778
828
|
case 'connect_lmtp':
|
|
779
|
-
send_command('LHLO', mx.bind_helo)
|
|
780
|
-
break
|
|
829
|
+
send_command('LHLO', mx.bind_helo)
|
|
830
|
+
break
|
|
781
831
|
case 'lhlo':
|
|
782
832
|
case 'ehlo':
|
|
783
|
-
process_ehlo_data()
|
|
784
|
-
break
|
|
833
|
+
process_ehlo_data()
|
|
834
|
+
break
|
|
785
835
|
case 'starttls': {
|
|
786
|
-
const tls_options = obtls.get_tls_options(mx)
|
|
787
|
-
if (self.force_tls) tls_options.rejectUnauthorized = true
|
|
836
|
+
const tls_options = obtls.get_tls_options(mx)
|
|
837
|
+
if (self.force_tls) tls_options.rejectUnauthorized = true
|
|
788
838
|
|
|
789
|
-
smtp_properties = {}
|
|
839
|
+
smtp_properties = {}
|
|
790
840
|
socket.upgrade(tls_options, (authorized, verifyError, cert, cipher) => {
|
|
791
841
|
const loginfo = {
|
|
792
|
-
verified: authorized
|
|
793
|
-
}
|
|
842
|
+
verified: authorized,
|
|
843
|
+
}
|
|
794
844
|
if (cipher) {
|
|
795
|
-
loginfo.cipher = cipher.name
|
|
796
|
-
loginfo.version = cipher.version
|
|
845
|
+
loginfo.cipher = cipher.name
|
|
846
|
+
loginfo.version = cipher.version
|
|
797
847
|
}
|
|
798
|
-
if (verifyError) loginfo.error = verifyError
|
|
848
|
+
if (verifyError) loginfo.error = verifyError
|
|
799
849
|
if (cert?.subject) {
|
|
800
|
-
loginfo.cn = cert.subject.CN
|
|
801
|
-
loginfo.organization = cert.subject.O
|
|
850
|
+
loginfo.cn = cert.subject.CN
|
|
851
|
+
loginfo.organization = cert.subject.O
|
|
802
852
|
}
|
|
803
|
-
if (cert?.issuer)
|
|
804
|
-
if (cert?.valid_to) loginfo.expires = cert.valid_to
|
|
805
|
-
if (cert?.fingerprint) loginfo.fingerprint = cert.fingerprint
|
|
806
|
-
self.loginfo('secured', loginfo)
|
|
853
|
+
if (cert?.issuer) loginfo.issuer = cert.issuer.O
|
|
854
|
+
if (cert?.valid_to) loginfo.expires = cert.valid_to
|
|
855
|
+
if (cert?.fingerprint) loginfo.fingerprint = cert.fingerprint
|
|
856
|
+
self.loginfo('secured', loginfo)
|
|
807
857
|
|
|
808
858
|
if (self.force_tls && !authorized) {
|
|
809
|
-
processing_mail = false
|
|
810
|
-
socket.end()
|
|
811
|
-
self.temp_fail('Host failed TLS verification required by configuration.')
|
|
812
|
-
client_pool.release_client(socket, mx)
|
|
859
|
+
processing_mail = false
|
|
860
|
+
socket.end()
|
|
861
|
+
self.temp_fail('Host failed TLS verification required by configuration.')
|
|
862
|
+
client_pool.release_client(socket, mx)
|
|
813
863
|
}
|
|
814
|
-
})
|
|
815
|
-
break
|
|
864
|
+
})
|
|
865
|
+
break
|
|
816
866
|
}
|
|
817
867
|
case 'auth':
|
|
818
|
-
authenticating = false
|
|
819
|
-
authenticated = true
|
|
820
|
-
send_command('MAIL', get_reverse_path_with_params())
|
|
821
|
-
break
|
|
868
|
+
authenticating = false
|
|
869
|
+
authenticated = true
|
|
870
|
+
send_command('MAIL', get_reverse_path_with_params())
|
|
871
|
+
break
|
|
822
872
|
case 'helo':
|
|
823
|
-
send_command('MAIL', get_reverse_path_with_params())
|
|
824
|
-
break
|
|
873
|
+
send_command('MAIL', get_reverse_path_with_params())
|
|
874
|
+
break
|
|
825
875
|
case 'mail':
|
|
826
|
-
last_recip = recipients[recip_index]
|
|
827
|
-
recip_index
|
|
828
|
-
send_command('RCPT', `TO:${last_recip.format(!smtp_properties.smtp_utf8)}`)
|
|
829
|
-
break
|
|
876
|
+
last_recip = recipients[recip_index]
|
|
877
|
+
recip_index++
|
|
878
|
+
send_command('RCPT', `TO:${last_recip.format(!smtp_properties.smtp_utf8)}`)
|
|
879
|
+
break
|
|
830
880
|
case 'rcpt':
|
|
831
881
|
if (last_recip && code.match(/^250/)) {
|
|
832
|
-
ok_recips.push(last_recip)
|
|
882
|
+
ok_recips.push(last_recip)
|
|
833
883
|
}
|
|
834
|
-
if (recip_index === recipients.length) {
|
|
884
|
+
if (recip_index === recipients.length) {
|
|
885
|
+
// End of RCPT TOs
|
|
835
886
|
if (ok_recips.length > 0) {
|
|
836
|
-
send_command('DATA')
|
|
887
|
+
send_command('DATA')
|
|
888
|
+
} else {
|
|
889
|
+
finish_processing_mail(false)
|
|
837
890
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
else {
|
|
843
|
-
last_recip = recipients[recip_index];
|
|
844
|
-
recip_index++;
|
|
845
|
-
send_command('RCPT', `TO:${last_recip.format(!smtp_properties.smtp_utf8)}`);
|
|
891
|
+
} else {
|
|
892
|
+
last_recip = recipients[recip_index]
|
|
893
|
+
recip_index++
|
|
894
|
+
send_command('RCPT', `TO:${last_recip.format(!smtp_properties.smtp_utf8)}`)
|
|
846
895
|
}
|
|
847
|
-
break
|
|
896
|
+
break
|
|
848
897
|
case 'data': {
|
|
849
|
-
const data_stream = self.data_stream()
|
|
850
|
-
data_stream.on('data', data => {
|
|
851
|
-
self.logdata(`C: ${data}`)
|
|
852
|
-
})
|
|
853
|
-
data_stream.on('error', err => {
|
|
854
|
-
self.logerror(`Reading from the data stream failed: ${err}`)
|
|
855
|
-
})
|
|
898
|
+
const data_stream = self.data_stream()
|
|
899
|
+
data_stream.on('data', (data) => {
|
|
900
|
+
self.logdata(`C: ${data}`)
|
|
901
|
+
})
|
|
902
|
+
data_stream.on('error', (err) => {
|
|
903
|
+
self.logerror(`Reading from the data stream failed: ${err}`)
|
|
904
|
+
})
|
|
856
905
|
data_stream.on('end', () => {
|
|
857
|
-
send_command(mx.using_lmtp ? 'dot_lmtp' : 'dot')
|
|
858
|
-
})
|
|
859
|
-
data_stream.pipe(socket, {end: false})
|
|
860
|
-
break
|
|
906
|
+
send_command(mx.using_lmtp ? 'dot_lmtp' : 'dot')
|
|
907
|
+
})
|
|
908
|
+
data_stream.pipe(socket, { end: false })
|
|
909
|
+
break
|
|
861
910
|
}
|
|
862
911
|
case 'dot':
|
|
863
|
-
finish_processing_mail(true)
|
|
864
|
-
break
|
|
912
|
+
finish_processing_mail(true)
|
|
913
|
+
break
|
|
865
914
|
case 'dot_lmtp':
|
|
866
|
-
if (code.match(/^2/)) lmtp_rcpt_idx
|
|
915
|
+
if (code.match(/^2/)) lmtp_rcpt_idx++
|
|
867
916
|
if (lmtp_rcpt_idx === ok_recips.length) {
|
|
868
|
-
finish_processing_mail(true)
|
|
917
|
+
finish_processing_mail(true)
|
|
869
918
|
}
|
|
870
|
-
break
|
|
919
|
+
break
|
|
871
920
|
case 'quit':
|
|
872
921
|
case 'rset':
|
|
873
|
-
client_pool.release_client(socket, mx)
|
|
874
|
-
break
|
|
922
|
+
client_pool.release_client(socket, mx)
|
|
923
|
+
break
|
|
875
924
|
default:
|
|
876
925
|
// should never get here - means we did something
|
|
877
926
|
// wrong.
|
|
878
|
-
throw new Error(`Unknown command: ${command}`)
|
|
927
|
+
throw new Error(`Unknown command: ${command}`)
|
|
879
928
|
}
|
|
880
|
-
})
|
|
929
|
+
})
|
|
881
930
|
|
|
882
931
|
if (socket.__fromPool) {
|
|
883
|
-
logger.debug(this, 'got socket, trying to deliver')
|
|
884
|
-
secured = socket.isEncrypted()
|
|
885
|
-
logger.debug(this, `got ${secured ? 'TLS ' : ''
|
|
886
|
-
send_command('MAIL', get_reverse_path_with_params())
|
|
932
|
+
logger.debug(this, 'got socket, trying to deliver')
|
|
933
|
+
secured = socket.isEncrypted()
|
|
934
|
+
logger.debug(this, `got ${secured ? 'TLS ' : ''}socket, trying to deliver`)
|
|
935
|
+
send_command('MAIL', get_reverse_path_with_params())
|
|
887
936
|
}
|
|
888
937
|
}
|
|
889
938
|
|
|
890
|
-
extend_rcpt_with_dsn
|
|
891
|
-
rcpt.dsn_code = dsn.code
|
|
892
|
-
rcpt.dsn_msg = dsn.msg
|
|
893
|
-
rcpt.dsn_status = `${dsn.cls}.${dsn.sub}.${dsn.det}
|
|
939
|
+
extend_rcpt_with_dsn(rcpt, dsn) {
|
|
940
|
+
rcpt.dsn_code = dsn.code
|
|
941
|
+
rcpt.dsn_msg = dsn.msg
|
|
942
|
+
rcpt.dsn_status = `${dsn.cls}.${dsn.sub}.${dsn.det}`
|
|
894
943
|
if (dsn.cls == 4) {
|
|
895
|
-
rcpt.dsn_action = 'delayed'
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
rcpt.dsn_action = 'failed';
|
|
944
|
+
rcpt.dsn_action = 'delayed'
|
|
945
|
+
} else if (dsn.cls == 5) {
|
|
946
|
+
rcpt.dsn_action = 'failed'
|
|
899
947
|
}
|
|
900
948
|
}
|
|
901
949
|
|
|
902
|
-
populate_bounce_message
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const header = new message.Header();
|
|
950
|
+
populate_bounce_message(from, to, reason, cb) {
|
|
951
|
+
let buf = ''
|
|
952
|
+
const original_header_lines = []
|
|
953
|
+
let headers_done = false
|
|
954
|
+
const header = new message.Header()
|
|
908
955
|
|
|
909
956
|
try {
|
|
910
|
-
const data_stream = this.data_stream()
|
|
911
|
-
data_stream.on('data', data => {
|
|
957
|
+
const data_stream = this.data_stream()
|
|
958
|
+
data_stream.on('data', (data) => {
|
|
912
959
|
if (headers_done === false) {
|
|
913
|
-
buf += data
|
|
914
|
-
let results
|
|
960
|
+
buf += data
|
|
961
|
+
let results
|
|
915
962
|
while ((results = utils.line_regexp.exec(buf))) {
|
|
916
|
-
const this_line = results[1]
|
|
963
|
+
const this_line = results[1]
|
|
917
964
|
if (this_line === '\n' || this_line == '\r\n') {
|
|
918
|
-
headers_done = true
|
|
919
|
-
break
|
|
965
|
+
headers_done = true
|
|
966
|
+
break
|
|
920
967
|
}
|
|
921
|
-
buf = buf.slice(this_line.length)
|
|
922
|
-
original_header_lines.push(this_line)
|
|
968
|
+
buf = buf.slice(this_line.length)
|
|
969
|
+
original_header_lines.push(this_line)
|
|
923
970
|
}
|
|
924
971
|
}
|
|
925
|
-
})
|
|
972
|
+
})
|
|
926
973
|
data_stream.on('end', () => {
|
|
927
974
|
if (original_header_lines.length > 0) {
|
|
928
|
-
header.parse(original_header_lines)
|
|
975
|
+
header.parse(original_header_lines)
|
|
929
976
|
}
|
|
930
|
-
this.populate_bounce_message_with_headers(from, to, reason, header, cb)
|
|
931
|
-
})
|
|
932
|
-
data_stream.on('error', err => {
|
|
933
|
-
cb(err)
|
|
934
|
-
})
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
this.populate_bounce_message_with_headers(from, to, reason, header, cb);
|
|
977
|
+
this.populate_bounce_message_with_headers(from, to, reason, header, cb)
|
|
978
|
+
})
|
|
979
|
+
data_stream.on('error', (err) => {
|
|
980
|
+
cb(err)
|
|
981
|
+
})
|
|
982
|
+
} catch (err) {
|
|
983
|
+
this.populate_bounce_message_with_headers(from, to, reason, header, cb)
|
|
938
984
|
}
|
|
939
985
|
}
|
|
940
986
|
|
|
@@ -960,509 +1006,516 @@ class HMailItem extends events.EventEmitter {
|
|
|
960
1006
|
* @param header
|
|
961
1007
|
* @param cb - a callback for fn(err, message_body_lines)
|
|
962
1008
|
*/
|
|
963
|
-
populate_bounce_message_with_headers
|
|
964
|
-
const CRLF = '\r\n'
|
|
1009
|
+
populate_bounce_message_with_headers(from, to, reason, header, cb) {
|
|
1010
|
+
const CRLF = '\r\n'
|
|
965
1011
|
|
|
966
|
-
const originalMessageId = header.get('Message-Id')
|
|
1012
|
+
const originalMessageId = header.get('Message-Id')
|
|
967
1013
|
|
|
968
|
-
const bounce_msg_ = config.get('outbound.bounce_message', 'data')
|
|
969
|
-
const bounce_msg_html_ = config.get('outbound.bounce_message_html', 'data')
|
|
970
|
-
const bounce_msg_image_ = config.get('outbound.bounce_message_image', 'data')
|
|
1014
|
+
const bounce_msg_ = config.get('outbound.bounce_message', 'data')
|
|
1015
|
+
const bounce_msg_html_ = config.get('outbound.bounce_message_html', 'data')
|
|
1016
|
+
const bounce_msg_image_ = config.get('outbound.bounce_message_image', 'data')
|
|
971
1017
|
|
|
972
|
-
const bounce_header_lines = []
|
|
973
|
-
const bounce_body_lines = []
|
|
974
|
-
const bounce_html_lines = []
|
|
975
|
-
const bounce_image_lines = []
|
|
976
|
-
let bounce_headers_done = false
|
|
1018
|
+
const bounce_header_lines = []
|
|
1019
|
+
const bounce_body_lines = []
|
|
1020
|
+
const bounce_html_lines = []
|
|
1021
|
+
const bounce_image_lines = []
|
|
1022
|
+
let bounce_headers_done = false
|
|
977
1023
|
|
|
978
1024
|
const values = {
|
|
979
1025
|
date: utils.date_to_str(new Date()),
|
|
980
|
-
me:
|
|
1026
|
+
me: net_utils.get_primary_host_name(),
|
|
981
1027
|
from,
|
|
982
1028
|
to,
|
|
983
1029
|
subject: header.get_decoded('Subject').trim(),
|
|
984
1030
|
recipients: this.todo.rcpt_to.join(', '),
|
|
985
1031
|
reason,
|
|
986
|
-
extended_reason: this.todo.rcpt_to
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1032
|
+
extended_reason: this.todo.rcpt_to
|
|
1033
|
+
.map((recip) => {
|
|
1034
|
+
if (recip.reason) {
|
|
1035
|
+
return `${recip.original}: ${recip.reason}`
|
|
1036
|
+
}
|
|
1037
|
+
})
|
|
1038
|
+
.join('\n'),
|
|
991
1039
|
pid: process.pid,
|
|
992
1040
|
msgid: `<${utils.uuid()}@${net_utils.get_primary_host_name()}>`,
|
|
993
|
-
}
|
|
1041
|
+
}
|
|
994
1042
|
|
|
995
|
-
bounce_msg_.forEach(line => {
|
|
996
|
-
line = line.replace(/\{(\w+)\}/g, (i, word) => values[word] || '?')
|
|
1043
|
+
bounce_msg_.forEach((line) => {
|
|
1044
|
+
line = line.replace(/\{(\w+)\}/g, (i, word) => values[word] || '?')
|
|
997
1045
|
|
|
998
1046
|
if (bounce_headers_done == false && line == '') {
|
|
999
|
-
bounce_headers_done = true
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1047
|
+
bounce_headers_done = true
|
|
1048
|
+
} else if (bounce_headers_done == false) {
|
|
1049
|
+
bounce_header_lines.push(line)
|
|
1050
|
+
} else if (bounce_headers_done == true) {
|
|
1051
|
+
bounce_body_lines.push(line)
|
|
1003
1052
|
}
|
|
1004
|
-
|
|
1005
|
-
bounce_body_lines.push(line);
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1053
|
+
})
|
|
1008
1054
|
|
|
1009
1055
|
const escaped_chars = {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1056
|
+
'&': 'amp',
|
|
1057
|
+
'<': 'lt',
|
|
1058
|
+
'>': 'gt',
|
|
1013
1059
|
'"': 'quot',
|
|
1014
1060
|
"'": 'apos',
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
const escape_pattern = new RegExp(`[${Object.keys(escaped_chars).join('')}]`, 'g')
|
|
1061
|
+
'\r': '#10',
|
|
1062
|
+
'\n': '#13',
|
|
1063
|
+
}
|
|
1064
|
+
const escape_pattern = new RegExp(`[${Object.keys(escaped_chars).join('')}]`, 'g')
|
|
1019
1065
|
|
|
1020
|
-
bounce_msg_html_.forEach(line => {
|
|
1066
|
+
bounce_msg_html_.forEach((line) => {
|
|
1021
1067
|
line = line.replace(/\{(\w+)\}/g, (i, word) => {
|
|
1022
1068
|
if (word in values) {
|
|
1023
|
-
return String(values[word]).replace(escape_pattern, m => `&${escaped_chars[m]};`)
|
|
1069
|
+
return String(values[word]).replace(escape_pattern, (m) => `&${escaped_chars[m]};`)
|
|
1070
|
+
} else {
|
|
1071
|
+
return '?'
|
|
1024
1072
|
}
|
|
1025
|
-
|
|
1026
|
-
return '?';
|
|
1027
|
-
}
|
|
1028
|
-
});
|
|
1073
|
+
})
|
|
1029
1074
|
|
|
1030
|
-
bounce_html_lines.push(line)
|
|
1031
|
-
})
|
|
1075
|
+
bounce_html_lines.push(line)
|
|
1076
|
+
})
|
|
1032
1077
|
|
|
1033
|
-
bounce_msg_image_.forEach(line => {
|
|
1078
|
+
bounce_msg_image_.forEach((line) => {
|
|
1034
1079
|
bounce_image_lines.push(line)
|
|
1035
|
-
})
|
|
1080
|
+
})
|
|
1036
1081
|
|
|
1037
|
-
const boundary = `boundary_${utils.uuid()}
|
|
1038
|
-
const bounce_body = []
|
|
1082
|
+
const boundary = `boundary_${utils.uuid()}`
|
|
1083
|
+
const bounce_body = []
|
|
1039
1084
|
|
|
1040
|
-
bounce_header_lines.forEach(line => {
|
|
1041
|
-
bounce_body.push(`${line}${CRLF}`)
|
|
1042
|
-
})
|
|
1043
|
-
bounce_body.push(
|
|
1085
|
+
bounce_header_lines.forEach((line) => {
|
|
1086
|
+
bounce_body.push(`${line}${CRLF}`)
|
|
1087
|
+
})
|
|
1088
|
+
bounce_body.push(
|
|
1089
|
+
`Content-Type: multipart/report; report-type=delivery-status;${CRLF} boundary="${boundary}"${CRLF}`,
|
|
1090
|
+
)
|
|
1044
1091
|
// Adding references to original msg id
|
|
1045
1092
|
if (originalMessageId != '') {
|
|
1046
|
-
bounce_body.push(`References: ${originalMessageId.replace(/(\r?\n)*$/, '')}${CRLF}`)
|
|
1093
|
+
bounce_body.push(`References: ${originalMessageId.replace(/(\r?\n)*$/, '')}${CRLF}`)
|
|
1047
1094
|
}
|
|
1048
1095
|
|
|
1049
|
-
bounce_body.push(CRLF)
|
|
1050
|
-
bounce_body.push(`This is a MIME-encapsulated message.${CRLF}`)
|
|
1051
|
-
bounce_body.push(CRLF)
|
|
1096
|
+
bounce_body.push(CRLF)
|
|
1097
|
+
bounce_body.push(`This is a MIME-encapsulated message.${CRLF}`)
|
|
1098
|
+
bounce_body.push(CRLF)
|
|
1052
1099
|
|
|
1053
|
-
let boundary_incr = ''
|
|
1100
|
+
let boundary_incr = ''
|
|
1054
1101
|
if (bounce_html_lines.length > 1) {
|
|
1055
|
-
boundary_incr = 'a'
|
|
1056
|
-
bounce_body.push(`--${boundary}${CRLF}`)
|
|
1057
|
-
bounce_body.push(`Content-Type: multipart/related; boundary="${boundary}${boundary_incr}"${CRLF}`)
|
|
1058
|
-
bounce_body.push(CRLF)
|
|
1059
|
-
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1060
|
-
boundary_incr = 'b'
|
|
1061
|
-
bounce_body.push(`Content-Type: multipart/alternative; boundary="${boundary}${boundary_incr}"${CRLF}`)
|
|
1062
|
-
bounce_body.push(CRLF)
|
|
1102
|
+
boundary_incr = 'a'
|
|
1103
|
+
bounce_body.push(`--${boundary}${CRLF}`)
|
|
1104
|
+
bounce_body.push(`Content-Type: multipart/related; boundary="${boundary}${boundary_incr}"${CRLF}`)
|
|
1105
|
+
bounce_body.push(CRLF)
|
|
1106
|
+
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1107
|
+
boundary_incr = 'b'
|
|
1108
|
+
bounce_body.push(`Content-Type: multipart/alternative; boundary="${boundary}${boundary_incr}"${CRLF}`)
|
|
1109
|
+
bounce_body.push(CRLF)
|
|
1063
1110
|
}
|
|
1064
1111
|
|
|
1065
|
-
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1066
|
-
bounce_body.push(`Content-Type: text/plain; charset=us-ascii${CRLF}`)
|
|
1067
|
-
bounce_body.push(CRLF)
|
|
1068
|
-
bounce_body_lines.forEach(line => {
|
|
1069
|
-
bounce_body.push(`${line}${CRLF}`)
|
|
1070
|
-
})
|
|
1071
|
-
bounce_body.push(CRLF)
|
|
1112
|
+
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1113
|
+
bounce_body.push(`Content-Type: text/plain; charset=us-ascii${CRLF}`)
|
|
1114
|
+
bounce_body.push(CRLF)
|
|
1115
|
+
bounce_body_lines.forEach((line) => {
|
|
1116
|
+
bounce_body.push(`${line}${CRLF}`)
|
|
1117
|
+
})
|
|
1118
|
+
bounce_body.push(CRLF)
|
|
1072
1119
|
|
|
1073
1120
|
if (bounce_html_lines.length > 1) {
|
|
1074
|
-
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1075
|
-
bounce_body.push(`Content-Type: text/html; charset=us-ascii${CRLF}`)
|
|
1076
|
-
bounce_body.push(CRLF)
|
|
1077
|
-
bounce_html_lines.forEach(line => {
|
|
1078
|
-
bounce_body.push(`${line}${CRLF}`)
|
|
1079
|
-
})
|
|
1080
|
-
bounce_body.push(CRLF)
|
|
1081
|
-
bounce_body.push(`--${boundary}${boundary_incr}--${CRLF}`)
|
|
1121
|
+
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1122
|
+
bounce_body.push(`Content-Type: text/html; charset=us-ascii${CRLF}`)
|
|
1123
|
+
bounce_body.push(CRLF)
|
|
1124
|
+
bounce_html_lines.forEach((line) => {
|
|
1125
|
+
bounce_body.push(`${line}${CRLF}`)
|
|
1126
|
+
})
|
|
1127
|
+
bounce_body.push(CRLF)
|
|
1128
|
+
bounce_body.push(`--${boundary}${boundary_incr}--${CRLF}`)
|
|
1082
1129
|
|
|
1083
1130
|
if (bounce_image_lines.length > 1) {
|
|
1084
|
-
boundary_incr = 'a'
|
|
1085
|
-
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1131
|
+
boundary_incr = 'a'
|
|
1132
|
+
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1086
1133
|
//bounce_body.push(`Content-Type: text/html; charset=us-ascii${CRLF}`);
|
|
1087
1134
|
//bounce_body.push(CRLF);
|
|
1088
|
-
bounce_image_lines.forEach(line => {
|
|
1089
|
-
bounce_body.push(`${line}${CRLF}`)
|
|
1090
|
-
})
|
|
1091
|
-
bounce_body.push(CRLF)
|
|
1092
|
-
bounce_body.push(`--${boundary}${boundary_incr}--${CRLF}`)
|
|
1135
|
+
bounce_image_lines.forEach((line) => {
|
|
1136
|
+
bounce_body.push(`${line}${CRLF}`)
|
|
1137
|
+
})
|
|
1138
|
+
bounce_body.push(CRLF)
|
|
1139
|
+
bounce_body.push(`--${boundary}${boundary_incr}--${CRLF}`)
|
|
1093
1140
|
}
|
|
1094
1141
|
}
|
|
1095
1142
|
|
|
1096
|
-
bounce_body.push(`--${boundary}${CRLF}`)
|
|
1097
|
-
bounce_body.push(`Content-type: message/delivery-status${CRLF}`)
|
|
1098
|
-
bounce_body.push(CRLF)
|
|
1143
|
+
bounce_body.push(`--${boundary}${CRLF}`)
|
|
1144
|
+
bounce_body.push(`Content-type: message/delivery-status${CRLF}`)
|
|
1145
|
+
bounce_body.push(CRLF)
|
|
1099
1146
|
if (originalMessageId != '') {
|
|
1100
|
-
bounce_body.push(`Original-Envelope-Id: ${originalMessageId.replace(/(\r?\n)*$/, '')}${CRLF}`)
|
|
1147
|
+
bounce_body.push(`Original-Envelope-Id: ${originalMessageId.replace(/(\r?\n)*$/, '')}${CRLF}`)
|
|
1101
1148
|
}
|
|
1102
|
-
bounce_body.push(`Reporting-MTA: dns;${net_utils.get_primary_host_name()}${CRLF}`)
|
|
1149
|
+
bounce_body.push(`Reporting-MTA: dns;${net_utils.get_primary_host_name()}${CRLF}`)
|
|
1103
1150
|
if (this.todo.queue_time) {
|
|
1104
|
-
bounce_body.push(`Arrival-Date: ${utils.date_to_str(new Date(this.todo.queue_time))}${CRLF}`)
|
|
1151
|
+
bounce_body.push(`Arrival-Date: ${utils.date_to_str(new Date(this.todo.queue_time))}${CRLF}`)
|
|
1105
1152
|
}
|
|
1106
|
-
this.todo.rcpt_to.forEach(rcpt_to => {
|
|
1107
|
-
bounce_body.push(CRLF)
|
|
1108
|
-
bounce_body.push(`Final-Recipient: rfc822;${rcpt_to.address()}${CRLF}`)
|
|
1109
|
-
let dsn_action = null
|
|
1153
|
+
this.todo.rcpt_to.forEach((rcpt_to) => {
|
|
1154
|
+
bounce_body.push(CRLF)
|
|
1155
|
+
bounce_body.push(`Final-Recipient: rfc822;${rcpt_to.address()}${CRLF}`)
|
|
1156
|
+
let dsn_action = null
|
|
1110
1157
|
if (rcpt_to.dsn_action) {
|
|
1111
|
-
dsn_action = rcpt_to.dsn_action
|
|
1112
|
-
}
|
|
1113
|
-
else if (rcpt_to.dsn_code) {
|
|
1158
|
+
dsn_action = rcpt_to.dsn_action
|
|
1159
|
+
} else if (rcpt_to.dsn_code) {
|
|
1114
1160
|
if (/^5/.exec(rcpt_to.dsn_code)) {
|
|
1115
|
-
dsn_action = 'failed'
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1161
|
+
dsn_action = 'failed'
|
|
1162
|
+
} else if (/^4/.exec(rcpt_to.dsn_code)) {
|
|
1163
|
+
dsn_action = 'delayed'
|
|
1164
|
+
} else if (/^2/.exec(rcpt_to.dsn_code)) {
|
|
1165
|
+
dsn_action = 'delivered'
|
|
1119
1166
|
}
|
|
1120
|
-
|
|
1121
|
-
dsn_action = 'delivered';
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
else if (rcpt_to.dsn_smtp_code) {
|
|
1167
|
+
} else if (rcpt_to.dsn_smtp_code) {
|
|
1125
1168
|
if (/^5/.exec(rcpt_to.dsn_smtp_code)) {
|
|
1126
|
-
dsn_action = 'failed'
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
else if (/^2/.exec(rcpt_to.dsn_smtp_code)) {
|
|
1132
|
-
dsn_action = 'delivered';
|
|
1169
|
+
dsn_action = 'failed'
|
|
1170
|
+
} else if (/^4/.exec(rcpt_to.dsn_smtp_code)) {
|
|
1171
|
+
dsn_action = 'delayed'
|
|
1172
|
+
} else if (/^2/.exec(rcpt_to.dsn_smtp_code)) {
|
|
1173
|
+
dsn_action = 'delivered'
|
|
1133
1174
|
}
|
|
1134
1175
|
}
|
|
1135
1176
|
if (dsn_action != null) {
|
|
1136
|
-
bounce_body.push(`Action: ${dsn_action}${CRLF}`)
|
|
1177
|
+
bounce_body.push(`Action: ${dsn_action}${CRLF}`)
|
|
1137
1178
|
}
|
|
1138
1179
|
if (rcpt_to.dsn_status) {
|
|
1139
|
-
let { dsn_status } = rcpt_to
|
|
1180
|
+
let { dsn_status } = rcpt_to
|
|
1140
1181
|
if (rcpt_to.dsn_code || rcpt_to.dsn_msg) {
|
|
1141
|
-
dsn_status +=
|
|
1182
|
+
dsn_status += ' ('
|
|
1142
1183
|
if (rcpt_to.dsn_code) {
|
|
1143
|
-
dsn_status += rcpt_to.dsn_code
|
|
1184
|
+
dsn_status += rcpt_to.dsn_code
|
|
1144
1185
|
}
|
|
1145
1186
|
if (rcpt_to.dsn_code || rcpt_to.dsn_msg) {
|
|
1146
|
-
dsn_status +=
|
|
1187
|
+
dsn_status += ' '
|
|
1147
1188
|
}
|
|
1148
1189
|
if (rcpt_to.dsn_msg) {
|
|
1149
|
-
dsn_status += rcpt_to.dsn_msg
|
|
1190
|
+
dsn_status += rcpt_to.dsn_msg
|
|
1150
1191
|
}
|
|
1151
|
-
dsn_status +=
|
|
1192
|
+
dsn_status += ')'
|
|
1152
1193
|
}
|
|
1153
|
-
bounce_body.push(`Status: ${dsn_status}${CRLF}`)
|
|
1194
|
+
bounce_body.push(`Status: ${dsn_status}${CRLF}`)
|
|
1154
1195
|
}
|
|
1155
1196
|
if (rcpt_to.dsn_remote_mta) {
|
|
1156
|
-
bounce_body.push(`Remote-MTA: ${rcpt_to.dsn_remote_mta}${CRLF}`)
|
|
1197
|
+
bounce_body.push(`Remote-MTA: ${rcpt_to.dsn_remote_mta}${CRLF}`)
|
|
1157
1198
|
}
|
|
1158
|
-
let diag_code = null
|
|
1199
|
+
let diag_code = null
|
|
1159
1200
|
if (rcpt_to.dsn_smtp_code || rcpt_to.dsn_smtp_extc || rcpt_to.dsn_smtp_response) {
|
|
1160
|
-
diag_code =
|
|
1201
|
+
diag_code = 'smtp;'
|
|
1161
1202
|
if (rcpt_to.dsn_smtp_code) {
|
|
1162
|
-
diag_code += `${rcpt_to.dsn_smtp_code}
|
|
1203
|
+
diag_code += `${rcpt_to.dsn_smtp_code} `
|
|
1163
1204
|
}
|
|
1164
1205
|
if (rcpt_to.dsn_smtp_extc) {
|
|
1165
|
-
diag_code += `${rcpt_to.dsn_smtp_extc}
|
|
1206
|
+
diag_code += `${rcpt_to.dsn_smtp_extc} `
|
|
1166
1207
|
}
|
|
1167
1208
|
if (rcpt_to.dsn_smtp_response) {
|
|
1168
|
-
diag_code += `${rcpt_to.dsn_smtp_response}
|
|
1209
|
+
diag_code += `${rcpt_to.dsn_smtp_response} `
|
|
1169
1210
|
}
|
|
1170
1211
|
}
|
|
1171
1212
|
if (diag_code != null) {
|
|
1172
|
-
bounce_body.push(`Diagnostic-Code: ${diag_code}${CRLF}`)
|
|
1213
|
+
bounce_body.push(`Diagnostic-Code: ${diag_code}${CRLF}`)
|
|
1173
1214
|
}
|
|
1174
|
-
})
|
|
1175
|
-
bounce_body.push(CRLF)
|
|
1176
|
-
|
|
1177
|
-
bounce_body.push(`--${boundary}${CRLF}`)
|
|
1178
|
-
bounce_body.push(`Content-Description: Undelivered Message Headers${CRLF}`)
|
|
1179
|
-
bounce_body.push(`Content-Type: text/rfc822-headers${CRLF}`)
|
|
1180
|
-
bounce_body.push(CRLF)
|
|
1181
|
-
header.header_list.forEach(line => {
|
|
1182
|
-
bounce_body.push(line)
|
|
1183
|
-
})
|
|
1184
|
-
bounce_body.push(CRLF)
|
|
1215
|
+
})
|
|
1216
|
+
bounce_body.push(CRLF)
|
|
1217
|
+
|
|
1218
|
+
bounce_body.push(`--${boundary}${CRLF}`)
|
|
1219
|
+
bounce_body.push(`Content-Description: Undelivered Message Headers${CRLF}`)
|
|
1220
|
+
bounce_body.push(`Content-Type: text/rfc822-headers${CRLF}`)
|
|
1221
|
+
bounce_body.push(CRLF)
|
|
1222
|
+
header.header_list.forEach((line) => {
|
|
1223
|
+
bounce_body.push(line)
|
|
1224
|
+
})
|
|
1225
|
+
bounce_body.push(CRLF)
|
|
1185
1226
|
|
|
1186
|
-
bounce_body.push(`--${boundary}--${CRLF}`)
|
|
1227
|
+
bounce_body.push(`--${boundary}--${CRLF}`)
|
|
1187
1228
|
|
|
1188
|
-
cb(null, bounce_body)
|
|
1229
|
+
cb(null, bounce_body)
|
|
1189
1230
|
}
|
|
1190
1231
|
|
|
1191
|
-
bounce
|
|
1192
|
-
this.loginfo(`bouncing mail: ${err}`)
|
|
1232
|
+
bounce(err, opts) {
|
|
1233
|
+
this.loginfo(`bouncing mail: ${err}`)
|
|
1193
1234
|
if (!this.todo) {
|
|
1194
|
-
this.once('ready', () => {
|
|
1195
|
-
|
|
1235
|
+
this.once('ready', () => {
|
|
1236
|
+
this._bounce(err, opts)
|
|
1237
|
+
})
|
|
1238
|
+
return
|
|
1196
1239
|
}
|
|
1197
|
-
this._bounce(err, opts)
|
|
1240
|
+
this._bounce(err, opts)
|
|
1198
1241
|
}
|
|
1199
1242
|
|
|
1200
|
-
_bounce
|
|
1201
|
-
err = new Error(err)
|
|
1243
|
+
_bounce(err, opts) {
|
|
1244
|
+
err = new Error(err)
|
|
1202
1245
|
if (opts) {
|
|
1203
|
-
err.mx = opts.mx
|
|
1204
|
-
err.deferred_rcpt = opts.fail_recips
|
|
1205
|
-
err.bounced_rcpt = opts.bounce_recips
|
|
1246
|
+
err.mx = opts.mx
|
|
1247
|
+
err.deferred_rcpt = opts.fail_recips
|
|
1248
|
+
err.bounced_rcpt = opts.bounce_recips
|
|
1206
1249
|
}
|
|
1207
|
-
this.bounce_error = err
|
|
1208
|
-
plugins.run_hooks(
|
|
1250
|
+
this.bounce_error = err
|
|
1251
|
+
plugins.run_hooks('bounce', this, err)
|
|
1209
1252
|
}
|
|
1210
1253
|
|
|
1211
|
-
bounce_respond
|
|
1254
|
+
bounce_respond(retval, msg) {
|
|
1212
1255
|
if (retval !== constants.cont) {
|
|
1213
|
-
this.loginfo(`Plugin responded with: ${retval}. Not sending bounce.`)
|
|
1214
|
-
return this.discard()
|
|
1256
|
+
this.loginfo(`Plugin responded with: ${retval}. Not sending bounce.`)
|
|
1257
|
+
return this.discard() // calls next_cb
|
|
1215
1258
|
}
|
|
1216
1259
|
|
|
1217
|
-
const self = this
|
|
1218
|
-
const err
|
|
1260
|
+
const self = this
|
|
1261
|
+
const err = this.bounce_error
|
|
1219
1262
|
|
|
1220
1263
|
if (!this.todo.mail_from.user) {
|
|
1221
1264
|
// double bounce - mail was already a bounce
|
|
1222
|
-
return this.double_bounce(
|
|
1265
|
+
return this.double_bounce('Mail was already a bounce')
|
|
1223
1266
|
}
|
|
1224
1267
|
|
|
1225
|
-
const from = new Address
|
|
1226
|
-
const recip = new Address
|
|
1268
|
+
const from = new Address('<>')
|
|
1269
|
+
const recip = new Address(this.todo.mail_from.user, this.todo.mail_from.host)
|
|
1227
1270
|
this.populate_bounce_message(from, recip, err, function (err2, data_lines) {
|
|
1228
1271
|
if (err2) {
|
|
1229
|
-
return self.double_bounce(`Error populating bounce message: ${err2}`)
|
|
1272
|
+
return self.double_bounce(`Error populating bounce message: ${err2}`)
|
|
1230
1273
|
}
|
|
1231
1274
|
|
|
1232
|
-
outbound.send_email(
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1275
|
+
outbound.send_email(
|
|
1276
|
+
from,
|
|
1277
|
+
recip,
|
|
1278
|
+
data_lines.join(''),
|
|
1279
|
+
(code, msg2) => {
|
|
1280
|
+
if (code === constants.deny) {
|
|
1281
|
+
// failed to even queue the mail
|
|
1282
|
+
return self.double_bounce('Unable to queue the bounce message. Not sending bounce!')
|
|
1283
|
+
}
|
|
1284
|
+
self.discard()
|
|
1285
|
+
},
|
|
1286
|
+
{ origin: this },
|
|
1287
|
+
)
|
|
1288
|
+
})
|
|
1240
1289
|
}
|
|
1241
1290
|
|
|
1242
|
-
double_bounce
|
|
1243
|
-
this.lognotice(`Double bounce: ${err}`)
|
|
1244
|
-
fs.unlink(this.path, () => {})
|
|
1245
|
-
this.next_cb()
|
|
1291
|
+
double_bounce(err) {
|
|
1292
|
+
this.lognotice(`Double bounce: ${err}`)
|
|
1293
|
+
fs.unlink(this.path, () => {})
|
|
1294
|
+
this.next_cb()
|
|
1246
1295
|
// TODO: fill this in... ?
|
|
1247
1296
|
// One strategy is perhaps log to an mbox file. What do other servers do?
|
|
1248
1297
|
// Another strategy might be delivery "plugins" to cope with this.
|
|
1249
1298
|
}
|
|
1250
1299
|
|
|
1251
|
-
delivered
|
|
1252
|
-
const delay = (Date.now() - this.todo.queue_time)/1000
|
|
1300
|
+
delivered(ip, port, mode, host, response, ok_recips, fail_recips, bounce_recips, secured, authenticated) {
|
|
1301
|
+
const delay = (Date.now() - this.todo.queue_time) / 1000
|
|
1253
1302
|
this.lognotice({
|
|
1254
1303
|
'delivered file': this.filename,
|
|
1255
|
-
|
|
1304
|
+
domain: this.todo.domain,
|
|
1256
1305
|
host,
|
|
1257
1306
|
ip,
|
|
1258
1307
|
port,
|
|
1259
1308
|
mode,
|
|
1260
|
-
|
|
1261
|
-
|
|
1309
|
+
tls: secured ? 'Y' : 'N',
|
|
1310
|
+
auth: authenticated ? 'Y' : 'N',
|
|
1262
1311
|
response,
|
|
1263
1312
|
delay,
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
})
|
|
1267
|
-
plugins.run_hooks(
|
|
1313
|
+
fails: this.num_failures,
|
|
1314
|
+
rcpts: `${ok_recips.length}/${fail_recips.length}/${bounce_recips.length}`,
|
|
1315
|
+
})
|
|
1316
|
+
plugins.run_hooks('delivered', this, [host, ip, response, delay, port, mode, ok_recips, secured, authenticated])
|
|
1268
1317
|
}
|
|
1269
1318
|
|
|
1270
|
-
discard
|
|
1271
|
-
this.refcount
|
|
1319
|
+
discard() {
|
|
1320
|
+
this.refcount--
|
|
1272
1321
|
if (this.refcount === 0) {
|
|
1273
1322
|
// Remove the file.
|
|
1274
|
-
fs.unlink(this.path, () => {})
|
|
1275
|
-
this.next_cb()
|
|
1323
|
+
fs.unlink(this.path, () => {})
|
|
1324
|
+
this.next_cb()
|
|
1276
1325
|
}
|
|
1277
1326
|
}
|
|
1278
1327
|
|
|
1279
|
-
convert_temp_failed_to_bounce
|
|
1280
|
-
this.todo.rcpt_to.forEach(rcpt_to => {
|
|
1281
|
-
rcpt_to.dsn_action = 'failed'
|
|
1328
|
+
convert_temp_failed_to_bounce(err, extra) {
|
|
1329
|
+
this.todo.rcpt_to.forEach((rcpt_to) => {
|
|
1330
|
+
rcpt_to.dsn_action = 'failed'
|
|
1282
1331
|
if (rcpt_to.dsn_status) {
|
|
1283
|
-
rcpt_to.dsn_status =
|
|
1332
|
+
rcpt_to.dsn_status = `${rcpt_to.dsn_status}`.replace(/^4/, '5')
|
|
1284
1333
|
}
|
|
1285
|
-
})
|
|
1286
|
-
return this.bounce(err, extra)
|
|
1334
|
+
})
|
|
1335
|
+
return this.bounce(err, extra)
|
|
1287
1336
|
}
|
|
1288
1337
|
|
|
1289
|
-
temp_fail
|
|
1290
|
-
logger.debug(this, `Temp fail for: ${err}`)
|
|
1291
|
-
this.num_failures
|
|
1338
|
+
temp_fail(err, extra) {
|
|
1339
|
+
logger.debug(this, `Temp fail for: ${err}`)
|
|
1340
|
+
this.num_failures++
|
|
1292
1341
|
|
|
1293
1342
|
// Test for max failures which is configurable.
|
|
1294
|
-
if (this.num_failures >
|
|
1295
|
-
return this.convert_temp_failed_to_bounce(`Too many failures (${err})`, extra)
|
|
1343
|
+
if (this.num_failures > obc.cfg.temp_fail_intervals.length) {
|
|
1344
|
+
return this.convert_temp_failed_to_bounce(`Too many failures (${err})`, extra)
|
|
1296
1345
|
}
|
|
1297
1346
|
|
|
1298
|
-
const delay = obc.cfg.temp_fail_intervals[this.num_failures-1]
|
|
1347
|
+
const delay = obc.cfg.temp_fail_intervals[this.num_failures - 1]
|
|
1299
1348
|
|
|
1300
|
-
plugins.run_hooks('deferred', this, {delay, err})
|
|
1349
|
+
plugins.run_hooks('deferred', this, { delay, err, ...(extra || {}) })
|
|
1301
1350
|
}
|
|
1302
1351
|
|
|
1303
|
-
deferred_respond
|
|
1352
|
+
deferred_respond(retval, msg, params) {
|
|
1304
1353
|
if (retval !== constants.cont && retval !== constants.denysoft) {
|
|
1305
|
-
this.loginfo(`plugin responded with: ${retval}. Not deferring. Deleting mail.`)
|
|
1306
|
-
return this.discard()
|
|
1354
|
+
this.loginfo(`plugin responded with: ${retval}. Not deferring. Deleting mail.`)
|
|
1355
|
+
return this.discard() // calls next_cb
|
|
1307
1356
|
}
|
|
1308
1357
|
|
|
1309
|
-
let delay = params.delay * 1000
|
|
1358
|
+
let delay = params.delay * 1000
|
|
1310
1359
|
|
|
1311
1360
|
if (retval === constants.denysoft) {
|
|
1312
|
-
delay = parseInt(msg, 10) * 1000
|
|
1361
|
+
delay = parseInt(msg, 10) * 1000
|
|
1313
1362
|
}
|
|
1314
1363
|
|
|
1315
|
-
this.loginfo(`Temp failing ${this.filename} for ${delay/1000} seconds: ${params.err}`)
|
|
1316
|
-
const parts = _qfile.parts(this.filename)
|
|
1317
|
-
parts.next_attempt = Date.now() + delay
|
|
1318
|
-
parts.attempts = this.num_failures
|
|
1319
|
-
const new_filename = _qfile.name(parts)
|
|
1364
|
+
this.loginfo(`Temp failing ${this.filename} for ${delay / 1000} seconds: ${params.err}`)
|
|
1365
|
+
const parts = _qfile.parts(this.filename)
|
|
1366
|
+
parts.next_attempt = Date.now() + delay
|
|
1367
|
+
parts.attempts = this.num_failures
|
|
1368
|
+
const new_filename = _qfile.name(parts)
|
|
1320
1369
|
|
|
1321
|
-
fs.rename(this.path, path.join(queue_dir, new_filename), err => {
|
|
1370
|
+
fs.rename(this.path, path.join(queue_dir, new_filename), (err) => {
|
|
1322
1371
|
if (err) {
|
|
1323
|
-
return this.bounce(`Error re-queueing email: ${err}`)
|
|
1372
|
+
return this.bounce(`Error re-queueing email: ${err}`)
|
|
1324
1373
|
}
|
|
1325
1374
|
|
|
1326
|
-
this.path = path.join(queue_dir, new_filename)
|
|
1327
|
-
this.filename = new_filename
|
|
1375
|
+
this.path = path.join(queue_dir, new_filename)
|
|
1376
|
+
this.filename = new_filename
|
|
1328
1377
|
|
|
1329
|
-
this.next_cb()
|
|
1378
|
+
this.next_cb()
|
|
1330
1379
|
|
|
1331
|
-
temp_fail_queue.add(this.filename, delay, () => {
|
|
1332
|
-
|
|
1380
|
+
temp_fail_queue.add(this.filename, delay, () => {
|
|
1381
|
+
delivery_queue.push(this)
|
|
1382
|
+
})
|
|
1383
|
+
})
|
|
1333
1384
|
}
|
|
1334
1385
|
|
|
1335
1386
|
// The following handler impacts outgoing mail. It removes the queue file.
|
|
1336
|
-
delivered_respond
|
|
1387
|
+
delivered_respond(retval, msg) {
|
|
1337
1388
|
if (retval !== constants.cont && retval !== constants.ok) {
|
|
1338
|
-
this.logwarn(
|
|
1339
|
-
"delivered plugin responded",
|
|
1340
|
-
{ retval, msg }
|
|
1341
|
-
);
|
|
1389
|
+
this.logwarn('delivered plugin responded', { retval, msg })
|
|
1342
1390
|
}
|
|
1343
|
-
this.discard()
|
|
1391
|
+
this.discard()
|
|
1344
1392
|
}
|
|
1345
1393
|
|
|
1346
|
-
get_force_tls
|
|
1394
|
+
get_force_tls(mx) {
|
|
1347
1395
|
if (!mx.exchange) return false
|
|
1348
1396
|
if (!obtls.cfg.force_tls_hosts) return false
|
|
1349
1397
|
|
|
1350
1398
|
if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, mx.exchange)) {
|
|
1351
|
-
this.logdebug(`Forcing TLS for host ${mx.exchange}`)
|
|
1352
|
-
return true
|
|
1399
|
+
this.logdebug(`Forcing TLS for host ${mx.exchange}`)
|
|
1400
|
+
return true
|
|
1353
1401
|
}
|
|
1354
1402
|
|
|
1355
1403
|
if (mx.from_dns) {
|
|
1356
1404
|
// the MX was looked up in DNS and already resolved to IP(s).
|
|
1357
1405
|
// This checks the hostname.
|
|
1358
1406
|
if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, mx.from_dns)) {
|
|
1359
|
-
this.logdebug(`Forcing TLS for host ${mx.from_dns}`)
|
|
1360
|
-
return true
|
|
1407
|
+
this.logdebug(`Forcing TLS for host ${mx.from_dns}`)
|
|
1408
|
+
return true
|
|
1361
1409
|
}
|
|
1362
1410
|
}
|
|
1363
1411
|
|
|
1364
1412
|
if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, this.todo.domain)) {
|
|
1365
|
-
this.logdebug(`Forcing TLS for domain ${this.todo.domain}`)
|
|
1366
|
-
return true
|
|
1413
|
+
this.logdebug(`Forcing TLS for domain ${this.todo.domain}`)
|
|
1414
|
+
return true
|
|
1367
1415
|
}
|
|
1368
1416
|
|
|
1369
1417
|
return false
|
|
1370
1418
|
}
|
|
1371
1419
|
|
|
1372
|
-
sort_mx
|
|
1420
|
+
sort_mx(mx_list) {
|
|
1373
1421
|
// MXs must be sorted by priority.
|
|
1374
|
-
const sorted = mx_list.sort((a,b) => a.priority - b.priority)
|
|
1422
|
+
const sorted = mx_list.sort((a, b) => a.priority - b.priority)
|
|
1375
1423
|
|
|
1376
1424
|
// Matched priorities must be randomly shuffled.
|
|
1377
1425
|
// This isn't a very good shuffle but it'll do for now.
|
|
1378
|
-
for (let i=0,l=sorted.length-1; i<l; i++) {
|
|
1379
|
-
if (sorted[i].priority === sorted[i+1].priority) {
|
|
1380
|
-
if (Math.round(Math.random())) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
sorted[i
|
|
1426
|
+
for (let i = 0, l = sorted.length - 1; i < l; i++) {
|
|
1427
|
+
if (sorted[i].priority === sorted[i + 1].priority) {
|
|
1428
|
+
if (Math.round(Math.random())) {
|
|
1429
|
+
// 0 or 1
|
|
1430
|
+
const j = sorted[i]
|
|
1431
|
+
sorted[i] = sorted[i + 1]
|
|
1432
|
+
sorted[i + 1] = j
|
|
1384
1433
|
}
|
|
1385
1434
|
}
|
|
1386
1435
|
}
|
|
1387
1436
|
|
|
1388
|
-
return sorted
|
|
1437
|
+
return sorted
|
|
1389
1438
|
}
|
|
1390
1439
|
|
|
1391
|
-
split_to_new_recipients
|
|
1392
|
-
const hmail = this
|
|
1440
|
+
split_to_new_recipients(recipients, response, cb) {
|
|
1441
|
+
const hmail = this
|
|
1393
1442
|
if (recipients.length === hmail.todo.rcpt_to.length) {
|
|
1394
1443
|
// Split to new for no reason - increase refcount and return self
|
|
1395
|
-
hmail.refcount
|
|
1396
|
-
return cb(hmail)
|
|
1444
|
+
hmail.refcount++
|
|
1445
|
+
return cb(hmail)
|
|
1397
1446
|
}
|
|
1398
|
-
const fname = _qfile.name()
|
|
1399
|
-
const tmp_path = path.join(queue_dir, `${_qfile.platformDOT}${fname}`)
|
|
1400
|
-
const ws = new FsyncWriteStream(tmp_path, {
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1447
|
+
const fname = _qfile.name()
|
|
1448
|
+
const tmp_path = path.join(queue_dir, `${_qfile.platformDOT}${fname}`)
|
|
1449
|
+
const ws = new FsyncWriteStream(tmp_path, {
|
|
1450
|
+
flags: constants.WRITE_EXCL,
|
|
1451
|
+
})
|
|
1452
|
+
function err_handler(err, location) {
|
|
1453
|
+
logger.error(this, `Error while splitting to new recipients (${location}): ${err}`)
|
|
1454
|
+
hmail.todo.rcpt_to.forEach((rcpt) => {
|
|
1455
|
+
hmail.extend_rcpt_with_dsn(rcpt, DSN.sys_unspecified(`Error splitting to new recipients: ${err}`))
|
|
1456
|
+
})
|
|
1457
|
+
hmail.bounce(`Error splitting to new recipients: ${err}`)
|
|
1407
1458
|
}
|
|
1408
1459
|
|
|
1409
|
-
ws.on('error', err => {
|
|
1460
|
+
ws.on('error', (err) => {
|
|
1461
|
+
err_handler(err, 'tmp file writer')
|
|
1462
|
+
})
|
|
1410
1463
|
|
|
1411
|
-
let writing = false
|
|
1464
|
+
let writing = false
|
|
1412
1465
|
|
|
1413
|
-
function write_more
|
|
1414
|
-
if (writing) return
|
|
1415
|
-
writing = true
|
|
1416
|
-
const rs = hmail.data_stream()
|
|
1417
|
-
rs.pipe(ws, {end: false})
|
|
1418
|
-
rs.on('error', err => {
|
|
1419
|
-
err_handler(err,
|
|
1420
|
-
})
|
|
1466
|
+
function write_more() {
|
|
1467
|
+
if (writing) return
|
|
1468
|
+
writing = true
|
|
1469
|
+
const rs = hmail.data_stream()
|
|
1470
|
+
rs.pipe(ws, { end: false })
|
|
1471
|
+
rs.on('error', (err) => {
|
|
1472
|
+
err_handler(err, 'hmail.data_stream reader')
|
|
1473
|
+
})
|
|
1421
1474
|
rs.on('end', () => {
|
|
1422
1475
|
ws.on('close', () => {
|
|
1423
|
-
const dest_path = path.join(queue_dir, fname)
|
|
1424
|
-
fs.rename(tmp_path, dest_path, err => {
|
|
1476
|
+
const dest_path = path.join(queue_dir, fname)
|
|
1477
|
+
fs.rename(tmp_path, dest_path, (err) => {
|
|
1425
1478
|
if (err) {
|
|
1426
|
-
err_handler(err,
|
|
1479
|
+
err_handler(err, 'tmp file rename')
|
|
1427
1480
|
return
|
|
1428
1481
|
}
|
|
1429
|
-
const split_mail = new HMailItem
|
|
1482
|
+
const split_mail = new HMailItem(fname, dest_path, hmail.notes)
|
|
1430
1483
|
split_mail.once('ready', () => {
|
|
1431
|
-
cb(split_mail)
|
|
1432
|
-
})
|
|
1433
|
-
})
|
|
1434
|
-
})
|
|
1435
|
-
ws.destroySoon()
|
|
1436
|
-
})
|
|
1484
|
+
cb(split_mail)
|
|
1485
|
+
})
|
|
1486
|
+
})
|
|
1487
|
+
})
|
|
1488
|
+
ws.destroySoon()
|
|
1489
|
+
})
|
|
1437
1490
|
}
|
|
1438
1491
|
|
|
1439
|
-
ws.on('error', err => {
|
|
1440
|
-
logger.error(this, `Unable to write queue file (${fname}): ${err}`)
|
|
1441
|
-
ws.destroy()
|
|
1442
|
-
hmail.todo.rcpt_to.forEach(rcpt => {
|
|
1443
|
-
hmail.extend_rcpt_with_dsn(rcpt, DSN.sys_unspecified(`Error re-queueing some recipients: ${err}`))
|
|
1444
|
-
})
|
|
1445
|
-
hmail.bounce(`Error re-queueing some recipients: ${err}`)
|
|
1446
|
-
})
|
|
1447
|
-
|
|
1448
|
-
const new_todo = JSON.parse(JSON.stringify(hmail.todo))
|
|
1449
|
-
new_todo.rcpt_to = recipients
|
|
1450
|
-
outbound.build_todo(new_todo, ws, write_more)
|
|
1492
|
+
ws.on('error', (err) => {
|
|
1493
|
+
logger.error(this, `Unable to write queue file (${fname}): ${err}`)
|
|
1494
|
+
ws.destroy()
|
|
1495
|
+
hmail.todo.rcpt_to.forEach((rcpt) => {
|
|
1496
|
+
hmail.extend_rcpt_with_dsn(rcpt, DSN.sys_unspecified(`Error re-queueing some recipients: ${err}`))
|
|
1497
|
+
})
|
|
1498
|
+
hmail.bounce(`Error re-queueing some recipients: ${err}`)
|
|
1499
|
+
})
|
|
1500
|
+
|
|
1501
|
+
const new_todo = JSON.parse(JSON.stringify(hmail.todo))
|
|
1502
|
+
new_todo.rcpt_to = recipients
|
|
1503
|
+
outbound.build_todo(new_todo, ws, write_more)
|
|
1451
1504
|
}
|
|
1452
1505
|
}
|
|
1453
1506
|
|
|
1454
|
-
module.exports = HMailItem
|
|
1455
|
-
module.exports.obtls = obtls
|
|
1507
|
+
module.exports = HMailItem
|
|
1508
|
+
module.exports.obtls = obtls
|
|
1456
1509
|
|
|
1457
1510
|
logger.add_log_methods(HMailItem)
|
|
1458
1511
|
|
|
1459
|
-
const smtp_regexp = /^([2345]\d\d)([ -])#?(?:(\d\.\d\.\d)\s)?(.*)
|
|
1512
|
+
const smtp_regexp = /^([2345]\d\d)([ -])#?(?:(\d\.\d\.\d)\s)?(.*)/
|
|
1460
1513
|
|
|
1461
|
-
function cram_md5_response
|
|
1462
|
-
const crypto = require('crypto')
|
|
1463
|
-
const c = utils.unbase64(challenge)
|
|
1464
|
-
const hmac = crypto.createHmac('md5', password)
|
|
1465
|
-
hmac.update(c)
|
|
1466
|
-
const digest = hmac.digest('hex')
|
|
1467
|
-
return utils.base64(`${username} ${digest}`)
|
|
1514
|
+
function cram_md5_response(username, password, challenge) {
|
|
1515
|
+
const crypto = require('crypto')
|
|
1516
|
+
const c = utils.unbase64(challenge)
|
|
1517
|
+
const hmac = crypto.createHmac('md5', password)
|
|
1518
|
+
hmac.update(c)
|
|
1519
|
+
const digest = hmac.digest('hex')
|
|
1520
|
+
return utils.base64(`${username} ${digest}`)
|
|
1468
1521
|
}
|