mirror of
https://github.com/chatmail/relay.git
synced 2026-09-11 01:43:14 +00:00
feat: configurable hosts for listen and reinject (#84)
Adds configurable listen IP and postfix host via `filtermail_host` and `postfix_host` chatmail.ini config fields.
This commit is contained in:
@@ -85,6 +85,17 @@ item may start with `@` to whitelist whole recipient domains.
|
|||||||
- `mailboxes_dir` - path to mailboxes directory,
|
- `mailboxes_dir` - path to mailboxes directory,
|
||||||
defaults to `/home/vmail/mail/<mail_domain>`.
|
defaults to `/home/vmail/mail/<mail_domain>`.
|
||||||
|
|
||||||
|
The following options are Filtermail-specific,
|
||||||
|
they are not read by other chatmail relay components
|
||||||
|
and usually do not need to be set at all:
|
||||||
|
|
||||||
|
- `filtermail_host` - IP address to listen on,
|
||||||
|
defaults to `127.0.0.1`.
|
||||||
|
- `postfix_host` - hostname or IP address where postfix is set up,
|
||||||
|
a host is resolved only on Filtermail startup,
|
||||||
|
useful in case MTA runs somewhere outside of localhost,
|
||||||
|
defaults to `127.0.0.1`.
|
||||||
|
|
||||||
### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
Additional options that can be set using environment variables:
|
Additional options that can be set using environment variables:
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
//! Configuration file handling for filtermail.
|
//! Configuration file handling for filtermail.
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// Chatmail configuration subset used by filtermail.
|
/// Chatmail configuration subset used by filtermail.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
#[serde(default = "Config::default_filtermail_host")]
|
||||||
|
pub filtermail_host: IpAddr,
|
||||||
#[serde(default = "Config::default_filtermail_smtp_port")]
|
#[serde(default = "Config::default_filtermail_smtp_port")]
|
||||||
pub filtermail_smtp_port: u16,
|
pub filtermail_smtp_port: u16,
|
||||||
#[serde(default = "Config::default_filtermail_smtp_port_incoming")]
|
#[serde(default = "Config::default_filtermail_smtp_port_incoming")]
|
||||||
pub filtermail_smtp_port_incoming: u16,
|
pub filtermail_smtp_port_incoming: u16,
|
||||||
|
#[serde(default = "Config::default_postfix_host")]
|
||||||
|
pub postfix_host: String,
|
||||||
#[serde(default = "Config::default_postfix_reinject_port")]
|
#[serde(default = "Config::default_postfix_reinject_port")]
|
||||||
pub postfix_reinject_port: u16,
|
pub postfix_reinject_port: u16,
|
||||||
#[serde(default = "Config::default_postfix_reinject_port_incoming")]
|
#[serde(default = "Config::default_postfix_reinject_port_incoming")]
|
||||||
@@ -82,12 +87,18 @@ impl Config {
|
|||||||
|
|
||||||
// Following are needed since serde does not support default literals.
|
// Following are needed since serde does not support default literals.
|
||||||
|
|
||||||
|
const fn default_filtermail_host() -> IpAddr {
|
||||||
|
IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)
|
||||||
|
}
|
||||||
const fn default_filtermail_smtp_port() -> u16 {
|
const fn default_filtermail_smtp_port() -> u16 {
|
||||||
10080
|
10080
|
||||||
}
|
}
|
||||||
const fn default_filtermail_smtp_port_incoming() -> u16 {
|
const fn default_filtermail_smtp_port_incoming() -> u16 {
|
||||||
10081
|
10081
|
||||||
}
|
}
|
||||||
|
fn default_postfix_host() -> String {
|
||||||
|
"127.0.0.1".to_owned()
|
||||||
|
}
|
||||||
const fn default_postfix_reinject_port() -> u16 {
|
const fn default_postfix_reinject_port() -> u16 {
|
||||||
10025
|
10025
|
||||||
}
|
}
|
||||||
@@ -112,8 +123,10 @@ impl Default for Config {
|
|||||||
/// Used for tests.
|
/// Used for tests.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
filtermail_host: Self::default_filtermail_host(),
|
||||||
filtermail_smtp_port: Self::default_filtermail_smtp_port(),
|
filtermail_smtp_port: Self::default_filtermail_smtp_port(),
|
||||||
filtermail_smtp_port_incoming: Self::default_filtermail_smtp_port_incoming(),
|
filtermail_smtp_port_incoming: Self::default_filtermail_smtp_port_incoming(),
|
||||||
|
postfix_host: Self::default_postfix_host(),
|
||||||
postfix_reinject_port: Self::default_postfix_reinject_port(),
|
postfix_reinject_port: Self::default_postfix_reinject_port(),
|
||||||
postfix_reinject_port_incoming: Self::default_postfix_reinject_port_incoming(),
|
postfix_reinject_port_incoming: Self::default_postfix_reinject_port_incoming(),
|
||||||
max_message_size: Self::default_max_message_size(),
|
max_message_size: Self::default_max_message_size(),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::smtp_server::SmtpHandler;
|
|||||||
use crate::utils::{AddressDomain, extract_address, log_eml};
|
use crate::utils::{AddressDomain, extract_address, log_eml};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use mailparse::{MailHeaderMap, parse_mail};
|
use mailparse::{MailHeaderMap, parse_mail};
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// Handler for incoming SMTP messages.
|
/// Handler for incoming SMTP messages.
|
||||||
@@ -16,14 +17,18 @@ pub struct IncomingBeforeQueueHandler {
|
|||||||
config: Config,
|
config: Config,
|
||||||
dkim_verifier: DkimVerifier,
|
dkim_verifier: DkimVerifier,
|
||||||
skip_dkim: bool,
|
skip_dkim: bool,
|
||||||
|
reinject_addr: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IncomingBeforeQueueHandler {
|
impl IncomingBeforeQueueHandler {
|
||||||
pub fn new(config: Config, skip_dkim: bool) -> Result<Self, crate::error::Error> {
|
pub fn new(config: Config, skip_dkim: bool) -> Result<Self, crate::error::Error> {
|
||||||
|
let reinject_addr =
|
||||||
|
crate::resolve_addr(&config.postfix_host, config.postfix_reinject_port_incoming)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
config,
|
||||||
dkim_verifier: DkimVerifier::new()?,
|
dkim_verifier: DkimVerifier::new()?,
|
||||||
skip_dkim,
|
skip_dkim,
|
||||||
|
reinject_addr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +132,7 @@ impl SmtpHandler for IncomingBeforeQueueHandler {
|
|||||||
async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {
|
async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {
|
||||||
log::debug!("Re-injecting the mail that passed checks");
|
log::debug!("Re-injecting the mail that passed checks");
|
||||||
|
|
||||||
crate::smtp_client::send(self.config.postfix_reinject_port_incoming, envelope)
|
crate::smtp_client::send(self.reinject_addr, envelope)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::warn!("Failed to re-inject mail: {}", e);
|
log::warn!("Failed to re-inject mail: {}", e);
|
||||||
|
|||||||
+14
-5
@@ -41,6 +41,7 @@ use inbound::IncomingBeforeQueueHandler;
|
|||||||
use outbound::OutgoingBeforeQueueHandler;
|
use outbound::OutgoingBeforeQueueHandler;
|
||||||
use smtp_server::run_smtp_server;
|
use smtp_server::run_smtp_server;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -107,10 +108,10 @@ async fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if mode == Mode::Outgoing {
|
if mode == Mode::Outgoing {
|
||||||
let handler = Arc::new(OutgoingBeforeQueueHandler::new(config.clone()));
|
let addr = (config.filtermail_host, config.filtermail_smtp_port);
|
||||||
let addr = format!("127.0.0.1:{}", config.filtermail_smtp_port);
|
let handler = Arc::new(OutgoingBeforeQueueHandler::new(config.clone()).unwrap());
|
||||||
let max_size = config.max_message_size;
|
let max_size = config.max_message_size;
|
||||||
log::debug!("Outgoing SMTP server listening on {addr}");
|
log::debug!("Outgoing SMTP server listening on {}:{}", addr.0, addr.1);
|
||||||
|
|
||||||
if let Err(e) = run_smtp_server(&addr, handler, max_size).await {
|
if let Err(e) = run_smtp_server(&addr, handler, max_size).await {
|
||||||
eprintln!("Server error: {}", e);
|
eprintln!("Server error: {}", e);
|
||||||
@@ -126,13 +127,13 @@ async fn main() {
|
|||||||
log::warn!("DKIM verification DISABLED! This should not be used in production.");
|
log::warn!("DKIM verification DISABLED! This should not be used in production.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let addr = (config.filtermail_host, config.filtermail_smtp_port_incoming);
|
||||||
let handler = Arc::new(
|
let handler = Arc::new(
|
||||||
// We want to panic here if the handler cannot be created.
|
// We want to panic here if the handler cannot be created.
|
||||||
IncomingBeforeQueueHandler::new(config.clone(), skip_dkim).unwrap(),
|
IncomingBeforeQueueHandler::new(config.clone(), skip_dkim).unwrap(),
|
||||||
);
|
);
|
||||||
let addr = format!("127.0.0.1:{}", config.filtermail_smtp_port_incoming);
|
|
||||||
let max_size = config.max_message_size;
|
let max_size = config.max_message_size;
|
||||||
log::debug!("Incoming SMTP server listening on {addr}");
|
log::debug!("Incoming SMTP server listening on {}:{}", addr.0, addr.1);
|
||||||
|
|
||||||
if let Err(e) = run_smtp_server(&addr, handler, max_size).await {
|
if let Err(e) = run_smtp_server(&addr, handler, max_size).await {
|
||||||
eprintln!("Server error: {}", e);
|
eprintln!("Server error: {}", e);
|
||||||
@@ -140,3 +141,11 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_addr(host: &str, port: u16) -> Result<SocketAddr, error::Error> {
|
||||||
|
log::debug!("Resolving {host}");
|
||||||
|
Ok((host, port)
|
||||||
|
.to_socket_addrs()?
|
||||||
|
.next()
|
||||||
|
.ok_or(std::io::Error::other("Cannot resolve host"))?)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,21 +9,26 @@ use crate::utils::extract_address;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use governor::{DefaultKeyedRateLimiter, Quota, RateLimiter};
|
use governor::{DefaultKeyedRateLimiter, Quota, RateLimiter};
|
||||||
use mailparse::{MailHeaderMap, parse_mail};
|
use mailparse::{MailHeaderMap, parse_mail};
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
/// Handler for outgoing SMTP messages.
|
/// Handler for outgoing SMTP messages.
|
||||||
pub struct OutgoingBeforeQueueHandler {
|
pub struct OutgoingBeforeQueueHandler {
|
||||||
config: Config,
|
config: Config,
|
||||||
|
reinject_addr: SocketAddr,
|
||||||
send_rate_limiter: DefaultKeyedRateLimiter<String>,
|
send_rate_limiter: DefaultKeyedRateLimiter<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutgoingBeforeQueueHandler {
|
impl OutgoingBeforeQueueHandler {
|
||||||
pub fn new(config: Config) -> Self {
|
pub fn new(config: Config) -> Result<Self, crate::error::Error> {
|
||||||
|
let reinject_addr =
|
||||||
|
crate::resolve_addr(&config.postfix_host, config.postfix_reinject_port)?;
|
||||||
let quota = Quota::per_minute(config.max_user_send_per_minute)
|
let quota = Quota::per_minute(config.max_user_send_per_minute)
|
||||||
.allow_burst(config.max_user_send_burst_size);
|
.allow_burst(config.max_user_send_burst_size);
|
||||||
Self {
|
Ok(Self {
|
||||||
config,
|
config,
|
||||||
|
reinject_addr,
|
||||||
send_rate_limiter: RateLimiter::keyed(quota),
|
send_rate_limiter: RateLimiter::keyed(quota),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +130,7 @@ impl SmtpHandler for OutgoingBeforeQueueHandler {
|
|||||||
async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {
|
async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {
|
||||||
log::debug!("Re-injecting the mail that passed checks");
|
log::debug!("Re-injecting the mail that passed checks");
|
||||||
|
|
||||||
crate::smtp_client::send(self.config.postfix_reinject_port, envelope)
|
crate::smtp_client::send(self.reinject_addr, envelope)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::warn!("Failed to re-inject mail: {}", e);
|
log::warn!("Failed to re-inject mail: {}", e);
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
use crate::smtp_server::Envelope;
|
use crate::smtp_server::Envelope;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::SocketAddr;
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufStream};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufStream};
|
||||||
use tokio::net::TcpSocket;
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
const LOCALHOST: IpAddr = IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1));
|
/// Sends an email using an SMTP server at `smtp_addr`.
|
||||||
|
pub async fn send(smtp_addr: SocketAddr, envelope: &Envelope) -> Result<(), crate::error::Error> {
|
||||||
/// Sends an email using an SMTP server at `localhost:<smtp_port>`.
|
|
||||||
pub async fn send(smtp_port: u16, envelope: &Envelope) -> Result<(), crate::error::Error> {
|
|
||||||
let socket = TcpSocket::new_v4()?;
|
let socket = TcpSocket::new_v4()?;
|
||||||
|
|
||||||
// Disable Nagle's algorithm.
|
// Disable Nagle's algorithm.
|
||||||
socket.set_nodelay(true)?;
|
socket.set_nodelay(true)?;
|
||||||
|
|
||||||
let stream = socket
|
let stream = socket.connect(smtp_addr).await?;
|
||||||
.connect(SocketAddr::new(LOCALHOST, smtp_port))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut buf_stream = BufStream::new(stream);
|
let mut buf_stream = BufStream::new(stream);
|
||||||
let mut response = String::new();
|
let mut response = String::new();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub trait SmtpHandler: Send + Sync {
|
|||||||
|
|
||||||
/// Runs the SMTP server on the specified address with the given handler and maximum message size.
|
/// Runs the SMTP server on the specified address with the given handler and maximum message size.
|
||||||
pub async fn run_smtp_server<H>(
|
pub async fn run_smtp_server<H>(
|
||||||
addr: &str,
|
addr: &impl tokio::net::ToSocketAddrs,
|
||||||
handler: Arc<H>,
|
handler: Arc<H>,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
) -> Result<(), Box<dyn std::error::Error>>
|
) -> Result<(), Box<dyn std::error::Error>>
|
||||||
|
|||||||
Reference in New Issue
Block a user