/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use crate::{ inbound::dkim::DkimSign, outbound::DeliveryResult, queue::{ Error, ErrorDetails, FROM_AUTHENTICATED, FROM_UNAUTHENTICATED_DMARC, HostResponse, MessageSource, MessageWrapper, Status, UnexpectedResponse, quota::HasQueueQuota, rcpt_spam_percentage, spool::{QueueParams, SmtpSpool}, }, }; use common::Server; use email::message::delivery::{IngestMessage, IngestRecipient, LocalDeliveryStatus, MailDelivery}; use smtp_proto::Response; use trc::SieveEvent; impl MessageWrapper { pub(super) async fn deliver_local( &self, rcpt_idxs: &[usize], statuses: &mut Vec, server: &Server, ) { // Prepare recipients list let mut pending_recipients = Vec::new(); let mut recipients = Vec::new(); for &rcpt_idx in rcpt_idxs { let rcpt = &self.message.recipients[rcpt_idx]; let rcpt_addr = rcpt.address(); recipients.push(IngestRecipient { address: rcpt_addr.to_lowercase(), orcpt: rcpt.orcpt.as_ref().map(|orcpt| orcpt.to_string()), spam_percentage: rcpt_spam_percentage(rcpt.flags), }); pending_recipients.push((rcpt_idx, rcpt_addr)); } // Deliver message let delivery_result = server .deliver_message(IngestMessage { sender_address: self.message.return_path.to_string(), sender_authenticated: self.message.flags & (FROM_UNAUTHENTICATED_DMARC | FROM_AUTHENTICATED) != 0, recipients, message_blob: self.message.blob_hash.clone(), message_size: self.message.size, session_id: self.span_id, }) .await; // Process delivery results for ((rcpt_idx, rcpt_addr), result) in pending_recipients.into_iter().zip(delivery_result.status) { let status = match result { LocalDeliveryStatus::Success => Status::Completed(HostResponse { hostname: "localhost".into(), response: Response { code: 250, esc: [2, 1, 5], message: "OK".into(), }, }), LocalDeliveryStatus::TemporaryFailure { reason } => { Status::TemporaryFailure(ErrorDetails { entity: "localhost".into(), details: Error::UnexpectedResponse(UnexpectedResponse { command: format!("RCPT TO:<{rcpt_addr}>").into_boxed_str(), response: Response { code: 451, esc: [4, 3, 0], message: reason.into(), }, }), }) } LocalDeliveryStatus::PermanentFailure { code, reason } => { Status::PermanentFailure(ErrorDetails { entity: "localhost".into(), details: Error::UnexpectedResponse(UnexpectedResponse { command: format!("RCPT TO:<{rcpt_addr}>").into_boxed_str(), response: Response { code: 550, esc: code, message: reason.into(), }, }), }) } }; statuses.push(DeliveryResult::account(status, rcpt_idx)); } // Process autogenerated messages for autogenerated in delivery_result.autogenerated { let mut message = server.new_message( autogenerated.sender_address, MessageSource::Autogenerated, self.span_id, ); for rcpt in autogenerated.recipients { message.expand_and_add_recipient(rcpt, server).await; } // Queue Message message.message.size = autogenerated.message.len() as u64; if let Some(metadata) = server.has_quota(&mut message).await { let dkim_signers = server .eval_signers( &server.core.sieve.untrusted_sign, &message.message, self.span_id, ) .await; let _ = message .queue( QueueParams::new(&autogenerated.message, self.span_id, server) .with_dkim_signers(dkim_signers) .with_metadata(metadata), ) .await; } else { trc::event!( Sieve(SieveEvent::QuotaExceeded), SpanId = self.span_id, From = message.message.return_path, To = message .message .recipients .into_iter() .map(|r| trc::Value::from(r.address().to_string())) .collect::>(), ); } } } }