From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id F10C81FF16F for ; Fri, 29 Nov 2024 11:53:41 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 0FC5116AE1; Fri, 29 Nov 2024 11:53:42 +0100 (CET) From: Shannon Sterz To: pbs-devel@lists.proxmox.com Date: Fri, 29 Nov 2024 11:53:19 +0100 Message-Id: <20241129105321.143877-2-s.sterz@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20241129105321.143877-1-s.sterz@proxmox.com> References: <20241129105321.143877-1-s.sterz@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.039 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [PATCH proxmox 2/4] notify: switchi sendmail endpoint over to new crate X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Backup Server development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pbs-devel-bounces@lists.proxmox.com Sender: "pbs-devel" use the new `proxmox-sendmail` crate instead of the bespoke implementation in `proxmox-notify`. Signed-off-by: Shannon Sterz --- proxmox-notify/Cargo.toml | 3 +- proxmox-notify/src/endpoints/sendmail.rs | 171 +---------------------- 2 files changed, 9 insertions(+), 165 deletions(-) diff --git a/proxmox-notify/Cargo.toml b/proxmox-notify/Cargo.toml index b5b3719e..6e94930a 100644 --- a/proxmox-notify/Cargo.toml +++ b/proxmox-notify/Cargo.toml @@ -32,6 +32,7 @@ proxmox-human-byte.workspace = true proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] } proxmox-section-config = { workspace = true } proxmox-serde.workspace = true +proxmox-sendmail = { workspace = true, optional = true } proxmox-sys = { workspace = true, optional = true } proxmox-time.workspace = true proxmox-uuid = { workspace = true, features = ["serde"] } @@ -39,7 +40,7 @@ proxmox-uuid = { workspace = true, features = ["serde"] } [features] default = ["sendmail", "gotify", "smtp", "webhook"] mail-forwarder = ["dep:mail-parser", "dep:proxmox-sys"] -sendmail = ["dep:proxmox-sys", "dep:base64"] +sendmail = ["dep:proxmox-sys", "dep:base64", "dep:proxmox-sendmail"] gotify = ["dep:proxmox-http"] pve-context = ["dep:proxmox-sys"] pbs-context = ["dep:proxmox-sys"] diff --git a/proxmox-notify/src/endpoints/sendmail.rs b/proxmox-notify/src/endpoints/sendmail.rs index d31b9672..1268d372 100644 --- a/proxmox-notify/src/endpoints/sendmail.rs +++ b/proxmox-notify/src/endpoints/sendmail.rs @@ -137,15 +137,13 @@ impl Endpoint for SendmailEndpoint { .clone() .unwrap_or_else(|| context().default_sendmail_author()); - sendmail( - &recipients_str, - &subject, - &text_part, - &html_part, - &mailfrom, - &author, - ) - .map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into())) + let mut mail = + Mail::new(&author, &mailfrom, &subject, &text_part).with_html_alt(&html_part); + + recipients_str.iter().for_each(|r| mail.add_recipient(r)); + + mail.send() + .map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into())) } #[cfg(feature = "mail-forwarder")] Content::ForwardedMail { raw, uid, .. } => { @@ -165,107 +163,6 @@ impl Endpoint for SendmailEndpoint { } } -/// Sends multi-part mail with text and/or html to a list of recipients -/// -/// Includes the header `Auto-Submitted: auto-generated`, so that auto-replies -/// (i.e. OOO replies) won't trigger. -/// ``sendmail`` is used for sending the mail. -fn sendmail( - mailto: &[&str], - subject: &str, - text: &str, - html: &str, - mailfrom: &str, - author: &str, -) -> Result<(), Error> { - if mailto.is_empty() { - return Err(Error::Generic( - "At least one recipient has to be specified!".into(), - )); - } - let now = proxmox_time::epoch_i64(); - let body = format_mail(mailto, mailfrom, author, subject, text, html, now)?; - - let mut sendmail_process = Command::new("/usr/sbin/sendmail") - .arg("-B") - .arg("8BITMIME") - .arg("-f") - .arg(mailfrom) - .arg("--") - .args(mailto) - .stdin(Stdio::piped()) - .spawn() - .map_err(|err| Error::Generic(format!("could not spawn sendmail process: {err}")))?; - - sendmail_process - .stdin - .take() - .expect("stdin already taken") - .write_all(body.as_bytes()) - .map_err(|err| Error::Generic(format!("couldn't write to sendmail stdin: {err}")))?; - - sendmail_process - .wait() - .map_err(|err| Error::Generic(format!("sendmail did not exit successfully: {err}")))?; - - Ok(()) -} - -fn format_mail( - mailto: &[&str], - mailfrom: &str, - author: &str, - subject: &str, - text: &str, - html: &str, - timestamp: i64, -) -> Result { - use std::fmt::Write as _; - - let recipients = mailto.join(","); - let boundary = format!("----_=_NextPart_001_{timestamp}"); - - let mut body = String::new(); - - // Format email header - body.push_str("Content-Type: multipart/alternative;\n"); - let _ = writeln!(body, "\tboundary=\"{boundary}\""); - body.push_str("MIME-Version: 1.0\n"); - - if !subject.is_ascii() { - let _ = writeln!(body, "Subject: =?utf-8?B?{}?=", base64::encode(subject)); - } else { - let _ = writeln!(body, "Subject: {subject}"); - } - let _ = writeln!(body, "From: {author} <{mailfrom}>"); - let _ = writeln!(body, "To: {recipients}"); - let rfc2822_date = proxmox_time::epoch_to_rfc2822(timestamp) - .map_err(|err| Error::Generic(format!("failed to format time: {err}")))?; - let _ = writeln!(body, "Date: {rfc2822_date}"); - body.push_str("Auto-Submitted: auto-generated;\n"); - body.push('\n'); - - // Format email body - body.push_str("This is a multi-part message in MIME format.\n"); - let _ = write!(body, "\n--{boundary}\n"); - - body.push_str("Content-Type: text/plain;\n"); - body.push_str("\tcharset=\"UTF-8\"\n"); - body.push_str("Content-Transfer-Encoding: 8bit\n"); - body.push('\n'); - body.push_str(text); - let _ = write!(body, "\n--{boundary}\n"); - - body.push_str("Content-Type: text/html;\n"); - body.push_str("\tcharset=\"UTF-8\"\n"); - body.push_str("Content-Transfer-Encoding: 8bit\n"); - body.push('\n'); - body.push_str(html); - let _ = write!(body, "\n--{boundary}--"); - - Ok(body) -} - /// Forwards an email message to a given list of recipients. /// /// ``sendmail`` is used for sending the mail, thus `message` must be @@ -313,57 +210,3 @@ fn forward(mailto: &[&str], mailfrom: &str, message: &[u8], uid: Option) -> Ok(()) } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn email_without_recipients() { - let result = sendmail(&[], "Subject2", "", "HTML", "root", "Proxmox"); - assert!(result.is_err()); - } - - #[test] - fn test_format_mail_multipart() { - let message = format_mail( - &["Tony Est "], - "foobar@example.com", - "Fred Oobar", - "This is the subject", - "This is the plain body", - "This is the HTML body", - 1718977850, - ) - .expect("format_message failed"); - - assert_eq!( - message, - r#"Content-Type: multipart/alternative; - boundary="----_=_NextPart_001_1718977850" -MIME-Version: 1.0 -Subject: This is the subject -From: Fred Oobar -To: Tony Est -Date: Fri, 21 Jun 2024 15:50:50 +0200 -Auto-Submitted: auto-generated; - -This is a multi-part message in MIME format. - -------_=_NextPart_001_1718977850 -Content-Type: text/plain; - charset="UTF-8" -Content-Transfer-Encoding: 8bit - -This is the plain body -------_=_NextPart_001_1718977850 -Content-Type: text/html; - charset="UTF-8" -Content-Transfer-Encoding: 8bit - -This is the HTML body -------_=_NextPart_001_1718977850--"# - .to_owned() - ); - } -} -- 2.39.5 _______________________________________________ pbs-devel mailing list pbs-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel