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