diff --git a/filtermail/README.md b/filtermail/README.md index 00e4392b..bf29732a 100644 --- a/filtermail/README.md +++ b/filtermail/README.md @@ -37,8 +37,7 @@ depending on address type: - **domain** - performs a strict DKIM verification and domain alignment check (domain of address from `From` header must exactly match the DKIM signature domain), rejecting messages that fail. - - **domain-literal (IP address)** - rejects message if the IP in domain-literal of `From` header address - does not match the origin IP received by `XFORWARD` command. + - **domain-literal (IP address)** - currently no-op. 5. In case of a DKIM failure, the message is saved to `/tmp/filtermail-rejected/dkim-verify` directory for later inspection. @@ -104,9 +103,6 @@ with few considerations: - Filtermail expects to receive messages from a trusted server, and thus should not be exposed directly to the internet. -- In incoming mode it expects to receive `XFORWARD` commands with the origin IP, -if the other server doesn't support this, -it will lead to rejection of every email using domain-literals in it's `From` header address. - Issues outside of chatmail relay context are not necessarily considered bugs; PRs fixing them are not guaranteed to be accepted. (Trivial changes may still be considered, diff --git a/filtermail/src/config.rs b/filtermail/src/config.rs index f6f52909..f399f9ec 100644 --- a/filtermail/src/config.rs +++ b/filtermail/src/config.rs @@ -104,3 +104,25 @@ impl Config { NonZeroU32::new(10).expect("10 != 0") } } + +#[cfg(test)] +impl Default for Config { + /// Creates a default configuration with example.org domain. + /// + /// Used for tests. + fn default() -> Self { + Self { + filtermail_smtp_port: Self::default_filtermail_smtp_port(), + filtermail_smtp_port_incoming: Self::default_filtermail_smtp_port_incoming(), + postfix_reinject_port: Self::default_postfix_reinject_port(), + postfix_reinject_port_incoming: Self::default_postfix_reinject_port_incoming(), + max_message_size: Self::default_max_message_size(), + max_user_send_per_minute: Self::default_max_user_send_per_minute(), + max_user_send_burst_size: Self::default_max_user_send_burst_size(), + passthrough_senders: Vec::new(), + passthrough_recipients: Vec::new(), + mail_domain: "example.org".to_string(), + mailboxes_dir: None, + } + } +} diff --git a/filtermail/src/inbound.rs b/filtermail/src/inbound.rs index 7b4c28cd..dc981855 100644 --- a/filtermail/src/inbound.rs +++ b/filtermail/src/inbound.rs @@ -27,23 +27,16 @@ impl IncomingBeforeQueueHandler { }) } - /// Verify the origin of the email either by performing DKIM verification or by matching the FROM - /// domain literal with the origin IP. + /// Verify the origin of the email by performing a DKIM verification on a regular domain. + /// + /// Currently a no-op for valid domain-literals. async fn verify_origin(&self, envelope: &Envelope, from_addr: &str) -> Result<(), String> { let from_domain = AddressDomain::from_str(from_addr).map_err(|e| e.smtp_response())?; match from_domain { - AddressDomain::Literal(ip) => { - if !envelope.origin_ip.eq_ignore_ascii_case(&ip) { - log::warn!( - "Received invalid origin address: {ip}, actual: {}", - envelope.origin_ip - ); - return Err(format!( - "500 Invalid FROM domain literal: {ip} does not match origin IP {}", - envelope.origin_ip - )); - } + AddressDomain::Literal(_) => { + // Subject to change: we currently don't perform any additional authentication + // for domain-literals and rely purely on encryption. } AddressDomain::Name(domain) => { if !self.skip_dkim @@ -144,3 +137,35 @@ impl SmtpHandler for IncomingBeforeQueueHandler { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use rstest::{fixture, rstest}; + use testresult::TestResult; + + #[fixture] + fn config() -> Config { + Config::default() + } + + /// Test that domain-literals are not rejected by origin check. + #[rstest] + #[tokio::test] + #[case::ipv4(include_bytes!("../test_data/encrypted-ipv4.eml"), "one@[192.0.2.0]")] + #[case::ipv6(include_bytes!("../test_data/encrypted-ipv6.eml"), "one@[IPv6:2001:db8::1]")] + async fn test_domain_literals_allowed( + #[case] eml: &[u8], + #[case] address: &str, + config: Config, + ) -> TestResult { + let handler = IncomingBeforeQueueHandler::new(config, false)?; + let mut envelope = Envelope { + mail_from: address.to_string(), + origin_ip: "".to_string(), // Currently shouldn't be relevant. + data: eml.to_vec(), + rcpt_to: vec!["does.not.matter@example.org".to_string()], + }; + Ok(handler.check_data(&mut envelope).await?) + } +} diff --git a/filtermail/test_data/encrypted-ipv4.eml b/filtermail/test_data/encrypted-ipv4.eml new file mode 100644 index 00000000..0d32e3ac --- /dev/null +++ b/filtermail/test_data/encrypted-ipv4.eml @@ -0,0 +1,66 @@ +From: one@[192.0.2.0] +To: two@example.org +Subject: ... +Date: Sun, 15 Oct 2023 16:43:21 +0000 +Message-ID: +In-Reply-To: +References: + +Chat-Version: 1.0 +Autocrypt: addr=one@example.org; prefer-encrypt=mutual; + keydata=xjMEZSwWjhYJKwYBBAHaRw8BAQdAQBEhqeJh0GueHB6kF/DUQqYCxARNBVokg/AzT+7LqH + rNFzxiYXJiYXpAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUsFo4CGwMECwkIBwYVCAkKCwID + FgIBFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX9A4AEAnHWHp49eBCMHK5t66gYPiW + XQuB1mwUjzGfYWB+0RXUoA/0xcQ3FbUNlGKW7Blp6eMFfViv6Mv2d3kNSXACB6nmcMzjgEZSwWjhIK + KwYBBAGXVQEFAQEHQBpY5L2M1XHo0uxf8SX1wNLBp/OVvidoWHQF2Jz+kJsUAwEIB8J4BBgWCAAgBQ + JlLBaOAhsMFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX/INgEA37AJaNvruYsJVanP + IXnYw4CKd55UAwl8Zcy+M2diAbkA/0fHHcGV4r78hpbbL1Os52DPOdqYQRauIeJUeG+G6bQO +MIME-Version: 1.0 +Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; + boundary="YFrteb74qSXmggbOxZL9dRnhymywAi" + + +--YFrteb74qSXmggbOxZL9dRnhymywAi +Content-Description: PGP/MIME version identification +Content-Type: application/pgp-encrypted + +Version: 1 + + +--YFrteb74qSXmggbOxZL9dRnhymywAi +Content-Description: OpenPGP encrypted message +Content-Disposition: inline; filename="encrypted.asc"; +Content-Type: application/octet-stream; name="encrypted.asc" + +-----BEGIN PGP MESSAGE----- + +wU4DhW3gBZ/VvCYSAQdA8bMs2spwbKdGjVsL1ByPkNrqD7frpB73maeL6I6SzDYg +O5G53tv339RdKq3WRcCtEEvxjHlUx2XNwXzC04BpmfvBTgNfPUyLDzjXnxIBB0Ae +8ymwGvXMCCimHXN0Dg8Ui62KOi03h0UgheoHWovJSCDF4CKre/xtFr3nL7lq/PKI +JsjVNz7/RK9FSXF6WwfONtLCyQGEuVAsB/KXfCBEyfKhaMwGHvhujRidGW5uV1no +lMGl3ODmo29Lgeu2uSE7EpJRZoe6hU6ddmBkqxax61ZtkaFlGFFpdo2K8balNNdz +ZsJ/9mmI9x3oOJ4/l1nhQbUO9ADbs7gJhFdV5Qkp30b5fCI7bU+aoe1ccBbLe/WM +YUty1PqcuQT7XjA+XmYuL261tvW8pBetT+i33/E2d8PzzYt2IuK9qeevyS+yxdwA +kfwejFWzzsUlJaDxs1x4XOxkMgSj+jo+g12dFOb7fyClsAnq23iDb8AuaT/BScAI ++lO+gher69+6LmM7VGHLG5k762J1jTaQCaKt1s8TAWV99Eo4491vL6fyvk3l/Cfg +RXSwiWFgj19Pn0Rq7CD9v22UE2vdUMBTcV4aw79mClk1YQ23jbF0y5DCjPdJ62Zo +tskBgFt3NoWV80jZ76zIBLrrjLwCCll8JjJtFwSkt2GX5RFBsVa4A8IDht9RtEk7 +rrHgbSZQfkauEi/mH3/6CDZoLqSHudUZ7d4MaJwun1TkFYGe2ORwGJd4OBj3oGJp +H8YBwCpk///L/fKjX0Gg3M8nrpM4wrRFhPKidAgO/kcm25X4+ZHlVkWBTCt5RWKI +fHh6oLDZCqCfcgMkE1KKmwfIHaUkhq5BPRigwy6i5dh1DM4+1UCLh3dxzVbqE9b9 +61NB19nXdRtDA2sOUnj9ve6m/wEPyCb6/zBQZqvCBYb1/AjdXpUrFT+DbpfyxaXN +XfhDVb5mNqNM/IVj0V5fvTc6vOfYbzQtPm10H+FdWWfb+rJRfyC3MA2w2IqstFe3 +w3bu2iE6CQvSqRvge+ZqLKt/NqYwOURiUmpuklbl3kPJ97+mfKWoiqk8Iz1VY+bb +NMUC7aoGv+jcoj+WS6PYO8N6BeRVUUB3ZJSf8nzjgxm1/BcM+UD3BPrlhT11ODRs +baifGbprMWwt3dhb8cQgRT8GPdpO1OsDkzL6iikMjLHWWiA99GV6ruiHsIPw6boW +A6/uSOskbDHOROotKmddGTBd0iiHXAoQsJFt1ZjUkt6EHrgWs+GAvrvKpXs1mrz8 +uj3GwEFrHS+Xuf2UDgpszYT3hI2cL/kUtGakVR7m7vVMZqXBUbZdGAEb1PZNPwsI +E4aMK02+EVB+tSN4Fzj99N2YD0inVYt+oPjr2tHhUS6aSGBNS/48Ki47DOg4Sxkn +lkOWnEbCD+XTnbDd +=agR5 +-----END PGP MESSAGE----- + + +--YFrteb74qSXmggbOxZL9dRnhymywAi-- + + diff --git a/filtermail/test_data/encrypted-ipv6.eml b/filtermail/test_data/encrypted-ipv6.eml new file mode 100644 index 00000000..e728ce97 --- /dev/null +++ b/filtermail/test_data/encrypted-ipv6.eml @@ -0,0 +1,66 @@ +From: one@[IPv6:2001:db8::1] +To: two@example.org +Subject: ... +Date: Sun, 15 Oct 2023 16:43:21 +0000 +Message-ID: +In-Reply-To: +References: + +Chat-Version: 1.0 +Autocrypt: addr=one@example.org; prefer-encrypt=mutual; + keydata=xjMEZSwWjhYJKwYBBAHaRw8BAQdAQBEhqeJh0GueHB6kF/DUQqYCxARNBVokg/AzT+7LqH + rNFzxiYXJiYXpAYzIudGVzdHJ1bi5vcmc+wosEEBYIADMCGQEFAmUsFo4CGwMECwkIBwYVCAkKCwID + FgIBFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX9A4AEAnHWHp49eBCMHK5t66gYPiW + XQuB1mwUjzGfYWB+0RXUoA/0xcQ3FbUNlGKW7Blp6eMFfViv6Mv2d3kNSXACB6nmcMzjgEZSwWjhIK + KwYBBAGXVQEFAQEHQBpY5L2M1XHo0uxf8SX1wNLBp/OVvidoWHQF2Jz+kJsUAwEIB8J4BBgWCAAgBQ + JlLBaOAhsMFiEEFTfUNvVnY3b9F7yHnmme1PfUhX8ACgkQnmme1PfUhX/INgEA37AJaNvruYsJVanP + IXnYw4CKd55UAwl8Zcy+M2diAbkA/0fHHcGV4r78hpbbL1Os52DPOdqYQRauIeJUeG+G6bQO +MIME-Version: 1.0 +Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; + boundary="YFrteb74qSXmggbOxZL9dRnhymywAi" + + +--YFrteb74qSXmggbOxZL9dRnhymywAi +Content-Description: PGP/MIME version identification +Content-Type: application/pgp-encrypted + +Version: 1 + + +--YFrteb74qSXmggbOxZL9dRnhymywAi +Content-Description: OpenPGP encrypted message +Content-Disposition: inline; filename="encrypted.asc"; +Content-Type: application/octet-stream; name="encrypted.asc" + +-----BEGIN PGP MESSAGE----- + +wU4DhW3gBZ/VvCYSAQdA8bMs2spwbKdGjVsL1ByPkNrqD7frpB73maeL6I6SzDYg +O5G53tv339RdKq3WRcCtEEvxjHlUx2XNwXzC04BpmfvBTgNfPUyLDzjXnxIBB0Ae +8ymwGvXMCCimHXN0Dg8Ui62KOi03h0UgheoHWovJSCDF4CKre/xtFr3nL7lq/PKI +JsjVNz7/RK9FSXF6WwfONtLCyQGEuVAsB/KXfCBEyfKhaMwGHvhujRidGW5uV1no +lMGl3ODmo29Lgeu2uSE7EpJRZoe6hU6ddmBkqxax61ZtkaFlGFFpdo2K8balNNdz +ZsJ/9mmI9x3oOJ4/l1nhQbUO9ADbs7gJhFdV5Qkp30b5fCI7bU+aoe1ccBbLe/WM +YUty1PqcuQT7XjA+XmYuL261tvW8pBetT+i33/E2d8PzzYt2IuK9qeevyS+yxdwA +kfwejFWzzsUlJaDxs1x4XOxkMgSj+jo+g12dFOb7fyClsAnq23iDb8AuaT/BScAI ++lO+gher69+6LmM7VGHLG5k762J1jTaQCaKt1s8TAWV99Eo4491vL6fyvk3l/Cfg +RXSwiWFgj19Pn0Rq7CD9v22UE2vdUMBTcV4aw79mClk1YQ23jbF0y5DCjPdJ62Zo +tskBgFt3NoWV80jZ76zIBLrrjLwCCll8JjJtFwSkt2GX5RFBsVa4A8IDht9RtEk7 +rrHgbSZQfkauEi/mH3/6CDZoLqSHudUZ7d4MaJwun1TkFYGe2ORwGJd4OBj3oGJp +H8YBwCpk///L/fKjX0Gg3M8nrpM4wrRFhPKidAgO/kcm25X4+ZHlVkWBTCt5RWKI +fHh6oLDZCqCfcgMkE1KKmwfIHaUkhq5BPRigwy6i5dh1DM4+1UCLh3dxzVbqE9b9 +61NB19nXdRtDA2sOUnj9ve6m/wEPyCb6/zBQZqvCBYb1/AjdXpUrFT+DbpfyxaXN +XfhDVb5mNqNM/IVj0V5fvTc6vOfYbzQtPm10H+FdWWfb+rJRfyC3MA2w2IqstFe3 +w3bu2iE6CQvSqRvge+ZqLKt/NqYwOURiUmpuklbl3kPJ97+mfKWoiqk8Iz1VY+bb +NMUC7aoGv+jcoj+WS6PYO8N6BeRVUUB3ZJSf8nzjgxm1/BcM+UD3BPrlhT11ODRs +baifGbprMWwt3dhb8cQgRT8GPdpO1OsDkzL6iikMjLHWWiA99GV6ruiHsIPw6boW +A6/uSOskbDHOROotKmddGTBd0iiHXAoQsJFt1ZjUkt6EHrgWs+GAvrvKpXs1mrz8 +uj3GwEFrHS+Xuf2UDgpszYT3hI2cL/kUtGakVR7m7vVMZqXBUbZdGAEb1PZNPwsI +E4aMK02+EVB+tSN4Fzj99N2YD0inVYt+oPjr2tHhUS6aSGBNS/48Ki47DOg4Sxkn +lkOWnEbCD+XTnbDd +=agR5 +-----END PGP MESSAGE----- + + +--YFrteb74qSXmggbOxZL9dRnhymywAi-- + +