diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..2eca268 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,46 @@ +use crate::address::CodedAddress; +use anyhow::{anyhow, ensure, Result}; +use std::str::FromStr; + +macro_rules! next_param { + ($it: ident) => { + $it.next().ok_or(anyhow!("missing parameter")) + }; +} + +#[derive(Debug)] +pub struct Input { + session_id: String, + token: String, + address: CodedAddress, +} + +impl Input { + pub fn answer(&self, msg: &str) { + println!("filter‐result|{0}|{1}|{msg}", self.session_id, self.token); + } + + pub fn get_coded_address(&self) -> &CodedAddress { + &self.address + } +} + +pub fn parse_input(input: &str) -> Result { + let mut params_it = input.split(crate::PARAM_SEPARATOR); + let stream = next_param!(params_it)?; + ensure!(stream == "filter", "{stream}: invalid stream"); + let _version = next_param!(params_it)?; + let _timestamp = next_param!(params_it)?; + let _subsystem = next_param!(params_it)?; + let filter = next_param!(params_it)?; + ensure!(filter == "rcpt‐to", "{filter}: invalid filter"); + let session_id = next_param!(params_it)?.to_string(); + let token = next_param!(params_it)?.to_string(); + let address = next_param!(params_it)?.trim_end(); + let address = CodedAddress::from_str(address)?; + Ok(Input { + session_id, + token, + address, + }) +} diff --git a/src/main.rs b/src/main.rs index ce4f5e2..f8b1fa8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,13 @@ use std::process::ExitCode; mod address; mod config; +mod input; mod service; const COMMENT_CHAR: char = '#'; const DEFAULT_SEPARATOR: char = '+'; const KEY_SEPARATOR: char = ':'; +const PARAM_SEPARATOR: char = '|'; fn main() -> ExitCode { match service::start_service() { diff --git a/src/service.rs b/src/service.rs index b120416..6632015 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,14 +1,19 @@ -use crate::address::CodedAddress; +use crate::address::KeyedAddress; use crate::config::Config; +use crate::input::{parse_input, Input}; use anyhow::Result; use clap::Parser; +use std::collections::HashSet; use std::io; pub const BUFF_SIZE: usize = 4096; pub const CONFIG_END: &str = "config|ready\n"; +pub const ANSWER_OK: &str = "proceed"; +pub const ANSWER_ERR: &str = "reject|550 No such recipient here"; pub fn start_service() -> Result<()> { let cfg = Config::parse(); + let addresses = cfg.addresses()?; let mut buffer = String::with_capacity(BUFF_SIZE); let stdin = io::stdin(); @@ -20,12 +25,37 @@ pub fn start_service() -> Result<()> { break; } } - println!("register|report|smtp‐in|tx‐rcpt"); + println!("register|filter|smtp‐in|rcpt‐to"); println!("register|ready"); // Input processing loop { buffer.clear(); stdin.read_line(&mut buffer)?; + if buffer.is_empty() { + continue; + } + match parse_input(&buffer) { + Ok(input) => { + if allow_email(&input, &addresses) { + input.answer(ANSWER_OK); + } else { + input.answer(ANSWER_ERR); + } + } + Err(e) => { + eprintln!("error: {e:#}"); + } + } } } + +fn allow_email(input: &Input, addr_lst: &HashSet) -> bool { + let address = input.get_coded_address(); + for addr_k in addr_lst { + if addr_k == address { + return addr_k.check_code(&address); + } + } + true +}