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