Lines of
src/main.rs
from check-in 8ea7b79fca
that are changed by the sequence of edits moving toward
check-in 0f47e23e21:
1: //! Simple SMTP-to-Telegram gateway. Can parse email and send them as telegram
2: //! messages to specified chats, generally you specify which email address is
3: //! available in configuration, everything else is sent to default address.
4:
5: mod mail;
6: mod telegram;
7: mod utils;
8:
8ea7b79fca 2026-01-02 9: #[cfg(test)]
8ea7b79fca 2026-01-02 10: mod tests;
8ea7b79fca 2026-01-02 11:
12: use crate::mail::MailServer;
13:
14: use async_compat::Compat;
15: use just_getopt::{
16: OptFlags,
17: OptSpecs,
18: OptValue,
19: };
20: use smol::{
21: fs::metadata,
22: };
23: use stacked_errors::{
24: Result,
25: StackableErr,
26: bail,
27: };
28:
29: use std::{
30: io::Cursor,
31: os::unix::fs::PermissionsExt,
32: path::Path,
33: };
34:
35: fn main () -> Result<()> {
36: smol::block_on(Compat::new(async {
37: async_main().await.unwrap()
38: }));
39:
40: Ok(())
41: }
42:
43: async fn async_main () -> Result<()> {
44: let specs = OptSpecs::new()
45: .option("help", "h", OptValue::None)
46: .option("help", "help", OptValue::None)
47: .option("config", "c", OptValue::Required)
48: .option("config", "config", OptValue::Required)
49: .flag(OptFlags::OptionsEverywhere);
50: let mut args = std::env::args();
51: args.next();
52: let parsed = specs.getopt(args);
53: for u in &parsed.unknown {
54: println!("Unknown option: {u}");
55: }
56: if !(parsed.unknown.is_empty()) || parsed.options_first("help").is_some() {
57: println!("SMTP2TG v{}, (C) 2024 - 2025\n\n\
58: \t-h|--help\tDisplay this help\n\
59: \t-c|--config …\tSet configuration file location.",
60: env!("CARGO_PKG_VERSION"));
61: return Ok(());
62: };
63: let config_file = Path::new(if let Some(path) = parsed.options_value_last("config") {
64: &path[..]
65: } else {
66: "smtp2tg.toml"
67: });
68: if !config_file.exists() {
69: bail!("can't read configuration from {config_file:?}");
70: };
71: {
72: let meta = metadata(config_file).await.stack()?;
73: if (!0o100600 & meta.permissions().mode()) > 0 {
74: bail!("other users can read or write config file {config_file:?}\n\
75: File permissions: {:o}", meta.permissions().mode());
76: }
77: }
78: let settings: config::Config = config::Config::builder()
79: .set_default("api_gateway", "https://api.telegram.org").stack()?
80: .set_default("fields", vec!["date", "from", "subject"]).stack()?
81: .set_default("hostname", "smtp.2.tg").stack()?
82: .set_default("listen_on", "0.0.0.0:1025").stack()?
83: .set_default("unknown", "relay").stack()?
84: .set_default("domains", vec!["localhost", hostname::get().stack()?.to_str().expect("Failed to get current hostname")]).stack()?
85: .add_source(config::File::from(config_file))
86: .build()
87: .with_context(|| format!("[{config_file:?}] there was an error reading config\n\
88: \tplease consult \"smtp2tg.toml.example\" for details"))?;
89:
90: let listen_on = settings.get_string("listen_on").stack()?;
91: let server_name = settings.get_string("hostname").stack()?;
92: let core = MailServer::new(settings)?;
93: let mut server = mailin_embedded::Server::new(core);
94:
95: server.with_name(server_name)
96: .with_ssl(mailin_embedded::SslConfig::None).unwrap()
97: .with_addr(listen_on).unwrap();
98: server.serve().unwrap();
99:
100: Ok(())
101: }