From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 20E8CDD5D for ; Mon, 17 Jul 2023 17:01:57 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id D0885F8C2 for ; Mon, 17 Jul 2023 17:01:24 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Mon, 17 Jul 2023 17:01:19 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 8490442BA3 for ; Mon, 17 Jul 2023 17:01:19 +0200 (CEST) From: Lukas Wagner To: pve-devel@lists.proxmox.com Date: Mon, 17 Jul 2023 17:00:01 +0200 Message-Id: <20230717150051.710464-17-l.wagner@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230717150051.710464-1-l.wagner@proxmox.com> References: <20230717150051.710464-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.127 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 T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pve-devel] [PATCH v3 proxmox 16/66] notify: sendmail: allow users as recipients X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 17 Jul 2023 15:01:57 -0000 This introduces a new configuration parameter `mailto-user`. A user's email address will be looked up in the product-specific user database. Signed-off-by: Lukas Wagner --- proxmox-notify/src/api/sendmail.rs | 32 ++++++++++++++++--- proxmox-notify/src/context.rs | 4 ++- proxmox-notify/src/endpoints/sendmail.rs | 40 +++++++++++++++++++++--- proxmox-notify/src/schema.rs | 6 ++++ 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/proxmox-notify/src/api/sendmail.rs b/proxmox-notify/src/api/sendmail.rs index 6b0323e3..070ed6e7 100644 --- a/proxmox-notify/src/api/sendmail.rs +++ b/proxmox-notify/src/api/sendmail.rs @@ -45,6 +45,13 @@ pub fn add_endpoint(config: &mut Config, endpoint: &SendmailConfig) -> Result<() super::filter::get_filter(config, filter)?; } + if endpoint.mailto.is_none() && endpoint.mailto_user.is_none() { + return Err(ApiError::bad_request( + "must at least provide one recipient, either in mailto or in mailto-user", + None, + )); + } + config .config .set_data(&endpoint.name, SENDMAIL_TYPENAME, endpoint) @@ -81,12 +88,18 @@ pub fn update_endpoint( DeleteableSendmailProperty::Author => endpoint.author = None, DeleteableSendmailProperty::Comment => endpoint.comment = None, DeleteableSendmailProperty::Filter => endpoint.filter = None, + DeleteableSendmailProperty::Mailto => endpoint.mailto = None, + DeleteableSendmailProperty::MailtoUser => endpoint.mailto_user = None, } } } if let Some(mailto) = &updater.mailto { - endpoint.mailto = mailto.iter().map(String::from).collect(); + endpoint.mailto = Some(mailto.iter().map(String::from).collect()); + } + + if let Some(mailto_user) = &updater.mailto_user { + endpoint.mailto_user = Some(mailto_user.iter().map(String::from).collect()); } if let Some(from_address) = &updater.from_address { @@ -106,6 +119,13 @@ pub fn update_endpoint( endpoint.filter = Some(filter.into()); } + if endpoint.mailto.is_none() && endpoint.mailto_user.is_none() { + return Err(ApiError::bad_request( + "must at least provide one recipient, either in mailto or in mailto-user", + None, + )); + } + config .config .set_data(name, SENDMAIL_TYPENAME, &endpoint) @@ -143,7 +163,8 @@ pub mod tests { config, &SendmailConfig { name: name.into(), - mailto: vec!["user1@example.com".into()], + mailto: Some(vec!["user1@example.com".into()]), + mailto_user: None, from_address: Some("from@example.com".into()), author: Some("root".into()), comment: Some("Comment".into()), @@ -187,6 +208,7 @@ pub mod tests { "sendmail-endpoint", &SendmailConfigUpdater { mailto: Some(vec!["user2@example.com".into(), "user3@example.com".into()]), + mailto_user: None, from_address: Some("root@example.com".into()), author: Some("newauthor".into()), comment: Some("new comment".into()), @@ -212,6 +234,7 @@ pub mod tests { "sendmail-endpoint", &SendmailConfigUpdater { mailto: Some(vec!["user2@example.com".into(), "user3@example.com".into()]), + mailto_user: Some(vec!["root@pam".into()]), from_address: Some("root@example.com".into()), author: Some("newauthor".into()), comment: Some("new comment".into()), @@ -225,11 +248,12 @@ pub mod tests { assert_eq!( endpoint.mailto, - vec![ + Some(vec![ "user2@example.com".to_string(), "user3@example.com".to_string() - ] + ]) ); + assert_eq!(endpoint.mailto_user, Some(vec!["root@pam".to_string(),])); assert_eq!(endpoint.from_address, Some("root@example.com".to_string())); assert_eq!(endpoint.author, Some("newauthor".to_string())); assert_eq!(endpoint.comment, Some("new comment".to_string())); diff --git a/proxmox-notify/src/context.rs b/proxmox-notify/src/context.rs index 55c0eda1..25be949a 100644 --- a/proxmox-notify/src/context.rs +++ b/proxmox-notify/src/context.rs @@ -1,6 +1,8 @@ use std::sync::Mutex; -pub trait Context: Send + Sync {} +pub trait Context: Send + Sync { + fn lookup_email_for_user(&self, user: &str) -> Option; +} static CONTEXT: Mutex> = Mutex::new(None); diff --git a/proxmox-notify/src/endpoints/sendmail.rs b/proxmox-notify/src/endpoints/sendmail.rs index 9d06e7c4..c2b44b2d 100644 --- a/proxmox-notify/src/endpoints/sendmail.rs +++ b/proxmox-notify/src/endpoints/sendmail.rs @@ -1,6 +1,8 @@ +use crate::context::context; use crate::renderer::TemplateRenderer; -use crate::schema::{COMMENT_SCHEMA, EMAIL_SCHEMA, ENTITY_NAME_SCHEMA}; +use crate::schema::{COMMENT_SCHEMA, EMAIL_SCHEMA, ENTITY_NAME_SCHEMA, USER_SCHEMA}; use crate::{renderer, Endpoint, Error, Notification}; +use std::collections::HashSet; use proxmox_schema::{api, Updater}; use serde::{Deserialize, Serialize}; @@ -17,6 +19,14 @@ pub(crate) const SENDMAIL_TYPENAME: &str = "sendmail"; items: { schema: EMAIL_SCHEMA, }, + optional: true, + }, + "mailto-user": { + type: Array, + items: { + schema: USER_SCHEMA, + }, + optional: true, }, comment: { optional: true, @@ -36,7 +46,11 @@ pub struct SendmailConfig { #[updater(skip)] pub name: String, /// Mail recipients - pub mailto: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub mailto: Option>, + /// Mail recipients + #[serde(skip_serializing_if = "Option::is_none")] + pub mailto_user: Option>, /// `From` address for the mail #[serde(skip_serializing_if = "Option::is_none")] pub from_address: Option, @@ -58,6 +72,8 @@ pub enum DeleteableSendmailProperty { Author, Comment, Filter, + Mailto, + MailtoUser, } /// A sendmail notification endpoint. @@ -67,7 +83,21 @@ pub struct SendmailEndpoint { impl Endpoint for SendmailEndpoint { fn send(&self, notification: &Notification) -> Result<(), Error> { - let recipients: Vec<&str> = self.config.mailto.iter().map(String::as_str).collect(); + let mut recipients = HashSet::new(); + + if let Some(mailto_addrs) = self.config.mailto.as_ref() { + for addr in mailto_addrs { + recipients.insert(addr.clone()); + } + } + + if let Some(users) = self.config.mailto_user.as_ref() { + for user in users { + if let Some(addr) = context().lookup_email_for_user(user) { + recipients.insert(addr); + } + } + } let properties = notification.properties.as_ref(); @@ -85,8 +115,10 @@ impl Endpoint for SendmailEndpoint { // "Proxmox Backup Server" if it is not set. let author = self.config.author.as_deref().or(Some("")); + let recipients_str: Vec<&str> = recipients.iter().map(String::as_str).collect(); + proxmox_sys::email::sendmail( - &recipients, + &recipients_str, &subject, Some(&text_part), Some(&html_part), diff --git a/proxmox-notify/src/schema.rs b/proxmox-notify/src/schema.rs index 68f11959..006e918b 100644 --- a/proxmox-notify/src/schema.rs +++ b/proxmox-notify/src/schema.rs @@ -26,6 +26,12 @@ pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.") .max_length(64) .schema(); +pub const USER_SCHEMA: Schema = StringSchema::new("User ID including realm, e.g. root@pam.") + .format(&SINGLE_LINE_COMMENT_FORMAT) + .min_length(2) + .max_length(64) + .schema(); + pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX); -- 2.39.2