Index: README.md ================================================================== --- README.md +++ README.md @@ -22,11 +22,10 @@ ### Example configuration ```toml api_key = "replace-with-your-telegram-bot-token" api_gateway = "https://api.telegram.org" listen_on = "127.0.0.1:1025" -unknown = "relay" fields = ["date", "from", "subject"] domains = ["example.com", "localhost"] default = 0 @@ -33,23 +32,10 @@ [recipients] "admin@example.com" = 12345678 "alerts@example.com" = -10012345678 ``` -To catch bounces (so they wouldn't stuck in upper mail server) make sure sender -envelope address is real as required by mail library (actually not sure whether -this applies to mailin). For example Postfix has to be tweaked like this: - -$config_directory/main.cf: - smtp_generic_maps = hash:$config_directory/generic - -$config_directory/generic: - "" postmaster@example.com - <> postmaster@example.com - -Actually not sure which one works... - --- ## Usage ### Run Index: src/lib.rs ================================================================== --- src/lib.rs +++ src/lib.rs @@ -63,11 +63,10 @@ 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("unknown", "relay").stack()? .set_default("domains", vec!["localhost", hostname::get().expect("Failed to get current hostname") .to_str().expect("Can't convert hostname to string, bad UTF-8?")]).stack()? .add_source(config::File::from(config_file)) .build() Index: src/mail.rs ================================================================== --- src/mail.rs +++ src/mail.rs @@ -32,10 +32,11 @@ OK }, }; use regex::{ Regex, + RegexBuilder, escape, }; use stacked_errors::{ Result, StackableErr, @@ -53,11 +54,10 @@ /// `MailServer` Central object with TG api and configuration #[derive(Clone, Debug)] pub struct MailServer { data: Vec, headers: Option, - relay: bool, tg: Arc, fields: HashSet, address: Regex, } @@ -78,11 +78,11 @@ for (name, value) in settings.get_table("recipients") .expect("[smtp2tg.toml] missing table \"recipients\".\n") { let value = value.into_int() .context("[smtp2tg.toml] \"recipient\" table values should be integers.\n")?; - recipients.insert(name, value); + recipients.insert(name.to_lowercase().replace('.', ""), value); } let tg = Arc::new(TelegramTransport::new(api_key, recipients, &settings)?); let fields = HashSet::::from_iter(settings.get_array("fields") .expect("[smtp2tg.toml] \"fields\" should be an array") @@ -97,25 +97,16 @@ panic!("[smtp2tg.toml] can't check of domains in \"domains\": {domain}"); } } let domains = domains.into_iter().map(|s| escape(&s)) .collect::>().join("|"); - let address = Regex::new(&format!("^[a-z0-9][-a-z0-9]*(@({domains}))?$")).stack()?; - let relay = match settings.get_string("unknown") - .context("[smtp2tg.toml] can't get \"unknown\" policy.\n")?.as_str() - { - "relay" => true, - "deny" => false, - _ => { - bail!("[smtp2tg.toml] \"unknown\" should be either \"relay\" or \"deny\".\n"); - }, - }; + let address = RegexBuilder::new(&format!("^[a-z0-9][a-z0-9.-]*(@({domains}))?$")) + .case_insensitive(true).build().stack()?; Ok(MailServer { data: vec!(), headers: None, - relay, tg, fields, address, }) } @@ -144,11 +135,11 @@ .context("Failed to parse mail.")?; // Adding all known addresses to recipient list, for anyone else adding default // Also if list is empty also adding default let mut rcpt: HashSet<&ChatPeerId> = HashSet::new(); - if headers.to.is_empty() && !self.relay { + if headers.to.is_empty() { bail!("Relaying is disabled, and there's no destination address"); } for item in &headers.to { rcpt.insert(self.get_id(item)?); }; @@ -290,11 +281,11 @@ INVALID_CREDENTIALS } /// Verify whether address is deliverable fn rcpt (&mut self, to: &str) -> Response { - if self.relay || self.get_id(to).is_ok() { + if self.get_id(to).is_ok() { OK } else { NO_MAILBOX } } Index: src/telegram.rs ================================================================== --- src/telegram.rs +++ src/telegram.rs @@ -93,11 +93,11 @@ /// * `Result<&ChatPeerId>` - Chat ID if found. /// /// # Errors /// Returns an error if `name` is not configured. pub fn get (&self, name: &str) -> Result<&ChatPeerId> { - self.recipients.get(name) + self.recipients.get(&name.to_lowercase().replace('.', "")) .with_context(|| format!("Recipient \"{name}\" not found in configuration")) } /// Sends a text message to a specified chat. /// Index: tests/mail.rs ================================================================== --- tests/mail.rs +++ tests/mail.rs @@ -12,11 +12,10 @@ let settings = config::Config::builder() .add_source(config::File::from_str(r#" api_key = "test-api-key" api_gateway = "https://api.telegram.org" default = 0 - unknown = "relay" fields = ["date", "from", "subject"] domains = ["example.com"] [recipients] "someone@example.com" = 1 @@ -26,24 +25,34 @@ .stack()?; MailServer::new(settings) } #[test] -fn get_id_returns_configured_recipient () -> Result<()> { +fn get_id_properly_resolves_addresses () -> Result<()> { let server = build_server()?; let cases = [ ("someone@example.com", 1), ("someone", 0), ("root", -1), ("unknown@example.com", 0), + ("SOMEONE@example.com", 1), // uppercase local part + ("someone@EXAMPLE.COM", 1), // uppercase domain + ("some.one@example.com", 1), // functionally equivalent to skipping '.' + ("some-one-2", 0), // Hyphens ]; for (email, id) in cases { assert_eq!(*server.get_id(email)?, ChatPeerId::from(id), "email [{email}] expected to return id [{id}]"); } let cases = [ "someone@otherdomain.net", + "@example.com", // empty local part + "some@one@example.com", // more than one '@' + "someone@example.com.evil", + "someone@example.org", ]; for email in cases { - assert!(server.get_id(email).unwrap_err().to_string().contains("Doesn't look like address from one of our domains."), "email [{email}] expected to fail"); + let err = server.get_id(email).err() + .ok_or_else(|| format!("email [{email}] expected to fail")).stack()?; + assert!(err.to_string().contains("Doesn't look like address from one of our domains.")); } Ok(()) }