Initial commit

Seto committed Oct 9, 2025 at 14:13 UTC d047d95d3fe9913a8374b63437dda2b94988f66f
11 files changed +1096
.gitignore new
+1
@@ -0,0 +1 @@
1 +/target
Cargo.lock new
+763
@@ -0,0 +1,763 @@
1 +# This file is automatically @generated by Cargo.
2 +# It is not intended for manual editing.
3 +version = 4
4 +
5 +[[package]]
6 +name = "addr2line"
7 +version = "0.25.1"
8 +source = "registry+https://github.com/rust-lang/crates.io-index"
9 +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
10 +dependencies = [
11 + "gimli",
12 +]
13 +
14 +[[package]]
15 +name = "adler2"
16 +version = "2.0.1"
17 +source = "registry+https://github.com/rust-lang/crates.io-index"
18 +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
19 +
20 +[[package]]
21 +name = "aho-corasick"
22 +version = "1.1.3"
23 +source = "registry+https://github.com/rust-lang/crates.io-index"
24 +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
25 +dependencies = [
26 + "memchr",
27 +]
28 +
29 +[[package]]
30 +name = "anyhow"
31 +version = "1.0.100"
32 +source = "registry+https://github.com/rust-lang/crates.io-index"
33 +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
34 +
35 +[[package]]
36 +name = "backtrace"
37 +version = "0.3.76"
38 +source = "registry+https://github.com/rust-lang/crates.io-index"
39 +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
40 +dependencies = [
41 + "addr2line",
42 + "cfg-if",
43 + "libc",
44 + "miniz_oxide",
45 + "object",
46 + "rustc-demangle",
47 + "windows-link",
48 +]
49 +
50 +[[package]]
51 +name = "bitflags"
52 +version = "2.9.4"
53 +source = "registry+https://github.com/rust-lang/crates.io-index"
54 +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
55 +
56 +[[package]]
57 +name = "bytes"
58 +version = "1.10.1"
59 +source = "registry+https://github.com/rust-lang/crates.io-index"
60 +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
61 +
62 +[[package]]
63 +name = "cc"
64 +version = "1.2.40"
65 +source = "registry+https://github.com/rust-lang/crates.io-index"
66 +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb"
67 +dependencies = [
68 + "find-msvc-tools",
69 + "shlex",
70 +]
71 +
72 +[[package]]
73 +name = "cfg-if"
74 +version = "1.0.3"
75 +source = "registry+https://github.com/rust-lang/crates.io-index"
76 +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
77 +
78 +[[package]]
79 +name = "core-foundation"
80 +version = "0.9.4"
81 +source = "registry+https://github.com/rust-lang/crates.io-index"
82 +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
83 +dependencies = [
84 + "core-foundation-sys",
85 + "libc",
86 +]
87 +
88 +[[package]]
89 +name = "core-foundation-sys"
90 +version = "0.8.7"
91 +source = "registry+https://github.com/rust-lang/crates.io-index"
92 +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
93 +
94 +[[package]]
95 +name = "errno"
96 +version = "0.3.14"
97 +source = "registry+https://github.com/rust-lang/crates.io-index"
98 +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
99 +dependencies = [
100 + "libc",
101 + "windows-sys 0.59.0",
102 +]
103 +
104 +[[package]]
105 +name = "fastrand"
106 +version = "2.3.0"
107 +source = "registry+https://github.com/rust-lang/crates.io-index"
108 +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
109 +
110 +[[package]]
111 +name = "find-msvc-tools"
112 +version = "0.1.3"
113 +source = "registry+https://github.com/rust-lang/crates.io-index"
114 +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3"
115 +
116 +[[package]]
117 +name = "foreign-types"
118 +version = "0.3.2"
119 +source = "registry+https://github.com/rust-lang/crates.io-index"
120 +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
121 +dependencies = [
122 + "foreign-types-shared",
123 +]
124 +
125 +[[package]]
126 +name = "foreign-types-shared"
127 +version = "0.1.1"
128 +source = "registry+https://github.com/rust-lang/crates.io-index"
129 +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
130 +
131 +[[package]]
132 +name = "futures"
133 +version = "0.3.31"
134 +source = "registry+https://github.com/rust-lang/crates.io-index"
135 +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
136 +dependencies = [
137 + "futures-channel",
138 + "futures-core",
139 + "futures-executor",
140 + "futures-io",
141 + "futures-sink",
142 + "futures-task",
143 + "futures-util",
144 +]
145 +
146 +[[package]]
147 +name = "futures-channel"
148 +version = "0.3.31"
149 +source = "registry+https://github.com/rust-lang/crates.io-index"
150 +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
151 +dependencies = [
152 + "futures-core",
153 + "futures-sink",
154 +]
155 +
156 +[[package]]
157 +name = "futures-core"
158 +version = "0.3.31"
159 +source = "registry+https://github.com/rust-lang/crates.io-index"
160 +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
161 +
162 +[[package]]
163 +name = "futures-executor"
164 +version = "0.3.31"
165 +source = "registry+https://github.com/rust-lang/crates.io-index"
166 +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
167 +dependencies = [
168 + "futures-core",
169 + "futures-task",
170 + "futures-util",
171 +]
172 +
173 +[[package]]
174 +name = "futures-io"
175 +version = "0.3.31"
176 +source = "registry+https://github.com/rust-lang/crates.io-index"
177 +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
178 +
179 +[[package]]
180 +name = "futures-macro"
181 +version = "0.3.31"
182 +source = "registry+https://github.com/rust-lang/crates.io-index"
183 +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
184 +dependencies = [
185 + "proc-macro2",
186 + "quote",
187 + "syn",
188 +]
189 +
190 +[[package]]
191 +name = "futures-sink"
192 +version = "0.3.31"
193 +source = "registry+https://github.com/rust-lang/crates.io-index"
194 +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
195 +
196 +[[package]]
197 +name = "futures-task"
198 +version = "0.3.31"
199 +source = "registry+https://github.com/rust-lang/crates.io-index"
200 +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
201 +
202 +[[package]]
203 +name = "futures-util"
204 +version = "0.3.31"
205 +source = "registry+https://github.com/rust-lang/crates.io-index"
206 +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
207 +dependencies = [
208 + "futures-channel",
209 + "futures-core",
210 + "futures-io",
211 + "futures-macro",
212 + "futures-sink",
213 + "futures-task",
214 + "memchr",
215 + "pin-project-lite",
216 + "pin-utils",
217 + "slab",
218 +]
219 +
220 +[[package]]
221 +name = "getrandom"
222 +version = "0.3.3"
223 +source = "registry+https://github.com/rust-lang/crates.io-index"
224 +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
225 +dependencies = [
226 + "cfg-if",
227 + "libc",
228 + "r-efi",
229 + "wasi 0.14.7+wasi-0.2.4",
230 +]
231 +
232 +[[package]]
233 +name = "gimli"
234 +version = "0.32.3"
235 +source = "registry+https://github.com/rust-lang/crates.io-index"
236 +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
237 +
238 +[[package]]
239 +name = "io-uring"
240 +version = "0.7.10"
241 +source = "registry+https://github.com/rust-lang/crates.io-index"
242 +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
243 +dependencies = [
244 + "bitflags",
245 + "cfg-if",
246 + "libc",
247 +]
248 +
249 +[[package]]
250 +name = "libc"
251 +version = "0.2.176"
252 +source = "registry+https://github.com/rust-lang/crates.io-index"
253 +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
254 +
255 +[[package]]
256 +name = "linux-raw-sys"
257 +version = "0.11.0"
258 +source = "registry+https://github.com/rust-lang/crates.io-index"
259 +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
260 +
261 +[[package]]
262 +name = "log"
263 +version = "0.4.28"
264 +source = "registry+https://github.com/rust-lang/crates.io-index"
265 +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
266 +
267 +[[package]]
268 +name = "memchr"
269 +version = "2.7.6"
270 +source = "registry+https://github.com/rust-lang/crates.io-index"
271 +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
272 +
273 +[[package]]
274 +name = "miniz_oxide"
275 +version = "0.8.9"
276 +source = "registry+https://github.com/rust-lang/crates.io-index"
277 +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
278 +dependencies = [
279 + "adler2",
280 +]
281 +
282 +[[package]]
283 +name = "mio"
284 +version = "1.0.4"
285 +source = "registry+https://github.com/rust-lang/crates.io-index"
286 +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
287 +dependencies = [
288 + "libc",
289 + "wasi 0.11.1+wasi-snapshot-preview1",
290 + "windows-sys 0.59.0",
291 +]
292 +
293 +[[package]]
294 +name = "native-tls"
295 +version = "0.2.14"
296 +source = "registry+https://github.com/rust-lang/crates.io-index"
297 +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
298 +dependencies = [
299 + "libc",
300 + "log",
301 + "openssl",
302 + "openssl-probe",
303 + "openssl-sys",
304 + "schannel",
305 + "security-framework",
306 + "security-framework-sys",
307 + "tempfile",
308 +]
309 +
310 +[[package]]
311 +name = "object"
312 +version = "0.37.3"
313 +source = "registry+https://github.com/rust-lang/crates.io-index"
314 +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
315 +dependencies = [
316 + "memchr",
317 +]
318 +
319 +[[package]]
320 +name = "once_cell"
321 +version = "1.21.3"
322 +source = "registry+https://github.com/rust-lang/crates.io-index"
323 +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
324 +
325 +[[package]]
326 +name = "openssl"
327 +version = "0.10.73"
328 +source = "registry+https://github.com/rust-lang/crates.io-index"
329 +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
330 +dependencies = [
331 + "bitflags",
332 + "cfg-if",
333 + "foreign-types",
334 + "libc",
335 + "once_cell",
336 + "openssl-macros",
337 + "openssl-sys",
338 +]
339 +
340 +[[package]]
341 +name = "openssl-macros"
342 +version = "0.1.1"
343 +source = "registry+https://github.com/rust-lang/crates.io-index"
344 +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
345 +dependencies = [
346 + "proc-macro2",
347 + "quote",
348 + "syn",
349 +]
350 +
351 +[[package]]
352 +name = "openssl-probe"
353 +version = "0.1.6"
354 +source = "registry+https://github.com/rust-lang/crates.io-index"
355 +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
356 +
357 +[[package]]
358 +name = "openssl-sys"
359 +version = "0.9.109"
360 +source = "registry+https://github.com/rust-lang/crates.io-index"
361 +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
362 +dependencies = [
363 + "cc",
364 + "libc",
365 + "pkg-config",
366 + "vcpkg",
367 +]
368 +
369 +[[package]]
370 +name = "pin-project-lite"
371 +version = "0.2.16"
372 +source = "registry+https://github.com/rust-lang/crates.io-index"
373 +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
374 +
375 +[[package]]
376 +name = "pin-utils"
377 +version = "0.1.0"
378 +source = "registry+https://github.com/rust-lang/crates.io-index"
379 +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
380 +
381 +[[package]]
382 +name = "pkg-config"
383 +version = "0.3.32"
384 +source = "registry+https://github.com/rust-lang/crates.io-index"
385 +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
386 +
387 +[[package]]
388 +name = "proc-macro2"
389 +version = "1.0.101"
390 +source = "registry+https://github.com/rust-lang/crates.io-index"
391 +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
392 +dependencies = [
393 + "unicode-ident",
394 +]
395 +
396 +[[package]]
397 +name = "quote"
398 +version = "1.0.41"
399 +source = "registry+https://github.com/rust-lang/crates.io-index"
400 +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
401 +dependencies = [
402 + "proc-macro2",
403 +]
404 +
405 +[[package]]
406 +name = "r-efi"
407 +version = "5.3.0"
408 +source = "registry+https://github.com/rust-lang/crates.io-index"
409 +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
410 +
411 +[[package]]
412 +name = "regex"
413 +version = "1.11.3"
414 +source = "registry+https://github.com/rust-lang/crates.io-index"
415 +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
416 +dependencies = [
417 + "aho-corasick",
418 + "memchr",
419 + "regex-automata",
420 + "regex-syntax",
421 +]
422 +
423 +[[package]]
424 +name = "regex-automata"
425 +version = "0.4.11"
426 +source = "registry+https://github.com/rust-lang/crates.io-index"
427 +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
428 +dependencies = [
429 + "aho-corasick",
430 + "memchr",
431 + "regex-syntax",
432 +]
433 +
434 +[[package]]
435 +name = "regex-syntax"
436 +version = "0.8.6"
437 +source = "registry+https://github.com/rust-lang/crates.io-index"
438 +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
439 +
440 +[[package]]
441 +name = "rustc-demangle"
442 +version = "0.1.26"
443 +source = "registry+https://github.com/rust-lang/crates.io-index"
444 +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
445 +
446 +[[package]]
447 +name = "rustix"
448 +version = "1.1.2"
449 +source = "registry+https://github.com/rust-lang/crates.io-index"
450 +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
451 +dependencies = [
452 + "bitflags",
453 + "errno",
454 + "libc",
455 + "linux-raw-sys",
456 + "windows-sys 0.59.0",
457 +]
458 +
459 +[[package]]
460 +name = "schannel"
461 +version = "0.1.28"
462 +source = "registry+https://github.com/rust-lang/crates.io-index"
463 +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
464 +dependencies = [
465 + "windows-sys 0.61.2",
466 +]
467 +
468 +[[package]]
469 +name = "security-framework"
470 +version = "2.11.1"
471 +source = "registry+https://github.com/rust-lang/crates.io-index"
472 +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
473 +dependencies = [
474 + "bitflags",
475 + "core-foundation",
476 + "core-foundation-sys",
477 + "libc",
478 + "security-framework-sys",
479 +]
480 +
481 +[[package]]
482 +name = "security-framework-sys"
483 +version = "2.15.0"
484 +source = "registry+https://github.com/rust-lang/crates.io-index"
485 +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
486 +dependencies = [
487 + "core-foundation-sys",
488 + "libc",
489 +]
490 +
491 +[[package]]
492 +name = "shlex"
493 +version = "1.3.0"
494 +source = "registry+https://github.com/rust-lang/crates.io-index"
495 +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
496 +
497 +[[package]]
498 +name = "slab"
499 +version = "0.4.11"
500 +source = "registry+https://github.com/rust-lang/crates.io-index"
501 +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
502 +
503 +[[package]]
504 +name = "smbcloud-mail"
505 +version = "0.1.0"
506 +dependencies = [
507 + "anyhow",
508 + "futures",
509 + "native-tls",
510 + "openssl",
511 + "regex",
512 + "tokio",
513 + "tokio-native-tls",
514 + "tokio-util",
515 + "tracing",
516 +]
517 +
518 +[[package]]
519 +name = "socket2"
520 +version = "0.6.0"
521 +source = "registry+https://github.com/rust-lang/crates.io-index"
522 +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
523 +dependencies = [
524 + "libc",
525 + "windows-sys 0.59.0",
526 +]
527 +
528 +[[package]]
529 +name = "syn"
530 +version = "2.0.106"
531 +source = "registry+https://github.com/rust-lang/crates.io-index"
532 +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
533 +dependencies = [
534 + "proc-macro2",
535 + "quote",
536 + "unicode-ident",
537 +]
538 +
539 +[[package]]
540 +name = "tempfile"
541 +version = "3.23.0"
542 +source = "registry+https://github.com/rust-lang/crates.io-index"
543 +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
544 +dependencies = [
545 + "fastrand",
546 + "getrandom",
547 + "once_cell",
548 + "rustix",
549 + "windows-sys 0.59.0",
550 +]
551 +
552 +[[package]]
553 +name = "tokio"
554 +version = "1.47.1"
555 +source = "registry+https://github.com/rust-lang/crates.io-index"
556 +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
557 +dependencies = [
558 + "backtrace",
559 + "bytes",
560 + "io-uring",
561 + "libc",
562 + "mio",
563 + "pin-project-lite",
564 + "slab",
565 + "socket2",
566 + "tokio-macros",
567 + "windows-sys 0.59.0",
568 +]
569 +
570 +[[package]]
571 +name = "tokio-macros"
572 +version = "2.5.0"
573 +source = "registry+https://github.com/rust-lang/crates.io-index"
574 +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
575 +dependencies = [
576 + "proc-macro2",
577 + "quote",
578 + "syn",
579 +]
580 +
581 +[[package]]
582 +name = "tokio-native-tls"
583 +version = "0.3.1"
584 +source = "registry+https://github.com/rust-lang/crates.io-index"
585 +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
586 +dependencies = [
587 + "native-tls",
588 + "tokio",
589 +]
590 +
591 +[[package]]
592 +name = "tokio-util"
593 +version = "0.7.16"
594 +source = "registry+https://github.com/rust-lang/crates.io-index"
595 +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
596 +dependencies = [
597 + "bytes",
598 + "futures-core",
599 + "futures-sink",
600 + "pin-project-lite",
601 + "tokio",
602 +]
603 +
604 +[[package]]
605 +name = "tracing"
606 +version = "0.1.41"
607 +source = "registry+https://github.com/rust-lang/crates.io-index"
608 +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
609 +dependencies = [
610 + "pin-project-lite",
611 + "tracing-attributes",
612 + "tracing-core",
613 +]
614 +
615 +[[package]]
616 +name = "tracing-attributes"
617 +version = "0.1.30"
618 +source = "registry+https://github.com/rust-lang/crates.io-index"
619 +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
620 +dependencies = [
621 + "proc-macro2",
622 + "quote",
623 + "syn",
624 +]
625 +
626 +[[package]]
627 +name = "tracing-core"
628 +version = "0.1.34"
629 +source = "registry+https://github.com/rust-lang/crates.io-index"
630 +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
631 +dependencies = [
632 + "once_cell",
633 +]
634 +
635 +[[package]]
636 +name = "unicode-ident"
637 +version = "1.0.19"
638 +source = "registry+https://github.com/rust-lang/crates.io-index"
639 +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
640 +
641 +[[package]]
642 +name = "vcpkg"
643 +version = "0.2.15"
644 +source = "registry+https://github.com/rust-lang/crates.io-index"
645 +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
646 +
647 +[[package]]
648 +name = "wasi"
649 +version = "0.11.1+wasi-snapshot-preview1"
650 +source = "registry+https://github.com/rust-lang/crates.io-index"
651 +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
652 +
653 +[[package]]
654 +name = "wasi"
655 +version = "0.14.7+wasi-0.2.4"
656 +source = "registry+https://github.com/rust-lang/crates.io-index"
657 +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
658 +dependencies = [
659 + "wasip2",
660 +]
661 +
662 +[[package]]
663 +name = "wasip2"
664 +version = "1.0.1+wasi-0.2.4"
665 +source = "registry+https://github.com/rust-lang/crates.io-index"
666 +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
667 +dependencies = [
668 + "wit-bindgen",
669 +]
670 +
671 +[[package]]
672 +name = "windows-link"
673 +version = "0.2.1"
674 +source = "registry+https://github.com/rust-lang/crates.io-index"
675 +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
676 +
677 +[[package]]
678 +name = "windows-sys"
679 +version = "0.59.0"
680 +source = "registry+https://github.com/rust-lang/crates.io-index"
681 +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
682 +dependencies = [
683 + "windows-targets",
684 +]
685 +
686 +[[package]]
687 +name = "windows-sys"
688 +version = "0.61.2"
689 +source = "registry+https://github.com/rust-lang/crates.io-index"
690 +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
691 +dependencies = [
692 + "windows-link",
693 +]
694 +
695 +[[package]]
696 +name = "windows-targets"
697 +version = "0.52.6"
698 +source = "registry+https://github.com/rust-lang/crates.io-index"
699 +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
700 +dependencies = [
701 + "windows_aarch64_gnullvm",
702 + "windows_aarch64_msvc",
703 + "windows_i686_gnu",
704 + "windows_i686_gnullvm",
705 + "windows_i686_msvc",
706 + "windows_x86_64_gnu",
707 + "windows_x86_64_gnullvm",
708 + "windows_x86_64_msvc",
709 +]
710 +
711 +[[package]]
712 +name = "windows_aarch64_gnullvm"
713 +version = "0.52.6"
714 +source = "registry+https://github.com/rust-lang/crates.io-index"
715 +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
716 +
717 +[[package]]
718 +name = "windows_aarch64_msvc"
719 +version = "0.52.6"
720 +source = "registry+https://github.com/rust-lang/crates.io-index"
721 +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
722 +
723 +[[package]]
724 +name = "windows_i686_gnu"
725 +version = "0.52.6"
726 +source = "registry+https://github.com/rust-lang/crates.io-index"
727 +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
728 +
729 +[[package]]
730 +name = "windows_i686_gnullvm"
731 +version = "0.52.6"
732 +source = "registry+https://github.com/rust-lang/crates.io-index"
733 +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
734 +
735 +[[package]]
736 +name = "windows_i686_msvc"
737 +version = "0.52.6"
738 +source = "registry+https://github.com/rust-lang/crates.io-index"
739 +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
740 +
741 +[[package]]
742 +name = "windows_x86_64_gnu"
743 +version = "0.52.6"
744 +source = "registry+https://github.com/rust-lang/crates.io-index"
745 +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
746 +
747 +[[package]]
748 +name = "windows_x86_64_gnullvm"
749 +version = "0.52.6"
750 +source = "registry+https://github.com/rust-lang/crates.io-index"
751 +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
752 +
753 +[[package]]
754 +name = "windows_x86_64_msvc"
755 +version = "0.52.6"
756 +source = "registry+https://github.com/rust-lang/crates.io-index"
757 +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
758 +
759 +[[package]]
760 +name = "wit-bindgen"
761 +version = "0.46.0"
762 +source = "registry+https://github.com/rust-lang/crates.io-index"
763 +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
Cargo.toml new
+15
@@ -0,0 +1,15 @@
1 +[package]
2 +name = "smbcloud-mail"
3 +version = "0.1.0"
4 +edition = "2024"
5 +
6 +[dependencies]
7 +tokio = { version = "1", features = [ "macros","io-util","net","rt", "rt-multi-thread"] }
8 +tokio-util = { version = "0.7", features = ["codec"] }
9 +tokio-native-tls = "0.3"
10 +native-tls = "0.2"
11 +anyhow = "1"
12 +tracing = "0.1"
13 +regex = "1"
14 +futures = "0.3"
15 +openssl = "0.10"
src/generate_certificate.rs new
+46
@@ -0,0 +1,46 @@
1 +use openssl::{
2 + asn1::Asn1Time,
3 + pkey::PKey,
4 + rsa::Rsa,
5 + x509::{
6 + X509, X509NameBuilder,
7 + extension::{AuthorityKeyIdentifier, BasicConstraints, SubjectKeyIdentifier},
8 + },
9 +};
10 +
11 +pub fn generate_certificate() -> anyhow::Result<(String, String)> {
12 + let cert_result = {
13 + let rsa = Rsa::generate(4096)?;
14 + let pkey = PKey::from_rsa(rsa)?;
15 + let mut name = X509NameBuilder::new()?;
16 + name.append_entry_by_text("CN", "localhost")?;
17 + let name = name.build();
18 + let mut builder = X509::builder()?;
19 + builder.set_version(2)?;
20 + builder.set_subject_name(&name)?;
21 + builder.set_issuer_name(&name)?;
22 + builder.set_pubkey(&pkey)?;
23 + let now = Asn1Time::days_from_now(0)?;
24 + let later = Asn1Time::days_from_now(3650)?;
25 + builder.set_not_before(now.as_ref())?;
26 + builder.set_not_after(later.as_ref())?;
27 + builder.append_extension(BasicConstraints::new().critical().ca().build()?)?;
28 + builder.append_extension(
29 + SubjectKeyIdentifier::new().build(&builder.x509v3_context(None, None))?,
30 + )?;
31 + builder.append_extension(
32 + AuthorityKeyIdentifier::new()
33 + .keyid(true)
34 + .issuer(true)
35 + .build(&builder.x509v3_context(None, None))?,
36 + )?;
37 + builder.sign(&pkey, openssl::hash::MessageDigest::sha256())?;
38 + let c = builder.build();
39 + Ok((c.to_pem()?, pkey.private_key_to_pem_pkcs8()?))
40 + };
41 + let (pem_certificate, pem_private_key) = cert_result
42 + .as_ref()
43 + .map_err(|e| anyhow::anyhow!("Could not generate self-signed certificates: {}", e))?;
44 +
45 + Ok((pem_certificate, pem_private_key))
46 +}
src/handle_session.rs new
+123
@@ -0,0 +1,123 @@
1 +use futures::{SinkExt, StreamExt, stream::iter};
2 +use tokio::net::TcpStream;
3 +use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
4 +
5 +use crate::send_commands::send_commands;
6 +
7 +enum SmtpState {
8 + Command,
9 + Data,
10 + Quit,
11 +}
12 +
13 +pub async fn handle_session(mut stream: TcpStream) -> anyhow::Result<()> {
14 + let RE_SMTP_MAIL = regex::Regex::new(r"(?i)from: ?<(.+)>").unwrap();
15 + let RE_SMTP_RCPT = regex::Regex::new(r"(?i)to: ?<(.+)>").unwrap();
16 + let mut message = String::new();
17 + let mut state = SmtpState::Command;
18 + let mut mailfrom: Option<String> = None;
19 + let mut rcpts: Vec<String> = Vec::new();
20 + let mut framed = Framed::new(stream, LinesCodec::new());
21 + while let Some(line_str) = framed.next().await {
22 + let line = line_str?;
23 + match state {
24 + SmtpState::Command => {
25 + let space_pos = line.find(" ").unwrap_or(line.len());
26 + let (command, arg) = line.split_at(space_pos);
27 + let arg = arg.trim();
28 + match &*command.trim().to_uppercase() {
29 + "HELO" | "EHLO" => {
30 + send_commands(&mut framed, vec!["250 Hello".to_string()]).await?;
31 + }
32 + "MAIL" => {
33 + // Handle MAIL FROM command
34 + if let Some(address) = RE_SMTP_MAIL.captures(arg).and_then(|cap| cap.get(1))
35 + {
36 + mailfrom = Some(address.as_str().to_string());
37 + send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
38 + } else {
39 + send_commands(
40 + &mut framed,
41 + vec!["501 Syntax: MAIL From: <address>".to_string()],
42 + )
43 + .await?;
44 + }
45 + }
46 + "RCPT" => {
47 + // Handle RCPT TO command
48 + if mailfrom.is_none() {
49 + send_commands(
50 + &mut framed,
51 + vec!["503 Error: Send MAIL first".to_string()],
52 + )
53 + .await?;
54 + } else {
55 + if let Some(address) =
56 + RE_SMTP_RCPT.captures(arg).and_then(|cap| cap.get(1))
57 + {
58 + rcpts.push(address.as_str().to_string());
59 + send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
60 + } else {
61 + send_commands(
62 + &mut framed,
63 + vec!["501 Syntax: RCPT TO: <address>".to_string()],
64 + )
65 + .await?;
66 + }
67 + }
68 + }
69 + "DATA" => {
70 + if rcpts.is_empty() {
71 + send_commands(&mut framed, vec!["503 Error: MAIL FROM and RCPT TO must be set before sending DATA".to_string()]).await?;
72 + } else {
73 + state = SmtpState::Data;
74 + send_commands(
75 + &mut framed,
76 + vec!["354 End data with <CR><LF>.<CR><LF>".to_string()],
77 + )
78 + .await?;
79 + }
80 + }
81 + "NOOP" => {
82 + send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
83 + }
84 + "RSET" => {
85 + mailfrom = None;
86 + rcpts = Vec::new();
87 + message = String::new();
88 + send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
89 + }
90 + "QUIT" => {
91 + send_commands(&mut framed, vec!["221 Bye".to_string()]).await?;
92 + state = SmtpState::Quit;
93 + }
94 + _ => {
95 + send_commands(&mut framed, vec!["500 Unknown command".to_string()]).await?;
96 + }
97 + }
98 + }
99 + SmtpState::Data => {
100 + if line.trim() == "." {
101 + // The end of the email content has been received
102 + send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
103 + // reset the state and variables for the next email
104 + mailfrom = None;
105 + rcpts = Vec::new();
106 + message = String::new();
107 + state = SmtpState::Command;
108 + // we can now handle the email:
109 + //handle_email(mailfrom, rcpts, message);
110 + panic!()
111 + } else {
112 + // Add the received line to the email content
113 + message.push_str(&line);
114 + message.push_str("\n");
115 + }
116 + }
117 + SmtpState::Quit => {
118 + break;
119 + }
120 + }
121 + }
122 + Ok(())
123 +}
src/handle_starttls.rs new
+24
@@ -0,0 +1,24 @@
1 +use native_tls::{Identity, TlsAcceptor};
2 +use tokio::net::TcpStream;
3 +use tokio_native_tls::{TlsAcceptor as TokioTlsAcceptor, TlsStream};
4 +
5 +use crate::{generate_certificate::generate_certificate, handle_session::handle_session};
6 +
7 +async fn handle_starttls(stream: &mut TcpStream) -> anyhow::Result<()> {
8 + // ideally the certificate should only be loaded from here and not generated each time
9 + let (pem_certificate, pem_private_key) = generate_certificate()?;
10 + let identity = Identity::from_pkcs8(&pem_certificate, &pem_private_key)?;
11 + let tls_acceptor = TlsAcceptor::builder(identity).build()?;
12 + let tls_acceptor = TokioTlsAcceptor::from(tls_acceptor);
13 +
14 + match tls_acceptor.accept(stream).await {
15 + Ok(stream) => {
16 + // we can now handle the normal SMTP session
17 + handle_session(stream).await?;
18 + }
19 + Err(e) => {
20 + tracing::error!("Error establishing SMTP TLS connection: {:?}", e);
21 + }
22 + };
23 + Ok(())
24 +}
src/handle_unsecured_session.rs new
+58
@@ -0,0 +1,58 @@
1 +use tokio::{
2 + io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter},
3 + net::TcpStream,
4 +};
5 +
6 +use crate::handle_starttls;
7 +
8 +async fn handle_unsecured_session(
9 + reader: &mut BufReader<TcpStream>,
10 + writer: &mut BufWriter<TcpStream>,
11 +) -> anyhow::Result<()> {
12 + let mut is_tls = false;
13 + let mut line = String::new();
14 + while reader.read_line(&mut line).await? != 0 {
15 + let space_pos = line.find(" ").unwrap_or(line.len());
16 + let (command, _) = line.split_at(space_pos);
17 +
18 + match command.trim().to_uppercase().as_ref() {
19 + "EHLO" | "HELO" => {
20 + writer.write_all(b"250-windmill Hello\r\n").await?;
21 + writer.write_all(b"250-STARTTLS\r\n").await?;
22 + writer.write_all(b"250 What you've got?\r\n").await?;
23 + writer.flush().await?;
24 + }
25 + "STARTTLS" => {
26 + writer.write_all(b"220 GO ON\r\n").await?;
27 + writer.flush().await?;
28 + is_tls = true;
29 + break;
30 + }
31 + "QUIT" => {
32 + writer.write_all(b"221 Have a nice day!\r\n").await?;
33 + writer.flush().await?;
34 + break;
35 + }
36 + "NOOP" => {
37 + writer.write_all(b"250 OK\r\n").await?;
38 + writer.flush().await?;
39 + }
40 + "MAIL" | "RCPT" | "DATA" | "RSET" => {
41 + writer
42 + .write_all(b"530 Must issue a STARTTLS command first\r\n")
43 + .await?;
44 + writer.flush().await?;
45 + }
46 + _ => {
47 + writer.write_all(b"500 Unknown command\r\n").await?;
48 + writer.flush().await?;
49 + }
50 + }
51 +
52 + line.clear();
53 + }
54 +
55 + if is_tls {
56 + handle_starttls(stream).await?
57 + }
58 +}
src/lib.rs new
+6
@@ -0,0 +1,6 @@
1 +pub mod generate_certificate;
2 +pub mod handle_session;
3 +pub mod handle_starttls;
4 +pub mod handle_unsecured_session;
5 +pub mod listen;
6 +pub mod send_commands;
src/listen.rs new
+39
@@ -0,0 +1,39 @@
1 +use std::net::SocketAddr;
2 +use tokio::io::{AsyncWriteExt, BufReader, BufWriter};
3 +use tokio::net::{TcpListener, TcpStream};
4 +
5 +//use crate::handle_session::handle_session;
6 +
7 +pub async fn listen() -> anyhow::Result<()> {
8 + let addr = SocketAddr::from(([127, 0, 0, 1], 2525));
9 + let listener = TcpListener::bind(addr).await?;
10 +
11 + tracing::info!("SMTP server listening on {}", addr);
12 +
13 + loop {
14 + match listener.accept().await {
15 + Ok((mut stream, _)) => {
16 + tokio::spawn(async move {
17 + if let Err(err) = handle_connection(&mut stream).await {
18 + tracing::error!("Error handling SMTP connection: {:?}", err);
19 + };
20 + });
21 + }
22 + Err(err) => {
23 + tracing::error!("Error establishing SMTP connection: {:?}", err);
24 + }
25 + }
26 + }
27 +}
28 +
29 +async fn handle_connection(stream: &mut TcpStream) -> anyhow::Result<()> {
30 + let (reader, writer) = stream.split();
31 + let mut reader = BufReader::new(reader);
32 + let mut writer = BufWriter::new(writer);
33 + writer.write_all(b"220 My SMTP server\r\n").await?;
34 + writer.flush().await?;
35 +
36 + // handle session
37 + // handle_session(stream).await
38 + Ok(())
39 +}
src/main.rs new
+7
@@ -0,0 +1,7 @@
1 +use anyhow::Result;
2 +use smbcloud_mail::listen::listen;
3 +
4 +#[tokio::main]
5 +async fn main() -> Result<()> {
6 + listen().await
7 +}
src/send_commands.rs new
+14
@@ -0,0 +1,14 @@
1 +use futures::{SinkExt, StreamExt, stream::iter};
2 +use tokio::net::TcpStream;
3 +use tokio_native_tls::TlsStream;
4 +use tokio_util::codec::{Framed, LinesCodec};
5 +
6 +pub async fn send_commands(
7 + framed: &mut Framed<TcpStream, LinesCodec>,
8 + commands: Vec<String>,
9 +) -> anyhow::Result<()> {
10 + // only need to add \r because the codec only adds \n
11 + let messages = iter(commands.into_iter().map(|x| format!("{}\r", x)));
12 + framed.send_all(&mut messages.map(Ok)).await?;
13 + Ok(())
14 +}