vsmtp-config 2.0.1-rc.4

Next-gen MTA. Secured, Faster and Greener
Documentation
/*
 * vSMTP mail transfer agent
 * Copyright (C) 2022 viridIT SAS
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see https://www.gnu.org/licenses/.
 *
*/
use vsmtp_common::{auth::Mechanism, CodeID, Reply};

/// This structure contains all the field to configure the server at the startup.
///
/// This structure will be loaded from a configuration file `-c, --config`
/// argument of the program. See [`crate::Config::from_vsl_file`].
///
/// All field are optional and defaulted if missing.
///
/// You can also use the builder [`Config::builder`] to use the builder pattern,
/// and create an instance programmatically.
///
/// You can find examples of configuration files in
/// <https://github.com/viridIT/vSMTP/tree/develop/examples/config>
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
    /// vSMTP's version requirement to parse this configuration file.
    pub version_requirement: semver::VersionReq,
    /// See [`field::FieldServer`]
    #[serde(default)]
    pub server: field::FieldServer,
    /// See [`field::FieldApp`]
    #[serde(default)]
    pub app: field::FieldApp,
    /// Optional path of the configuration on disk.
    pub path: Option<std::path::PathBuf>,
}

/// The inner field of the `vSMTP`'s configuration.
#[allow(clippy::module_name_repetitions)]
pub mod field {
    use super::{CodeID, Mechanism, Reply};
    use vsmtp_auth::dkim;

