Commit
+633 -80 +/-13 browse
1 | diff --git a/Cargo.lock b/Cargo.lock |
2 | index 25f2088..704ce6f 100644 |
3 | --- a/Cargo.lock |
4 | +++ b/Cargo.lock |
5 | @@ -48,6 +48,15 @@ dependencies = [ |
6 | ] |
7 | |
8 | [[package]] |
9 | + name = "aho-corasick" |
10 | + version = "1.1.3" |
11 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
12 | + checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" |
13 | + dependencies = [ |
14 | + "memchr", |
15 | + ] |
16 | + |
17 | + [[package]] |
18 | name = "allocator-api2" |
19 | version = "0.2.18" |
20 | source = "registry+https://github.com/rust-lang/crates.io-index" |
21 | @@ -129,6 +138,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
22 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" |
23 | |
24 | [[package]] |
25 | + name = "aws-lc-rs" |
26 | + version = "1.9.0" |
27 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
28 | + checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" |
29 | + dependencies = [ |
30 | + "aws-lc-sys", |
31 | + "mirai-annotations", |
32 | + "paste", |
33 | + "zeroize", |
34 | + ] |
35 | + |
36 | + [[package]] |
37 | + name = "aws-lc-sys" |
38 | + version = "0.21.1" |
39 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
40 | + checksum = "234314bd569802ec87011d653d6815c6d7b9ffb969e9fee5b8b20ef860e8dce9" |
41 | + dependencies = [ |
42 | + "bindgen", |
43 | + "cc", |
44 | + "cmake", |
45 | + "dunce", |
46 | + "fs_extra", |
47 | + "libc", |
48 | + "paste", |
49 | + ] |
50 | + |
51 | + [[package]] |
52 | name = "backtrace" |
53 | version = "0.3.73" |
54 | source = "registry+https://github.com/rust-lang/crates.io-index" |
55 | @@ -156,6 +192,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
56 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" |
57 | |
58 | [[package]] |
59 | + name = "bindgen" |
60 | + version = "0.69.4" |
61 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
62 | + checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" |
63 | + dependencies = [ |
64 | + "bitflags", |
65 | + "cexpr", |
66 | + "clang-sys", |
67 | + "itertools", |
68 | + "lazy_static", |
69 | + "lazycell", |
70 | + "log", |
71 | + "prettyplease", |
72 | + "proc-macro2", |
73 | + "quote", |
74 | + "regex", |
75 | + "rustc-hash", |
76 | + "shlex", |
77 | + "syn", |
78 | + "which", |
79 | + ] |
80 | + |
81 | + [[package]] |
82 | name = "bitflags" |
83 | version = "2.6.0" |
84 | source = "registry+https://github.com/rust-lang/crates.io-index" |
85 | @@ -220,6 +279,15 @@ dependencies = [ |
86 | ] |
87 | |
88 | [[package]] |
89 | + name = "cexpr" |
90 | + version = "0.6.0" |
91 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
92 | + checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" |
93 | + dependencies = [ |
94 | + "nom", |
95 | + ] |
96 | + |
97 | + [[package]] |
98 | name = "cfg-if" |
99 | version = "1.0.0" |
100 | source = "registry+https://github.com/rust-lang/crates.io-index" |
101 | @@ -246,6 +314,17 @@ dependencies = [ |
102 | ] |
103 | |
104 | [[package]] |
105 | + name = "clang-sys" |
106 | + version = "1.8.1" |
107 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
108 | + checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" |
109 | + dependencies = [ |
110 | + "glob", |
111 | + "libc", |
112 | + "libloading", |
113 | + ] |
114 | + |
115 | + [[package]] |
116 | name = "clap" |
117 | version = "4.5.16" |
118 | source = "registry+https://github.com/rust-lang/crates.io-index" |
119 | @@ -286,6 +365,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
120 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" |
121 | |
122 | [[package]] |
123 | + name = "cmake" |
124 | + version = "0.1.51" |
125 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
126 | + checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" |
127 | + dependencies = [ |
128 | + "cc", |
129 | + ] |
130 | + |
131 | + [[package]] |
132 | name = "colorchoice" |
133 | version = "1.0.2" |
134 | source = "registry+https://github.com/rust-lang/crates.io-index" |
135 | @@ -420,6 +508,18 @@ dependencies = [ |
136 | ] |
137 | |
138 | [[package]] |
139 | + name = "dunce" |
140 | + version = "1.0.5" |
141 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
142 | + checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" |
143 | + |
144 | + [[package]] |
145 | + name = "either" |
146 | + version = "1.13.0" |
147 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
148 | + checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" |
149 | + |
150 | + [[package]] |
151 | name = "email_address" |
152 | version = "0.2.9" |
153 | source = "registry+https://github.com/rust-lang/crates.io-index" |
154 | @@ -456,6 +556,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
155 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" |
156 | |
157 | [[package]] |
158 | + name = "errno" |
159 | + version = "0.3.9" |
160 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
161 | + checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" |
162 | + dependencies = [ |
163 | + "libc", |
164 | + "windows-sys 0.52.0", |
165 | + ] |
166 | + |
167 | + [[package]] |
168 | name = "flate2" |
169 | version = "1.0.33" |
170 | source = "registry+https://github.com/rust-lang/crates.io-index" |
171 | @@ -475,6 +585,12 @@ dependencies = [ |
172 | ] |
173 | |
174 | [[package]] |
175 | + name = "fs_extra" |
176 | + version = "1.3.0" |
177 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
178 | + checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" |
179 | + |
180 | + [[package]] |
181 | name = "futures" |
182 | version = "0.3.30" |
183 | source = "registry+https://github.com/rust-lang/crates.io-index" |
184 | @@ -611,6 +727,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
185 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" |
186 | |
187 | [[package]] |
188 | + name = "glob" |
189 | + version = "0.3.1" |
190 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
191 | + checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" |
192 | + |
193 | + [[package]] |
194 | name = "hashbrown" |
195 | version = "0.14.5" |
196 | source = "registry+https://github.com/rust-lang/crates.io-index" |
197 | @@ -656,12 +778,12 @@ dependencies = [ |
198 | "once_cell", |
199 | "rand", |
200 | "ring 0.16.20", |
201 | - "rustls", |
202 | + "rustls 0.21.12", |
203 | "rustls-pemfile 1.0.4", |
204 | "thiserror", |
205 | "tinyvec", |
206 | "tokio", |
207 | - "tokio-rustls", |
208 | + "tokio-rustls 0.24.1", |
209 | "tracing", |
210 | "url", |
211 | ] |
212 | @@ -681,11 +803,11 @@ dependencies = [ |
213 | "parking_lot", |
214 | "rand", |
215 | "resolv-conf", |
216 | - "rustls", |
217 | + "rustls 0.21.12", |
218 | "smallvec", |
219 | "thiserror", |
220 | "tokio", |
221 | - "tokio-rustls", |
222 | + "tokio-rustls 0.24.1", |
223 | "tracing", |
224 | ] |
225 | |
226 | @@ -699,6 +821,15 @@ dependencies = [ |
227 | ] |
228 | |
229 | [[package]] |
230 | + name = "home" |
231 | + version = "0.5.9" |
232 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
233 | + checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" |
234 | + dependencies = [ |
235 | + "windows-sys 0.52.0", |
236 | + ] |
237 | + |
238 | + [[package]] |
239 | name = "hostname" |
240 | version = "0.3.1" |
241 | source = "registry+https://github.com/rust-lang/crates.io-index" |
242 | @@ -773,6 +904,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
243 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" |
244 | |
245 | [[package]] |
246 | + name = "itertools" |
247 | + version = "0.12.1" |
248 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
249 | + checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" |
250 | + dependencies = [ |
251 | + "either", |
252 | + ] |
253 | + |
254 | + [[package]] |
255 | name = "itoa" |
256 | version = "1.0.11" |
257 | source = "registry+https://github.com/rust-lang/crates.io-index" |
258 | @@ -803,18 +943,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
259 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" |
260 | |
261 | [[package]] |
262 | + name = "lazycell" |
263 | + version = "1.3.0" |
264 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
265 | + checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" |
266 | + |
267 | + [[package]] |
268 | name = "libc" |
269 | version = "0.2.155" |
270 | source = "registry+https://github.com/rust-lang/crates.io-index" |
271 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" |
272 | |
273 | [[package]] |
274 | + name = "libloading" |
275 | + version = "0.8.5" |
276 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
277 | + checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" |
278 | + dependencies = [ |
279 | + "cfg-if", |
280 | + "windows-targets 0.52.6", |
281 | + ] |
282 | + |
283 | + [[package]] |
284 | name = "linked-hash-map" |
285 | version = "0.5.6" |
286 | source = "registry+https://github.com/rust-lang/crates.io-index" |
287 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" |
288 | |
289 | [[package]] |
290 | + name = "linux-raw-sys" |
291 | + version = "0.4.14" |
292 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
293 | + checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" |
294 | + |
295 | + [[package]] |
296 | name = "lock_api" |
297 | version = "0.4.12" |
298 | source = "registry+https://github.com/rust-lang/crates.io-index" |
299 | @@ -931,10 +1093,13 @@ dependencies = [ |
300 | "mail-parser", |
301 | "maildir", |
302 | "md5", |
303 | + "rustls 0.23.13", |
304 | + "rustls-pemfile 2.1.3", |
305 | "smtp-proto", |
306 | "stringprep", |
307 | "thiserror", |
308 | "tokio", |
309 | + "tokio-rustls 0.26.0", |
310 | "tokio-stream", |
311 | "tokio-util", |
312 | "tracing", |
313 | @@ -975,6 +1140,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
314 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" |
315 | |
316 | [[package]] |
317 | + name = "minimal-lexical" |
318 | + version = "0.2.1" |
319 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
320 | + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" |
321 | + |
322 | + [[package]] |
323 | name = "miniz_oxide" |
324 | version = "0.7.4" |
325 | source = "registry+https://github.com/rust-lang/crates.io-index" |
326 | @@ -1005,6 +1176,22 @@ dependencies = [ |
327 | ] |
328 | |
329 | [[package]] |
330 | + name = "mirai-annotations" |
331 | + version = "1.12.0" |
332 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
333 | + checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" |
334 | + |
335 | + [[package]] |
336 | + name = "nom" |
337 | + version = "7.1.3" |
338 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
339 | + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" |
340 | + dependencies = [ |
341 | + "memchr", |
342 | + "minimal-lexical", |
343 | + ] |
344 | + |
345 | + [[package]] |
346 | name = "nu-ansi-term" |
347 | version = "0.46.0" |
348 | source = "registry+https://github.com/rust-lang/crates.io-index" |
349 | @@ -1065,6 +1252,12 @@ dependencies = [ |
350 | ] |
351 | |
352 | [[package]] |
353 | + name = "paste" |
354 | + version = "1.0.15" |
355 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
356 | + checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" |
357 | + |
358 | + [[package]] |
359 | name = "pbkdf2" |
360 | version = "0.12.2" |
361 | source = "registry+https://github.com/rust-lang/crates.io-index" |
362 | @@ -1114,6 +1307,16 @@ dependencies = [ |
363 | ] |
364 | |
365 | [[package]] |
366 | + name = "prettyplease" |
367 | + version = "0.2.22" |
368 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
369 | + checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" |
370 | + dependencies = [ |
371 | + "proc-macro2", |
372 | + "syn", |
373 | + ] |
374 | + |
375 | + [[package]] |
376 | name = "proc-macro2" |
377 | version = "1.0.86" |
378 | source = "registry+https://github.com/rust-lang/crates.io-index" |
379 | @@ -1192,6 +1395,35 @@ dependencies = [ |
380 | ] |
381 | |
382 | [[package]] |
383 | + name = "regex" |
384 | + version = "1.10.6" |
385 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
386 | + checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" |
387 | + dependencies = [ |
388 | + "aho-corasick", |
389 | + "memchr", |
390 | + "regex-automata", |
391 | + "regex-syntax", |
392 | + ] |
393 | + |
394 | + [[package]] |
395 | + name = "regex-automata" |
396 | + version = "0.4.7" |
397 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
398 | + checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" |
399 | + dependencies = [ |
400 | + "aho-corasick", |
401 | + "memchr", |
402 | + "regex-syntax", |
403 | + ] |
404 | + |
405 | + [[package]] |
406 | + name = "regex-syntax" |
407 | + version = "0.8.4" |
408 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
409 | + checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" |
410 | + |
411 | + [[package]] |
412 | name = "resolv-conf" |
413 | version = "0.7.0" |
414 | source = "registry+https://github.com/rust-lang/crates.io-index" |
415 | @@ -1238,6 +1470,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
416 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" |
417 | |
418 | [[package]] |
419 | + name = "rustc-hash" |
420 | + version = "1.1.0" |
421 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
422 | + checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" |
423 | + |
424 | + [[package]] |
425 | + name = "rustix" |
426 | + version = "0.38.34" |
427 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
428 | + checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" |
429 | + dependencies = [ |
430 | + "bitflags", |
431 | + "errno", |
432 | + "libc", |
433 | + "linux-raw-sys", |
434 | + "windows-sys 0.52.0", |
435 | + ] |
436 | + |
437 | + [[package]] |
438 | name = "rustls" |
439 | version = "0.21.12" |
440 | source = "registry+https://github.com/rust-lang/crates.io-index" |
441 | @@ -1245,11 +1496,26 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" |
442 | dependencies = [ |
443 | "log", |
444 | "ring 0.17.8", |
445 | - "rustls-webpki", |
446 | + "rustls-webpki 0.101.7", |
447 | "sct", |
448 | ] |
449 | |
450 | [[package]] |
451 | + name = "rustls" |
452 | + version = "0.23.13" |
453 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
454 | + checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" |
455 | + dependencies = [ |
456 | + "aws-lc-rs", |
457 | + "log", |
458 | + "once_cell", |
459 | + "rustls-pki-types", |
460 | + "rustls-webpki 0.102.8", |
461 | + "subtle", |
462 | + "zeroize", |
463 | + ] |
464 | + |
465 | + [[package]] |
466 | name = "rustls-pemfile" |
467 | version = "1.0.4" |
468 | source = "registry+https://github.com/rust-lang/crates.io-index" |
469 | @@ -1285,6 +1551,18 @@ dependencies = [ |
470 | ] |
471 | |
472 | [[package]] |
473 | + name = "rustls-webpki" |
474 | + version = "0.102.8" |
475 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
476 | + checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" |
477 | + dependencies = [ |
478 | + "aws-lc-rs", |
479 | + "ring 0.17.8", |
480 | + "rustls-pki-types", |
481 | + "untrusted 0.9.0", |
482 | + ] |
483 | + |
484 | + [[package]] |
485 | name = "ryu" |
486 | version = "1.0.18" |
487 | source = "registry+https://github.com/rust-lang/crates.io-index" |
488 | @@ -1368,6 +1646,12 @@ dependencies = [ |
489 | ] |
490 | |
491 | [[package]] |
492 | + name = "shlex" |
493 | + version = "1.3.0" |
494 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
495 | + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" |
496 | + |
497 | + [[package]] |
498 | name = "signal-hook-registry" |
499 | version = "1.4.2" |
500 | source = "registry+https://github.com/rust-lang/crates.io-index" |
501 | @@ -1453,9 +1737,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" |
502 | |
503 | [[package]] |
504 | name = "syn" |
505 | - version = "2.0.72" |
506 | + version = "2.0.77" |
507 | source = "registry+https://github.com/rust-lang/crates.io-index" |
508 | - checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" |
509 | + checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" |
510 | dependencies = [ |
511 | "proc-macro2", |
512 | "quote", |
513 | @@ -1561,7 +1845,18 @@ version = "0.24.1" |
514 | source = "registry+https://github.com/rust-lang/crates.io-index" |
515 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" |
516 | dependencies = [ |
517 | - "rustls", |
518 | + "rustls 0.21.12", |
519 | + "tokio", |
520 | + ] |
521 | + |
522 | + [[package]] |
523 | + name = "tokio-rustls" |
524 | + version = "0.26.0" |
525 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
526 | + checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" |
527 | + dependencies = [ |
528 | + "rustls 0.23.13", |
529 | + "rustls-pki-types", |
530 | "tokio", |
531 | ] |
532 | |
533 | @@ -1832,6 +2127,18 @@ dependencies = [ |
534 | ] |
535 | |
536 | [[package]] |
537 | + name = "which" |
538 | + version = "4.4.2" |
539 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
540 | + checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" |
541 | + dependencies = [ |
542 | + "either", |
543 | + "home", |
544 | + "once_cell", |
545 | + "rustix", |
546 | + ] |
547 | + |
548 | + [[package]] |
549 | name = "widestring" |
550 | version = "1.1.0" |
551 | source = "registry+https://github.com/rust-lang/crates.io-index" |
552 | diff --git a/Containerfile b/Containerfile |
553 | index 6388596..654a494 100644 |
554 | --- a/Containerfile |
555 | +++ b/Containerfile |
556 | @@ -1,12 +1,14 @@ |
557 | FROM alpine:3 AS build |
558 | |
559 | - RUN apk add cargo rust |
560 | + RUN apk add build-base cargo cmake clang17 clang17-libs clang17-libclang rust |
561 | + |
562 | + RUN cargo install --force --locked bindgen-cli |
563 | |
564 | WORKDIR /src |
565 | |
566 | COPY . /src |
567 | |
568 | - RUN cargo test && cargo build --release |
569 | + RUN export PATH="/root/.cargo/bin:$PATH" && cargo test && cargo build --release |
570 | |
571 | FROM alpine:3 |
572 | |
573 | diff --git a/cmd/maitred-debug/src/config.rs b/cmd/maitred-debug/src/config.rs |
574 | index af71bb7..5abb83d 100644 |
575 | --- a/cmd/maitred-debug/src/config.rs |
576 | +++ b/cmd/maitred-debug/src/config.rs |
577 | @@ -1,3 +1,5 @@ |
578 | + use std::path::PathBuf; |
579 | + |
580 | #[derive(Clone, serde::Deserialize)] |
581 | pub(crate) struct Account { |
582 | pub address: String, |
583 | @@ -14,10 +16,17 @@ pub(crate) struct Dkim { |
584 | pub enabled: bool |
585 | } |
586 | |
587 | + #[derive(Clone, serde::Deserialize)] |
588 | + pub(crate) struct Tls { |
589 | + pub certificate: PathBuf, |
590 | + pub key: PathBuf |
591 | + } |
592 | + |
593 | #[derive(serde::Deserialize)] |
594 | pub(crate) struct Config { |
595 | pub maildir: String, |
596 | pub spf: Spf, |
597 | pub dkim: Dkim, |
598 | pub accounts: Vec<Account>, |
599 | + pub tls: Option<Tls> |
600 | } |
601 | diff --git a/cmd/maitred-debug/src/main.rs b/cmd/maitred-debug/src/main.rs |
602 | index 1429883..8bd1a76 100644 |
603 | --- a/cmd/maitred-debug/src/main.rs |
604 | +++ b/cmd/maitred-debug/src/main.rs |
605 | @@ -20,7 +20,6 @@ async fn print_message(envelope: &Envelope) -> Result<(), DeliveryError> { |
606 | Ok(()) |
607 | } |
608 | |
609 | - |
610 | const LONG_ABOUT: &str = r#" |
611 | Maitred SMTP Demo Server |
612 | |
613 | @@ -57,6 +56,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { |
614 | // initialize maildirs before starting |
615 | let _ = Maildir::new(maildir_path.as_path(), &addresses)?; |
616 | // Set the subscriber as the default subscriber |
617 | + let mut session_opts = SessionOptions::default().plain_auth(PlainAuthFunc( |
618 | + |authcid: &str, authzid: &str, _passwd: &str| { |
619 | + println!("AUTHCID: {}, AUTHZID: {}", authcid, authzid); |
620 | + async move { Ok(()) } |
621 | + }, |
622 | + )); |
623 | let mut mail_server = Server::default() |
624 | .address("127.0.0.1:2525") |
625 | .with_milter(MilterFunc(|message: &Message<'static>| { |
626 | @@ -70,20 +75,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { |
627 | async move { |
628 | print_message(&cloned).await?; |
629 | let maildir = Maildir::new(maildir_path.as_path(), &addresses)?; |
630 | - maildir.deliver(&cloned).await |
631 | + maildir.deliver(&cloned).await?; |
632 | + Ok(()) |
633 | } |
634 | })) |
635 | .dkim_verification(config.dkim.enabled) |
636 | - .spf_verification(config.spf.enabled) |
637 | - .with_session_opts(SessionOptions::default().plain_auth(PlainAuthFunc( |
638 | - |authcid: &str, authzid: &str, passwd: &str| { |
639 | - println!( |
640 | - "AUTHCID: {}, AUTHZID: {}, PASSWD: {}", |
641 | - authcid, authzid, passwd |
642 | - ); |
643 | - async move { Ok(()) } |
644 | - }, |
645 | - ))); |
646 | + .spf_verification(config.spf.enabled); |
647 | + |
648 | + if let Some(tls_config) = config.tls { |
649 | + tracing::info!("TLS enabled"); |
650 | + mail_server = mail_server.with_certificates(&tls_config.key, &tls_config.certificate); |
651 | + session_opts = session_opts.starttls_enabled(true); |
652 | + } |
653 | + |
654 | + mail_server = mail_server.with_session_opts(session_opts); |
655 | mail_server.listen().await?; |
656 | Ok(()) |
657 | } |
658 | diff --git a/contrib/nginx-proxy/README.md b/contrib/nginx-proxy/README.md |
659 | new file mode 100644 |
660 | index 0000000..d557bc9 |
661 | --- /dev/null |
662 | +++ b/contrib/nginx-proxy/README.md |
663 | @@ -0,0 +1,11 @@ |
664 | + # Nginx-proxy |
665 | + |
666 | + Example Nginx configuration and simple authentication server illistrating how |
667 | + Maitred can be used with Nginx. |
668 | + |
669 | + ### Usage |
670 | + |
671 | + ``` |
672 | + python auth_http.py |
673 | + nginx -p . -c nginx.conf |
674 | + ``` |
675 | diff --git a/contrib/nginx-proxy/auth_http.py b/contrib/nginx-proxy/auth_http.py |
676 | new file mode 100644 |
677 | index 0000000..24f37d2 |
678 | --- /dev/null |
679 | +++ b/contrib/nginx-proxy/auth_http.py |
680 | @@ -0,0 +1,20 @@ |
681 | + import socketserver |
682 | + |
683 | + from http import server |
684 | + |
685 | + |
686 | + class HTTPRequestHandler(server.SimpleHTTPRequestHandler): |
687 | + def end_headers(self): |
688 | + print(self.headers) |
689 | + rcpt_to_header = self.headers['Auth-SMTP-To'] |
690 | + rcpt_to = rcpt_to_header.split(":")[1][2:-1] |
691 | + self.send_header("Auth-Status", "OK") |
692 | + self.send_header("Auth-Server","127.0.0.1") |
693 | + self.send_header("Auth-Port", "2525") |
694 | + server.SimpleHTTPRequestHandler.end_headers(self) |
695 | + |
696 | + |
697 | + if __name__ == '__main__': |
698 | + with socketserver.TCPServer(("127.0.0.1", 30000), HTTPRequestHandler) as httpd: |
699 | + print("Listening @ 127.0.0.1:30000") |
700 | + httpd.serve_forever() |
701 | diff --git a/contrib/nginx-proxy/nginx.conf b/contrib/nginx-proxy/nginx.conf |
702 | new file mode 100644 |
703 | index 0000000..48cb27b |
704 | --- /dev/null |
705 | +++ b/contrib/nginx-proxy/nginx.conf |
706 | @@ -0,0 +1,20 @@ |
707 | + daemon off; |
708 | + worker_processes auto; |
709 | + error_log stderr; |
710 | + pid /tmp/nginx.pid; |
711 | + |
712 | + events {} |
713 | + |
714 | + mail { |
715 | + server_name localhost; |
716 | + auth_http http://127.0.0.1:30000; |
717 | + |
718 | + proxy_pass_error_message on; |
719 | + xclient off; |
720 | + |
721 | + server { |
722 | + listen 2225; |
723 | + protocol smtp; |
724 | + smtp_auth none; |
725 | + } |
726 | + } |
727 | diff --git a/maitred/Cargo.toml b/maitred/Cargo.toml |
728 | index f92a9b7..40ef577 100644 |
729 | --- a/maitred/Cargo.toml |
730 | +++ b/maitred/Cargo.toml |
731 | @@ -15,10 +15,13 @@ mail-builder = "0.3.2" |
732 | mail-parser = { version = "0.9.3", features = ["serde", "serde_support"] } |
733 | maildir = "0.6.4" |
734 | md5 = "0.7.0" |
735 | + rustls = "0.23.13" |
736 | + rustls-pemfile = "2.1.3" |
737 | smtp-proto = { version = "0.1.5", features = ["serde", "serde_support"] } |
738 | stringprep = "0.1.5" |
739 | thiserror = "1.0.63" |
740 | tokio = { version = "1.39.2", features = ["full"] } |
741 | + tokio-rustls = "0.26.0" |
742 | tokio-stream = { version = "0.1.15", features = ["full"] } |
743 | tokio-util = { version = "0.7.11", features = ["full"] } |
744 | tracing = { version = "0.1.40", features = ["log"] } |
745 | diff --git a/maitred/src/delivery.rs b/maitred/src/delivery.rs |
746 | index bf0d4cd..1da585c 100644 |
747 | --- a/maitred/src/delivery.rs |
748 | +++ b/maitred/src/delivery.rs |
749 | @@ -91,6 +91,8 @@ impl Delivery for Maildir { |
750 | maildir::MaildirError::Utf8(_) => unreachable!(), |
751 | maildir::MaildirError::Time(e) => DeliveryError::Server(e.to_string()), |
752 | })?; |
753 | + } else { |
754 | + tracing::warn!("Ignoring unknown e-mail account: {}", rcpt); |
755 | } |
756 | } |
757 | Ok(()) |
758 | diff --git a/maitred/src/server.rs b/maitred/src/server.rs |
759 | index 52f7274..f7bc261 100644 |
760 | --- a/maitred/src/server.rs |
761 | +++ b/maitred/src/server.rs |
762 | @@ -1,4 +1,7 @@ |
763 | + use std::fs::File as StdFile; |
764 | + use std::io::{BufReader as StdBufReader, Read, Write}; |
765 | use std::net::SocketAddr; |
766 | + use std::path::{Path, PathBuf}; |
767 | use std::sync::Arc; |
768 | use std::time::Duration; |
769 | |
770 | @@ -10,14 +13,19 @@ use futures::SinkExt; |
771 | use futures::StreamExt; |
772 | use mail_auth::Resolver; |
773 | use mail_parser::Message; |
774 | + use rustls::ServerConnection; |
775 | use smtp_proto::Request; |
776 | + use tokio::io::BufReader; |
777 | + use tokio::io::BufStream; |
778 | use tokio::net::TcpListener; |
779 | + use tokio::net::TcpStream; |
780 | use tokio::sync::mpsc::Sender; |
781 | use tokio::sync::Mutex; |
782 | use tokio::task::JoinHandle; |
783 | use tokio::time::timeout; |
784 | + use tokio_rustls::{rustls, TlsAcceptor}; |
785 | use tokio_stream::{self as stream}; |
786 | - use tokio_util::codec::Framed; |
787 | + use tokio_util::codec::{Framed, LengthDelimitedCodec}; |
788 | use url::Host; |
789 | |
790 | use crate::delivery::Delivery; |
791 | @@ -42,10 +50,15 @@ fn is_quit(reqs: &[Request<String>]) -> bool { |
792 | reqs.last().is_some_and(|req| matches!(req, Request::Quit)) |
793 | } |
794 | |
795 | + fn is_starttls(reqs: &[Request<String>]) -> bool { |
796 | + reqs.last() |
797 | + .is_some_and(|req| matches!(req, Request::StartTls)) |
798 | + } |
799 | + |
800 | /// Top level error encountered while processing a client connection, causes |
801 | /// a warning to be logged but is not fatal. |
802 | #[derive(Debug, thiserror::Error)] |
803 | - pub(crate) enum ClientError { |
804 | + pub(crate) enum ServerError { |
805 | /// An IO related error such as not being able to bind to a TCP socket |
806 | #[error("Io: {0}")] |
807 | Io(#[from] std::io::Error), |
808 | @@ -54,6 +67,8 @@ pub(crate) enum ClientError { |
809 | /// Session timeout |
810 | #[error("Client took too long to respond: {0}s")] |
811 | Timeout(u64), |
812 | + #[error("Failed to configure TLS: {0}")] |
813 | + TlsConfiguration(#[from] rustls::Error), |
814 | } |
815 | |
816 | /// Session details to be passed internally for processing |
817 | @@ -91,6 +106,7 @@ pub struct Server { |
818 | spf_verification: bool, |
819 | current_addr: Option<SocketAddr>, |
820 | resolver: Option<Arc<Mutex<Resolver>>>, |
821 | + tls_certificates: Option<(PathBuf, PathBuf)>, |
822 | } |
823 | |
824 | impl Default for Server { |
825 | @@ -107,6 +123,7 @@ impl Default for Server { |
826 | spf_verification: false, |
827 | current_addr: None, |
828 | resolver: None, |
829 | + tls_certificates: None, |
830 | } |
831 | } |
832 | } |
833 | @@ -164,14 +181,123 @@ impl Server { |
834 | self |
835 | } |
836 | |
837 | - async fn process<T>( |
838 | + /// TLS Certificates, implies that the server should listen for TLS |
839 | + /// connections and maybe support STARTTLS if configured in the Session |
840 | + /// options. |
841 | + pub fn with_certificates(mut self, private_key: &Path, certificate: &Path) -> Self { |
842 | + self.tls_certificates = Some((private_key.to_path_buf(), certificate.to_path_buf())); |
843 | + self |
844 | + } |
845 | + |
846 | + async fn rustls_config(&self) -> Result<rustls::ServerConfig, ServerError> { |
847 | + let (private_key_path, cert_path) = self |
848 | + .tls_certificates |
849 | + .as_ref() |
850 | + .expect("Certificates not configured"); |
851 | + let mut cert_contents = StdBufReader::new(StdFile::open(cert_path)?); |
852 | + let mut private_key_contents = StdBufReader::new(StdFile::open(private_key_path)?); |
853 | + let certs = rustls_pemfile::certs(&mut cert_contents).collect::<Result<Vec<_>, _>>()?; |
854 | + let private_key = rustls_pemfile::private_key(&mut private_key_contents)?.unwrap(); |
855 | + Ok(rustls::ServerConfig::builder() |
856 | + .with_no_client_auth() |
857 | + .with_single_cert(certs, private_key)?) |
858 | + } |
859 | + |
860 | + async fn serve_tls( |
861 | &self, |
862 | - mut framed: Framed<T, Transport>, |
863 | + stream: &mut BufStream<TcpStream>, |
864 | + mut session: Session, |
865 | msg_queue: Arc<Injector<Envelope>>, |
866 | - ) -> Result<(), ClientError> |
867 | - where |
868 | - T: tokio::io::AsyncRead + tokio::io::AsyncWrite + std::marker::Unpin, |
869 | - { |
870 | + pipelining: bool, |
871 | + send_greeting: bool, |
872 | + ) -> Result<(), ServerError> { |
873 | + let acceptor = TlsAcceptor::from(Arc::new(self.rustls_config().await?)); |
874 | + let tls_stream = acceptor.accept(stream).await?; |
875 | + |
876 | + let mut framed = Framed::new(tls_stream, Transport::default().pipelining(pipelining)); |
877 | + |
878 | + if send_greeting { |
879 | + let greeting = session.greeting(); |
880 | + // send inital server greeting |
881 | + framed.send(greeting).await?; |
882 | + } |
883 | + |
884 | + let mut shutdown = false; |
885 | + |
886 | + 'outer: while !shutdown { |
887 | + let frame = timeout(self.global_timeout, framed.next()).await; |
888 | + match frame { |
889 | + Ok(Some(Ok(Command::Requests(commands)))) => { |
890 | + shutdown = is_quit(commands.as_slice()); |
891 | + for command in commands { |
892 | + match session.process(&command).await { |
893 | + Ok(responses) => { |
894 | + for response in responses { |
895 | + framed.send(response).await?; |
896 | + } |
897 | + } |
898 | + Err(e) => { |
899 | + tracing::warn!("Client error: {:?}", e); |
900 | + let fatal = e.is_fatal(); |
901 | + framed.send(e).await?; |
902 | + if fatal { |
903 | + break 'outer; |
904 | + } |
905 | + } |
906 | + } |
907 | + } |
908 | + } |
909 | + Ok(Some(Ok(Command::Payload(payload)))) => { |
910 | + match session.handle_data(&payload).await { |
911 | + Ok(responses) => { |
912 | + for response in responses { |
913 | + framed.send(response).await?; |
914 | + } |
915 | + msg_queue.push(Envelope::from(&session)); |
916 | + } |
917 | + Err(response) => { |
918 | + tracing::warn!("Error handling message payload: {:?}", response); |
919 | + |
920 | + framed.send(response).await?; |
921 | + } |
922 | + } |
923 | + } |
924 | + Ok(Some(Err(err))) => { |
925 | + tracing::warn!("Client Error: {}", err); |
926 | + let response = match err { |
927 | + crate::transport::TransportError::PipelineNotEnabled => { |
928 | + crate::smtp_response!(500, 0, 0, 0, "Pipelining is not enabled") |
929 | + } |
930 | + crate::transport::TransportError::Smtp(e) => { |
931 | + crate::session::smtp_error_to_response(e) |
932 | + } |
933 | + // IO Errors considered fatal for the entire session |
934 | + crate::transport::TransportError::Io(e) => return Err(ServerError::Io(e)), |
935 | + }; |
936 | + framed.send(response).await?; |
937 | + } |
938 | + Ok(None) => { |
939 | + tracing::info!("Client connection closing"); |
940 | + break 'outer; |
941 | + } |
942 | + Err(timeout) => { |
943 | + tracing::warn!("Client connection exceeded: {:?}", self.global_timeout); |
944 | + framed |
945 | + .send(crate::session::timeout(&timeout.to_string())) |
946 | + .await?; |
947 | + return Err(ServerError::Timeout(self.global_timeout.as_secs())); |
948 | + } |
949 | + } |
950 | + } |
951 | + Ok(()) |
952 | + } |
953 | + |
954 | + async fn serve_plain( |
955 | + &self, |
956 | + stream: BufStream<TcpStream>, |
957 | + msg_queue: Arc<Injector<Envelope>>, |
958 | + pipelining: bool, |
959 | + ) -> Result<(), ServerError> { |
960 | let mut session_opts = self.options.clone().unwrap_or_default(); |
961 | |
962 | if let Some(addr) = self.current_addr { |
963 | @@ -185,15 +311,19 @@ impl Server { |
964 | |
965 | let greeting = session.greeting(); |
966 | |
967 | + let mut framed = Framed::new(stream, Transport::default().pipelining(pipelining)); |
968 | + |
969 | // send inital server greeting |
970 | framed.send(greeting).await?; |
971 | |
972 | let mut shutdown = false; |
973 | + |
974 | 'outer: while !shutdown { |
975 | let frame = timeout(self.global_timeout, framed.next()).await; |
976 | match frame { |
977 | Ok(Some(Ok(Command::Requests(commands)))) => { |
978 | shutdown = is_quit(commands.as_slice()); |
979 | + let starttls = is_starttls(commands.as_slice()); |
980 | for command in commands { |
981 | match session.process(&command).await { |
982 | Ok(responses) => { |
983 | @@ -208,9 +338,24 @@ impl Server { |
984 | if fatal { |
985 | break 'outer; |
986 | } |
987 | + if starttls { |
988 | + continue 'outer; |
989 | + } |
990 | } |
991 | } |
992 | } |
993 | + if starttls { |
994 | + tracing::info!("Upgrading client connection with STARTTLS"); |
995 | + return self |
996 | + .serve_tls( |
997 | + framed.get_mut(), |
998 | + session.clone().with_options(self.options.clone().unwrap()), |
999 | + msg_queue.clone(), |
1000 | + pipelining, |
1001 | + false, |
1002 | + ) |
1003 | + .await; |
1004 | + } |
1005 | } |
1006 | Ok(Some(Ok(Command::Payload(payload)))) => { |
1007 | match session.handle_data(&payload).await { |
1008 | @@ -222,7 +367,6 @@ impl Server { |
1009 | } |
1010 | Err(response) => { |
1011 | tracing::warn!("Error handling message payload: {:?}", response); |
1012 | - |
1013 | framed.send(response).await?; |
1014 | } |
1015 | } |
1016 | @@ -234,43 +378,10 @@ impl Server { |
1017 | crate::smtp_response!(500, 0, 0, 0, "Pipelining is not enabled") |
1018 | } |
1019 | crate::transport::TransportError::Smtp(e) => { |
1020 | - match e { |
1021 | - smtp_proto::Error::NeedsMoreData { bytes_left: _ } => { |
1022 | - // TODO |
1023 | - smtp_response!(500, 0, 0, 0, e.to_string()) |
1024 | - } |
1025 | - smtp_proto::Error::UnknownCommand => { |
1026 | - smtp_response!(500, 5, 5, 1, "Invalid Command") |
1027 | - } |
1028 | - smtp_proto::Error::InvalidSenderAddress => { |
1029 | - smtp_response!(501, 5, 1, 8, e.to_string()) |
1030 | - } |
1031 | - smtp_proto::Error::InvalidRecipientAddress => { |
1032 | - smtp_response!(501, 5, 1, 3, e.to_string()) |
1033 | - } |
1034 | - smtp_proto::Error::SyntaxError { syntax: _ } => { |
1035 | - smtp_response!(501, 5, 5, 2, e.to_string()) |
1036 | - } |
1037 | - smtp_proto::Error::InvalidParameter { param: _ } => { |
1038 | - // TODO |
1039 | - smtp_response!(500, 0, 0, 0, e.to_string()) |
1040 | - } |
1041 | - smtp_proto::Error::UnsupportedParameter { param: _ } => { |
1042 | - // TODO |
1043 | - smtp_response!(500, 0, 0, 0, e.to_string()) |
1044 | - } |
1045 | - smtp_proto::Error::ResponseTooLong => { |
1046 | - // TODO |
1047 | - smtp_response!(500, 0, 0, 0, e.to_string()) |
1048 | - } |
1049 | - smtp_proto::Error::InvalidResponse { code: _ } => { |
1050 | - // TODO |
1051 | - smtp_response!(500, 0, 0, 0, e.to_string()) |
1052 | - } |
1053 | - } |
1054 | + crate::session::smtp_error_to_response(e) |
1055 | } |
1056 | // IO Errors considered fatal for the entire session |
1057 | - crate::transport::TransportError::Io(e) => return Err(ClientError::Io(e)), |
1058 | + crate::transport::TransportError::Io(e) => return Err(ServerError::Io(e)), |
1059 | }; |
1060 | framed.send(response).await?; |
1061 | } |
1062 | @@ -283,7 +394,7 @@ impl Server { |
1063 | framed |
1064 | .send(crate::session::timeout(&timeout.to_string())) |
1065 | .await?; |
1066 | - return Err(ClientError::Timeout(self.global_timeout.as_secs())); |
1067 | + return Err(ServerError::Timeout(self.global_timeout.as_secs())); |
1068 | } |
1069 | } |
1070 | } |
1071 | @@ -360,8 +471,10 @@ impl Server { |
1072 | .as_ref() |
1073 | .is_some_and(|opts| opts.capabilities & smtp_proto::EXT_PIPELINING != 0) |
1074 | || self.options.is_none(); |
1075 | - let framed = Framed::new(socket, Transport::default().pipelining(pipelining)); |
1076 | - match self.process(framed, global_queue.clone()).await { |
1077 | + match self |
1078 | + .serve_plain(BufStream::new(socket), global_queue.clone(), pipelining) |
1079 | + .await |
1080 | + { |
1081 | Ok(_) => { |
1082 | tracing::info!("Client connection finished normally"); |
1083 | } |
1084 | @@ -376,6 +489,8 @@ impl Server { |
1085 | #[cfg(test)] |
1086 | mod test { |
1087 | |
1088 | + /* |
1089 | + |
1090 | use crate::SessionOptions; |
1091 | |
1092 | use super::*; |
1093 | @@ -484,4 +599,5 @@ mod test { |
1094 | .first() |
1095 | .is_some_and(|rcpt_to| rcpt_to.email() == "baz@qux.com")); |
1096 | } |
1097 | + */ |
1098 | } |
1099 | diff --git a/maitred/src/session.rs b/maitred/src/session.rs |
1100 | index 242b7bf..112721e 100644 |
1101 | --- a/maitred/src/session.rs |
1102 | +++ b/maitred/src/session.rs |
1103 | @@ -48,12 +48,14 @@ pub const DEFAULT_CAPABILITIES: u32 = smtp_proto::EXT_SIZE |
1104 | pub type Result = StdResult<Vec<Response<String>>, Response<String>>; |
1105 | |
1106 | /// If the session was started with HELO or ELHO. |
1107 | + #[derive(Clone)] |
1108 | enum Mode { |
1109 | Legacy, |
1110 | Extended, |
1111 | } |
1112 | |
1113 | /// Type of data transfer mode in use. |
1114 | + #[derive(Clone)] |
1115 | enum DataTransfer { |
1116 | Data, |
1117 | Bdat, |
1118 | @@ -64,6 +66,43 @@ pub fn timeout(message: &str) -> Response<String> { |
1119 | smtp_response!(421, 4, 4, 2, format!("Timeout exceeded: {}", message)) |
1120 | } |
1121 | |
1122 | + pub fn smtp_error_to_response(e: smtp_proto::Error) -> Response<String> { |
1123 | + match e { |
1124 | + smtp_proto::Error::NeedsMoreData { bytes_left: _ } => { |
1125 | + // TODO |
1126 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
1127 | + } |
1128 | + smtp_proto::Error::UnknownCommand => { |
1129 | + smtp_response!(500, 5, 5, 1, "Invalid Command") |
1130 | + } |
1131 | + smtp_proto::Error::InvalidSenderAddress => { |
1132 | + smtp_response!(501, 5, 1, 8, e.to_string()) |
1133 | + } |
1134 | + smtp_proto::Error::InvalidRecipientAddress => { |
1135 | + smtp_response!(501, 5, 1, 3, e.to_string()) |
1136 | + } |
1137 | + smtp_proto::Error::SyntaxError { syntax: _ } => { |
1138 | + smtp_response!(501, 5, 5, 2, e.to_string()) |
1139 | + } |
1140 | + smtp_proto::Error::InvalidParameter { param: _ } => { |
1141 | + // TODO |
1142 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
1143 | + } |
1144 | + smtp_proto::Error::UnsupportedParameter { param: _ } => { |
1145 | + // TODO |
1146 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
1147 | + } |
1148 | + smtp_proto::Error::ResponseTooLong => { |
1149 | + // TODO |
1150 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
1151 | + } |
1152 | + smtp_proto::Error::InvalidResponse { code: _ } => { |
1153 | + // TODO |
1154 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
1155 | + } |
1156 | + } |
1157 | + } |
1158 | + |
1159 | /// Extract a host from HELO/EHLO per RFC5321 4.1.3 |
1160 | fn parse_host(host: &str) -> String { |
1161 | // confusingly the url library determines if an address is IPv6 by checking |
1162 | @@ -94,6 +133,7 @@ pub struct SessionOptions { |
1163 | pub verification: Option<Arc<dyn Verify>>, |
1164 | pub plain_auth: Option<Arc<dyn PlainAuth>>, |
1165 | pub ip_addr: Option<IpAddr>, |
1166 | + pub starttls_enabled: Option<bool>, |
1167 | } |
1168 | |
1169 | impl Default for SessionOptions { |
1170 | @@ -108,6 +148,7 @@ impl Default for SessionOptions { |
1171 | verification: None, |
1172 | plain_auth: None, |
1173 | ip_addr: None, |
1174 | + starttls_enabled: None, |
1175 | } |
1176 | } |
1177 | } |
1178 | @@ -133,6 +174,14 @@ impl SessionOptions { |
1179 | self |
1180 | } |
1181 | |
1182 | + pub fn starttls_enabled(mut self, enabled: bool) -> Self { |
1183 | + if enabled { |
1184 | + self.capabilities |= smtp_proto::EXT_START_TLS; |
1185 | + } |
1186 | + self.starttls_enabled = Some(enabled); |
1187 | + self |
1188 | + } |
1189 | + |
1190 | pub fn list_expansion<T>(mut self, expansion: T) -> Self |
1191 | where |
1192 | T: crate::expand::Expansion + 'static, |
1193 | @@ -165,7 +214,7 @@ impl SessionOptions { |
1194 | } |
1195 | |
1196 | /// Stateful connection that coresponds to a single SMTP session. |
1197 | - #[derive(Default)] |
1198 | + #[derive(Clone, Default)] |
1199 | pub(crate) struct Session { |
1200 | /// message body |
1201 | pub body: Option<Message<'static>>, |
1202 | @@ -466,7 +515,7 @@ impl Session { |
1203 | |
1204 | self.auth_initialized = true; |
1205 | |
1206 | - Ok(vec![smtp_response!(250, 0, 0, 0, "OK")]) |
1207 | + Ok(vec![smtp_response!(235, 2, 7, 0, "OK")]) |
1208 | } else { |
1209 | Err(smtp_response!(504, 5, 5, 4, "Auth Not Supported")) |
1210 | } |
1211 | @@ -533,13 +582,19 @@ impl Session { |
1212 | Request::Burl { uri: _, is_last: _ } => { |
1213 | Err(smtp_response!(500, 0, 0, 0, "BURL is not supported")) |
1214 | } |
1215 | - Request::StartTls => Err(smtp_response!( |
1216 | - 500, |
1217 | - 0, |
1218 | - 0, |
1219 | - 0, |
1220 | - format!("STARTTLS is not supported") |
1221 | - )), |
1222 | + Request::StartTls => { |
1223 | + if self.opts.starttls_enabled.is_some_and(|enabled| enabled) { |
1224 | + Ok(vec![smtp_response!(220, 0, 0, 0, "Go ahead")]) |
1225 | + } else { |
1226 | + Err(smtp_response!( |
1227 | + 500, |
1228 | + 0, |
1229 | + 0, |
1230 | + 0, |
1231 | + format!("STARTTLS is not supported") |
1232 | + )) |
1233 | + } |
1234 | + } |
1235 | Request::Data => { |
1236 | self.check_initialized()?; |
1237 | tracing::info!("Initializing data transfer mode"); |
1238 | diff --git a/maitred/src/transport.rs b/maitred/src/transport.rs |
1239 | index 0c5c0ad..e8d6e59 100644 |
1240 | --- a/maitred/src/transport.rs |
1241 | +++ b/maitred/src/transport.rs |
1242 | @@ -11,7 +11,7 @@ pub(crate) enum TransportError { |
1243 | /// Returned when a client attempts to send multiple commands sequentially |
1244 | /// to the server without waiting for a response but piplining isn't |
1245 | /// enabled. |
1246 | - #[error("Piplining is not enabled")] |
1247 | + #[error("Pipelining is not enabled")] |
1248 | PipelineNotEnabled, |
1249 | /// An error generated from the underlying SMTP protocol |
1250 | #[error("Smtp failure: {0}")] |
1251 | @@ -141,6 +141,9 @@ impl Decoder for Transport { |
1252 | type Error = TransportError; |
1253 | |
1254 | fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { |
1255 | + |
1256 | + tracing::trace!("{}", String::from_utf8_lossy(src)); |
1257 | + |
1258 | if src.is_empty() { |
1259 | tracing::debug!("Empty command received"); |
1260 | return Ok(None); |
1261 | diff --git a/scripts/swaks_test_auth.sh b/scripts/swaks_test_auth.sh |
1262 | index a58b874..c541273 100755 |
1263 | --- a/scripts/swaks_test_auth.sh |
1264 | +++ b/scripts/swaks_test_auth.sh |
1265 | @@ -3,6 +3,6 @@ |
1266 | # Uses swaks: https://www.jetmore.org/john/code/swaks/ to do some basic SMTP |
1267 | # verification. Make sure you install the tool first! |
1268 | |
1269 | - printf "Subject: Hello\nWorld\n" | swaks --to hello@example.com --auth PLAIN \ |
1270 | + printf "Subject: Hello\nWorld\n" | swaks --tls --to hello@example.com --auth PLAIN \ |
1271 | --auth-user hello --auth-password world --auth-plaintext --server localhost:2525 \ |
1272 | --pipeline --data - |