Haraka 3.1.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +4 -0
- package/CONTRIBUTORS.md +5 -5
- package/Changes.md +62 -50
- package/Plugins.md +3 -1
- package/README.md +1 -1
- package/bin/haraka +475 -479
- package/config/outbound.ini +3 -0
- package/connection.js +1072 -1108
- package/docs/Connection.md +29 -30
- package/docs/CoreConfig.md +38 -39
- package/docs/CustomReturnCodes.md +0 -1
- package/docs/HAProxy.md +2 -2
- package/docs/Header.md +1 -1
- package/docs/Logging.md +29 -5
- package/docs/Outbound.md +93 -78
- package/docs/Plugins.md +103 -108
- package/docs/Transaction.md +49 -51
- package/docs/Tutorial.md +127 -143
- package/docs/deprecated/access.md +0 -1
- package/docs/deprecated/backscatterer.md +2 -3
- package/docs/deprecated/connect.rdns_access.md +18 -27
- package/docs/deprecated/data.headers.md +0 -1
- package/docs/deprecated/data.nomsgid.md +1 -2
- package/docs/deprecated/data.noreceived.md +1 -2
- package/docs/deprecated/data.rfc5322_header_checks.md +1 -2
- package/docs/deprecated/dkim_sign.md +13 -17
- package/docs/deprecated/dkim_verify.md +9 -17
- package/docs/deprecated/dnsbl.md +36 -38
- package/docs/deprecated/dnswl.md +41 -43
- package/docs/deprecated/lookup_rdns.strict.md +21 -34
- package/docs/deprecated/mail_from.access.md +17 -25
- package/docs/deprecated/mail_from.blocklist.md +9 -12
- package/docs/deprecated/mail_from.nobounces.md +1 -2
- package/docs/deprecated/rcpt_to.access.md +20 -27
- package/docs/deprecated/rcpt_to.blocklist.md +10 -13
- package/docs/deprecated/rcpt_to.routes.md +0 -1
- package/docs/deprecated/rdns.regexp.md +13 -15
- package/docs/plugins/aliases.md +89 -89
- package/docs/plugins/auth/auth_bridge.md +5 -7
- package/docs/plugins/auth/auth_ldap.md +11 -14
- package/docs/plugins/auth/auth_proxy.md +10 -12
- package/docs/plugins/auth/auth_vpopmaild.md +5 -6
- package/docs/plugins/auth/flat_file.md +4 -4
- package/docs/plugins/block_me.md +3 -3
- package/docs/plugins/data.signatures.md +1 -2
- package/docs/plugins/delay_deny.md +3 -4
- package/docs/plugins/max_unrecognized_commands.md +4 -4
- package/docs/plugins/prevent_credential_leaks.md +6 -6
- package/docs/plugins/process_title.md +18 -18
- package/docs/plugins/queue/deliver.md +2 -3
- package/docs/plugins/queue/discard.md +4 -4
- package/docs/plugins/queue/lmtp.md +1 -3
- package/docs/plugins/queue/qmail-queue.md +7 -9
- package/docs/plugins/queue/quarantine.md +16 -21
- package/docs/plugins/queue/rabbitmq.md +8 -11
- package/docs/plugins/queue/rabbitmq_amqplib.md +43 -39
- package/docs/plugins/queue/smtp_bridge.md +7 -10
- package/docs/plugins/queue/smtp_forward.md +42 -34
- package/docs/plugins/queue/smtp_proxy.md +30 -29
- package/docs/plugins/queue/test.md +1 -3
- package/docs/plugins/rcpt_to.in_host_list.md +6 -6
- package/docs/plugins/rcpt_to.max_count.md +1 -1
- package/docs/plugins/record_envelope_addresses.md +3 -3
- package/docs/plugins/reseed_rng.md +6 -6
- package/docs/plugins/status.md +9 -8
- package/docs/plugins/tarpit.md +7 -11
- package/docs/plugins/tls.md +12 -17
- package/docs/plugins/toobusy.md +4 -4
- package/docs/plugins/xclient.md +3 -3
- package/docs/tutorials/Migrating_from_v1_to_v2.md +19 -41
- package/docs/tutorials/SettingUpOutbound.md +6 -9
- package/endpoint.js +35 -38
- package/eslint.config.mjs +22 -19
- package/haraka.js +42 -47
- package/host_pool.js +75 -79
- package/http/html/404.html +45 -49
- package/http/html/index.html +39 -28
- package/http/package.json +2 -4
- package/line_socket.js +27 -28
- package/logger.js +182 -201
- package/outbound/client_pool.js +33 -33
- package/outbound/config.js +64 -59
- package/outbound/fsync_writestream.js +24 -25
- package/outbound/hmail.js +888 -835
- package/outbound/index.js +194 -187
- package/outbound/qfile.js +49 -52
- package/outbound/queue.js +197 -190
- package/outbound/timer_queue.js +41 -43
- package/outbound/tls.js +68 -61
- package/outbound/todo.js +11 -11
- package/package.json +32 -32
- package/plugins/.eslintrc.yaml +0 -1
- package/plugins/auth/auth_base.js +123 -127
- package/plugins/auth/auth_bridge.js +7 -7
- package/plugins/auth/auth_proxy.js +121 -126
- package/plugins/auth/auth_vpopmaild.js +84 -85
- package/plugins/auth/flat_file.js +18 -17
- package/plugins/block_me.js +31 -31
- package/plugins/data.signatures.js +13 -13
- package/plugins/delay_deny.js +65 -61
- package/plugins/prevent_credential_leaks.js +23 -23
- package/plugins/process_title.js +125 -128
- package/plugins/profile.js +5 -5
- package/plugins/queue/deliver.js +3 -3
- package/plugins/queue/discard.js +13 -14
- package/plugins/queue/lmtp.js +16 -17
- package/plugins/queue/qmail-queue.js +54 -55
- package/plugins/queue/quarantine.js +68 -70
- package/plugins/queue/rabbitmq.js +80 -87
- package/plugins/queue/rabbitmq_amqplib.js +75 -54
- package/plugins/queue/smtp_bridge.js +16 -16
- package/plugins/queue/smtp_forward.js +175 -179
- package/plugins/queue/smtp_proxy.js +69 -71
- package/plugins/queue/test.js +9 -9
- package/plugins/rcpt_to.host_list_base.js +30 -34
- package/plugins/rcpt_to.in_host_list.js +19 -19
- package/plugins/record_envelope_addresses.js +4 -4
- package/plugins/reseed_rng.js +4 -4
- package/plugins/status.js +90 -97
- package/plugins/tarpit.js +25 -14
- package/plugins/tls.js +68 -68
- package/plugins/toobusy.js +21 -23
- package/plugins/xclient.js +51 -53
- package/plugins.js +276 -293
- package/rfc1869.js +30 -35
- package/server.js +308 -299
- package/smtp_client.js +244 -228
- package/test/.eslintrc.yaml +0 -1
- package/test/connection.js +127 -134
- package/test/endpoint.js +53 -47
- package/test/fixtures/line_socket.js +12 -12
- package/test/fixtures/util_hmailitem.js +89 -85
- package/test/host_pool.js +90 -92
- package/test/installation/plugins/base_plugin.js +2 -2
- package/test/installation/plugins/folder_plugin/index.js +2 -3
- package/test/installation/plugins/inherits.js +3 -3
- package/test/installation/plugins/load_first.js +2 -3
- package/test/installation/plugins/plugin.js +1 -3
- package/test/installation/plugins/tls.js +2 -4
- package/test/logger.js +135 -116
- package/test/outbound/hmail.js +49 -35
- package/test/outbound/index.js +118 -101
- package/test/outbound/qfile.js +51 -53
- package/test/outbound_bounce_net_errors.js +84 -69
- package/test/outbound_bounce_rfc3464.js +235 -165
- package/test/plugins/auth/auth_base.js +420 -279
- package/test/plugins/auth/auth_vpopmaild.js +38 -39
- package/test/plugins/queue/smtp_forward.js +126 -104
- package/test/plugins/rcpt_to.host_list_base.js +85 -67
- package/test/plugins/rcpt_to.in_host_list.js +159 -112
- package/test/plugins/status.js +71 -64
- package/test/plugins/tls.js +37 -34
- package/test/plugins.js +97 -92
- package/test/rfc1869.js +19 -26
- package/test/server.js +293 -272
- package/test/smtp_client.js +180 -176
- package/test/tls_socket.js +62 -66
- package/test/transaction.js +159 -160
- package/tls_socket.js +331 -333
- package/transaction.js +129 -137
package/test/tls_socket.js
CHANGED
|
@@ -4,36 +4,36 @@ const path = require('node:path')
|
|
|
4
4
|
const os = require('node:os')
|
|
5
5
|
|
|
6
6
|
const _setup = (done) => {
|
|
7
|
-
this.socket = require('../tls_socket')
|
|
7
|
+
this.socket = require('../tls_socket')
|
|
8
8
|
|
|
9
9
|
// use test/config instead of ./config
|
|
10
|
-
this.socket.config = this.socket.config.module_config(path.resolve('test'))
|
|
11
|
-
done()
|
|
10
|
+
this.socket.config = this.socket.config.module_config(path.resolve('test'))
|
|
11
|
+
done()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
describe('tls_socket', () => {
|
|
15
15
|
beforeEach(_setup)
|
|
16
16
|
|
|
17
17
|
it('loads', () => {
|
|
18
|
-
assert.ok(this.socket)
|
|
18
|
+
assert.ok(this.socket)
|
|
19
19
|
})
|
|
20
20
|
it('exports createConnection', () => {
|
|
21
|
-
assert.equal(typeof this.socket.createConnection, 'function')
|
|
21
|
+
assert.equal(typeof this.socket.createConnection, 'function')
|
|
22
22
|
})
|
|
23
23
|
it('exports createServer', () => {
|
|
24
24
|
// console.log(this.socket);
|
|
25
|
-
assert.equal(typeof this.socket.createServer, 'function')
|
|
25
|
+
assert.equal(typeof this.socket.createServer, 'function')
|
|
26
26
|
})
|
|
27
27
|
it('exports shutdown', () => {
|
|
28
28
|
// console.log(this.socket);
|
|
29
|
-
assert.equal(typeof this.socket.shutdown, 'function')
|
|
29
|
+
assert.equal(typeof this.socket.shutdown, 'function')
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
describe('createServer', () => {
|
|
33
33
|
beforeEach(_setup)
|
|
34
34
|
|
|
35
35
|
it('returns a net.Server', () => {
|
|
36
|
-
const server = this.socket.createServer(sock => {
|
|
36
|
+
const server = this.socket.createServer((sock) => {
|
|
37
37
|
// TODO: socket test?
|
|
38
38
|
})
|
|
39
39
|
assert.ok(server)
|
|
@@ -44,8 +44,8 @@ describe('tls_socket', () => {
|
|
|
44
44
|
beforeEach(_setup)
|
|
45
45
|
|
|
46
46
|
it('tls.ini loads', () => {
|
|
47
|
-
assert.ok(this.socket.load_tls_ini().main !== undefined)
|
|
48
|
-
assert.ok(this.socket.certsByHost['*'].key)
|
|
47
|
+
assert.ok(this.socket.load_tls_ini().main !== undefined)
|
|
48
|
+
assert.ok(this.socket.certsByHost['*'].key)
|
|
49
49
|
// console.log(this.socket.cfg);
|
|
50
50
|
// console.log(this.socket.certsByHost);
|
|
51
51
|
})
|
|
@@ -55,10 +55,10 @@ describe('tls_socket', () => {
|
|
|
55
55
|
beforeEach(_setup)
|
|
56
56
|
|
|
57
57
|
it('loads certs from test/loud/config/tls', async () => {
|
|
58
|
-
this.socket.config = this.socket.config.module_config(path.resolve('test', 'loud'))
|
|
58
|
+
this.socket.config = this.socket.config.module_config(path.resolve('test', 'loud'))
|
|
59
59
|
this.socket.load_tls_ini()
|
|
60
60
|
const certs = await this.socket.get_certs_dir('tls')
|
|
61
|
-
assert.ok(certs)
|
|
61
|
+
assert.ok(certs)
|
|
62
62
|
})
|
|
63
63
|
})
|
|
64
64
|
|
|
@@ -66,7 +66,7 @@ describe('tls_socket', () => {
|
|
|
66
66
|
beforeEach(_setup)
|
|
67
67
|
|
|
68
68
|
it('loads certs from test/config/tls', async () => {
|
|
69
|
-
this.socket.config = this.socket.config.module_config(path.resolve('test'))
|
|
69
|
+
this.socket.config = this.socket.config.module_config(path.resolve('test'))
|
|
70
70
|
this.socket.load_tls_ini()
|
|
71
71
|
try {
|
|
72
72
|
const certs = await this.socket.get_certs_dir('tls')
|
|
@@ -74,9 +74,8 @@ describe('tls_socket', () => {
|
|
|
74
74
|
assert.ok(certs['mail.haraka.io'])
|
|
75
75
|
assert.ok(certs['haraka.local'])
|
|
76
76
|
assert.ok(certs['*.example.com'])
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
assert.ifError(err);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
assert.ifError(err)
|
|
80
79
|
}
|
|
81
80
|
})
|
|
82
81
|
})
|
|
@@ -86,10 +85,10 @@ describe('tls_socket', () => {
|
|
|
86
85
|
|
|
87
86
|
it('gets socket opts for *', async () => {
|
|
88
87
|
const certs = await this.socket.get_certs_dir('tls')
|
|
89
|
-
this.socket.getSocketOpts('*').then(opts => {
|
|
88
|
+
this.socket.getSocketOpts('*').then((opts) => {
|
|
90
89
|
// console.log(opts);
|
|
91
|
-
assert.ok(opts.key)
|
|
92
|
-
assert.ok(opts.cert)
|
|
90
|
+
assert.ok(opts.key)
|
|
91
|
+
assert.ok(opts.cert)
|
|
93
92
|
})
|
|
94
93
|
})
|
|
95
94
|
})
|
|
@@ -97,45 +96,44 @@ describe('tls_socket', () => {
|
|
|
97
96
|
describe('ensureDhparams', () => {
|
|
98
97
|
beforeEach(_setup)
|
|
99
98
|
it('generates a missing dhparams file', () => {
|
|
100
|
-
this.socket.load_tls_ini()
|
|
99
|
+
this.socket.load_tls_ini()
|
|
101
100
|
this.socket.ensureDhparams((err, dhparams) => {
|
|
102
101
|
// console.log(dhparams);
|
|
103
|
-
assert.ifError(err)
|
|
104
|
-
assert.ok(dhparams)
|
|
102
|
+
assert.ifError(err)
|
|
103
|
+
assert.ok(dhparams)
|
|
105
104
|
})
|
|
106
105
|
})
|
|
107
106
|
})
|
|
108
107
|
|
|
109
108
|
describe('load_tls_ini2', () => {
|
|
110
109
|
beforeEach((done) => {
|
|
111
|
-
this.socket = require('../tls_socket')
|
|
112
|
-
delete process.env.HARAKA_TEST_DIR
|
|
113
|
-
done()
|
|
110
|
+
this.socket = require('../tls_socket')
|
|
111
|
+
delete process.env.HARAKA_TEST_DIR
|
|
112
|
+
done()
|
|
114
113
|
})
|
|
115
114
|
|
|
116
115
|
it('loads missing tls.ini default config', () => {
|
|
117
|
-
this.socket.config = this.socket.config.module_config(path.resolve('non-exist'))
|
|
118
|
-
assert.deepEqual(this.socket.load_tls_ini(),
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
116
|
+
this.socket.config = this.socket.config.module_config(path.resolve('non-exist'))
|
|
117
|
+
assert.deepEqual(this.socket.load_tls_ini(), {
|
|
118
|
+
main: {
|
|
119
|
+
requestCert: true,
|
|
120
|
+
rejectUnauthorized: false,
|
|
121
|
+
honorCipherOrder: true,
|
|
122
|
+
requestOCSP: false,
|
|
123
|
+
// enableOCSPStapling: false,
|
|
124
|
+
requireAuthorized: [],
|
|
125
|
+
mutual_tls: false,
|
|
126
|
+
no_starttls_ports: [],
|
|
127
|
+
},
|
|
128
|
+
redis: { disable_for_failed_hosts: false },
|
|
129
|
+
no_tls_hosts: {},
|
|
130
|
+
mutual_auth_hosts: {},
|
|
131
|
+
mutual_auth_hosts_exclude: {},
|
|
132
|
+
})
|
|
135
133
|
})
|
|
136
134
|
|
|
137
135
|
it('loads tls.ini from test dir', () => {
|
|
138
|
-
this.socket.config = this.socket.config.module_config(path.resolve('test'))
|
|
136
|
+
this.socket.config = this.socket.config.module_config(path.resolve('test'))
|
|
139
137
|
assert.deepEqual(this.socket.load_tls_ini(), {
|
|
140
138
|
main: {
|
|
141
139
|
requestCert: true,
|
|
@@ -145,7 +143,8 @@ describe('tls_socket', () => {
|
|
|
145
143
|
key: 'tls_key.pem',
|
|
146
144
|
cert: 'tls_cert.pem',
|
|
147
145
|
dhparam: 'dhparams.pem',
|
|
148
|
-
ciphers:
|
|
146
|
+
ciphers:
|
|
147
|
+
'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384',
|
|
149
148
|
minVersion: 'TLSv1',
|
|
150
149
|
requireAuthorized: [2465, 2587],
|
|
151
150
|
mutual_tls: false,
|
|
@@ -169,7 +168,7 @@ describe('tls_socket', () => {
|
|
|
169
168
|
honorCipherOrder: false,
|
|
170
169
|
force_tls_hosts: ['first.example.com', 'second.example.net'],
|
|
171
170
|
no_tls_hosts: ['127.0.0.2', '192.168.31.1/24'],
|
|
172
|
-
}
|
|
171
|
+
},
|
|
173
172
|
})
|
|
174
173
|
})
|
|
175
174
|
})
|
|
@@ -179,25 +178,21 @@ describe('tls_socket', () => {
|
|
|
179
178
|
|
|
180
179
|
it('returns empty object on empty input', async () => {
|
|
181
180
|
const res = await this.socket.parse_x509()
|
|
182
|
-
assert.deepEqual(res, {})
|
|
181
|
+
assert.deepEqual(res, {})
|
|
183
182
|
})
|
|
184
183
|
|
|
185
184
|
it('returns key from BEGIN PRIVATE KEY block', async () => {
|
|
186
|
-
const res = await this.socket.parse_x509('-BEGIN PRIVATE KEY-\nhello\n--END PRIVATE KEY--\n-its me-\n')
|
|
187
|
-
assert.deepEqual(
|
|
188
|
-
|
|
189
|
-
'-BEGIN PRIVATE KEY-\nhello\n--END PRIVATE KEY--',
|
|
190
|
-
);
|
|
191
|
-
assert.deepEqual(res.cert, undefined);
|
|
185
|
+
const res = await this.socket.parse_x509('-BEGIN PRIVATE KEY-\nhello\n--END PRIVATE KEY--\n-its me-\n')
|
|
186
|
+
assert.deepEqual(res.keys[0].toString(), '-BEGIN PRIVATE KEY-\nhello\n--END PRIVATE KEY--')
|
|
187
|
+
assert.deepEqual(res.cert, undefined)
|
|
192
188
|
})
|
|
193
189
|
|
|
194
190
|
it('returns key from BEGIN RSA PRIVATE KEY block', async () => {
|
|
195
|
-
const res = await this.socket.parse_x509(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
200
|
-
assert.deepEqual(res.cert, undefined);
|
|
191
|
+
const res = await this.socket.parse_x509(
|
|
192
|
+
'-BEGIN RSA PRIVATE KEY-\nhello\n--END RSA PRIVATE KEY--\n-its me-\n',
|
|
193
|
+
)
|
|
194
|
+
assert.deepEqual(res.keys[0].toString(), '-BEGIN RSA PRIVATE KEY-\nhello\n--END RSA PRIVATE KEY--')
|
|
195
|
+
assert.deepEqual(res.cert, undefined)
|
|
201
196
|
})
|
|
202
197
|
|
|
203
198
|
it.skip('returns a key and certificate chain', async () => {
|
|
@@ -239,13 +234,13 @@ WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
|
|
|
239
234
|
he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
|
|
240
235
|
Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
|
|
241
236
|
-----END CERTIFICATE-----`
|
|
242
|
-
const res = await this.socket.parse_x509(str)
|
|
243
|
-
assert.deepEqual(res.key.length, 446)
|
|
244
|
-
assert.deepEqual(res.cert.length, 1195)
|
|
237
|
+
const res = await this.socket.parse_x509(str)
|
|
238
|
+
assert.deepEqual(res.key.length, 446)
|
|
239
|
+
assert.deepEqual(res.cert.length, 1195)
|
|
245
240
|
})
|
|
246
241
|
|
|
247
242
|
it('returns cert and key from EC pem', async () => {
|
|
248
|
-
const fp = await fs.readFile(path.join('test','config','tls','ec.pem'))
|
|
243
|
+
const fp = await fs.readFile(path.join('test', 'config', 'tls', 'ec.pem'))
|
|
249
244
|
const res = await this.socket.parse_x509(fp.toString())
|
|
250
245
|
assert.deepEqual(
|
|
251
246
|
res.keys[0].toString().split(os.EOL).join('\n'),
|
|
@@ -253,8 +248,8 @@ Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
|
|
|
253
248
|
MHcCAQEEIIDhiI5q6l7txfMJ6kIEYjK12EFcHLvDIkfWIwzdZBsloAoGCCqGSM49
|
|
254
249
|
AwEHoUQDQgAEZg2nHEFy9nquFPF3DQyQE28e/ytjXeb4nD/8U+L4KHKFtglaX3R4
|
|
255
250
|
uZ+5JcwfcDghpL4Z8h4ouUD/xqe957e2+g==
|
|
256
|
-
-----END EC PRIVATE KEY
|
|
257
|
-
)
|
|
251
|
+
-----END EC PRIVATE KEY-----`,
|
|
252
|
+
)
|
|
258
253
|
assert.deepEqual(
|
|
259
254
|
res.chain[0].toString().split(os.EOL).join('\n'),
|
|
260
255
|
`-----BEGIN CERTIFICATE-----
|
|
@@ -271,7 +266,8 @@ OCGkvhnyHii5QP/Gp73nt7b6o1MwUTAdBgNVHQ4EFgQU094ROMLHmLEspT4ZoCfX
|
|
|
271
266
|
Rz0mR/YwHwYDVR0jBBgwFoAU094ROMLHmLEspT4ZoCfXRz0mR/YwDwYDVR0TAQH/
|
|
272
267
|
BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAsmshzvMDjmYDHyGRrKdMmsnnESFd
|
|
273
268
|
GMtfRXYIv0AZe7ICIGD2Sta9LL0zZ44ARGXhh+sPjxd78I/+0FdIPsofr2I+
|
|
274
|
-
-----END CERTIFICATE
|
|
269
|
+
-----END CERTIFICATE-----`,
|
|
270
|
+
)
|
|
275
271
|
})
|
|
276
272
|
})
|
|
277
273
|
})
|