mirror of
https://github.com/chatmail/relay.git
synced 2026-08-10 10:30:51 +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,
|
||||
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
|
||||
|
||||
Additional options that can be set using environment variables:
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
//! Configuration file handling for filtermail.
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::net::IpAddr;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Chatmail configuration subset used by filtermail.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "Config::default_filtermail_host")]
|
||||
pub filtermail_host: IpAddr,
|
||||
#[serde(default = "Config::default_filtermail_smtp_port")]
|
||||
pub filtermail_smtp_port: u16,
|
||||
#[serde(default = "Config::default_filtermail_smtp_port_incoming")]
|
||||
pub filtermail_smtp_port_incoming: u16,
|
||||
#[serde(default = "Config::default_postfix_host")]
|
||||
pub postfix_host: String,
|
||||
#[serde(default = "Config::default_postfix_reinject_port")]
|
||||
pub postfix_reinject_port: u16,
|
||||
#[serde(default = "Config::default_postfix_reinject_port_incoming")]
|
||||
@@ -82,12 +87,18 @@ impl Config {
|
||||
|
||||
// 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 {
|
||||
10080
|
||||
}
|
||||
const fn default_filtermail_smtp_port_incoming() -> u16 {
|
||||
10081
|
||||
}
|
||||
fn default_postfix_host() -> String {
|
||||
"127.0.0.1".to_owned()
|
||||
}
|
||||
const fn default_postfix_reinject_port() -> u16 {
|
||||
10025
|
||||
}
|
||||
@@ -112,8 +123,10 @@ impl Default for Config {
|
||||
/// Used for tests.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
filtermail_host: Self::default_filtermail_host(),
|
||||
filtermail_smtp_port: Self::default_filtermail_smtp_port(),
|
||||
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_incoming: Self::default_postfix_reinject_port_incoming(),
|
||||
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 async_trait::async_trait;
|
||||
use mailparse::{MailHeaderMap, parse_mail};
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Handler for incoming SMTP messages.
|
||||
@@ -16,14 +17,18 @@ pub struct IncomingBeforeQueueHandler {
|
||||
config: Config,
|
||||
dkim_verifier: DkimVerifier,
|
||||
skip_dkim: bool,
|
||||
reinject_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl IncomingBeforeQueueHandler {
|
||||
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 {
|
||||
config,
|
||||
dkim_verifier: DkimVerifier::new()?,
|
||||
skip_dkim,
|
||||
reinject_addr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -127,7 +132,7 @@ impl SmtpHandler for IncomingBeforeQueueHandler {
|
||||
async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {
|
||||
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
|
||||
.map_err(|e| {
|
||||
log::warn!("Failed to re-inject mail: {}", e);
|
||||
|
||||
+14
-5
@@ -41,6 +41,7 @@ use inbound::IncomingBeforeQueueHandler;
|
||||
use outbound::OutgoingBeforeQueueHandler;
|
||||
use smtp_server::run_smtp_server;
|
||||
use std::env;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -107,10 +108,10 @@ async fn main() {
|
||||
};
|
||||
|
||||
if mode == Mode::Outgoing {
|
||||
let handler = Arc::new(OutgoingBeforeQueueHandler::new(config.clone()));
|
||||
let addr = format!("127.0.0.1:{}", config.filtermail_smtp_port);
|
||||
let addr = (config.filtermail_host, config.filtermail_smtp_port);
|
||||
let handler = Arc::new(OutgoingBeforeQueueHandler::new(config.clone()).unwrap());
|
||||
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 {
|
||||
eprintln!("Server error: {}", e);
|
||||
@@ -126,13 +127,13 @@ async fn main() {
|
||||
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(
|
||||
// We want to panic here if the handler cannot be created.
|
||||
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;
|
||||
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 {
|
||||
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 governor::{DefaultKeyedRateLimiter, Quota, RateLimiter};
|
||||
use mailparse::{MailHeaderMap, parse_mail};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
/// Handler for outgoing SMTP messages.
|
||||
pub struct OutgoingBeforeQueueHandler {
|
||||
config: Config,
|
||||
reinject_addr: SocketAddr,
|
||||
send_rate_limiter: DefaultKeyedRateLimiter<String>,
|
||||
}
|
||||
|
||||
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)
|
||||
.allow_burst(config.max_user_send_burst_size);
|
||||
Self {
|
||||
Ok(Self {
|
||||
config,
|
||||
reinject_addr,
|
||||
send_rate_limiter: RateLimiter::keyed(quota),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +130,7 @@ impl SmtpHandler for OutgoingBeforeQueueHandler {
|
||||
async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {
|
||||
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
|
||||
.map_err(|e| {
|
||||
log::warn!("Failed to re-inject mail: {}", e);
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
use crate::smtp_server::Envelope;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufStream};
|
||||
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 `localhost:<smtp_port>`.
|
||||
pub async fn send(smtp_port: u16, envelope: &Envelope) -> Result<(), crate::error::Error> {
|
||||
/// Sends an email using an SMTP server at `smtp_addr`.
|
||||
pub async fn send(smtp_addr: SocketAddr, envelope: &Envelope) -> Result<(), crate::error::Error> {
|
||||
let socket = TcpSocket::new_v4()?;
|
||||
|
||||
// Disable Nagle's algorithm.
|
||||
socket.set_nodelay(true)?;
|
||||
|
||||
let stream = socket
|
||||
.connect(SocketAddr::new(LOCALHOST, smtp_port))
|
||||
.await?;
|
||||
let stream = socket.connect(smtp_addr).await?;
|
||||
|
||||
let mut buf_stream = BufStream::new(stream);
|
||||
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.
|
||||
pub async fn run_smtp_server<H>(
|
||||
addr: &str,
|
||||
addr: &impl tokio::net::ToSocketAddrs,
|
||||
handler: Arc<H>,
|
||||
max_size: usize,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
|
||||
Reference in New Issue
Block a user