    /// This structure contains all the field to configure the server at the startup.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServer {
        /// Name of the server.
        ///
        /// Used with the response [`CodeID::Greetings`], and [`CodeID::Helo`],
        /// and [`CodeID::EhloPain`], and [`CodeID::EhloSecured`].
        #[serde(default = "FieldServer::hostname")]
        pub name: String,
        /// Maximum number of client served at the same time.
        ///
        /// The client will be rejected if the server is full.
        ///
        /// If this value is `-1`, then the server will accept any number of client.
        #[serde(default = "FieldServer::default_client_count_max")]
        pub client_count_max: i64,
        /// Maximum size in bytes of the message.
        #[serde(default = "FieldServer::default_message_size_limit")]
        pub message_size_limit: usize,
        /// see [`FieldServerSystem`]
        #[serde(default)]
        pub system: FieldServerSystem,
        /// see [`FieldServerInterfaces`]
        #[serde(default)]
        pub interfaces: FieldServerInterfaces,
        /// see [`FieldServerLogs`]
        #[serde(default)]
        pub logs: FieldServerLogs,
        /// see [`FieldServerQueues`]
        #[serde(default)]
        pub queues: FieldServerQueues,
        /// see [`FieldServerTls`]
        pub tls: Option<FieldServerTls>,
        /// see [`FieldServerSMTP`]
        #[serde(default)]
        pub smtp: FieldServerSMTP,
        /// see [`FieldServerDNS`]
        #[serde(default)]
        pub dns: FieldServerDNS,
        /// see [`FieldServerVirtual`]
        #[serde(default)]
        pub r#virtual: std::collections::BTreeMap<String, FieldServerVirtual>,
    }

    /// Readonly configuration for the dkim module.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldDkim {
        /// The private key used to sign the mail.
        pub private_key: Vec<SecretFile<std::sync::Arc<dkim::PrivateKey>>>,
    }

    /// The field related to the privileges used by `vSMTP`.
    #[derive(Debug, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerSystem {
        /// User running the server after the drop of privileges using `setuid`.
        #[serde(default = "FieldServerSystem::default_user")]
        #[serde(
            serialize_with = "crate::parser::syst_user::serialize",
            deserialize_with = "crate::parser::syst_user::deserialize"
        )]
        pub user: users::User,
        /// Group running the server after the drop of privileges using `setgid`.
        #[serde(default = "FieldServerSystem::default_group")]
        #[serde(
            serialize_with = "crate::parser::syst_group::serialize",
            deserialize_with = "crate::parser::syst_group::deserialize"
        )]
        pub group: users::Group,
        /// Group right set for the local delivery (maildir/mbox).
        #[serde(default)]
        #[serde(
            serialize_with = "crate::parser::syst_group::opt_serialize",
            deserialize_with = "crate::parser::syst_group::opt_deserialize"
        )]
        pub group_local: Option<users::Group>,
        /// see [`FieldServerSystemThreadPool`]
        #[serde(default)]
        pub thread_pool: FieldServerSystemThreadPool,
    }

    impl PartialEq for FieldServerSystem {
        fn eq(&self, other: &Self) -> bool {
            self.user.uid() == other.user.uid()
                && self.group.gid() == other.group.gid()
                && self.group_local.as_ref().map(users::Group::gid)
                    == other.group_local.as_ref().map(users::Group::gid)
                && self.thread_pool == other.thread_pool
        }
    }

    impl Eq for FieldServerSystem {}

    /// The field related to the thread allocation.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerSystemThreadPool {
        /// Number of thread used by the pool `receiver`.
        ///
        /// This pool receive the client connection and handle the SMTP transaction.
        pub receiver: usize,
        /// Number of thread used by the pool `processing`.
        ///
        /// This pool forward the mails received by the `receiver` pool to the `delivery` pool.
        ///
        /// The mails treated has been accepted with a code [`CodeID::Ok`].
        ///
        /// "Offline" modification are applied here.
        pub processing: usize,
        /// Number of thread used by the pool `delivery`.
        ///
        /// This pool send the mails to the recipient, and handle the delivery side.
        pub delivery: usize,
    }

    /// Address served by `vSMTP`. Either ipv4 or ipv6.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerInterfaces {
        /// List of address for the protocol SMTP.
        #[serde(default)]
        #[serde(deserialize_with = "crate::parser::socket_addr::deserialize")]
        pub addr: Vec<std::net::SocketAddr>,
        /// List of address for the protocol ESMTPA.
        #[serde(default)]
        #[serde(deserialize_with = "crate::parser::socket_addr::deserialize")]
        pub addr_submission: Vec<std::net::SocketAddr>,
        /// List of address for the protocol ESMTPSA.
        #[serde(default)]
        #[serde(deserialize_with = "crate::parser::socket_addr::deserialize")]
        pub addr_submissions: Vec<std::net::SocketAddr>,
    }

    /// The field related to the logs.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerLogs {
        /// Path and name of the log of the server.
        #[serde(default = "FieldServerLogs::default_filename")]
        pub filename: std::path::PathBuf,
        /// Customize the log level of the different part of the program.
        ///
        /// See <https://docs.rs/tracing-subscriber/0.3.15/tracing_subscriber/filter/struct.EnvFilter.html>
        #[serde(
            default = "FieldServerLogs::default_level",
            serialize_with = "crate::parser::tracing_directive::serialize",
            deserialize_with = "crate::parser::tracing_directive::deserialize"
        )]
        pub level: Vec<tracing_subscriber::filter::Directive>,
        /// see [`FieldServerLogSystem`]
        pub system: Option<FieldServerLogSystem>,
    }

    ///
    #[derive(
        Debug,
        Default,
        Copy,
        Clone,
        PartialEq,
        Eq,
        strum::Display,
        strum::EnumString,
        serde_with::DeserializeFromStr,
        serde_with::SerializeDisplay,
    )]
    pub enum SyslogFormat {
        ///
        #[strum(serialize = "3164")]
        Rfc3164,
        ///
        #[default]
        #[strum(serialize = "5424")]
        Rfc5424,
    }

    /// Configure how the logs are sent to the system log.
    #[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields, tag = "type", rename_all = "lowercase")]
    pub enum SyslogSocket {
        /// Send logs using udp.
        Udp {
            /// Local address for the UDP stream.
            #[serde(default = "SyslogSocket::default_udp_local")]
            local: std::net::SocketAddr,
            /// Remote address for the UDP stream.
            #[serde(default = "SyslogSocket::default_udp_server")]
            server: std::net::SocketAddr,
        },
        /// Send logs using tcp.
        Tcp {
            ///
            #[serde(default = "SyslogSocket::default_tcp_server")]
            server: std::net::SocketAddr,
        },
        /// Send logs using a unix socket with a custom path.
        Unix {
            /// Path to the unix socket.
            path: Option<std::path::PathBuf>,
        },
    }

    /// The configuration of the `system logging module`.
    ///
    /// The implementation is backended for `syslogd` or `journald`.
    #[serde_with::serde_as]
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, strum::Display)]
    #[serde(deny_unknown_fields, tag = "backend", rename_all = "lowercase")]
    pub enum FieldServerLogSystem {
        /// Parameters for the `syslogd` backend.
        Syslogd {
            ///
            #[serde_as(as = "serde_with::DisplayFromStr")]
            level: tracing::Level,
            ///
            #[serde(default)]
            format: SyslogFormat,
            ///
            #[serde(default)]
            socket: SyslogSocket,
        },
        ///
        Journald {
            ///
            #[serde_as(as = "serde_with::DisplayFromStr")]
            level: tracing::Level,
        },
    }

    /// The configuration of the `working queue`.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldQueueWorking {
        /// Size of the channel queue communicating the mails from the `receiver` pool to the `processing` pool.
        #[serde(default = "FieldQueueWorking::default_channel_size")]
        pub channel_size: usize,
    }

    /// The configuration of the `vqueue`
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldQueueDelivery {
        /// Size of the channel queue communicating the mails from the `processing` pool to the `delivery` pool.
        #[serde(default = "FieldQueueDelivery::default_channel_size")]
        pub channel_size: usize,
        /// Maximum number of attempt to deliver the mail before being considered dead.
        #[serde(default = "FieldQueueDelivery::default_deferred_retry_max")]
        pub deferred_retry_max: usize,
        /// The mail in the `deferred` are resend in a clock with this period.
        #[serde(with = "humantime_serde")]
        #[serde(default = "FieldQueueDelivery::default_deferred_retry_period")]
        pub deferred_retry_period: std::time::Duration,
    }

    /// The configuration of the filesystem for the mail queuer.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerQueues {
        /// The root directory for the queuer system.
        pub dirpath: std::path::PathBuf,
        /// see [`FieldQueueWorking`]
        #[serde(default)]
        pub working: FieldQueueWorking,
        /// see [`FieldQueueDelivery`]
        #[serde(default)]
        pub delivery: FieldQueueDelivery,
    }

    /// The configuration of one virtual entry for the server.
    #[derive(Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerVirtual {
        /// see [`FieldServerVirtualTls`]
        pub tls: Option<FieldServerVirtualTls>,
        /// see [`FieldServerDNS`]
        pub dns: Option<FieldServerDNS>,
        /// see [`FieldDkim`]
        // TODO: should not be an Option<> and should be under #[cfg(feature = "dkim")] ?
        pub dkim: Option<FieldDkim>,
    }

    /// The TLS parameter for the **OUTGOING SIDE** of the virtual entry.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerVirtualTls {
        /// TLS protocol supported
        pub protocol_version: Vec<vsmtp_common::ProtocolVersion>,
        /// Certificate chain to use for the TLS connection.
        /// (the first certificate should certify KEYFILE, the last should be a root CA)
        pub certificate: SecretFile<Vec<rustls::Certificate>>,
        /// Private key to use for the TLS connection.
        pub private_key: SecretFile<rustls::PrivateKey>,
    }

    #[doc(hidden)]
    #[derive(Debug, PartialEq, Eq, serde::Serialize)]
    #[serde(transparent, deny_unknown_fields)]
    pub struct SecretFile<T> {
        #[serde(skip_serializing)]
        pub inner: T,
        pub path: std::path::PathBuf,
    }

    /// The TLS parameter for the **INCOMING SIDE** of the server (common with the virtual entry).
    // TODO: should use [`FieldServerVirtualTls`] flattened ?
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerTls {
        /// Ignore the client’s ciphersuite order.
        /// Instead, choose the top ciphersuite in the server list which is supported by the client.
        #[serde(default)]
        pub preempt_cipherlist: bool,
        /// Timeout for the TLS handshake. Sending a [`CodeID::Timeout`] to the client.
        #[serde(with = "humantime_serde")]
        #[serde(default = "FieldServerTls::default_handshake_timeout")]
        pub handshake_timeout: std::time::Duration,
        /// TLS protocol supported
        pub protocol_version: Vec<vsmtp_common::ProtocolVersion>,
        /// TLS cipher suite supported
        #[serde(default = "FieldServerTls::default_cipher_suite")]
        pub cipher_suite: Vec<vsmtp_common::CipherSuite>,
    }

    /// Configuration of the client's error handling.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerSMTPError {
        /// The maximum number of errors before the client is delay between each response.
        ///
        /// `-1` to disable
        pub soft_count: i64,
        /// The maximum number of errors before the client is disconnected.
        ///
        /// `-1` to disable
        pub hard_count: i64,
        /// The delay used between each response, after `soft_count` errors.
        /// Unused if `soft_count` is `-1`.
        #[serde(with = "humantime_serde")]
        pub delay: std::time::Duration,
    }

    /// Configuration of the receiver timeout between each message.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerSMTPTimeoutClient {
        /// Delay between the connection and the first `HELO/EHLO`
        #[serde(with = "humantime_serde")]
        pub connect: std::time::Duration,
        /// Delay between the last `HELO/EHLO` and the next `MAIL FROM`
        #[serde(with = "humantime_serde")]
        pub helo: std::time::Duration,
        /// Delay between the last `MAIL FROM` and the next `RCPT TO`
        #[serde(with = "humantime_serde")]
        pub mail_from: std::time::Duration,
        /// Delay between the last `RCPT TO` and the next `DATA`
        #[serde(with = "humantime_serde")]
        pub rcpt_to: std::time::Duration,
        /// Delay between each message after the `DATA` command.
        #[serde(with = "humantime_serde")]
        pub data: std::time::Duration,
    }

    /// Policy of the extension AUTH.
    #[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerSMTPAuth {
        /// Some mechanisms are considered unsecure under non-TLS connections.
        /// If `false`, the server will allow to use them even on clair connections.
        ///
        /// `false` by default.
        #[serde(default = "FieldServerSMTPAuth::default_enable_dangerous_mechanism_in_clair")]
        pub enable_dangerous_mechanism_in_clair: bool,
        /// List of mechanisms supported by the server.
        #[serde(default = "FieldServerSMTPAuth::default_mechanisms")]
        pub mechanisms: Vec<Mechanism>,
        /// If the AUTH exchange is canceled, the server will not consider the connection as closing,
        /// increasing the number of attempt failed, until `attempt_count_max`, producing an error.
        #[serde(default = "FieldServerSMTPAuth::default_attempt_count_max")]
        pub attempt_count_max: i64,
    }

    /// Parameters of the SMTP.
    #[serde_with::serde_as]
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldServerSMTP {
        /// Maximum number of recipients received in the envelop, extra recipient will produce an [`CodeID::TooManyRecipients`].
        #[serde(default = "FieldServerSMTP::default_rcpt_count_max")]
        pub rcpt_count_max: usize,
        /// SMTP's error policy.
        #[serde(default)]
        pub error: FieldServerSMTPError,
        /// SMTP's timeout policy.
        #[serde(default)]
        pub timeout_client: FieldServerSMTPTimeoutClient,
        /// Dictionary of the reply sent by the server during the SMTP transaction.
        #[serde(default)]
        #[serde_as(as = "std::collections::BTreeMap<serde_with::DisplayFromStr, _>")]
        pub codes: std::collections::BTreeMap<CodeID, Reply>,
        /// SMTP's authentication policy.
        // TODO: should not be an Option<> and should be under #[cfg(feature = "esmtpa")]
        pub auth: Option<FieldServerSMTPAuth>,
    }

    /// Configuration of the DNS resolver.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[allow(clippy::large_enum_variant)]
    #[serde(tag = "type", deny_unknown_fields)]
    pub enum FieldServerDNS {
        /// Using the resolver of the system (/etc/resolv.conf).
        #[serde(rename = "system")]
        System,
        /// Using the google DNS resolver.
        #[serde(rename = "google")]
        Google {
            /// Parameters
            #[serde(default)]
            options: ResolverOptsWrapper,
        },
        /// Using the google DNS resolver.
        #[serde(rename = "cloudflare")]
        CloudFlare {
            /// Parameters
            #[serde(default)]
            options: ResolverOptsWrapper,
        },
        /// A custom resolver.
        #[serde(rename = "custom")]
        Custom {
            /// Configuration of the resolver.
            config: trust_dns_resolver::config::ResolverConfig,
            /// Parameters
            #[serde(default)]
            options: ResolverOptsWrapper,
        },
    }

    /// Parameter for the DNS resolver.
    // TODO: remove that and use serde_with
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[allow(clippy::struct_excessive_bools)]
    #[serde(deny_unknown_fields)]
    pub struct ResolverOptsWrapper {
        /// Specify the timeout for a request. Defaults to 5 seconds
        #[serde(with = "humantime_serde")]
        #[serde(default = "ResolverOptsWrapper::default_timeout")]
        pub timeout: std::time::Duration,
        /// Number of retries after lookup failure before giving up. Defaults to 2
        #[serde(default = "ResolverOptsWrapper::default_attempts")]
        pub attempts: usize,
        /// Rotate through the resource records in the response (if there is more than one for a given name)
        #[serde(default = "ResolverOptsWrapper::default_rotate")]
        pub rotate: bool,
        /// Use DNSSec to validate the request
        #[serde(default = "ResolverOptsWrapper::default_dnssec")]
        pub dnssec: bool,
        /// The ip_strategy for the Resolver to use when lookup Ipv4 or Ipv6 addresses
        #[serde(default = "ResolverOptsWrapper::default_ip_strategy")]
        pub ip_strategy: trust_dns_resolver::config::LookupIpStrategy,
        /// Cache size is in number of records (some records can be large)
        #[serde(default = "ResolverOptsWrapper::default_cache_size")]
        pub cache_size: usize,
        /// Check /ect/hosts file before dns requery (only works for unix like OS)
        #[serde(default = "ResolverOptsWrapper::default_use_hosts_file")]
        pub use_hosts_file: bool,
        /// Number of concurrent requests per query
        ///
        /// Where more than one nameserver is configured, this configures the resolver to send queries
        /// to a number of servers in parallel. Defaults to 2; 0 or 1 will execute requests serially.
        #[serde(default = "ResolverOptsWrapper::default_num_concurrent_reqs")]
        pub num_concurrent_reqs: usize,
    }

    /// Configuration of the application run by `vSMTP`.
    #[derive(Default, Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldAppVSL {
        /// Directory containing filtering rules per domain.
        pub domain_dir: Option<std::path::PathBuf>,
        /// Entry point for the rule engine.
        pub filter_path: Option<std::path::PathBuf>,
    }

    /// Application's parameter of the logs, same properties than [`FieldServerLogs`].
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldAppLogs {
        ///
        #[serde(default = "FieldAppLogs::default_filename")]
        pub filename: std::path::PathBuf,
    }

    /// Configuration of the application run by `vSMTP`.
    #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
    #[serde(deny_unknown_fields)]
    pub struct FieldApp {
        /// Folder under which artifact will be stored.
        #[serde(default = "FieldApp::default_dirpath")]
        pub dirpath: std::path::PathBuf,
        /// see [`FieldAppVSL`]
        #[serde(default)]
        pub vsl: FieldAppVSL,
        /// see [`FieldAppLogs`]
        #[serde(default)]
        pub logs: FieldAppLogs,
    }
}