Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 2e1302ad88a6081e0cb17c39a26835fbe0dbc704
Timestamp: Thu, 24 Oct 2024 12:03:47 +0000 (1 month ago)

+1227 -993 +/-26 browse
hack up some new code for ayllu-mail based on maitred
1diff --git a/Cargo.lock b/Cargo.lock
2index 0d4e9d6..1b5bc64 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -18,6 +18,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
6 checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
7
8 [[package]]
9+ name = "aes"
10+ version = "0.8.4"
11+ source = "registry+https://github.com/rust-lang/crates.io-index"
12+ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
13+ dependencies = [
14+ "cfg-if",
15+ "cipher",
16+ "cpufeatures",
17+ ]
18+
19+ [[package]]
20 name = "ahash"
21 version = "0.7.8"
22 source = "registry+https://github.com/rust-lang/crates.io-index"
23 @@ -91,9 +102,9 @@ dependencies = [
24
25 [[package]]
26 name = "anstream"
27- version = "0.6.15"
28+ version = "0.6.17"
29 source = "registry+https://github.com/rust-lang/crates.io-index"
30- checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
31+ checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338"
32 dependencies = [
33 "anstyle",
34 "anstyle-parse",
35 @@ -106,43 +117,52 @@ dependencies = [
36
37 [[package]]
38 name = "anstyle"
39- version = "1.0.8"
40+ version = "1.0.9"
41 source = "registry+https://github.com/rust-lang/crates.io-index"
42- checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
43+ checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56"
44
45 [[package]]
46 name = "anstyle-parse"
47- version = "0.2.5"
48+ version = "0.2.6"
49 source = "registry+https://github.com/rust-lang/crates.io-index"
50- checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
51+ checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
52 dependencies = [
53 "utf8parse",
54 ]
55
56 [[package]]
57 name = "anstyle-query"
58- version = "1.1.1"
59+ version = "1.1.2"
60 source = "registry+https://github.com/rust-lang/crates.io-index"
61- checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
62+ checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
63 dependencies = [
64- "windows-sys 0.52.0",
65+ "windows-sys 0.59.0",
66 ]
67
68 [[package]]
69 name = "anstyle-wincon"
70- version = "3.0.4"
71+ version = "3.0.6"
72 source = "registry+https://github.com/rust-lang/crates.io-index"
73- checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
74+ checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
75 dependencies = [
76 "anstyle",
77- "windows-sys 0.52.0",
78+ "windows-sys 0.59.0",
79 ]
80
81 [[package]]
82 name = "anyhow"
83- version = "1.0.89"
84+ version = "1.0.91"
85 source = "registry+https://github.com/rust-lang/crates.io-index"
86- checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
87+ checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
88+
89+ [[package]]
90+ name = "arbitrary"
91+ version = "1.3.2"
92+ source = "registry+https://github.com/rust-lang/crates.io-index"
93+ checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
94+ dependencies = [
95+ "derive_arbitrary",
96+ ]
97
98 [[package]]
99 name = "arrayvec"
100 @@ -167,7 +187,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
101 dependencies = [
102 "proc-macro2",
103 "quote",
104- "syn 2.0.79",
105+ "syn 2.0.85",
106 ]
107
108 [[package]]
109 @@ -216,6 +236,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
110 checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
111
112 [[package]]
113+ name = "aws-lc-rs"
114+ version = "1.10.0"
115+ source = "registry+https://github.com/rust-lang/crates.io-index"
116+ checksum = "cdd82dba44d209fddb11c190e0a94b78651f95299598e472215667417a03ff1d"
117+ dependencies = [
118+ "aws-lc-sys",
119+ "mirai-annotations",
120+ "paste",
121+ "zeroize",
122+ ]
123+
124+ [[package]]
125+ name = "aws-lc-sys"
126+ version = "0.22.0"
127+ source = "registry+https://github.com/rust-lang/crates.io-index"
128+ checksum = "df7a4168111d7eb622a31b214057b8509c0a7e1794f44c546d742330dc793972"
129+ dependencies = [
130+ "bindgen",
131+ "cc",
132+ "cmake",
133+ "dunce",
134+ "fs_extra",
135+ "libc",
136+ "paste",
137+ ]
138+
139+ [[package]]
140 name = "axum"
141 version = "0.7.7"
142 source = "registry+https://github.com/rust-lang/crates.io-index"
143 @@ -229,7 +276,7 @@ dependencies = [
144 "http 1.1.0",
145 "http-body 1.0.1",
146 "http-body-util",
147- "hyper 1.4.1",
148+ "hyper 1.5.0",
149 "hyper-util",
150 "itoa",
151 "matchit",
152 @@ -304,7 +351,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
153 dependencies = [
154 "proc-macro2",
155 "quote",
156- "syn 2.0.79",
157+ "syn 2.0.85",
158 ]
159
160 [[package]]
161 @@ -424,6 +471,25 @@ dependencies = [
162 ]
163
164 [[package]]
165+ name = "ayllu-mail"
166+ version = "0.2.1"
167+ dependencies = [
168+ "async-trait",
169+ "axum",
170+ "ayllu_config",
171+ "ayllu_database",
172+ "clap 4.5.20",
173+ "clap_complete",
174+ "futures",
175+ "maitred",
176+ "serde",
177+ "thiserror",
178+ "tokio",
179+ "tracing",
180+ "tracing-subscriber",
181+ ]
182+
183+ [[package]]
184 name = "ayllu_api"
185 version = "0.2.1"
186 dependencies = [
187 @@ -540,6 +606,29 @@ dependencies = [
188 ]
189
190 [[package]]
191+ name = "bindgen"
192+ version = "0.69.5"
193+ source = "registry+https://github.com/rust-lang/crates.io-index"
194+ checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
195+ dependencies = [
196+ "bitflags 2.6.0",
197+ "cexpr",
198+ "clang-sys",
199+ "itertools 0.12.1",
200+ "lazy_static",
201+ "lazycell",
202+ "log",
203+ "prettyplease",
204+ "proc-macro2",
205+ "quote",
206+ "regex",
207+ "rustc-hash",
208+ "shlex",
209+ "syn 2.0.85",
210+ "which",
211+ ]
212+
213+ [[package]]
214 name = "bit-set"
215 version = "0.5.3"
216 source = "registry+https://github.com/rust-lang/crates.io-index"
217 @@ -630,9 +719,9 @@ dependencies = [
218
219 [[package]]
220 name = "bytemuck"
221- version = "1.18.0"
222+ version = "1.19.0"
223 source = "registry+https://github.com/rust-lang/crates.io-index"
224- checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
225+ checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
226
227 [[package]]
228 name = "byteorder"
229 @@ -642,9 +731,30 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
230
231 [[package]]
232 name = "bytes"
233- version = "1.7.2"
234+ version = "1.8.0"
235+ source = "registry+https://github.com/rust-lang/crates.io-index"
236+ checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
237+
238+ [[package]]
239+ name = "bzip2"
240+ version = "0.4.4"
241+ source = "registry+https://github.com/rust-lang/crates.io-index"
242+ checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
243+ dependencies = [
244+ "bzip2-sys",
245+ "libc",
246+ ]
247+
248+ [[package]]
249+ name = "bzip2-sys"
250+ version = "0.1.11+1.0.8"
251 source = "registry+https://github.com/rust-lang/crates.io-index"
252- checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
253+ checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
254+ dependencies = [
255+ "cc",
256+ "libc",
257+ "pkg-config",
258+ ]
259
260 [[package]]
261 name = "caseless"
262 @@ -658,9 +768,9 @@ dependencies = [
263
264 [[package]]
265 name = "cc"
266- version = "1.1.29"
267+ version = "1.1.31"
268 source = "registry+https://github.com/rust-lang/crates.io-index"
269- checksum = "58e804ac3194a48bb129643eb1d62fcc20d18c6b8c181704489353d13120bcd1"
270+ checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
271 dependencies = [
272 "jobserver",
273 "libc",
274 @@ -668,12 +778,31 @@ dependencies = [
275 ]
276
277 [[package]]
278+ name = "cexpr"
279+ version = "0.6.0"
280+ source = "registry+https://github.com/rust-lang/crates.io-index"
281+ checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
282+ dependencies = [
283+ "nom",
284+ ]
285+
286+ [[package]]
287 name = "cfg-if"
288 version = "1.0.0"
289 source = "registry+https://github.com/rust-lang/crates.io-index"
290 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
291
292 [[package]]
293+ name = "charset"
294+ version = "0.1.5"
295+ source = "registry+https://github.com/rust-lang/crates.io-index"
296+ checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e"
297+ dependencies = [
298+ "base64 0.22.1",
299+ "encoding_rs",
300+ ]
301+
302+ [[package]]
303 name = "chrono"
304 version = "0.4.38"
305 source = "registry+https://github.com/rust-lang/crates.io-index"
306 @@ -711,6 +840,27 @@ dependencies = [
307 ]
308
309 [[package]]
310+ name = "cipher"
311+ version = "0.4.4"
312+ source = "registry+https://github.com/rust-lang/crates.io-index"
313+ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
314+ dependencies = [
315+ "crypto-common",
316+ "inout",
317+ ]
318+
319+ [[package]]
320+ name = "clang-sys"
321+ version = "1.8.1"
322+ source = "registry+https://github.com/rust-lang/crates.io-index"
323+ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
324+ dependencies = [
325+ "glob",
326+ "libc",
327+ "libloading",
328+ ]
329+
330+ [[package]]
331 name = "clap"
332 version = "2.34.0"
333 source = "registry+https://github.com/rust-lang/crates.io-index"
334 @@ -750,9 +900,9 @@ dependencies = [
335
336 [[package]]
337 name = "clap_complete"
338- version = "4.5.33"
339+ version = "4.5.35"
340 source = "registry+https://github.com/rust-lang/crates.io-index"
341- checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb"
342+ checksum = "07a13ab5b8cb13dbe35e68b83f6c12f9293b2f601797b71bc9f23befdb329feb"
343 dependencies = [
344 "clap 4.5.20",
345 ]
346 @@ -766,7 +916,7 @@ dependencies = [
347 "heck",
348 "proc-macro2",
349 "quote",
350- "syn 2.0.79",
351+ "syn 2.0.85",
352 ]
353
354 [[package]]
355 @@ -776,6 +926,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
356 checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
357
358 [[package]]
359+ name = "cmake"
360+ version = "0.1.51"
361+ source = "registry+https://github.com/rust-lang/crates.io-index"
362+ checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a"
363+ dependencies = [
364+ "cc",
365+ ]
366+
367+ [[package]]
368 name = "color_quant"
369 version = "1.1.0"
370 source = "registry+https://github.com/rust-lang/crates.io-index"
371 @@ -783,9 +942,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
372
373 [[package]]
374 name = "colorchoice"
375- version = "1.0.2"
376+ version = "1.0.3"
377 source = "registry+https://github.com/rust-lang/crates.io-index"
378- checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
379+ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
380
381 [[package]]
382 name = "comrak"
383 @@ -844,6 +1003,12 @@ dependencies = [
384 ]
385
386 [[package]]
387+ name = "constant_time_eq"
388+ version = "0.3.1"
389+ source = "registry+https://github.com/rust-lang/crates.io-index"
390+ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
391+
392+ [[package]]
393 name = "convert_case"
394 version = "0.6.0"
395 source = "registry+https://github.com/rust-lang/crates.io-index"
396 @@ -1030,7 +1195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
397 checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
398 dependencies = [
399 "quote",
400- "syn 2.0.79",
401+ "syn 2.0.85",
402 ]
403
404 [[package]]
405 @@ -1054,7 +1219,7 @@ dependencies = [
406 "proc-macro2",
407 "quote",
408 "strsim 0.11.1",
409- "syn 2.0.79",
410+ "syn 2.0.85",
411 ]
412
413 [[package]]
414 @@ -1065,7 +1230,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
415 dependencies = [
416 "darling_core",
417 "quote",
418- "syn 2.0.79",
419+ "syn 2.0.85",
420 ]
421
422 [[package]]
423 @@ -1108,6 +1273,12 @@ dependencies = [
424 ]
425
426 [[package]]
427+ name = "deflate64"
428+ version = "0.1.9"
429+ source = "registry+https://github.com/rust-lang/crates.io-index"
430+ checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
431+
432+ [[package]]
433 name = "der"
434 version = "0.7.9"
435 source = "registry+https://github.com/rust-lang/crates.io-index"
436 @@ -1129,6 +1300,17 @@ dependencies = [
437 ]
438
439 [[package]]
440+ name = "derive_arbitrary"
441+ version = "1.3.2"
442+ source = "registry+https://github.com/rust-lang/crates.io-index"
443+ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
444+ dependencies = [
445+ "proc-macro2",
446+ "quote",
447+ "syn 2.0.85",
448+ ]
449+
450+ [[package]]
451 name = "derive_builder"
452 version = "0.20.2"
453 source = "registry+https://github.com/rust-lang/crates.io-index"
454 @@ -1146,7 +1328,7 @@ dependencies = [
455 "darling",
456 "proc-macro2",
457 "quote",
458- "syn 2.0.79",
459+ "syn 2.0.85",
460 ]
461
462 [[package]]
463 @@ -1156,7 +1338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
464 checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
465 dependencies = [
466 "derive_builder_core",
467- "syn 2.0.79",
468+ "syn 2.0.85",
469 ]
470
471 [[package]]
472 @@ -1228,6 +1410,17 @@ dependencies = [
473 ]
474
475 [[package]]
476+ name = "displaydoc"
477+ version = "0.2.5"
478+ source = "registry+https://github.com/rust-lang/crates.io-index"
479+ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
480+ dependencies = [
481+ "proc-macro2",
482+ "quote",
483+ "syn 2.0.85",
484+ ]
485+
486+ [[package]]
487 name = "dlib"
488 version = "0.5.2"
489 source = "registry+https://github.com/rust-lang/crates.io-index"
490 @@ -1267,6 +1460,12 @@ dependencies = [
491 ]
492
493 [[package]]
494+ name = "dunce"
495+ version = "1.0.5"
496+ source = "registry+https://github.com/rust-lang/crates.io-index"
497+ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
498+
499+ [[package]]
500 name = "dwrote"
501 version = "0.11.2"
502 source = "registry+https://github.com/rust-lang/crates.io-index"
503 @@ -1300,10 +1499,19 @@ dependencies = [
504 ]
505
506 [[package]]
507+ name = "email_address"
508+ version = "0.2.9"
509+ source = "registry+https://github.com/rust-lang/crates.io-index"
510+ checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
511+ dependencies = [
512+ "serde",
513+ ]
514+
515+ [[package]]
516 name = "encoding_rs"
517- version = "0.8.34"
518+ version = "0.8.35"
519 source = "registry+https://github.com/rust-lang/crates.io-index"
520- checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
521+ checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
522 dependencies = [
523 "cfg-if",
524 ]
525 @@ -1324,6 +1532,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
526 checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
527
528 [[package]]
529+ name = "enum-as-inner"
530+ version = "0.6.1"
531+ source = "registry+https://github.com/rust-lang/crates.io-index"
532+ checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
533+ dependencies = [
534+ "heck",
535+ "proc-macro2",
536+ "quote",
537+ "syn 2.0.85",
538+ ]
539+
540+ [[package]]
541 name = "enum-ordinalize"
542 version = "3.1.15"
543 source = "registry+https://github.com/rust-lang/crates.io-index"
544 @@ -1333,7 +1553,7 @@ dependencies = [
545 "num-traits",
546 "proc-macro2",
547 "quote",
548- "syn 2.0.79",
549+ "syn 2.0.85",
550 ]
551
552 [[package]]
553 @@ -1462,13 +1682,13 @@ checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
554
555 [[package]]
556 name = "flume"
557- version = "0.11.0"
558+ version = "0.11.1"
559 source = "registry+https://github.com/rust-lang/crates.io-index"
560- checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
561+ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
562 dependencies = [
563 "futures-core",
564 "futures-sink",
565- "spin",
566+ "spin 0.9.8",
567 ]
568
569 [[package]]
570 @@ -1529,7 +1749,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
571 dependencies = [
572 "proc-macro2",
573 "quote",
574- "syn 2.0.79",
575+ "syn 2.0.85",
576 ]
577
578 [[package]]
579 @@ -1565,6 +1785,12 @@ dependencies = [
580 ]
581
582 [[package]]
583+ name = "fs_extra"
584+ version = "1.3.0"
585+ source = "registry+https://github.com/rust-lang/crates.io-index"
586+ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
587+
588+ [[package]]
589 name = "funty"
590 version = "2.0.0"
591 source = "registry+https://github.com/rust-lang/crates.io-index"
592 @@ -1637,7 +1863,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
593 dependencies = [
594 "proc-macro2",
595 "quote",
596- "syn 2.0.79",
597+ "syn 2.0.85",
598 ]
599
600 [[package]]
601 @@ -1690,6 +1916,26 @@ dependencies = [
602 ]
603
604 [[package]]
605+ name = "gethostname"
606+ version = "0.2.3"
607+ source = "registry+https://github.com/rust-lang/crates.io-index"
608+ checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
609+ dependencies = [
610+ "libc",
611+ "winapi",
612+ ]
613+
614+ [[package]]
615+ name = "gethostname"
616+ version = "0.4.3"
617+ source = "registry+https://github.com/rust-lang/crates.io-index"
618+ checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
619+ dependencies = [
620+ "libc",
621+ "windows-targets 0.48.5",
622+ ]
623+
624+ [[package]]
625 name = "getrandom"
626 version = "0.2.15"
627 source = "registry+https://github.com/rust-lang/crates.io-index"
628 @@ -1772,9 +2018,9 @@ dependencies = [
629
630 [[package]]
631 name = "grep-searcher"
632- version = "0.1.13"
633+ version = "0.1.14"
634 source = "registry+https://github.com/rust-lang/crates.io-index"
635- checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
636+ checksum = "b9b6c14b3fc2e0a107d6604d3231dec0509e691e62447104bc385a46a7892cda"
637 dependencies = [
638 "bstr",
639 "encoding_rs",
640 @@ -1909,6 +2155,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
641 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
642
643 [[package]]
644+ name = "hickory-proto"
645+ version = "0.24.1"
646+ source = "registry+https://github.com/rust-lang/crates.io-index"
647+ checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512"
648+ dependencies = [
649+ "async-trait",
650+ "cfg-if",
651+ "data-encoding",
652+ "enum-as-inner",
653+ "futures-channel",
654+ "futures-io",
655+ "futures-util",
656+ "idna 0.4.0",
657+ "ipnet",
658+ "once_cell",
659+ "rand",
660+ "ring 0.16.20",
661+ "rustls 0.21.12",
662+ "rustls-pemfile 1.0.4",
663+ "thiserror",
664+ "tinyvec",
665+ "tokio",
666+ "tokio-rustls 0.24.1",
667+ "tracing",
668+ "url",
669+ ]
670+
671+ [[package]]
672+ name = "hickory-resolver"
673+ version = "0.24.1"
674+ source = "registry+https://github.com/rust-lang/crates.io-index"
675+ checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243"
676+ dependencies = [
677+ "cfg-if",
678+ "futures-util",
679+ "hickory-proto",
680+ "ipconfig",
681+ "lru-cache",
682+ "once_cell",
683+ "parking_lot 0.12.3",
684+ "rand",
685+ "resolv-conf",
686+ "rustls 0.21.12",
687+ "smallvec",
688+ "thiserror",
689+ "tokio",
690+ "tokio-rustls 0.24.1",
691+ "tracing",
692+ ]
693+
694+ [[package]]
695 name = "hkdf"
696 version = "0.12.4"
697 source = "registry+https://github.com/rust-lang/crates.io-index"
698 @@ -1936,6 +2233,17 @@ dependencies = [
699 ]
700
701 [[package]]
702+ name = "hostname"
703+ version = "0.3.1"
704+ source = "registry+https://github.com/rust-lang/crates.io-index"
705+ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
706+ dependencies = [
707+ "libc",
708+ "match_cfg",
709+ "winapi",
710+ ]
711+
712+ [[package]]
713 name = "http"
714 version = "0.2.12"
715 source = "registry+https://github.com/rust-lang/crates.io-index"
716 @@ -2020,9 +2328,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
717
718 [[package]]
719 name = "hyper"
720- version = "0.14.30"
721+ version = "0.14.31"
722 source = "registry+https://github.com/rust-lang/crates.io-index"
723- checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
724+ checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85"
725 dependencies = [
726 "bytes",
727 "futures-channel",
728 @@ -2044,9 +2352,9 @@ dependencies = [
729
730 [[package]]
731 name = "hyper"
732- version = "1.4.1"
733+ version = "1.5.0"
734 source = "registry+https://github.com/rust-lang/crates.io-index"
735- checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
736+ checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
737 dependencies = [
738 "bytes",
739 "futures-channel",
740 @@ -2071,12 +2379,12 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
741 dependencies = [
742 "futures-util",
743 "http 1.1.0",
744- "hyper 1.4.1",
745+ "hyper 1.5.0",
746 "hyper-util",
747- "rustls",
748+ "rustls 0.23.15",
749 "rustls-pki-types",
750 "tokio",
751- "tokio-rustls",
752+ "tokio-rustls 0.26.0",
753 "tower-service",
754 ]
755
756 @@ -2087,7 +2395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
757 checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
758 dependencies = [
759 "bytes",
760- "hyper 0.14.30",
761+ "hyper 0.14.31",
762 "native-tls",
763 "tokio",
764 "tokio-native-tls",
765 @@ -2101,7 +2409,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
766 dependencies = [
767 "bytes",
768 "http-body-util",
769- "hyper 1.4.1",
770+ "hyper 1.5.0",
771 "hyper-util",
772 "native-tls",
773 "tokio",
774 @@ -2120,7 +2428,7 @@ dependencies = [
775 "futures-util",
776 "http 1.1.0",
777 "http-body 1.0.1",
778- "hyper 1.4.1",
779+ "hyper 1.5.0",
780 "pin-project-lite",
781 "socket2",
782 "tokio",
783 @@ -2159,6 +2467,16 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
784
785 [[package]]
786 name = "idna"
787+ version = "0.4.0"
788+ source = "registry+https://github.com/rust-lang/crates.io-index"
789+ checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
790+ dependencies = [
791+ "unicode-bidi",
792+ "unicode-normalization",
793+ ]
794+
795+ [[package]]
796+ name = "idna"
797 version = "0.5.0"
798 source = "registry+https://github.com/rust-lang/crates.io-index"
799 checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
800 @@ -2240,6 +2558,15 @@ dependencies = [
801 ]
802
803 [[package]]
804+ name = "inout"
805+ version = "0.1.3"
806+ source = "registry+https://github.com/rust-lang/crates.io-index"
807+ checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
808+ dependencies = [
809+ "generic-array",
810+ ]
811+
812+ [[package]]
813 name = "instant"
814 version = "0.1.13"
815 source = "registry+https://github.com/rust-lang/crates.io-index"
816 @@ -2249,6 +2576,18 @@ dependencies = [
817 ]
818
819 [[package]]
820+ name = "ipconfig"
821+ version = "0.3.2"
822+ source = "registry+https://github.com/rust-lang/crates.io-index"
823+ checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
824+ dependencies = [
825+ "socket2",
826+ "widestring",
827+ "windows-sys 0.48.0",
828+ "winreg",
829+ ]
830+
831+ [[package]]
832 name = "ipnet"
833 version = "2.10.1"
834 source = "registry+https://github.com/rust-lang/crates.io-index"
835 @@ -2270,6 +2609,15 @@ dependencies = [
836 ]
837
838 [[package]]
839+ name = "itertools"
840+ version = "0.12.1"
841+ source = "registry+https://github.com/rust-lang/crates.io-index"
842+ checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
843+ dependencies = [
844+ "either",
845+ ]
846+
847+ [[package]]
848 name = "itoa"
849 version = "1.0.11"
850 source = "registry+https://github.com/rust-lang/crates.io-index"
851 @@ -2317,7 +2665,7 @@ dependencies = [
852 "convert_case",
853 "proc-macro2",
854 "quote",
855- "syn 2.0.79",
856+ "syn 2.0.85",
857 ]
858
859 [[package]]
860 @@ -2326,14 +2674,20 @@ version = "1.5.0"
861 source = "registry+https://github.com/rust-lang/crates.io-index"
862 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
863 dependencies = [
864- "spin",
865+ "spin 0.9.8",
866 ]
867
868 [[package]]
869+ name = "lazycell"
870+ version = "1.3.0"
871+ source = "registry+https://github.com/rust-lang/crates.io-index"
872+ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
873+
874+ [[package]]
875 name = "libc"
876- version = "0.2.159"
877+ version = "0.2.161"
878 source = "registry+https://github.com/rust-lang/crates.io-index"
879- checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
880+ checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
881
882 [[package]]
883 name = "libgit2-sys"
884 @@ -2419,70 +2773,190 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
885 checksum = "53e225b3fa0a8bd5562c8833b1a32afa88761c4e661d3177b8cdc4e13cbf078e"
886 dependencies = [
887 "ahash 0.8.11",
888- "bitflags 2.6.0",
889- "const-str",
890- "cssparser",
891- "cssparser-color",
892- "dashmap 5.5.3",
893- "data-encoding",
894- "getrandom",
895- "itertools",
896- "lazy_static",
897- "lightningcss-derive",
898- "parcel_selectors",
899- "parcel_sourcemap",
900- "paste",
901- "pathdiff",
902- "rayon",
903+ "bitflags 2.6.0",
904+ "const-str",
905+ "cssparser",
906+ "cssparser-color",
907+ "dashmap 5.5.3",
908+ "data-encoding",
909+ "getrandom",
910+ "itertools 0.10.5",
911+ "lazy_static",
912+ "lightningcss-derive",
913+ "parcel_selectors",
914+ "parcel_sourcemap",
915+ "paste",
916+ "pathdiff",
917+ "rayon",
918+ "serde",
919+ "smallvec",
920+ ]
921+
922+ [[package]]
923+ name = "lightningcss-derive"
924+ version = "1.0.0-alpha.43"
925+ source = "registry+https://github.com/rust-lang/crates.io-index"
926+ checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252"
927+ dependencies = [
928+ "convert_case",
929+ "proc-macro2",
930+ "quote",
931+ "syn 1.0.109",
932+ ]
933+
934+ [[package]]
935+ name = "linked-hash-map"
936+ version = "0.5.6"
937+ source = "registry+https://github.com/rust-lang/crates.io-index"
938+ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
939+
940+ [[package]]
941+ name = "linux-raw-sys"
942+ version = "0.4.14"
943+ source = "registry+https://github.com/rust-lang/crates.io-index"
944+ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
945+
946+ [[package]]
947+ name = "litrs"
948+ version = "0.4.1"
949+ source = "registry+https://github.com/rust-lang/crates.io-index"
950+ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
951+
952+ [[package]]
953+ name = "lock_api"
954+ version = "0.4.12"
955+ source = "registry+https://github.com/rust-lang/crates.io-index"
956+ checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
957+ dependencies = [
958+ "autocfg",
959+ "scopeguard",
960+ ]
961+
962+ [[package]]
963+ name = "lockfree-object-pool"
964+ version = "0.1.6"
965+ source = "registry+https://github.com/rust-lang/crates.io-index"
966+ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
967+
968+ [[package]]
969+ name = "log"
970+ version = "0.4.22"
971+ source = "registry+https://github.com/rust-lang/crates.io-index"
972+ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
973+
974+ [[package]]
975+ name = "lru-cache"
976+ version = "0.1.2"
977+ source = "registry+https://github.com/rust-lang/crates.io-index"
978+ checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
979+ dependencies = [
980+ "linked-hash-map",
981+ ]
982+
983+ [[package]]
984+ name = "lzma-rs"
985+ version = "0.3.0"
986+ source = "registry+https://github.com/rust-lang/crates.io-index"
987+ checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
988+ dependencies = [
989+ "byteorder",
990+ "crc",
991+ ]
992+
993+ [[package]]
994+ name = "mail-auth"
995+ version = "0.5.0"
996+ source = "git+https://github.com/stalwartlabs/mail-auth.git?branch=main#9cadc33fc096545f003399ca5169543539cd1434"
997+ dependencies = [
998+ "ahash 0.8.11",
999+ "flate2",
1000+ "hickory-resolver",
1001+ "lru-cache",
1002+ "mail-builder",
1003+ "mail-parser",
1004+ "parking_lot 0.12.3",
1005+ "ring 0.17.8",
1006+ "rustls-pemfile 2.2.0",
1007 "serde",
1008- "smallvec",
1009+ "serde_json",
1010+ "zip",
1011 ]
1012
1013 [[package]]
1014- name = "lightningcss-derive"
1015- version = "1.0.0-alpha.43"
1016+ name = "mail-builder"
1017+ version = "0.3.2"
1018 source = "registry+https://github.com/rust-lang/crates.io-index"
1019- checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252"
1020+ checksum = "25f5871d5270ed80f2ee750b95600c8d69b05f8653ad3be913b2ad2e924fefcb"
1021 dependencies = [
1022- "convert_case",
1023- "proc-macro2",
1024- "quote",
1025- "syn 1.0.109",
1026+ "gethostname 0.4.3",
1027 ]
1028
1029 [[package]]
1030- name = "linked-hash-map"
1031- version = "0.5.6"
1032+ name = "mail-parser"
1033+ version = "0.9.4"
1034 source = "registry+https://github.com/rust-lang/crates.io-index"
1035- checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
1036+ checksum = "93c3b9e5d8b17faf573330bbc43b37d6e918c0a3bf8a88e7d0a220ebc84af9fc"
1037+ dependencies = [
1038+ "encoding_rs",
1039+ "serde",
1040+ ]
1041
1042 [[package]]
1043- name = "linux-raw-sys"
1044- version = "0.4.14"
1045+ name = "maildir"
1046+ version = "0.6.4"
1047 source = "registry+https://github.com/rust-lang/crates.io-index"
1048- checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
1049+ checksum = "879a6ae6743ab8219fdee64a569094485bfe18434e82b78b27fac5cce09e1437"
1050+ dependencies = [
1051+ "gethostname 0.2.3",
1052+ "mailparse",
1053+ ]
1054
1055 [[package]]
1056- name = "litrs"
1057- version = "0.4.1"
1058+ name = "mailparse"
1059+ version = "0.14.1"
1060 source = "registry+https://github.com/rust-lang/crates.io-index"
1061- checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
1062+ checksum = "2d096594926cab442e054e047eb8c1402f7d5b2272573b97ba68aa40629f9757"
1063+ dependencies = [
1064+ "charset",
1065+ "data-encoding",
1066+ "quoted_printable",
1067+ ]
1068
1069 [[package]]
1070- name = "lock_api"
1071- version = "0.4.12"
1072- source = "registry+https://github.com/rust-lang/crates.io-index"
1073- checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
1074+ name = "maitred"
1075+ version = "0.1.0"
1076+ source = "git+https://ayllu-forge.org/ayllu/maitred?branch=main#87fff6c5a73e751a916ea97ec7e689ebd7ec71e3"
1077 dependencies = [
1078- "autocfg",
1079- "scopeguard",
1080+ "async-trait",
1081+ "base64 0.22.1",
1082+ "bytes",
1083+ "crossbeam-deque",
1084+ "email_address",
1085+ "futures",
1086+ "mail-auth",
1087+ "mail-builder",
1088+ "mail-parser",
1089+ "maildir",
1090+ "md5",
1091+ "proxy-header",
1092+ "rustls 0.23.15",
1093+ "rustls-pemfile 2.2.0",
1094+ "smtp-proto",
1095+ "stringprep",
1096+ "thiserror",
1097+ "tokio",
1098+ "tokio-rustls 0.26.0",
1099+ "tokio-stream",
1100+ "tokio-util",
1101+ "tracing",
1102+ "url",
1103 ]
1104
1105 [[package]]
1106- name = "log"
1107- version = "0.4.22"
1108+ name = "match_cfg"
1109+ version = "0.1.0"
1110 source = "registry+https://github.com/rust-lang/crates.io-index"
1111- checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
1112+ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
1113
1114 [[package]]
1115 name = "matchers"
1116 @@ -2516,6 +2990,12 @@ dependencies = [
1117 ]
1118
1119 [[package]]
1120+ name = "md5"
1121+ version = "0.7.0"
1122+ source = "registry+https://github.com/rust-lang/crates.io-index"
1123+ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
1124+
1125+ [[package]]
1126 name = "memchr"
1127 version = "2.7.4"
1128 source = "registry+https://github.com/rust-lang/crates.io-index"
1129 @@ -2523,9 +3003,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
1130
1131 [[package]]
1132 name = "memmap2"
1133- version = "0.9.4"
1134+ version = "0.9.5"
1135 source = "registry+https://github.com/rust-lang/crates.io-index"
1136- checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
1137+ checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
1138 dependencies = [
1139 "libc",
1140 ]
1141 @@ -2575,6 +3055,12 @@ dependencies = [
1142 ]
1143
1144 [[package]]
1145+ name = "mirai-annotations"
1146+ version = "1.12.0"
1147+ source = "registry+https://github.com/rust-lang/crates.io-index"
1148+ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1"
1149+
1150+ [[package]]
1151 name = "native-tls"
1152 version = "0.2.12"
1153 source = "registry+https://github.com/rust-lang/crates.io-index"
1154 @@ -2719,7 +3205,7 @@ dependencies = [
1155 "kinded",
1156 "proc-macro2",
1157 "quote",
1158- "syn 2.0.79",
1159+ "syn 2.0.85",
1160 "urlencoding",
1161 ]
1162
1163 @@ -2762,9 +3248,9 @@ dependencies = [
1164
1165 [[package]]
1166 name = "openssl"
1167- version = "0.10.66"
1168+ version = "0.10.68"
1169 source = "registry+https://github.com/rust-lang/crates.io-index"
1170- checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
1171+ checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
1172 dependencies = [
1173 "bitflags 2.6.0",
1174 "cfg-if",
1175 @@ -2783,7 +3269,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
1176 dependencies = [
1177 "proc-macro2",
1178 "quote",
1179- "syn 2.0.79",
1180+ "syn 2.0.85",
1181 ]
1182
1183 [[package]]
1184 @@ -2794,9 +3280,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
1185
1186 [[package]]
1187 name = "openssl-sys"
1188- version = "0.9.103"
1189+ version = "0.9.104"
1190 source = "registry+https://github.com/rust-lang/crates.io-index"
1191- checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
1192+ checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
1193 dependencies = [
1194 "cc",
1195 "libc",
1196 @@ -2966,9 +3452,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
1197
1198 [[package]]
1199 name = "pathdiff"
1200- version = "0.2.1"
1201+ version = "0.2.2"
1202 source = "registry+https://github.com/rust-lang/crates.io-index"
1203- checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
1204+ checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
1205
1206 [[package]]
1207 name = "pathfinder_geometry"
1208 @@ -2990,6 +3476,16 @@ dependencies = [
1209 ]
1210
1211 [[package]]
1212+ name = "pbkdf2"
1213+ version = "0.12.2"
1214+ source = "registry+https://github.com/rust-lang/crates.io-index"
1215+ checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
1216+ dependencies = [
1217+ "digest",
1218+ "hmac",
1219+ ]
1220+
1221+ [[package]]
1222 name = "pem-rfc7468"
1223 version = "0.7.0"
1224 source = "registry+https://github.com/rust-lang/crates.io-index"
1225 @@ -3006,9 +3502,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1226
1227 [[package]]
1228 name = "pest"
1229- version = "2.7.13"
1230+ version = "2.7.14"
1231 source = "registry+https://github.com/rust-lang/crates.io-index"
1232- checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9"
1233+ checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442"
1234 dependencies = [
1235 "memchr",
1236 "thiserror",
1237 @@ -3017,9 +3513,9 @@ dependencies = [
1238
1239 [[package]]
1240 name = "pest_derive"
1241- version = "2.7.13"
1242+ version = "2.7.14"
1243 source = "registry+https://github.com/rust-lang/crates.io-index"
1244- checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0"
1245+ checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd"
1246 dependencies = [
1247 "pest",
1248 "pest_generator",
1249 @@ -3027,22 +3523,22 @@ dependencies = [
1250
1251 [[package]]
1252 name = "pest_generator"
1253- version = "2.7.13"
1254+ version = "2.7.14"
1255 source = "registry+https://github.com/rust-lang/crates.io-index"
1256- checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e"
1257+ checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e"
1258 dependencies = [
1259 "pest",
1260 "pest_meta",
1261 "proc-macro2",
1262 "quote",
1263- "syn 2.0.79",
1264+ "syn 2.0.85",
1265 ]
1266
1267 [[package]]
1268 name = "pest_meta"
1269- version = "2.7.13"
1270+ version = "2.7.14"
1271 source = "registry+https://github.com/rust-lang/crates.io-index"
1272- checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f"
1273+ checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d"
1274 dependencies = [
1275 "once_cell",
1276 "pest",
1277 @@ -3118,7 +3614,7 @@ dependencies = [
1278 "phf_shared 0.11.2",
1279 "proc-macro2",
1280 "quote",
1281- "syn 2.0.79",
1282+ "syn 2.0.85",
1283 ]
1284
1285 [[package]]
1286 @@ -3141,29 +3637,29 @@ dependencies = [
1287
1288 [[package]]
1289 name = "pin-project"
1290- version = "1.1.6"
1291+ version = "1.1.7"
1292 source = "registry+https://github.com/rust-lang/crates.io-index"
1293- checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec"
1294+ checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95"
1295 dependencies = [
1296 "pin-project-internal",
1297 ]
1298
1299 [[package]]
1300 name = "pin-project-internal"
1301- version = "1.1.6"
1302+ version = "1.1.7"
1303 source = "registry+https://github.com/rust-lang/crates.io-index"
1304- checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8"
1305+ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
1306 dependencies = [
1307 "proc-macro2",
1308 "quote",
1309- "syn 2.0.79",
1310+ "syn 2.0.85",
1311 ]
1312
1313 [[package]]
1314 name = "pin-project-lite"
1315- version = "0.2.14"
1316+ version = "0.2.15"
1317 source = "registry+https://github.com/rust-lang/crates.io-index"
1318- checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
1319+ checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
1320
1321 [[package]]
1322 name = "pin-utils"
1323 @@ -3292,15 +3788,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1324 checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
1325
1326 [[package]]
1327+ name = "prettyplease"
1328+ version = "0.2.25"
1329+ source = "registry+https://github.com/rust-lang/crates.io-index"
1330+ checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
1331+ dependencies = [
1332+ "proc-macro2",
1333+ "syn 2.0.85",
1334+ ]
1335+
1336+ [[package]]
1337 name = "proc-macro2"
1338- version = "1.0.87"
1339+ version = "1.0.89"
1340 source = "registry+https://github.com/rust-lang/crates.io-index"
1341- checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
1342+ checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
1343 dependencies = [
1344 "unicode-ident",
1345 ]
1346
1347 [[package]]
1348+ name = "proxy-header"
1349+ version = "0.1.2"
1350+ source = "registry+https://github.com/rust-lang/crates.io-index"
1351+ checksum = "dc1493f63ddddfba840c3169e997c2905d09538ace72d64e84af6324c6e0e065"
1352+
1353+ [[package]]
1354 name = "ptr_meta"
1355 version = "0.1.4"
1356 source = "registry+https://github.com/rust-lang/crates.io-index"
1357 @@ -3321,6 +3833,12 @@ dependencies = [
1358 ]
1359
1360 [[package]]
1361+ name = "quick-error"
1362+ version = "1.2.3"
1363+ source = "registry+https://github.com/rust-lang/crates.io-index"
1364+ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
1365+
1366+ [[package]]
1367 name = "quick-xml"
1368 version = "0.32.0"
1369 source = "registry+https://github.com/rust-lang/crates.io-index"
1370 @@ -3369,6 +3887,12 @@ dependencies = [
1371 ]
1372
1373 [[package]]
1374+ name = "quoted_printable"
1375+ version = "0.5.1"
1376+ source = "registry+https://github.com/rust-lang/crates.io-index"
1377+ checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
1378+
1379+ [[package]]
1380 name = "radium"
1381 version = "0.7.0"
1382 source = "registry+https://github.com/rust-lang/crates.io-index"
1383 @@ -3455,9 +3979,9 @@ dependencies = [
1384
1385 [[package]]
1386 name = "regex"
1387- version = "1.11.0"
1388+ version = "1.11.1"
1389 source = "registry+https://github.com/rust-lang/crates.io-index"
1390- checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
1391+ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
1392 dependencies = [
1393 "aho-corasick 1.1.3",
1394 "memchr",
1395 @@ -3520,7 +4044,7 @@ dependencies = [
1396 "h2 0.3.26",
1397 "http 0.2.12",
1398 "http-body 0.4.6",
1399- "hyper 0.14.30",
1400+ "hyper 0.14.31",
1401 "hyper-tls 0.5.0",
1402 "ipnet",
1403 "js-sys",
1404 @@ -3561,7 +4085,7 @@ dependencies = [
1405 "http 1.1.0",
1406 "http-body 1.0.1",
1407 "http-body-util",
1408- "hyper 1.4.1",
1409+ "hyper 1.5.0",
1410 "hyper-rustls",
1411 "hyper-tls 0.6.0",
1412 "hyper-util",
1413 @@ -3590,6 +4114,31 @@ dependencies = [
1414 ]
1415
1416 [[package]]
1417+ name = "resolv-conf"
1418+ version = "0.7.0"
1419+ source = "registry+https://github.com/rust-lang/crates.io-index"
1420+ checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
1421+ dependencies = [
1422+ "hostname",
1423+ "quick-error",
1424+ ]
1425+
1426+ [[package]]
1427+ name = "ring"
1428+ version = "0.16.20"
1429+ source = "registry+https://github.com/rust-lang/crates.io-index"
1430+ checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
1431+ dependencies = [
1432+ "cc",
1433+ "libc",
1434+ "once_cell",
1435+ "spin 0.5.2",
1436+ "untrusted 0.7.1",
1437+ "web-sys",
1438+ "winapi",
1439+ ]
1440+
1441+ [[package]]
1442 name = "ring"
1443 version = "0.17.8"
1444 source = "registry+https://github.com/rust-lang/crates.io-index"
1445 @@ -3599,8 +4148,8 @@ dependencies = [
1446 "cfg-if",
1447 "getrandom",
1448 "libc",
1449- "spin",
1450- "untrusted",
1451+ "spin 0.9.8",
1452+ "untrusted 0.9.0",
1453 "windows-sys 0.52.0",
1454 ]
1455
1456 @@ -3672,6 +4221,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1457 checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
1458
1459 [[package]]
1460+ name = "rustc-hash"
1461+ version = "1.1.0"
1462+ source = "registry+https://github.com/rust-lang/crates.io-index"
1463+ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
1464+
1465+ [[package]]
1466 name = "rustc_version"
1467 version = "0.4.1"
1468 source = "registry+https://github.com/rust-lang/crates.io-index"
1469 @@ -3695,14 +4250,28 @@ dependencies = [
1470
1471 [[package]]
1472 name = "rustls"
1473- version = "0.23.14"
1474+ version = "0.21.12"
1475+ source = "registry+https://github.com/rust-lang/crates.io-index"
1476+ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
1477+ dependencies = [
1478+ "log",
1479+ "ring 0.17.8",
1480+ "rustls-webpki 0.101.7",
1481+ "sct",
1482+ ]
1483+
1484+ [[package]]
1485+ name = "rustls"
1486+ version = "0.23.15"
1487 source = "registry+https://github.com/rust-lang/crates.io-index"
1488- checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8"
1489+ checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993"
1490 dependencies = [
1491+ "aws-lc-rs",
1492+ "log",
1493 "once_cell",
1494- "ring",
1495+ "ring 0.17.8",
1496 "rustls-pki-types",
1497- "rustls-webpki",
1498+ "rustls-webpki 0.102.8",
1499 "subtle",
1500 "zeroize",
1501 ]
1502 @@ -3727,9 +4296,19 @@ dependencies = [
1503
1504 [[package]]
1505 name = "rustls-pki-types"
1506- version = "1.9.0"
1507+ version = "1.10.0"
1508 source = "registry+https://github.com/rust-lang/crates.io-index"
1509- checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55"
1510+ checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
1511+
1512+ [[package]]
1513+ name = "rustls-webpki"
1514+ version = "0.101.7"
1515+ source = "registry+https://github.com/rust-lang/crates.io-index"
1516+ checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
1517+ dependencies = [
1518+ "ring 0.17.8",
1519+ "untrusted 0.9.0",
1520+ ]
1521
1522 [[package]]
1523 name = "rustls-webpki"
1524 @@ -3737,16 +4316,17 @@ version = "0.102.8"
1525 source = "registry+https://github.com/rust-lang/crates.io-index"
1526 checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
1527 dependencies = [
1528- "ring",
1529+ "aws-lc-rs",
1530+ "ring 0.17.8",
1531 "rustls-pki-types",
1532- "untrusted",
1533+ "untrusted 0.9.0",
1534 ]
1535
1536 [[package]]
1537 name = "rustversion"
1538- version = "1.0.17"
1539+ version = "1.0.18"
1540 source = "registry+https://github.com/rust-lang/crates.io-index"
1541- checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
1542+ checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
1543
1544 [[package]]
1545 name = "ryu"
1546 @@ -3779,6 +4359,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1547 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1548
1549 [[package]]
1550+ name = "sct"
1551+ version = "0.7.1"
1552+ source = "registry+https://github.com/rust-lang/crates.io-index"
1553+ checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
1554+ dependencies = [
1555+ "ring 0.17.8",
1556+ "untrusted 0.9.0",
1557+ ]
1558+
1559+ [[package]]
1560 name = "seahash"
1561 version = "4.1.0"
1562 source = "registry+https://github.com/rust-lang/crates.io-index"
1563 @@ -3815,22 +4405,22 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
1564
1565 [[package]]
1566 name = "serde"
1567- version = "1.0.210"
1568+ version = "1.0.213"
1569 source = "registry+https://github.com/rust-lang/crates.io-index"
1570- checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
1571+ checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
1572 dependencies = [
1573 "serde_derive",
1574 ]
1575
1576 [[package]]
1577 name = "serde_derive"
1578- version = "1.0.210"
1579+ version = "1.0.213"
1580 source = "registry+https://github.com/rust-lang/crates.io-index"
1581- checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
1582+ checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
1583 dependencies = [
1584 "proc-macro2",
1585 "quote",
1586- "syn 2.0.79",
1587+ "syn 2.0.85",
1588 ]
1589
1590 [[package]]
1591 @@ -3848,9 +4438,9 @@ dependencies = [
1592
1593 [[package]]
1594 name = "serde_json"
1595- version = "1.0.128"
1596+ version = "1.0.132"
1597 source = "registry+https://github.com/rust-lang/crates.io-index"
1598- checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
1599+ checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
1600 dependencies = [
1601 "itoa",
1602 "memchr",
1603 @@ -3916,7 +4506,7 @@ dependencies = [
1604 "darling",
1605 "proc-macro2",
1606 "quote",
1607- "syn 2.0.79",
1608+ "syn 2.0.85",
1609 ]
1610
1611 [[package]]
1612 @@ -4037,6 +4627,15 @@ dependencies = [
1613 ]
1614
1615 [[package]]
1616+ name = "smtp-proto"
1617+ version = "0.1.5"
1618+ source = "registry+https://github.com/rust-lang/crates.io-index"
1619+ checksum = "51b8ad3dd187f0d4debab02ad65405a9919d6a4f7bce25bd64a258781063a53a"
1620+ dependencies = [
1621+ "serde",
1622+ ]
1623+
1624+ [[package]]
1625 name = "socket2"
1626 version = "0.5.7"
1627 source = "registry+https://github.com/rust-lang/crates.io-index"
1628 @@ -4048,6 +4647,12 @@ dependencies = [
1629
1630 [[package]]
1631 name = "spin"
1632+ version = "0.5.2"
1633+ source = "registry+https://github.com/rust-lang/crates.io-index"
1634+ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
1635+
1636+ [[package]]
1637+ name = "spin"
1638 version = "0.9.8"
1639 source = "registry+https://github.com/rust-lang/crates.io-index"
1640 checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
1641 @@ -4115,7 +4720,7 @@ dependencies = [
1642 "once_cell",
1643 "paste",
1644 "percent-encoding",
1645- "rustls",
1646+ "rustls 0.23.15",
1647 "rustls-pemfile 2.2.0",
1648 "serde",
1649 "serde_json",
1650 @@ -4141,7 +4746,7 @@ dependencies = [
1651 "quote",
1652 "sqlx-core",
1653 "sqlx-macros-core",
1654- "syn 2.0.79",
1655+ "syn 2.0.85",
1656 ]
1657
1658 [[package]]
1659 @@ -4164,7 +4769,7 @@ dependencies = [
1660 "sqlx-mysql",
1661 "sqlx-postgres",
1662 "sqlx-sqlite",
1663- "syn 2.0.79",
1664+ "syn 2.0.85",
1665 "tempfile",
1666 "tokio",
1667 "url",
1668 @@ -4330,9 +4935,9 @@ dependencies = [
1669
1670 [[package]]
1671 name = "syn"
1672- version = "2.0.79"
1673+ version = "2.0.85"
1674 source = "registry+https://github.com/rust-lang/crates.io-index"
1675- checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
1676+ checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
1677 dependencies = [
1678 "proc-macro2",
1679 "quote",
1680 @@ -4544,22 +5149,22 @@ dependencies = [
1681
1682 [[package]]
1683 name = "thiserror"
1684- version = "1.0.64"
1685+ version = "1.0.65"
1686 source = "registry+https://github.com/rust-lang/crates.io-index"
1687- checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
1688+ checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
1689 dependencies = [
1690 "thiserror-impl",
1691 ]
1692
1693 [[package]]
1694 name = "thiserror-impl"
1695- version = "1.0.64"
1696+ version = "1.0.65"
1697 source = "registry+https://github.com/rust-lang/crates.io-index"
1698- checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
1699+ checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
1700 dependencies = [
1701 "proc-macro2",
1702 "quote",
1703- "syn 2.0.79",
1704+ "syn 2.0.85",
1705 ]
1706
1707 [[package]]
1708 @@ -4652,9 +5257,9 @@ dependencies = [
1709
1710 [[package]]
1711 name = "tokio"
1712- version = "1.40.0"
1713+ version = "1.41.0"
1714 source = "registry+https://github.com/rust-lang/crates.io-index"
1715- checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
1716+ checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
1717 dependencies = [
1718 "backtrace",
1719 "bytes",
1720 @@ -4676,7 +5281,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
1721 dependencies = [
1722 "proc-macro2",
1723 "quote",
1724- "syn 2.0.79",
1725+ "syn 2.0.85",
1726 ]
1727
1728 [[package]]
1729 @@ -4691,11 +5296,21 @@ dependencies = [
1730
1731 [[package]]
1732 name = "tokio-rustls"
1733+ version = "0.24.1"
1734+ source = "registry+https://github.com/rust-lang/crates.io-index"
1735+ checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
1736+ dependencies = [
1737+ "rustls 0.21.12",
1738+ "tokio",
1739+ ]
1740+
1741+ [[package]]
1742+ name = "tokio-rustls"
1743 version = "0.26.0"
1744 source = "registry+https://github.com/rust-lang/crates.io-index"
1745 checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
1746 dependencies = [
1747- "rustls",
1748+ "rustls 0.23.15",
1749 "rustls-pki-types",
1750 "tokio",
1751 ]
1752 @@ -4725,6 +5340,7 @@ dependencies = [
1753 "futures-core",
1754 "pin-project-lite",
1755 "tokio",
1756+ "tokio-util",
1757 ]
1758
1759 [[package]]
1760 @@ -4737,6 +5353,8 @@ dependencies = [
1761 "futures-core",
1762 "futures-io",
1763 "futures-sink",
1764+ "futures-util",
1765+ "hashbrown 0.14.5",
1766 "pin-project-lite",
1767 "slab",
1768 "tokio",
1769 @@ -4849,7 +5467,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
1770 dependencies = [
1771 "proc-macro2",
1772 "quote",
1773- "syn 2.0.79",
1774+ "syn 2.0.85",
1775 ]
1776
1777 [[package]]
1778 @@ -5018,12 +5636,9 @@ dependencies = [
1779
1780 [[package]]
1781 name = "unicase"
1782- version = "2.7.0"
1783+ version = "2.8.0"
1784 source = "registry+https://github.com/rust-lang/crates.io-index"
1785- checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
1786- dependencies = [
1787- "version_check",
1788- ]
1789+ checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
1790
1791 [[package]]
1792 name = "unicode-bidi"
1793 @@ -5072,6 +5687,12 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
1794
1795 [[package]]
1796 name = "untrusted"
1797+ version = "0.7.1"
1798+ source = "registry+https://github.com/rust-lang/crates.io-index"
1799+ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
1800+
1801+ [[package]]
1802+ name = "untrusted"
1803 version = "0.9.0"
1804 source = "registry+https://github.com/rust-lang/crates.io-index"
1805 checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
1806 @@ -5083,7 +5704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1807 checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
1808 dependencies = [
1809 "form_urlencoded",
1810- "idna",
1811+ "idna 0.5.0",
1812 "percent-encoding",
1813 "serde",
1814 ]
1815 @@ -5102,9 +5723,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1816
1817 [[package]]
1818 name = "uuid"
1819- version = "1.10.0"
1820+ version = "1.11.0"
1821 source = "registry+https://github.com/rust-lang/crates.io-index"
1822- checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
1823+ checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
1824
1825 [[package]]
1826 name = "valuable"
1827 @@ -5189,7 +5810,7 @@ dependencies = [
1828 "once_cell",
1829 "proc-macro2",
1830 "quote",
1831- "syn 2.0.79",
1832+ "syn 2.0.85",
1833 "wasm-bindgen-shared",
1834 ]
1835
1836 @@ -5223,7 +5844,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
1837 dependencies = [
1838 "proc-macro2",
1839 "quote",
1840- "syn 2.0.79",
1841+ "syn 2.0.85",
1842 "wasm-bindgen-backend",
1843 "wasm-bindgen-shared",
1844 ]
1845 @@ -5291,6 +5912,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1846 checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
1847
1848 [[package]]
1849+ name = "which"
1850+ version = "4.4.2"
1851+ source = "registry+https://github.com/rust-lang/crates.io-index"
1852+ checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
1853+ dependencies = [
1854+ "either",
1855+ "home",
1856+ "once_cell",
1857+ "rustix",
1858+ ]
1859+
1860+ [[package]]
1861 name = "whoami"
1862 version = "1.5.2"
1863 source = "registry+https://github.com/rust-lang/crates.io-index"
1864 @@ -5301,6 +5934,12 @@ dependencies = [
1865 ]
1866
1867 [[package]]
1868+ name = "widestring"
1869+ version = "1.1.0"
1870+ source = "registry+https://github.com/rust-lang/crates.io-index"
1871+ checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
1872+
1873+ [[package]]
1874 name = "winapi"
1875 version = "0.3.9"
1876 source = "registry+https://github.com/rust-lang/crates.io-index"
1877 @@ -5599,7 +6238,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
1878 dependencies = [
1879 "proc-macro2",
1880 "quote",
1881- "syn 2.0.79",
1882+ "syn 2.0.85",
1883 ]
1884
1885 [[package]]
1886 @@ -5607,3 +6246,88 @@ name = "zeroize"
1887 version = "1.8.1"
1888 source = "registry+https://github.com/rust-lang/crates.io-index"
1889 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
1890+ dependencies = [
1891+ "zeroize_derive",
1892+ ]
1893+
1894+ [[package]]
1895+ name = "zeroize_derive"
1896+ version = "1.4.2"
1897+ source = "registry+https://github.com/rust-lang/crates.io-index"
1898+ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
1899+ dependencies = [
1900+ "proc-macro2",
1901+ "quote",
1902+ "syn 2.0.85",
1903+ ]
1904+
1905+ [[package]]
1906+ name = "zip"
1907+ version = "2.2.0"
1908+ source = "registry+https://github.com/rust-lang/crates.io-index"
1909+ checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494"
1910+ dependencies = [
1911+ "aes",
1912+ "arbitrary",
1913+ "bzip2",
1914+ "constant_time_eq",
1915+ "crc32fast",
1916+ "crossbeam-utils",
1917+ "deflate64",
1918+ "displaydoc",
1919+ "flate2",
1920+ "hmac",
1921+ "indexmap 2.6.0",
1922+ "lzma-rs",
1923+ "memchr",
1924+ "pbkdf2",
1925+ "rand",
1926+ "sha1",
1927+ "thiserror",
1928+ "time",
1929+ "zeroize",
1930+ "zopfli",
1931+ "zstd",
1932+ ]
1933+
1934+ [[package]]
1935+ name = "zopfli"
1936+ version = "0.8.1"
1937+ source = "registry+https://github.com/rust-lang/crates.io-index"
1938+ checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
1939+ dependencies = [
1940+ "bumpalo",
1941+ "crc32fast",
1942+ "lockfree-object-pool",
1943+ "log",
1944+ "once_cell",
1945+ "simd-adler32",
1946+ ]
1947+
1948+ [[package]]
1949+ name = "zstd"
1950+ version = "0.13.2"
1951+ source = "registry+https://github.com/rust-lang/crates.io-index"
1952+ checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
1953+ dependencies = [
1954+ "zstd-safe",
1955+ ]
1956+
1957+ [[package]]
1958+ name = "zstd-safe"
1959+ version = "7.2.1"
1960+ source = "registry+https://github.com/rust-lang/crates.io-index"
1961+ checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
1962+ dependencies = [
1963+ "zstd-sys",
1964+ ]
1965+
1966+ [[package]]
1967+ name = "zstd-sys"
1968+ version = "2.0.13+zstd.1.5.6"
1969+ source = "registry+https://github.com/rust-lang/crates.io-index"
1970+ checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
1971+ dependencies = [
1972+ "cc",
1973+ "pkg-config",
1974+ ]
1975 diff --git a/Cargo.toml b/Cargo.toml
1976index 180d60a..dad91ef 100644
1977--- a/Cargo.toml
1978+++ b/Cargo.toml
1979 @@ -9,7 +9,7 @@ members = [
1980 "crates/database",
1981 "ayllu",
1982 # "ayllu-build",
1983- # "ayllu-mail",
1984+ "ayllu-mail",
1985 # "ayllu-xmpp",
1986 "quipu"
1987 , "ayllu-jobs", "crates/timeutil"]
1988 diff --git a/ayllu-mail/Cargo.toml b/ayllu-mail/Cargo.toml
1989index 3c050f6..221411b 100644
1990--- a/ayllu-mail/Cargo.toml
1991+++ b/ayllu-mail/Cargo.toml
1992 @@ -7,9 +7,8 @@ edition = "2021"
1993 name = "ayllu-mail"
1994
1995 [dependencies]
1996- ayllu_api = {path = "../crates/api"}
1997 ayllu_config = {path = "../crates/config"}
1998- ayllu_rpc = {path = "../crates/rpc"}
1999+ ayllu_database = {path = "../crates/database"}
2000
2001 serde = { version = "1.0.188", features = ["derive"] }
2002 clap = { version = "4.4.6", features = ["derive"] }
2003 @@ -17,9 +16,12 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
2004 tracing = "0.1.37"
2005 tokio = { version = "1.33.0", features = ["full"] }
2006 futures = "0.3.28"
2007- melib = { git = "https://ayllu-forge.org/meli/meli", branch = "master" }
2008- mailpot = { git = "https://ayllu-forge.org/ayllu/mailpot", branch = "main" }
2009- maitred = { git = "https://ayllu-forge.org/ayllu/maitred", branch = "main" }
2010 clap_complete = "4.4.5"
2011- anyhow = "1.0.78"
2012 axum = "0.7.5"
2013+ thiserror = "1.0.65"
2014+ async-trait = "0.1.83"
2015+
2016+ [dependencies.maitred]
2017+ git = "https://ayllu-forge.org/ayllu/maitred"
2018+ branch = "main"
2019+ features = ["auth", "server"]
2020 diff --git a/ayllu-mail/src/config.rs b/ayllu-mail/src/config.rs
2021index dc05a68..7f86cba 100644
2022--- a/ayllu-mail/src/config.rs
2023+++ b/ayllu-mail/src/config.rs
2024 @@ -3,10 +3,10 @@ use std::path::PathBuf;
2025 use serde::{Deserialize, Serialize};
2026
2027 use ayllu_config::{data_dir, runtime_dir, Configurable};
2028- use mailpot::{Configuration as MailPotConfig, SendMail};
2029
2030 pub const EXAMPLE_CONFIG: &str = include_str!("../../config.example.toml");
2031
2032+
2033 #[derive(Serialize, Deserialize, Clone)]
2034 pub struct Database {
2035 pub migrate: Option<bool>,
2036 @@ -31,178 +31,56 @@ impl Default for Database {
2037 }
2038 }
2039
2040- /// PostPolicy
2041- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
2042- pub enum PostPolicy {
2043- AnnounceOnly,
2044- SubscriptionOnly,
2045- ApprovalNeeded,
2046- Open,
2047- #[default]
2048- Custom,
2049- }
2050-
2051- #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq, Eq)]
2052- pub enum SubscriptionPolicyKind {
2053- Request,
2054- Open,
2055- Manual,
2056- #[default]
2057- Custom,
2058- }
2059-
2060- /// SubscriptionPolicy
2061- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
2062- pub struct SubscriptionPolicy {
2063- pub send_confirmation: bool,
2064- pub kind: SubscriptionPolicyKind,
2065- }
2066-
2067 #[derive(Deserialize, Serialize, Clone, Debug, Default)]
2068 pub struct MailingList {
2069- pub id: String,
2070- pub name: Option<String>,
2071+ pub name: String,
2072 pub address: String,
2073 pub description: Option<String>,
2074 pub topics: Vec<String>,
2075- pub post_policy: PostPolicy,
2076- pub subscription_policy: SubscriptionPolicy,
2077- }
2078-
2079- #[derive(Deserialize, Serialize, Clone, Debug)]
2080- pub struct Http {
2081- #[serde(default = "Http::default_address")]
2082- pub address: String,
2083 }
2084
2085- impl Http {
2086- fn default_address() -> String {
2087- String::from("127.0.0.1:32001")
2088- }
2089+ #[derive(Clone, Default, Serialize, Deserialize)]
2090+ pub struct Dkim {
2091+ pub enabled: Option<bool>,
2092 }
2093
2094- impl Default for Http {
2095- fn default() -> Self {
2096- Http {
2097- address: Http::default_address(),
2098- }
2099- }
2100- }
2101-
2102- #[derive(Deserialize, Serialize, Clone, Debug)]
2103- pub struct NginxAuth {
2104- /// address to listen on for ngx_mail_auth_http_module requests
2105- #[serde(default = "NginxAuth::default_address")]
2106- pub listen: String,
2107- /// hostname of your smtp server
2108- #[serde(default = "NginxAuth::default_host")]
2109- pub host: String,
2110- /// port your smtp server is listening on
2111- #[serde(default = "NginxAuth::default_port")]
2112- pub port: u16,
2113- /// An array of domains that the mail server may accept mail for, if not
2114- /// specified mail to any domain will be accepted.
2115- pub domains: Option<Vec<String>>,
2116- }
2117-
2118- impl NginxAuth {
2119- fn default_host() -> String {
2120- String::from("127.0.0.1")
2121- }
2122-
2123- fn default_port() -> u16 {
2124- 2525
2125- }
2126-
2127- fn default_address() -> String {
2128- String::from("127.0.0.1:32001")
2129- }
2130+ #[derive(Clone, Default, Serialize, Deserialize)]
2131+ pub struct Spf {
2132+ pub enabled: Option<bool>,
2133 }
2134
2135- impl Default for NginxAuth {
2136- fn default() -> Self {
2137- NginxAuth {
2138- host: NginxAuth::default_host(),
2139- port: NginxAuth::default_port(),
2140- listen: NginxAuth::default_address(),
2141- domains: None,
2142- }
2143- }
2144+ #[derive(Clone, Default, Serialize, Deserialize)]
2145+ pub struct Tls {
2146+ pub certificate: PathBuf,
2147+ pub key: PathBuf,
2148 }
2149
2150- #[derive(Serialize, Deserialize, Clone)]
2151+ #[derive(Clone, Default, Serialize, Deserialize)]
2152 pub struct Mail {
2153- #[serde(default = "Mail::default_socket_path")]
2154- pub socket_path: String,
2155- #[serde(default = "Database::default")]
2156- pub database: Database,
2157- #[serde(default = "Mail::default_sendmail_command")]
2158- pub sendmail_command: String,
2159+ #[serde(default = "Mail::default_address")]
2160+ pub address: String,
2161+ pub dkim: Dkim,
2162+ pub spf: Spf,
2163+ pub tls: Option<Tls>,
2164+ pub proxy_protocol: Option<bool>,
2165 pub lists: Vec<MailingList>,
2166- #[serde(default = "Mail::default_postfix_user")]
2167- pub postfix_user: String,
2168- #[serde(default = "Mail::default_postfix_user")]
2169- pub postfix_group: String,
2170- #[serde(default = "NginxAuth::default")]
2171- pub nginx_auth: NginxAuth,
2172 }
2173
2174 impl Mail {
2175- fn default_socket_path() -> String {
2176- runtime_dir().to_str().unwrap().to_string() + "/ayllu-mail.sock"
2177- }
2178-
2179- fn default_sendmail_command() -> String {
2180- String::from("/usr/bin/false")
2181- }
2182-
2183- fn default_postfix_user() -> String {
2184- String::from("ayllu")
2185- }
2186- }
2187-
2188- impl Default for Mail {
2189- fn default() -> Self {
2190- Mail {
2191- socket_path: Mail::default_socket_path(),
2192- database: Database::default(),
2193- sendmail_command: Mail::default_sendmail_command(),
2194- lists: Vec::new(),
2195- postfix_user: Mail::default_postfix_user(),
2196- postfix_group: Mail::default_postfix_user(),
2197- nginx_auth: NginxAuth::default(),
2198- }
2199+ pub fn default_address() -> String {
2200+ String::from("127.0.0.1:30025")
2201 }
2202 }
2203
2204 #[derive(Serialize, Deserialize, Clone)]
2205 pub struct Config {
2206 pub sysadmin: Option<String>,
2207+ pub database: Database,
2208 pub log_level: String,
2209 #[serde(default = "Mail::default")]
2210 pub mail: Mail,
2211- #[serde(default = "NginxAuth::default")]
2212- pub smtp: NginxAuth,
2213 }
2214
2215- impl Config {
2216- pub fn mailpot_config(&self) -> MailPotConfig {
2217- let data_path = self
2218- .mail
2219- .database
2220- .path
2221- .parent()
2222- .expect("cannot resolve data path");
2223- MailPotConfig {
2224- send_mail: SendMail::ShellCommand(self.mail.sendmail_command.clone()),
2225- data_path: data_path.to_path_buf(),
2226- db_path: self.mail.database.path.clone(),
2227- administrators: self
2228- .sysadmin
2229- .clone()
2230- .map_or(vec![], |admin| vec![admin.clone()]),
2231- }
2232- }
2233- }
2234+ impl Config {}
2235
2236 impl Configurable for Config {}
2237 diff --git a/ayllu-mail/src/declarative.rs b/ayllu-mail/src/declarative.rs
2238deleted file mode 100644
2239index 6a842a7..0000000
2240--- a/ayllu-mail/src/declarative.rs
2241+++ /dev/null
2242 @@ -1,123 +0,0 @@
2243- use std::collections::HashMap;
2244-
2245- use mailpot::{
2246- models::{changesets::MailingListChangeset, MailingList, PostPolicy, SubscriptionPolicy},
2247- Connection, Result as MpResult,
2248- };
2249- use tracing::log;
2250-
2251- use crate::{config, config::Config};
2252-
2253- /// ensure the mailing lists in the global config are available
2254- pub fn initialize(cfg: &Config) -> MpResult<()> {
2255- let db = Connection::open_or_create_db(cfg.mailpot_config())?.trusted();
2256-
2257- let mut lists_by_id: HashMap<String, i64> = HashMap::new();
2258- let mut managed_lists: Vec<String> = Vec::new();
2259-
2260- for list in db.lists()? {
2261- lists_by_id.insert(list.id.clone(), list.pk);
2262- }
2263-
2264- // create missing lists
2265-
2266- for list in cfg.mail.lists.iter() {
2267- log::info!("configuring mailing list: {} [{}]", list.id, list.address);
2268- if !lists_by_id.contains_key(&list.id) {
2269- log::info!("creating mailing list: {}", list.address);
2270- let db_list = db.create_list(MailingList {
2271- pk: -1,
2272- name: list.name.clone().unwrap_or(list.id.clone()),
2273- id: list.id.clone(),
2274- address: list.address.clone(),
2275- topics: list.topics.clone(),
2276- description: list.description.clone(),
2277- archive_url: None,
2278- })?;
2279- lists_by_id.insert(db_list.id.clone(), db_list.pk);
2280- } else {
2281- let pk = *lists_by_id.get(&list.id).unwrap();
2282- // ensure the configuration is consistent
2283- db.update_list(MailingListChangeset {
2284- pk,
2285- // TODO: topics cannot be updated?
2286- description: list
2287- .description
2288- .as_ref()
2289- .map(|description| Some(description.clone())),
2290- ..Default::default()
2291- })?;
2292- }
2293- managed_lists.push(list.id.clone());
2294- }
2295-
2296- // set policies
2297-
2298- for list in cfg.mail.lists.iter() {
2299- let list_pk = *lists_by_id.get(&list.id).unwrap();
2300- let post_policy = PostPolicy {
2301- pk: 0,
2302- list: list_pk,
2303- announce_only: matches!(list.post_policy, config::PostPolicy::AnnounceOnly),
2304- subscription_only: matches!(list.post_policy, config::PostPolicy::SubscriptionOnly),
2305- approval_needed: matches!(list.post_policy, config::PostPolicy::ApprovalNeeded),
2306- open: matches!(list.post_policy, config::PostPolicy::Open),
2307- custom: matches!(list.post_policy, config::PostPolicy::Custom),
2308- };
2309- log::info!("setting post policy: {:?}", list.post_policy);
2310- db.set_list_post_policy(post_policy)?;
2311-
2312- let sub_policy = SubscriptionPolicy {
2313- pk: 0,
2314- list: list_pk,
2315- send_confirmation: list.subscription_policy.send_confirmation,
2316- open: matches!(
2317- list.subscription_policy.kind,
2318- config::SubscriptionPolicyKind::Open
2319- ),
2320- manual: matches!(
2321- list.subscription_policy.kind,
2322- config::SubscriptionPolicyKind::Manual
2323- ),
2324- request: matches!(
2325- list.subscription_policy.kind,
2326- config::SubscriptionPolicyKind::Request
2327- ),
2328- custom: matches!(
2329- list.subscription_policy.kind,
2330- config::SubscriptionPolicyKind::Custom
2331- ),
2332- };
2333-
2334- log::info!(
2335- "setting subscription policy: {:?}",
2336- list.subscription_policy
2337- );
2338-
2339- db.set_list_subscription_policy(sub_policy)?;
2340- }
2341-
2342- let extras: Vec<&String> = lists_by_id
2343- .keys()
2344- .filter(|name| !managed_lists.contains(name))
2345- .collect();
2346-
2347- if !extras.is_empty() {
2348- log::info!("database contains the following superfluous lists:");
2349- extras.iter().for_each(|extra| {
2350- log::info!("List: {}", extra);
2351- });
2352- for extra in extras.iter() {
2353- log::info!("disabling mailing list: {}", extra);
2354- let pk = *lists_by_id.get(&extra.to_string()).unwrap();
2355- db.update_list(MailingListChangeset {
2356- pk,
2357- enabled: Some(false),
2358- hidden: Some(true),
2359- ..Default::default()
2360- })?;
2361- }
2362- }
2363-
2364- Ok(())
2365- }
2366 diff --git a/ayllu-mail/src/error.rs b/ayllu-mail/src/error.rs
2367new file mode 100644
2368index 0000000..cdbbbb0
2369--- /dev/null
2370+++ b/ayllu-mail/src/error.rs
2371 @@ -0,0 +1,9 @@
2372+ use maitred::ServerError;
2373+
2374+ #[derive(Debug, thiserror::Error)]
2375+ pub enum Error {
2376+ #[error("SMTP Server Error: {0}")]
2377+ Maitred(#[from] ServerError),
2378+ #[error("Database: {0}")]
2379+ Database(#[from] ayllu_database::Error),
2380+ }
2381 diff --git a/ayllu-mail/src/mail_utils.rs b/ayllu-mail/src/mail_utils.rs
2382new file mode 100644
2383index 0000000..0396fd0
2384--- /dev/null
2385+++ b/ayllu-mail/src/mail_utils.rs
2386 @@ -0,0 +1,21 @@
2387+ use maitred::mail_parser::{Address, HeaderValue, Message};
2388+
2389+ pub fn reply_to(message: &Message) -> Option<Address<'static>> {
2390+ let address = match message.headers().iter().find(|header| {
2391+ let name = header.name().to_lowercase();
2392+ if name == "in-reply-to" {
2393+ return true;
2394+ }
2395+ false
2396+ }) {
2397+ Some(header) => {
2398+ if let HeaderValue::Address(address) = &header.value {
2399+ Some(address.clone().into_owned())
2400+ } else {
2401+ None
2402+ }
2403+ }
2404+ None => None,
2405+ };
2406+ address
2407+ }
2408 diff --git a/ayllu-mail/src/main.rs b/ayllu-mail/src/main.rs
2409index ef7c926..277876e 100644
2410--- a/ayllu-mail/src/main.rs
2411+++ b/ayllu-mail/src/main.rs
2412 @@ -1,26 +1,15 @@
2413- use std::io::{stdin, stdout, Read, Write};
2414+ use std::io::stdout;
2415 use std::path::PathBuf;
2416- use std::process::{Command as SystemCommand, Stdio};
2417 use std::str::FromStr;
2418
2419- use anyhow::{format_err, Error, Result};
2420 use clap::{arg, Command, CommandFactory, Parser, Subcommand};
2421 use clap_complete::{generate, Generator, Shell};
2422- use mailpot::models::{DbVal, MailingList, PostPolicy};
2423- use mailpot::{
2424- melib::Envelope,
2425- postfix::PostfixConfiguration,
2426- queue::{Queue, QueueEntry},
2427- transaction::TransactionBehavior,
2428- Connection,
2429- };
2430- use tokio::{runtime::Builder as TokioBuilder, task::LocalSet};
2431- use tracing::{log, Level};
2432+
2433+ use tracing::Level;
2434
2435 mod config;
2436- mod declarative;
2437- /// implements https://nginx.org/en/docs/mail/ngx_mail_auth_http_module.html#protocol
2438- mod nginx_auth;
2439+ mod error;
2440+ mod mail_utils;
2441 mod server;
2442
2443 #[derive(Parser)]
2444 @@ -44,14 +33,6 @@ struct Cli {
2445 command: Commands,
2446 }
2447
2448- #[derive(Subcommand, Debug, PartialEq)]
2449- enum Postfix {
2450- /// Generate service line entry for Postfix master.cf
2451- MasterCf {},
2452- /// Generate transport maps for Postfix
2453- Maps {},
2454- }
2455-
2456 #[derive(Subcommand, Debug)]
2457 enum Commands {
2458 /// generate autocomplete commands for common shells
2459 @@ -62,17 +43,8 @@ enum Commands {
2460 /// Configuration options.
2461 #[clap(subcommand)]
2462 Config(ayllu_config::Command),
2463- /// run the rpc server and mailing list manager
2464+ /// Run the mail server
2465 Serve {},
2466- /// post a new message into the mail queue from stdin
2467- Post {},
2468- /// send all queued messages to their recipients
2469- Send {},
2470- /// generate a postfix configuration
2471- Postfix {
2472- #[command(subcommand)]
2473- command: Postfix,
2474- },
2475 }
2476
2477 fn print_completions<G: Generator>(gen: G, cmd: &mut Command) {
2478 @@ -89,23 +61,10 @@ fn init_logger(level: Level) {
2479 tracing::info!("logger initialized");
2480 }
2481
2482- #[allow(clippy::type_complexity)]
2483- fn main() -> Result<(), Box<dyn std::error::Error>> {
2484+ #[tokio::main]
2485+ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2486 let cli = Cli::parse();
2487 let mut mail_cfg: config::Config = ayllu_config::Reader::load(cli.config.as_deref())?;
2488- if let Some(db_path) = cli.database {
2489- mail_cfg.mail.database.path.clone_from(&db_path);
2490- }
2491- // ensure the database path exists since mailpot doesn't create it for us
2492- std::fs::create_dir_all(
2493- mail_cfg
2494- .mail
2495- .database
2496- .path
2497- .parent()
2498- .expect("db path has no parent"),
2499- )?;
2500- declarative::initialize(&mail_cfg)?;
2501 match cli.command {
2502 Commands::Complete { shell } => {
2503 let mut cmd = Cli::command();
2504 @@ -117,133 +76,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
2505 Commands::Serve {} => {
2506 init_logger(cli.level.unwrap_or(Level::from_str(&mail_cfg.log_level)?));
2507 let cfg = mail_cfg.clone();
2508- let http_cfg = cfg.clone();
2509- std::thread::spawn(move || {
2510- TokioBuilder::new_current_thread()
2511- .enable_all()
2512- .build()
2513- .unwrap()
2514- .block_on(async move { nginx_auth::serve(http_cfg).await });
2515- });
2516- TokioBuilder::new_current_thread()
2517- .enable_all()
2518- .build()
2519- .unwrap()
2520- .block_on(async move { LocalSet::new().run_until(server::serve(&cfg)).await })?;
2521- }
2522- Commands::Post {} => {
2523- init_logger(cli.level.unwrap_or(Level::from_str(&mail_cfg.log_level)?));
2524- let mut input = String::new();
2525- stdin().read_to_string(&mut input)?;
2526- let envelope = Envelope::from_bytes(input.as_bytes(), None)?;
2527- let mut db = Connection::open_or_create_db(mail_cfg.mailpot_config())?.trusted();
2528- let tx = db.transaction(TransactionBehavior::Exclusive)?;
2529- tx.post(&envelope, input.as_bytes(), false)?;
2530- tx.commit()?;
2531- }
2532- Commands::Send {} => {
2533- init_logger(cli.level.unwrap_or(Level::from_str(&mail_cfg.log_level)?));
2534- let mut db = Connection::open_or_create_db(mail_cfg.mailpot_config())?.trusted();
2535- let tx = db.transaction(TransactionBehavior::Exclusive)?;
2536- let messages = tx.queue(Queue::Out)?;
2537- let mut failed: Vec<QueueEntry> = Vec::with_capacity(messages.len());
2538- fn submit(cmd: &str, msg: &QueueEntry) -> Result<()> {
2539- let mut child = SystemCommand::new("sh")
2540- .arg("-c")
2541- .arg(cmd)
2542- .env("TO_ADDRESS", msg.to_addresses.clone())
2543- .stdout(Stdio::piped())
2544- .stdin(Stdio::piped())
2545- .stderr(Stdio::piped())
2546- .spawn()?;
2547- let mut stdin = child
2548- .stdin
2549- .take()
2550- .ok_or_else(|| format_err!("failed to attach to stdin"))?;
2551-
2552- let builder = std::thread::Builder::new();
2553-
2554- std::thread::scope(|s| {
2555- let handler = builder.spawn_scoped(s, move || {
2556- stdin
2557- .write_all(&msg.message)
2558- .expect("Failed to write to stdin");
2559- })?;
2560-
2561- handler.join().map_err(|_| {
2562- format_err!(
2563- "Could not join with IPC communication thread for SMTP ShellCommand \
2564- process"
2565- )
2566- })?;
2567- let result = child.wait_with_output()?;
2568- if !result.status.success() {
2569- return Err(format_err!(
2570- "sendmail proccess failed with exit code: {:?}\n{}",
2571- result.status.code(),
2572- String::from_utf8(result.stderr).unwrap()
2573- ));
2574- }
2575- Ok::<(), Error>(())
2576- })?;
2577- Ok(())
2578- }
2579- for message in messages {
2580- if let Err(err) = submit(&mail_cfg.mail.sendmail_command, &message) {
2581- log::error!(
2582- "error sending message: {}\n{}",
2583- message.message_id,
2584- err.to_string()
2585- );
2586- failed.push(message.0);
2587- }
2588- }
2589- for mut message in failed {
2590- message.queue = Queue::Deferred;
2591- tx.insert_to_queue(message)?;
2592- }
2593- tx.commit()?;
2594- }
2595- Commands::Postfix { command } => {
2596- let this_exe = std::env::current_exe().unwrap();
2597- let cfg = PostfixConfiguration {
2598- user: std::borrow::Cow::from(mail_cfg.mail.postfix_user.clone()),
2599- binary_path: this_exe,
2600- ..Default::default()
2601- };
2602- match command {
2603- Postfix::MasterCf {} => {
2604- print!(
2605- "{}",
2606- cfg.generate_master_cf_entry(
2607- &mail_cfg.mailpot_config(),
2608- cli.config
2609- .expect("need to specify an absolute path to your config file")
2610- .as_path()
2611- )
2612- );
2613- }
2614- Postfix::Maps {} => {
2615- let mut db =
2616- Connection::open_or_create_db(mail_cfg.mailpot_config())?.trusted();
2617- let tx = db.transaction(TransactionBehavior::Exclusive)?;
2618- let lists: Result<
2619- Vec<(DbVal<MailingList>, Option<DbVal<PostPolicy>>)>,
2620- mailpot::Error,
2621- > = tx
2622- .lists()?
2623- .iter()
2624- .map(|list| {
2625- let policy = tx.list_post_policy(list.1)?;
2626- Ok((list.clone(), policy))
2627- })
2628- .collect();
2629- let maps = cfg.generate_maps(lists?.as_slice());
2630- print!("{}", maps);
2631- }
2632- }
2633+ server::serve(&cfg).await?
2634 }
2635 }
2636-
2637 Ok(())
2638 }
2639 diff --git a/ayllu-mail/src/nginx_auth.rs b/ayllu-mail/src/nginx_auth.rs
2640deleted file mode 100644
2641index f4fad42..0000000
2642--- a/ayllu-mail/src/nginx_auth.rs
2643+++ /dev/null
2644 @@ -1,88 +0,0 @@
2645- use std::sync::Arc;
2646-
2647- use axum::{
2648- body::Body,
2649- extract::{Request, State},
2650- http::{header::ToStrError, HeaderValue, StatusCode},
2651- response::Response,
2652- routing::get,
2653- Router,
2654- };
2655- use melib::email::parser::address::address;
2656-
2657- use crate::config::Config;
2658-
2659- fn to_str_err(e: ToStrError) -> StatusCode {
2660- tracing::error!("error reading header: {}", e.to_string());
2661- StatusCode::BAD_REQUEST
2662- }
2663-
2664- async fn handle(
2665- State(cfg): State<Arc<Config>>,
2666- req: Request,
2667- ) -> Result<Response<Body>, StatusCode> {
2668- tracing::info!("processing new SMTP authentication request with headers:");
2669- req.headers().iter().for_each(|(key, value)| {
2670- tracing::info!("{:?}: {:?}", key, value);
2671- });
2672- let auth_cfg = cfg.mail.nginx_auth.clone();
2673- let domains = auth_cfg.domains.unwrap_or(Vec::new());
2674- if let Some(helo) = req.headers().get("auth-smtp-helo") {
2675- let helo = helo.to_str().map_err(to_str_err)?;
2676- if !domains.is_empty() && !domains.contains(&helo.to_string()) {
2677- tracing::warn!("domain {} is not authroized", helo);
2678- return Err(StatusCode::UNAUTHORIZED);
2679- }
2680- } else {
2681- tracing::warn!("missing auth-smtp-helo header");
2682- };
2683- if let Some(to) = req.headers().get("auth-smtp-to") {
2684- let recipient = to.to_str().map_err(to_str_err)?;
2685- let recipient = recipient.trim_start_matches("RCPT TO:");
2686- let recipient_addr = address(recipient.as_bytes()).map_err(|e| {
2687- tracing::warn!("cannot parse mail address: {:?}", e);
2688- StatusCode::BAD_REQUEST
2689- })?;
2690- let recipient_addr = recipient_addr.1;
2691- if let Some(fqdn) = recipient_addr.get_fqdn() {
2692- if !domains.is_empty() && !domains.contains(&fqdn) {
2693- tracing::warn!("mail is not permitted to be sent to domain: {}", fqdn);
2694- return Err(StatusCode::UNAUTHORIZED);
2695- }
2696- } else {
2697- tracing::warn!("recipient address has not FQDN");
2698- return Err(StatusCode::BAD_REQUEST);
2699- }
2700- } else {
2701- tracing::warn!("missing auth-smtp-to header");
2702- return Err(StatusCode::BAD_REQUEST);
2703- }
2704- let mut res = Response::new(Body::empty());
2705- let headers = res.headers_mut();
2706- headers.insert("Auth-Status", HeaderValue::from_static("OK"));
2707- headers.insert(
2708- "Auth-Server",
2709- HeaderValue::from_str(&cfg.smtp.host).expect("invalid downstream smtp server"),
2710- );
2711- headers.insert(
2712- "Auth-Port",
2713- HeaderValue::from_str(&cfg.smtp.port.to_string()).unwrap(),
2714- );
2715- Ok(res)
2716- }
2717-
2718- /// Simple HTTP server implementing Nginx's ngx_mail_auth_http protocol
2719- /// https://nginx.org/en/docs/mail/ngx_mail_auth_http_module.html#protocol
2720- /// which is used to authenticate and redirect requests to an e-mail server.
2721- /// The primary use case for this is to avoid exposing Postfix directly to
2722- /// the internet and proxy SMTP requests via Nginx. This also enables
2723- /// termination of TLS with Nginx.
2724- pub async fn serve(cfg: Config) {
2725- let listen_addr = cfg.mail.nginx_auth.listen.clone();
2726- let app = Router::new()
2727- .route("/", get(handle))
2728- .with_state(Arc::new(cfg.clone()));
2729- tracing::info!("nginx_auth module listing @ {}", listen_addr);
2730- let listener = tokio::net::TcpListener::bind(listen_addr).await.unwrap();
2731- axum::serve(listener, app).await.unwrap();
2732- }
2733 diff --git a/ayllu-mail/src/server.rs b/ayllu-mail/src/server.rs
2734index a23bf72..6f94384 100644
2735--- a/ayllu-mail/src/server.rs
2736+++ b/ayllu-mail/src/server.rs
2737 @@ -1,242 +1,66 @@
2738- use std::error::Error as StdError;
2739- use std::path::Path;
2740-
2741- use anyhow::Result;
2742- use mailpot::{
2743- models::{DbVal, Post},
2744- Configuration, Connection, Error as MailpotError,
2745- };
2746- use melib::Envelope;
2747- use tracing::log::info;
2748+ use maitred::delivery::{Delivery, DeliveryError};
2749+ use maitred::mail_parser::Message;
2750+ use maitred::milter::MilterFunc;
2751+ use maitred::server::Server;
2752+ use maitred::session::Envelope;
2753
2754 use crate::config::Config;
2755+ use crate::error::Error;
2756
2757- use ayllu_api::{
2758- error::ApiError,
2759- mail::{MailingList, Message, Server as MailServer, Thread},
2760- };
2761-
2762- use ayllu_rpc::{
2763- futures::prelude::*,
2764- init_socket, spawn,
2765- tarpc::{
2766- context::Context,
2767- serde_transport::unix,
2768- server::{BaseChannel, Channel},
2769- tokio_serde::formats::Bincode,
2770- },
2771- };
2772-
2773- fn is_patch(subject: &str) -> bool {
2774- subject.contains("[PATCH]") || subject.contains("[patch]")
2775- }
2776+ use ayllu_database::{mail::MailExt, Builder, Wrapper as Database};
2777
2778- fn to_api_error(e: MailpotError) -> ApiError {
2779- ApiError::Message(e.to_string())
2780+ pub struct DbStore {
2781+ db: Database,
2782 }
2783
2784- fn to_message(post: &DbVal<Post>) -> Message {
2785- let envelope = Envelope::from_bytes(&post.message, None).unwrap();
2786- let text = envelope
2787- .body_bytes(&post.message)
2788- .text(melib::attachment_types::Text::Rfc822);
2789- let body = String::from_utf8(post.message.to_vec()).unwrap();
2790- Message {
2791- id: post.pk,
2792- message_id: post.message_id.clone(),
2793- timestamp: post.timestamp,
2794- from: envelope.from[0].get_email(),
2795- address: post.address.clone(),
2796- subject: envelope.subject.clone(),
2797- body,
2798- text,
2799- is_patch: envelope.subject.map_or(false, |subject| is_patch(&subject)),
2800+ impl DbStore {
2801+ pub fn new(db: Database) -> Self {
2802+ DbStore { db }
2803 }
2804 }
2805
2806- #[derive(Clone)]
2807- struct ServerImpl {
2808- mailpot_config: Configuration,
2809- }
2810-
2811- // BUG: these are all read-only functions but mailpot requires trusted()
2812-
2813- impl MailServer for ServerImpl {
2814- #[doc = r" return all managed mailing lists"]
2815- async fn lists(self, _context: Context) -> Result<Vec<MailingList>, ApiError> {
2816- let db = Connection::open_or_create_db(self.mailpot_config.clone())
2817- .map_err(to_api_error)?
2818- .trusted();
2819- Ok(db
2820- .lists()
2821- .map_err(|e| ApiError::Message(e.to_string()))?
2822- .iter()
2823- .map(|db_val| MailingList {
2824- id: db_val.id.clone(),
2825- name: db_val.name.clone(),
2826- request_address: db_val.request_subaddr().clone(),
2827- address: db_val.address.clone(),
2828- description: db_val
2829- .description
2830- .as_ref()
2831- .map_or(String::new(), |desc| desc.clone()),
2832- topics: db_val.topics.clone(),
2833- })
2834- .collect())
2835- }
2836-
2837- #[doc = r" list all of the threads associated with a mailing list"]
2838- async fn list_threads(
2839- self,
2840- _context: Context,
2841- id: String,
2842- _offset: i64,
2843- _limit: i64,
2844- ) -> Result<Vec<Thread>, ApiError> {
2845- let db = Connection::open_or_create_db(self.mailpot_config.clone())
2846- .map_err(to_api_error)?
2847- .trusted();
2848- let list = db
2849- .list_by_id(&id)
2850- .map_err(to_api_error)?
2851- .ok_or(ApiError::NotFound(format!(
2852- "no mailing list with id {} exists",
2853- id
2854- )))?;
2855- let posts = db.list_posts(list.pk, None).map_err(to_api_error)?;
2856- // TODO: add a new database method to only return posts without
2857- // reply headers to save cpu cycles
2858- let posts: Vec<&DbVal<Post>> = posts
2859- .iter()
2860- .filter(|post| {
2861- let envelope = Envelope::from_bytes(&post.0.message, None).unwrap();
2862- envelope.in_reply_to.is_none()
2863- })
2864- .collect();
2865- let mut threads: Vec<Thread> = Vec::with_capacity(posts.len());
2866- for post in posts.iter() {
2867- let replies = db
2868- .list_thread(list.pk, &post.message_id)
2869- .map_err(to_api_error)?;
2870- let first = to_message(post);
2871- let has_patch = first.is_patch;
2872- threads.push(Thread {
2873- first,
2874- n_replies: replies.len() as i64,
2875- has_patch,
2876- });
2877+ #[async_trait::async_trait]
2878+ impl Delivery for DbStore {
2879+ async fn deliver(&self, message: &Envelope) -> Result<(), DeliveryError> {
2880+ println!("RCPT TO: {:?}", message.rcpt_to);
2881+ for header in message.body.headers() {
2882+ println!("HEADER: {}: {:?}", header.name(), header.value());
2883 }
2884- Ok(threads)
2885+ Ok(())
2886 }
2887+ }
2888
2889- #[doc = r" list all of the responses associated with a post starting from messageId"]
2890- async fn read_thread(
2891- self,
2892- _context: Context,
2893- id: String,
2894- message_id: String,
2895- _offset: i64,
2896- _limit: i64,
2897- ) -> Result<Vec<Message>, ApiError> {
2898- let db = Connection::open_or_create_db(self.mailpot_config.clone())
2899- .map_err(to_api_error)?
2900- .trusted();
2901- let list = db
2902- .list_by_id(&id)
2903- .map_err(to_api_error)?
2904- .ok_or(ApiError::NotFound(format!(
2905- "no mailing list with id {} exists",
2906- id
2907- )))?;
2908- let mut posts: Vec<Message> = Vec::new();
2909- // Read the initial post
2910- let first_post = db
2911- .list_post_by_message_id(list.pk, &message_id)
2912- .map_err(to_api_error)?
2913- .ok_or(ApiError::NotFound(format!(
2914- "no post with id {} exists",
2915- list.pk
2916- )))?;
2917- posts.push(to_message(&first_post));
2918- // then all of it's replies
2919- let replies = db.list_thread(list.pk, &message_id).map_err(to_api_error)?;
2920- for (_, post) in replies.iter() {
2921- posts.push(to_message(post));
2922- }
2923- Ok(posts)
2924- }
2925+ pub async fn serve(config: &Config) -> Result<(), Error> {
2926+ let db = Builder::default()
2927+ .url(config.database.path.to_str().unwrap())
2928+ .log_queries(config.log_level == "DEBUG")
2929+ .read_only(true)
2930+ .build()
2931+ .await?;
2932+ let db_store = DbStore::new(db);
2933+ let mail_config = &config.mail;
2934+ // initialize maildirs before starting
2935+ let mut mail_server = Server::default()
2936+ .address(&mail_config.address)
2937+ .with_milter(MilterFunc(|message: &Message<'static>| {
2938+ let message = message.clone();
2939+ async move { Ok(message.to_owned()) }
2940+ }))
2941+ .with_delivery(db_store)
2942+ .dkim_verification(mail_config.dkim.enabled.is_some_and(|enabled| enabled))
2943+ .spf_verification(mail_config.spf.enabled.is_some_and(|enabled| enabled));
2944
2945- #[doc = r" read a single post"]
2946- async fn read_post(
2947- self,
2948- _context: Context,
2949- id: String,
2950- message_id: String,
2951- ) -> Result<Message, ApiError> {
2952- let db = Connection::open_or_create_db(self.mailpot_config.clone())
2953- .map_err(to_api_error)?
2954- .trusted();
2955- let list = db
2956- .list_by_id(&id)
2957- .map_err(to_api_error)?
2958- .ok_or(ApiError::NotFound(format!(
2959- "no mailing list with id {} exists",
2960- id
2961- )))?;
2962- let post = db
2963- .list_post_by_message_id(list.pk, &message_id)
2964- .map_err(to_api_error)?
2965- .ok_or(ApiError::NotFound(format!(
2966- "no post with id {}",
2967- message_id
2968- )))?;
2969- Ok(to_message(&post))
2970+ if let Some(tls_config) = mail_config.tls.as_ref() {
2971+ tracing::info!("TLS enabled");
2972+ mail_server = mail_server.with_certificates(&tls_config.key, &tls_config.certificate);
2973+ // session_opts = session_opts.starttls_enabled(true);
2974 }
2975
2976- #[doc = r" export one or more messages in mbox format"]
2977- async fn export(
2978- self,
2979- _context: Context,
2980- id: String,
2981- message_id: Option<String>,
2982- as_thread: bool,
2983- ) -> Result<Vec<u8>, ApiError> {
2984- let db = Connection::open_or_create_db(self.mailpot_config.clone())
2985- .map_err(to_api_error)?
2986- .trusted();
2987- let list = db
2988- .list_by_id(&id)
2989- .map_err(to_api_error)?
2990- .ok_or(ApiError::NotFound(format!(
2991- "no mailing list with id {} exists",
2992- id
2993- )))?;
2994- let buf = db
2995- .export_mbox(list.pk, message_id.as_deref(), as_thread)
2996- .map_err(to_api_error)?;
2997- Ok(buf)
2998- }
2999- }
3000+ if mail_config.proxy_protocol.is_some_and(|enabled| enabled) {
3001+ mail_server = mail_server.proxy_protocol(true);
3002+ };
3003
3004- pub async fn serve(cfg: &Config) -> Result<(), Box<dyn StdError>> {
3005- let socket_path = Path::new(&cfg.mail.socket_path);
3006- init_socket(socket_path)?;
3007- info!("mail server listening @ {:?}", socket_path);
3008- let mut listener = unix::listen(socket_path, Bincode::default).await?;
3009- listener.config_mut().max_frame_length(usize::MAX);
3010- listener
3011- // Ignore accept errors.
3012- .filter_map(|r| future::ready(r.ok()))
3013- .map(BaseChannel::with_defaults)
3014- .map(move |channel| {
3015- let server = ServerImpl {
3016- mailpot_config: cfg.mailpot_config(),
3017- };
3018- channel.execute(server.serve()).for_each(spawn)
3019- })
3020- // Max 10 channels.
3021- .buffer_unordered(10)
3022- .for_each(|_| async {})
3023- .await;
3024+ // mail_server = mail_server.with_session_opts(session_opts);
3025+ mail_server.listen().await?;
3026 Ok(())
3027 }
3028 diff --git a/ayllu/src/config.rs b/ayllu/src/config.rs
3029index 78ed315..47ebf6f 100644
3030--- a/ayllu/src/config.rs
3031+++ b/ayllu/src/config.rs
3032 @@ -177,11 +177,11 @@ impl Xmpp {
3033
3034 #[derive(Deserialize, Serialize, Clone, Debug, Default)]
3035 pub struct MailingList {
3036- pub id: String,
3037- pub name: Option<String>,
3038+ pub name: String,
3039 pub address: String,
3040 pub description: Option<String>,
3041 pub topics: Vec<String>,
3042+ pub request_address: String,
3043 // pub post_policy: PostPolicy,
3044 // pub subscription_policy: SubscriptionPolicy,
3045 }
3046 diff --git a/ayllu/src/web2/error.rs b/ayllu/src/web2/error.rs
3047index 4f20162..2b40dfb 100644
3048--- a/ayllu/src/web2/error.rs
3049+++ b/ayllu/src/web2/error.rs
3050 @@ -20,6 +20,8 @@ pub enum Error {
3051 Message(String),
3052 #[error("Resource not found: {0}")]
3053 NotFound(String),
3054+ #[error("Component not enabled: {0}")]
3055+ ComponentNotEnabled(String)
3056 }
3057
3058 impl IntoResponse for Error {
3059 diff --git a/ayllu/src/web2/middleware/error.rs b/ayllu/src/web2/middleware/error.rs
3060index 57edae0..03bbdf3 100644
3061--- a/ayllu/src/web2/middleware/error.rs
3062+++ b/ayllu/src/web2/middleware/error.rs
3063 @@ -25,6 +25,7 @@ pub async fn middleware(
3064 let status_code = match error {
3065 Error::Message(_) => StatusCode::INTERNAL_SERVER_ERROR,
3066 Error::NotFound(_) => StatusCode::NOT_FOUND,
3067+ Error::ComponentNotEnabled(_) => StatusCode::INTERNAL_SERVER_ERROR,
3068 };
3069 // reload the theme since the middleware may not have ran yet
3070 let loader = Loader {
3071 diff --git a/ayllu/src/web2/routes/mail.rs b/ayllu/src/web2/routes/mail.rs
3072index 5052cbf..76550ad 100644
3073--- a/ayllu/src/web2/routes/mail.rs
3074+++ b/ayllu/src/web2/routes/mail.rs
3075 @@ -7,15 +7,15 @@ use axum::{
3076 };
3077 use serde::{Deserialize, Serialize};
3078
3079- use crate::highlight::Highlighter;
3080- use crate::web2::error::Error;
3081 use crate::web2::middleware::template::Template;
3082 use crate::web2::navigation;
3083+ use crate::{config::Config, highlight::Highlighter};
3084+ use crate::{config::Mail, web2::error::Error};
3085 use ayllu_rpc::tarpc::context;
3086
3087 #[derive(Deserialize)]
3088 pub struct Params {
3089- pub list_id: String,
3090+ pub list_name: String,
3091 pub thread_id: Option<String>,
3092 pub message_id: Option<String>,
3093 }
3094 @@ -42,21 +42,16 @@ struct Message {
3095 pub is_patch: bool,
3096 }
3097
3098- #[derive(Debug, Serialize, Default, PartialEq)]
3099- struct List {
3100- pub id: String,
3101- pub name: String,
3102- pub description: Option<String>,
3103- pub address: String,
3104- pub topics: Vec<String>,
3105- pub request_address: String,
3106- }
3107-
3108 pub async fn lists(
3109+ Extension(cfg): Extension<Config>,
3110 Extension((templates, mut ctx)): Extension<Template>,
3111 ) -> Result<Html<String>, Error> {
3112 ctx.insert("title", "lists");
3113 ctx.insert("nav_elements", &navigation::global("mail", true));
3114+ let mail_cfg = cfg
3115+ .mail
3116+ .ok_or(Error::ComponentNotEnabled(String::from("mail")))?;
3117+ ctx.insert("lists", &mail_cfg.lists.clone());
3118 // let mail_client = initiator.mail.unwrap();
3119 // let rpc_lists = mail_client.lists(context::current()).await??;
3120 // ctx.insert("lists", &rpc_lists);
3121 @@ -67,14 +62,28 @@ pub async fn lists(
3122
3123 #[debug_handler]
3124 pub async fn threads(
3125+ Extension(cfg): Extension<Config>,
3126 Path(params): Path<Params>,
3127 // Extension(db): Extension<Arc<Database>>,
3128 Extension((templates, mut ctx)): Extension<Template>,
3129 ) -> Result<Html<String>, Error> {
3130- Err(Error::NotFound(format!(
3131- "no mailing list with id: {} exists",
3132- params.list_id
3133- )))
3134+ ctx.insert("title", "lists");
3135+ ctx.insert("nav_elements", &navigation::global("mail", true));
3136+ let mail_cfg = cfg
3137+ .mail
3138+ .ok_or(Error::ComponentNotEnabled(String::from("mail")))?;
3139+ let mailing_list = mail_cfg
3140+ .lists
3141+ .iter()
3142+ .find(|list| list.name == params.list_name)
3143+ .ok_or(Error::NotFound(params.list_name))?;
3144+ ctx.insert("list", &mailing_list);
3145+
3146+ // FIXME
3147+ let threads: Vec<Thread> = Vec::new();
3148+ ctx.insert("threads", &threads);
3149+ let body = templates.render("threads.html", &ctx)?;
3150+ Ok(Html(body))
3151 // let mailing_list = lists.iter().find(|list| list.id == params.list_id);
3152 // if let Some(mailing_list) = mailing_list {
3153 // ctx.insert("title", &format!("list {}", mailing_list.name));
3154 @@ -106,8 +115,8 @@ pub async fn thread(
3155 Extension((templates, mut ctx)): Extension<Template>,
3156 ) -> Result<Html<String>, Error> {
3157 Err(Error::NotFound(format!(
3158- "no mailing list with id: {} exists",
3159- params.list_id
3160+ "no mailing list with name: {} exists",
3161+ params.list_name
3162 )))
3163 // let client = initiator.mail.unwrap();
3164 // let lists = client.lists(context::current()).await??;
3165 @@ -163,8 +172,8 @@ pub async fn message(
3166 Extension((templates, mut ctx)): Extension<Template>,
3167 ) -> Result<Html<String>, Error> {
3168 Err(Error::NotFound(format!(
3169- "no mailing list with id: {} exists",
3170- params.list_id
3171+ "no mailing list with name: {} exists",
3172+ params.list_name
3173 )))
3174 // let client = initiator.mail.unwrap();
3175 // let lists = client.lists(context::current()).await??;
3176 @@ -200,8 +209,8 @@ pub async fn message(
3177
3178 pub async fn export(Path(params): Path<Params>) -> Result<Response, Error> {
3179 Err(Error::NotFound(format!(
3180- "no mailing list with id: {} exists",
3181- params.list_id
3182+ "no mailing list with name: {} exists",
3183+ params.list_name
3184 )))
3185 // let client = initiator.mail.unwrap();
3186 // let lists = client.lists(context::current()).await??;
3187 diff --git a/ayllu/src/web2/server.rs b/ayllu/src/web2/server.rs
3188index f03b1f1..324bdf8 100644
3189--- a/ayllu/src/web2/server.rs
3190+++ b/ayllu/src/web2/server.rs
3191 @@ -146,15 +146,15 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
3192 "/mail",
3193 Router::new()
3194 .route("/", routing::get(mail::lists))
3195- .route("/:list_id", routing::get(mail::threads))
3196- .route("/export/:list_id", routing::get(mail::export))
3197- .route("/export/:list_id/:thread_id", routing::get(mail::export))
3198+ .route("/:list_name", routing::get(mail::threads))
3199+ .route("/export/:list_name", routing::get(mail::export))
3200+ .route("/export/:list_name/:thread_id", routing::get(mail::export))
3201 .route(
3202- "/export/:list_id/:thread_id/:message_id",
3203+ "/export/:list_name/:thread_id/:message_id",
3204 routing::get(mail::export),
3205 )
3206- .route("/thread/:list_id/:message_id", routing::get(mail::thread))
3207- .route("/message/:list_id/:message_id", routing::get(mail::message))
3208+ .route("/thread/:list_name/:message_id", routing::get(mail::thread))
3209+ .route("/message/:list_name/:message_id", routing::get(mail::message))
3210 .layer(from_fn_with_state(
3211 Arc::new((cfg.clone(), themes.clone())),
3212 template::middleware,
3213 diff --git a/ayllu/themes/default/templates/base.html b/ayllu/themes/default/templates/base.html
3214index e68a222..b71fcc3 100644
3215--- a/ayllu/themes/default/templates/base.html
3216+++ b/ayllu/themes/default/templates/base.html
3217 @@ -24,7 +24,7 @@
3218 </div>
3219 </main>
3220 <footer>
3221- Rendered {%- if render_time %} in ({{ render_time }} ms){%- endif %} @ {{ current_time }}
3222+ Rendered {%- if render_time %}in ({{ render_time }} ms){%- endif %} @ {{ current_time }}
3223 </footer>
3224 </body>
3225 </html>
3226 diff --git a/ayllu/themes/default/templates/blob.html b/ayllu/themes/default/templates/blob.html
3227index d78942a..e880df9 100644
3228--- a/ayllu/themes/default/templates/blob.html
3229+++ b/ayllu/themes/default/templates/blob.html
3230 @@ -4,9 +4,9 @@
3231 <section>
3232 {{ macros::navigation(items=subnav_elements, title="Blob") }}
3233 <section class="info-bar">
3234- {%- if hint -%}
3235- <span class="hint" style="color: {{ color }}">{{ hint }}</span>
3236- {%- endif -%}
3237+ {%- if hint -%}
3238+ <span class="hint" style="color: {{ color }}">{{ hint }}</span>
3239+ {%- endif -%}
3240 <span>{{ file_name }}</span>
3241 <span class="right">{{ file_mode | filemode }} {{ file_size | human_bytes }}</span>
3242 </section>
3243 diff --git a/ayllu/themes/default/templates/index.html b/ayllu/themes/default/templates/index.html
3244index 1fdcc6e..7b1ab65 100644
3245--- a/ayllu/themes/default/templates/index.html
3246+++ b/ayllu/themes/default/templates/index.html
3247 @@ -34,7 +34,7 @@
3248 {%- if repo.is_mirror %} <span class="tiny-highlight">[mirror]</span> {%- endif -%}
3249 </div>
3250 </td>
3251- <td>{{ repo.description | truncate(length=50, end="...")}}</td>
3252+ <td>{{ repo.description | truncate(length=50, end="...") }}</td>
3253 <td>{{ repo.age }}</td>
3254 <td class="collapse">{{ repo.activity }}</td>
3255 </tr>
3256 diff --git a/ayllu/themes/default/templates/lists.html b/ayllu/themes/default/templates/lists.html
3257index ad55e09..d91cef2 100644
3258--- a/ayllu/themes/default/templates/lists.html
3259+++ b/ayllu/themes/default/templates/lists.html
3260 @@ -2,18 +2,26 @@
3261 {% extends "base.html" %}
3262 {% block content %}
3263 <h1>Mailing Lists</h1>
3264- <div class="stretch">
3265- {% for list in lists %}
3266- <section class="card">
3267- <article class="segmented-4">
3268- <div>
3269- <a href="/mail/{{ list.id }}">{{ list.name }} [{{ list.id }}]</a>
3270- </div>
3271- <div>{{ list.description }}</div>
3272- <div>{{ list.address }}</div>
3273- <div>▃▅▇▇▁▅▃▅▅▃▅▁▁</div>
3274- </article>
3275- </section>
3276- {% endfor %}
3277- </div>
3278+ <section>
3279+ <table class="data-table">
3280+ <thead>
3281+ <tr>
3282+ <th>Name</th>
3283+ <th>Description</th>
3284+ <th>Address</th>
3285+ </tr>
3286+ </thead>
3287+ <tbody>
3288+ {% for list in lists %}
3289+ <tr>
3290+ <td>
3291+ <a href="/mail/{{ list.name }}">{{ list.name }} [{{ list.id }}]</a>
3292+ </td>
3293+ <td>{{ list.description }}</td>
3294+ <td>{{ list.address }}</td>
3295+ </tr>
3296+ {% endfor %}
3297+ </tbody>
3298+ </table>
3299+ </section>
3300 {% endblock %}
3301 diff --git a/ayllu/themes/default/templates/threads.html b/ayllu/themes/default/templates/threads.html
3302index c45476a..57fd7a1 100644
3303--- a/ayllu/themes/default/templates/threads.html
3304+++ b/ayllu/themes/default/templates/threads.html
3305 @@ -14,18 +14,18 @@
3306 </br>
3307 <h4>Subscribe</h4>
3308 <p>
3309- Send an e-mail to <a href="mailto:{{ request_address }}?subject=subscribe">{{ request_address }}</a> with the following subject: <code>subscribe</code>
3310+ Send an e-mail to <a href="mailto:{{ list.request_address }}?subject=subscribe">{{ list.request_address }}</a> with the following subject: <code>subscribe</code>
3311 </p>
3312 <h4>Unsubscribe</h4>
3313 <p>
3314- Send an e-mail to <a href="mailto:{{ request_address }}?subject=unsubscribe">{{ request_address }}</a> with the following subject: <code>unsubscribe</code>
3315+ Send an e-mail to <a href="mailto:{{ list.request_address }}?subject=unsubscribe">{{ list.request_address }}</a> with the following subject: <code>unsubscribe</code>
3316 </p>
3317 <h4>Export List</h4>
3318 <p>
3319- <a href="/mail/export/{{ list.id }}">mbox</a>
3320+ <a href="/mail/export/{{ list.name }}">mbox</a>
3321 </p>
3322 </div>
3323- <h4>Messages</h4>
3324+ <h4>Threads</h4>
3325 <div class="stretch">
3326 {% for thread in threads %}
3327 <section class="card">
3328 diff --git a/contrib/demo.neomuttrc b/contrib/demo.neomuttrc
3329new file mode 100644
3330index 0000000..5cf6e57
3331--- /dev/null
3332+++ b/contrib/demo.neomuttrc
3333 @@ -0,0 +1,55 @@
3334+ # vim: set filetype=neomuttrc :
3335+
3336+ set mbox_type=Maildir
3337+ set folder="./mail/demo@example.org"
3338+ set spoolfile=+
3339+
3340+ set mbox_type=Maildir
3341+ set folder="./mail/demo2@example.org"
3342+ set spoolfile=+
3343+
3344+ set realname = 'Demo User 1'
3345+ set from = demo-1@example.org
3346+
3347+ set smtp_url=smtp://127.0.0.1:30025
3348+ set ssl_starttls = no
3349+ set ssl_force_tls = false
3350+
3351+ set sidebar_visible
3352+ set sidebar_format = "%B%<F? [%F]>%* %<N?%N/>%S"
3353+ # set mail_check_stats
3354+
3355+ color normal default default # Text is "Text"
3356+ color index color2 default ~N # New Messages are Green
3357+ color index color1 default ~F # Flagged messages are Red
3358+ color index color13 default ~T # Tagged Messages are Red
3359+ color index color1 default ~D # Messages to delete are Red
3360+ color attachment color5 default # Attachments are Pink
3361+ color signature color8 default # Signatures are Surface 2
3362+ color search color4 default # Highlighted results are Blue
3363+
3364+ color indicator default color8 # currently highlighted message Surface 2=Background Text=Foreground
3365+ color error color1 default # error messages are Red
3366+ color status color15 default # status line "Subtext 0"
3367+ color tree color15 default # thread tree arrows Subtext 0
3368+ color tilde color15 default # blank line padding Subtext 0
3369+
3370+ color hdrdefault color13 default # default headers Pink
3371+ color header color13 default "^From:"
3372+ color header color13 default "^Subject:"
3373+
3374+ color quoted color15 default # Subtext 0
3375+ color quoted1 color7 default # Subtext 1
3376+ color quoted2 color8 default # Surface 2
3377+ color quoted3 color0 default # Surface 1
3378+ color quoted4 color0 default
3379+ color quoted5 color0 default
3380+
3381+ color body color2 default [\-\.+_a-zA-Z0-9]+@[\-\.a-zA-Z0-9]+ # email addresses Green
3382+ color body color2 default (https?|ftp)://[\-\.,/%~_:?&=\#a-zA-Z0-9]+ # URLs Green
3383+ color body color4 default (^|[[:space:]])\\*[^[:space:]]+\\*([[:space:]]|$) # *bold* text Blue
3384+ color body color4 default (^|[[:space:]])_[^[:space:]]+_([[:space:]]|$) # _underlined_ text Blue
3385+ color body color4 default (^|[[:space:]])/[^[:space:]]+/([[:space:]]|$) # /italic/ text Blue
3386+
3387+ color sidebar_flagged color1 default # Mailboxes with flagged mails are Red
3388+ color sidebar_new color10 default # Mailboxes with new mail are Green
3389 diff --git a/crates/database/queries/mail_create.sql b/crates/database/queries/mail_create.sql
3390new file mode 100644
3391index 0000000..8b13789
3392--- /dev/null
3393+++ b/crates/database/queries/mail_create.sql
3394 @@ -0,0 +1 @@
3395+
3396 diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs
3397index c1e8a1e..0ceda9f 100644
3398--- a/crates/database/src/lib.rs
3399+++ b/crates/database/src/lib.rs
3400 @@ -10,6 +10,7 @@ use tracing::log;
3401 pub mod contributors;
3402 pub mod jobs;
3403 pub mod languages;
3404+ pub mod mail;
3405 pub mod stats;
3406
3407 pub type Error = SqlxError;
3408 diff --git a/crates/database/src/mail.rs b/crates/database/src/mail.rs
3409new file mode 100644
3410index 0000000..1bef062
3411--- /dev/null
3412+++ b/crates/database/src/mail.rs
3413 @@ -0,0 +1,59 @@
3414+ use async_trait::async_trait;
3415+ use serde::{Deserialize, Serialize};
3416+
3417+ use crate::Error;
3418+ use crate::Wrapper as Database;
3419+
3420+ #[derive(Clone, Default, Serialize, Deserialize)]
3421+ pub struct Thread(Vec<Message>);
3422+
3423+ impl Thread {
3424+ pub fn has_patch(&self) -> bool {
3425+ todo!()
3426+ }
3427+ }
3428+
3429+ #[derive(Clone, Default, Serialize, Deserialize)]
3430+ pub struct Message {}
3431+
3432+ impl Message {
3433+ pub fn is_patch(&self) -> bool {
3434+ todo!()
3435+ }
3436+ }
3437+
3438+ #[async_trait]
3439+ pub trait MailExt {
3440+ async fn create_message(
3441+ &self,
3442+ mail_from: &str,
3443+ message_id: &str,
3444+ message_to: &[&str],
3445+ message_body: &[u8],
3446+ reply_to: Option<&str>,
3447+ ) -> Result<i64, Error>;
3448+ async fn read_message(&self, message_id: &str) -> Result<Message, Error>;
3449+ async fn list_thread(&self, message_id: &str) -> Result<Thread, Error>;
3450+ }
3451+
3452+ #[async_trait]
3453+ impl MailExt for Database {
3454+ async fn create_message(
3455+ &self,
3456+ mail_from: &str,
3457+ message_id: &str,
3458+ message_to: &[&str],
3459+ message_body: &[u8],
3460+ reply_to: Option<&str>,
3461+ ) -> Result<i64, Error> {
3462+ todo!()
3463+ }
3464+
3465+ async fn read_message(&self, message_id: &str) -> Result<Message, Error> {
3466+ todo!()
3467+ }
3468+
3469+ async fn list_thread(&self, message_id: &str) -> Result<Thread, Error> {
3470+ todo!()
3471+ }
3472+ }
3473 diff --git a/scripts/neomutt.sh b/scripts/neomutt.sh
3474new file mode 100755
3475index 0000000..a7a8058
3476--- /dev/null
3477+++ b/scripts/neomutt.sh
3478 @@ -0,0 +1,5 @@
3479+ #!/bin/sh
3480+
3481+ mkdir -p mail/demo@example.org
3482+
3483+ neomutt -F contrib/demo.neomuttrc
3484 diff --git a/scripts/send_test_email.sh b/scripts/send_test_email.sh
3485new file mode 100755
3486index 0000000..55bd149
3487--- /dev/null
3488+++ b/scripts/send_test_email.sh
3489 @@ -0,0 +1,12 @@
3490+ #!/bin/sh
3491+ # Generate a patch of the current HEAD for testing purposes
3492+
3493+ SMTP_TARGET="127.0.0.1:30025"
3494+
3495+ git send-email \
3496+ --8bit-encoding UTF8 \
3497+ --to dev@ayllu-forge.org \
3498+ --from hello@ayllu-forge.org \
3499+ --subject "Ayllu Test Patch" \
3500+ --smtp-server "$SMTP_TARGET" \
3501+ HEAD^