Index: Cargo.lock ================================================================== --- Cargo.lock +++ Cargo.lock @@ -73,13 +73,13 @@ "pin-project-lite", ] [[package]] name = "async-compat" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" +checksum = "4c97d7ff3c25d6c10d64170c12acaf5d4245e76dece3779c1d92b153a64f11df" dependencies = [ "futures-core", "futures-io", "once_cell", "pin-project-lite", @@ -271,13 +271,13 @@ "byteorder", ] [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "blocking" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -314,25 +314,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.4.5" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313" dependencies = [ "find-msvc-tools", "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -349,47 +349,47 @@ "rand_core", ] [[package]] name = "clap" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" -version = "4.6.4" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "cmake" version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -423,13 +423,13 @@ "crossbeam-utils", ] [[package]] name = "config" -version = "0.15.25" +version = "0.15.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85f248a4de22d204ceabc6299d89d2c70fbd7f09fea53c06c852369652d8139" +checksum = "790374c727a4cb20877dd2cc99ce7011cbe03b0b8df083acccd37df0d161304d" dependencies = [ "pathdiff", "serde_core", "toml", "winnow", @@ -484,11 +484,11 @@ dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "darling_macro" version = "0.24.1" @@ -495,11 +495,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "derive_more" version = "2.1.1" @@ -527,11 +527,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "dunce" version = "1.0.5" @@ -586,13 +586,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -647,11 +647,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "futures-sink" version = "0.3.34" @@ -813,13 +813,13 @@ "want", ] [[package]] name = "hyper-rustls" -version = "0.27.9" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ "http", "hyper", "hyper-util", "rustls", @@ -1089,19 +1089,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lru-slab" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] name = "mail-parser" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b3a9080c1fb8190e232df37a10aa1b3d6b08be084e537069913f025e0ce86c5" +checksum = "4ec00bda90c6e645a54506c630c2820cd6b1890cfd2b0a169b50f74b2b8c7c86" dependencies = [ "hashify", ] [[package]] @@ -1278,13 +1278,13 @@ "unicode-ident", ] [[package]] name = "quinn" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", @@ -1298,13 +1298,13 @@ "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.17" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" dependencies = [ "aws-lc-rs", "bytes", "getrandom 0.4.3", "lru-slab", @@ -1350,13 +1350,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "chacha20", "getrandom 0.4.3", "rand_core", ] @@ -1475,13 +1475,13 @@ "semver", ] [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", @@ -1488,13 +1488,13 @@ "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.44" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "aws-lc-rs", "log", "once_cell", "rustls-pki-types", @@ -1657,11 +1657,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "serde_json" version = "1.0.151" @@ -1701,11 +1701,11 @@ checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" dependencies = [ "darling", "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "shellwords" version = "1.1.0" @@ -1760,13 +1760,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aca054fd9f8c2ebe8557a2433f307e038c0716124efd045daa0388afa5172189" [[package]] name = "smallvec" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" [[package]] name = "smol" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1780,14 +1780,27 @@ "async-net", "async-process", "blocking", "futures-lite", ] + +[[package]] +name = "smol-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcaedb62e0475a6898988138995ec7b1e5d116167a72bb12c7b59d0649fbbc2" +dependencies = [ + "async-executor", + "async-io", + "async-lock", + "event-listener", + "futures-lite", +] [[package]] name = "smtp2tg" -version = "0.6.8" +version = "0.6.9" dependencies = [ "async-compat", "clap", "config", "hostname", @@ -1794,10 +1807,11 @@ "html-escape", "mail-parser", "mailin-embedded", "regex", "smol", + "smol-macros", "stacked_errors", "tgbot", ] [[package]] @@ -1851,13 +1865,13 @@ "unicode-ident", ] [[package]] name = "syn" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] @@ -1871,17 +1885,17 @@ "futures-core", ] [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] name = "ternop" version = "1.0.1" @@ -1912,32 +1926,32 @@ "zmij", ] [[package]] name = "thin-vec" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79def32ffcd477db1ff26f76dab9e3a91f0bd42a85ca96577089b24623056f9d" +checksum = "4568d7e143ec86d2021c338bae2afa88699e84b8e0af523626654fe6f03a1748" [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "tinystr" version = "0.8.4" @@ -1948,22 +1962,13 @@ "zerovec", ] [[package]] name = "tinyvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2001,13 +2006,13 @@ "tokio", ] [[package]] name = "toml" -version = "1.1.5+spec-1.1.0" +version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ "serde_core", "serde_spanned", "toml_datetime", "toml_parser", @@ -2108,13 +2113,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2209,11 +2214,11 @@ checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" @@ -2389,17 +2394,17 @@ "zerofrom", ] [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "synstructure", ] [[package]] name = "zerofrom" @@ -2410,17 +2415,17 @@ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "synstructure", ] [[package]] name = "zeroize" @@ -2456,13 +2461,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" Index: Cargo.toml ================================================================== --- Cargo.toml +++ Cargo.toml @@ -1,8 +1,8 @@ [package] name = "smtp2tg" -version = "0.6.8" +version = "0.6.9" authors = [ "arcade@b1t.name" ] edition = "2024" license = "0BSD" repository = "http://fs.b1t.name/smtp2tg" @@ -14,10 +14,11 @@ clap = { version = "4.6", features = [ "derive" ] } mail-parser = "0.11" mailin-embedded = "0.8" regex = "1.11.1" smol = "2.0.2" +smol-macros = "0.1" stacked_errors = "0.8" tgbot = "0.48" # All tests are moved to tests/ [lib] Index: src/lib.rs ================================================================== --- src/lib.rs +++ src/lib.rs @@ -1,98 +1,14 @@ //! SMTP-to-Telegram gateway main library. //! -//! This module provides the core functionality for receiving emails via SMTP +//! This lib provides the core functionality for receiving emails via SMTP //! and forwarding them to Telegram chats. //! //! As we are not actually exporting this lib there would be no local Error's //! for now, everything will be just .stack()?'ed and propagated like in real -//! bin The lib here is just to separate all tests from main code into tests/ +//! bin. The lib here is just to separate all tests from main code into tests/ pub mod mail; mod telegram; pub mod utils; -use crate::mail::MailServer; - -use std::{ - io::Cursor, - os::unix::fs::PermissionsExt, - path::Path, -}; - -use clap::Parser; -use smol::{ - fs::metadata, -}; -use stacked_errors::{ - Result, - StackableErr, - bail, -}; - -/// SMTP-to-Telegram gateway -#[derive(Parser, Debug)] -#[command(name = "smtp2tg")] -#[command(about = format!("SMTP-to-Telegram gateway v{}, (C) 2024 - 2026", env!("CARGO_PKG_VERSION")), long_about = None)] -struct Args { - /// Set configuration file location - #[arg(short, long, default_value = "smtp2tg.toml")] - config: String, -} - -/// Main asynchronous entry point for the application. Runs the gateway using -/// the configuration selected on the command line. -/// -/// The configuration file must use owner-only permissions. Once configured, -/// the SMTP server runs until it stops or encounters an error. -/// -/// # Errors -/// Returns an error if the configuration file is missing, inaccessible, -/// insecure, malformed, or contains invalid required settings. -/// -/// # Panics -/// Panics if configuring or serving the SMTP server fails. -pub async fn async_main () -> Result<()> { - let args = Args::parse(); - let config_file = Path::new(&args.config); - if !config_file.try_exists().stack()? { - bail!("Configuration file not found: {config_file:?}\n\ - Hint: Ensure the file exists and the path is correct."); - }; - { - let meta = metadata(config_file).await.stack()?; - if (!0o100600 & meta.permissions().mode()) > 0 { - bail!("Configuration file permissions are insecure {config_file:?}\n\ - Current permissions: {:o}\n\ - Required: 0600 (owner read/write only).\n\ - Fix with: chmod 600 {config_file:?}", - meta.permissions().mode()); - } } - let settings: config::Config = config::Config::builder() - .set_default("api_gateway", "https://api.telegram.org").stack()? - .set_default("fields", vec!["date", "from", "subject"]).stack()? - .set_default("hostname", "smtp.2.tg").stack()? - .set_default("listen_on", "0.0.0.0:1025").stack()? - .set_default("domains", vec!["localhost", - hostname::get().context("Failed to get current hostname")? - .to_str().context("Can't convert hostname to string, bad UTF-8?")?]).stack()? - .add_source(config::File::from(config_file)) - .build() - .with_context(|| format!( - "Failed to parse configuration file: {config_file:?}\n\ - Check syntax against smtp2tg.toml.example.\n\ - Common issues: missing quotes, trailing commas in inline tables, or invalid types." - ))?; - - let listen_on = settings.get_string("listen_on").stack()?; - let server_name = settings.get_string("hostname").stack()?; - let core = MailServer::new(settings)?; - let mut server = mailin_embedded::Server::new(core); - - // TODO: remove unwraps when mailin-embedded bumps with better error handling - server.with_name(server_name) - .with_ssl(mailin_embedded::SslConfig::None).unwrap() - .with_addr(listen_on).unwrap(); - server.serve().unwrap(); - - Ok(()) -} +use std::io::Cursor; Index: src/main.rs ================================================================== --- src/main.rs +++ src/main.rs @@ -1,13 +1,91 @@ //! Simple SMTP-to-Telegram gateway. Can parse email and send them as telegram //! messages to specified chats, generally you specify which email address is //! available in configuration, everything else is sent to default address. -use async_compat::Compat; -use stacked_errors:: Result; - -// main function stub that executes main code from lib -fn main () -> Result<()> { - smol::block_on(Compat::new(async { - smtp2tg::async_main().await - })) -} +use smol_macros::main; + +use std::{ + os::unix::fs::PermissionsExt, + path::Path, +}; + +use clap::Parser; +use smol::{ + fs::metadata, +}; +use stacked_errors::{ + Result, + StackableErr, + bail, +}; + +use smtp2tg::mail::MailServer; + +/// SMTP-to-Telegram gateway +#[derive(Parser, Debug)] +#[command(name = "smtp2tg")] +#[command(about = format!("SMTP-to-Telegram gateway v{}, (C) 2024 - 2026", env!("CARGO_PKG_VERSION")), long_about = None)] +struct Args { + /// Set configuration file location + #[arg(short, long, default_value = "smtp2tg.toml")] + config: String, +} + +main! { +/// Main asynchronous entry point for the application. Runs the gateway using +/// the configuration selected on the command line. +/// +/// The configuration file must use owner-only permissions. Once configured, +/// the SMTP server runs until it stops or encounters an error. +/// +/// # Errors +/// Returns an error if the configuration file is missing, inaccessible, +/// insecure, malformed, or contains invalid required settings. +/// +/// # Panics +/// Panics if configuring or serving the SMTP server fails. + async fn main () -> Result<()> { + let args = Args::parse(); + let config_file = Path::new(&args.config); + if !config_file.try_exists().stack()? { + bail!("Configuration file not found: {config_file:?}\n\ + Hint: Ensure the file exists and the path is correct."); + }; + { + let meta = metadata(config_file).await.stack()?; + if (!0o100600 & meta.permissions().mode()) > 0 { + bail!("Configuration file permissions are insecure {config_file:?}\n\ + Current permissions: {:o}\n\ + Required: 0600 (owner read/write only).\n\ + Fix with: chmod 600 {config_file:?}", + meta.permissions().mode()); + } } + let settings: config::Config = config::Config::builder() + .set_default("api_gateway", "https://api.telegram.org").stack()? + .set_default("fields", vec!["date", "from", "subject"]).stack()? + .set_default("hostname", "smtp.2.tg").stack()? + .set_default("listen_on", "0.0.0.0:1025").stack()? + .set_default("domains", vec!["localhost", + hostname::get().context("Failed to get current hostname")? + .to_str().context("Can't convert hostname to string, bad UTF-8?")?]).stack()? + .add_source(config::File::from(config_file)) + .build() + .with_context(|| format!( + "Failed to parse configuration file: {config_file:?}\n\ + Check syntax against smtp2tg.toml.example.\n\ + Common issues: missing quotes, trailing commas in inline tables, or invalid types." + ))?; + + let listen_on = settings.get_string("listen_on").stack()?; + let server_name = settings.get_string("hostname").stack()?; + let core = MailServer::new(settings)?; + let mut server = mailin_embedded::Server::new(core); + + // TODO: remove unwraps when mailin-embedded bumps with better error handling + server.with_name(server_name) + .with_ssl(mailin_embedded::SslConfig::None).unwrap() + .with_addr(listen_on).unwrap(); + server.serve().unwrap(); + + Ok(()) +} }