ic-bn-lib 0.2.3

Internet Computer Boundary Nodes shared modules
Documentation
use std::str::FromStr;

use fqdn::FQDN;
use tracing::{debug, info};

use crate::{
    http::dns::is_error_negative_lookup,
    network::AsyncReadWrite,
    smtp::{
        ProtocolError,
        inbound::{Session, SessionResult},
    },
};

impl<S: AsyncReadWrite> Session<S> {
    /// Handles EHLO/HELO commands
    pub async fn handle_ehlo(&mut self, host: &str, extended: bool) -> SessionResult<()> {
        // Validate hostname
        let Ok(ehlo_hostname) = FQDN::from_str(host) else {
            info!("{self}: {host}: Invalid EHLO hostname");
            self.set_error(ProtocolError::InvalidEhloHostname(format!(
                "{host}: incorrect hostname"
            )));
            return self.reply("550", "5.5.0", "Invalid EHLO hostname.").await;
        };

        // If EHLO hostname is already set to the same value - just reply directly,
        // avoid redundant checks
        if let Some(v) = &self.data.ehlo_hostname
            && v == &ehlo_hostname
        {
            return self.send_ehlo(extended).await;
        }

        if ehlo_hostname.depth() < 2 {
            info!("{self}: {host}: EHLO is not FQDN");
            self.set_error(ProtocolError::InvalidEhloHostname(format!(
                "{host}: not FQDN"
            )));
            return self
                .reply("550", "5.5.0", "EHLO hostname must be an FQDN.")
                .await;
        };

        // Check if EHLO hostname resolves if configured
        if self.cfg.verify_ehlo_hostname {
            match self.cfg.authenticator.resolver().lookup_ip(host).await {
                Ok(v) => match v.iter().next() {
                    Some(v) => {
                        debug!("{self}: {host}: EHLO hostname found in DNS: {v}");
                    }
                    None => {
                        info!("{self}: {host}: EHLO not found in DNS");
                        self.set_error(ProtocolError::InvalidEhloHostname(format!(
                            "{host}: not found in DNS"
                        )));
                        return self
                            .reply("550", "5.5.0", "EHLO hostname not found in DNS.")
                            .await;
                    }
                },

                Err(e) => {
                    info!("{self}: {host}: EHLO not found in DNS: {e:#}");

                    if is_error_negative_lookup(&e) {
                        self.set_error(ProtocolError::InvalidEhloHostname(format!(
                            "{host}: not found in DNS: {e:#}"
                        )));

                        return self
                            .reply("550", "5.5.0", "EHLO hostname not found in DNS.")
                            .await;
                    }

                    return self
                        .reply("451", "4.7.25", "Temporary error validating EHLO hostname.")
                        .await;
                }
            }
        }

        self.reset_message();
        self.data.ehlo_hostname = Some(ehlo_hostname);

        return self.send_ehlo(extended).await;
    }

    async fn send_ehlo(&mut self, extended: bool) -> SessionResult<()> {
        let buf = if !extended {
            &self.cfg.helo
        } else if self.tls_info.is_none() && self.cfg.tls_mode.enabled() {
            &self.cfg.ehlo_tls
        } else {
            &self.cfg.ehlo
        };

        self.write(&buf.clone()).await
    }
}