mod common; pub use common::*; use indymilter_test::*; use spf_milter::*; use std::{ net::{Ipv4Addr, SocketAddr}, path::{Path, PathBuf}, }; use tokio::fs; use viaspf::lookup::LookupError; #[tokio::test] async fn config_reload() { let config_file = config_file_name(); let opts = configure_logging(CliOptions::builder()) .config_file(&config_file) .build(); let lookup = MockLookup::new() .with_lookup_txt(|name| match name.as_str() { "example.org." => Ok(vec!["v=spf1 a -all".into()]), _ => Err(LookupError::NoRecords), }) .with_lookup_a(|name| match name.as_str() { "example.org." => Ok(vec![Ipv4Addr::new(123, 123, 123, 123)]), _ => Err(LookupError::NoRecords), }); // Write initial configuration with `Received-SPF` header. fs::write( &config_file, " socket = inet:127.0.0.1:0 verify_helo = no header = Received-SPF ", ) .await .unwrap(); let config = Config::read_with_lookup(opts, lookup).await.unwrap(); let milter = SpfMilter::spawn(config).await.unwrap(); let addr = milter.addr(); exec_test(addr, true).await.unwrap(); // Write updated configuration with `Authentication-Results` header. Also // try changing `socket`, which needs a restart to become effective, but // does not prevent reloading. fs::write( &config_file, " socket = inet:127.0.0.1:3333 verify_helo = no header = Authentication-Results ", ) .await .unwrap(); milter.reload_config().await; exec_test(addr, false).await.unwrap(); // Write configuration with the initial `Received-SPF` header, but since the // configuration is now invalid it won’t get reloaded. fs::write( &config_file, " socket = inet:127.0.0.1:3333 verify_helo = no header = Received-SPF invalid_key = invalid_value ", ) .await .unwrap(); milter.reload_config().await; exec_test(addr, false).await.unwrap(); milter.shutdown().await.unwrap(); } async fn exec_test(addr: SocketAddr, received_spf: bool) -> TestResult<()> { let mut conn = TestConnection::open(addr).await?; conn.macros(MacroStage::Connect, [("j", "mail.gluet.ch")]).await?; let status = conn.connect("client.example.org", [123, 123, 123, 123]).await?; assert_eq!(status, Status::Continue); let status = conn.helo("mail.example.org").await?; assert_eq!(status, Status::Continue); let status = conn.mail([""]).await?; assert_eq!(status, Status::Continue); conn.macros(MacroStage::Data, [("i", "1234567ABC")]).await?; let (actions, status) = conn.eom().await?; assert_eq!(status, Status::Continue); if received_spf { assert!(actions.has_insert_header(any(), "Received-SPF", any())); } else { assert!(actions.has_insert_header(any(), "Authentication-Results", any())); } conn.close().await?; Ok(()) } fn config_file_name() -> PathBuf { // Place configuration file next to the `spf-milter` executable used to run // this integration test. let file_path = to_config_file_name(file!()); Path::new(env!("CARGO_BIN_EXE_spf-milter")) .parent() .unwrap() .join(file_path.file_name().unwrap()) }