From: Lukas Wagner <l.wagner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH proxmox 05/11] notify: add PVE/PBS context
Date: Thu, 31 Aug 2023 13:06:15 +0200 [thread overview]
Message-ID: <20230831110621.340832-6-l.wagner@proxmox.com> (raw)
In-Reply-To: <20230831110621.340832-1-l.wagner@proxmox.com>
This commit moves PVEContext from `proxmox-perl-rs` into the
`proxmox-notify` crate, since we now also need to access it from
`promxox-mail-forward`. The context is now hidden behind a feature
flag `pve-context`, ensuring that we only compile it when needed.
This commit adds PBSContext, since we now require it for
`proxmox-mail-forward`.
This commit also changes the global context from being stored in a
`once_cell` to a regular `Mutex`, since we now need to set/reset
the context in `proxmox-mail-forward`.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
proxmox-notify/Cargo.toml | 3 +-
proxmox-notify/src/context/common.rs | 27 ++++
.../src/{context.rs => context/mod.rs} | 14 +-
proxmox-notify/src/context/pbs.rs | 130 ++++++++++++++++++
proxmox-notify/src/context/pve.rs | 82 +++++++++++
5 files changed, 251 insertions(+), 5 deletions(-)
create mode 100644 proxmox-notify/src/context/common.rs
rename proxmox-notify/src/{context.rs => context/mod.rs} (54%)
create mode 100644 proxmox-notify/src/context/pbs.rs
create mode 100644 proxmox-notify/src/context/pve.rs
diff --git a/proxmox-notify/Cargo.toml b/proxmox-notify/Cargo.toml
index 441b6e1..8d8caaf 100644
--- a/proxmox-notify/Cargo.toml
+++ b/proxmox-notify/Cargo.toml
@@ -12,7 +12,6 @@ handlebars = { workspace = true }
lazy_static.workspace = true
log.workspace = true
mail-parser = { workspace = true, optional = true }
-once_cell.workspace = true
openssl.workspace = true
proxmox-http = { workspace = true, features = ["client-sync"], optional = true }
proxmox-http-error.workspace = true
@@ -30,3 +29,5 @@ default = ["sendmail", "gotify"]
mail-forwarder = ["dep:mail-parser"]
sendmail = ["dep:proxmox-sys"]
gotify = ["dep:proxmox-http"]
+pve-context = ["dep:proxmox-sys"]
+pbs-context = ["dep:proxmox-sys"]
diff --git a/proxmox-notify/src/context/common.rs b/proxmox-notify/src/context/common.rs
new file mode 100644
index 0000000..7580bd1
--- /dev/null
+++ b/proxmox-notify/src/context/common.rs
@@ -0,0 +1,27 @@
+use std::path::Path;
+
+pub(crate) fn attempt_file_read<P: AsRef<Path>>(path: P) -> Option<String> {
+ match proxmox_sys::fs::file_read_optional_string(path) {
+ Ok(contents) => contents,
+ Err(err) => {
+ log::error!("{err}");
+ None
+ }
+ }
+}
+
+pub(crate) fn lookup_datacenter_config_key(content: &str, key: &str) -> Option<String> {
+ let key_prefix = format!("{key}:");
+ normalize_for_return(
+ content
+ .lines()
+ .find_map(|line| line.strip_prefix(&key_prefix)),
+ )
+}
+
+pub(crate) fn normalize_for_return(s: Option<&str>) -> Option<String> {
+ match s?.trim() {
+ "" => None,
+ s => Some(s.to_string()),
+ }
+}
diff --git a/proxmox-notify/src/context.rs b/proxmox-notify/src/context/mod.rs
similarity index 54%
rename from proxmox-notify/src/context.rs
rename to proxmox-notify/src/context/mod.rs
index 370c7ee..00de2b0 100644
--- a/proxmox-notify/src/context.rs
+++ b/proxmox-notify/src/context/mod.rs
@@ -1,6 +1,12 @@
use std::fmt::Debug;
+use std::sync::Mutex;
-use once_cell::sync::OnceCell;
+#[cfg(any(feature = "pve-context", feature = "pbs-context"))]
+pub mod common;
+#[cfg(feature = "pbs-context")]
+pub mod pbs;
+#[cfg(feature = "pve-context")]
+pub mod pve;
pub trait Context: Send + Sync + Debug {
fn lookup_email_for_user(&self, user: &str) -> Option<String>;
@@ -9,13 +15,13 @@ pub trait Context: Send + Sync + Debug {
fn http_proxy_config(&self) -> Option<String>;
}
-static CONTEXT: OnceCell<&'static dyn Context> = OnceCell::new();
+static CONTEXT: Mutex<Option<&'static dyn Context>> = Mutex::new(None);
pub fn set_context(context: &'static dyn Context) {
- CONTEXT.set(context).expect("context has already been set");
+ *CONTEXT.lock().unwrap() = Some(context);
}
#[allow(unused)] // context is not used if all endpoint features are disabled
pub(crate) fn context() -> &'static dyn Context {
- *CONTEXT.get().expect("context has not been yet")
+ (*CONTEXT.lock().unwrap()).expect("context for proxmox-notify has not been set yet")
}
diff --git a/proxmox-notify/src/context/pbs.rs b/proxmox-notify/src/context/pbs.rs
new file mode 100644
index 0000000..1e79566
--- /dev/null
+++ b/proxmox-notify/src/context/pbs.rs
@@ -0,0 +1,130 @@
+use serde::Deserialize;
+
+use proxmox_schema::{ObjectSchema, Schema, StringSchema};
+use proxmox_section_config::{SectionConfig, SectionConfigPlugin};
+
+use crate::context::{common, Context};
+
+const PBS_USER_CFG_FILENAME: &str = "/etc/proxmox-backup/user.cfg";
+const PBS_NODE_CFG_FILENAME: &str = "/etc/proxmox-backup/node.cfg";
+
+// FIXME: Switch to the actual schema when possible in terms of dependency.
+// It's safe to assume that the config was written with the actual schema restrictions, so parsing
+// it with the less restrictive schema should be enough for the purpose of getting the mail address.
+const DUMMY_ID_SCHEMA: Schema = StringSchema::new("dummy ID").min_length(3).schema();
+const DUMMY_EMAIL_SCHEMA: Schema = StringSchema::new("dummy email").schema();
+const DUMMY_USER_SCHEMA: ObjectSchema = ObjectSchema {
+ description: "minimal PBS user",
+ properties: &[
+ ("userid", false, &DUMMY_ID_SCHEMA),
+ ("email", true, &DUMMY_EMAIL_SCHEMA),
+ ],
+ additional_properties: true,
+ default_key: None,
+};
+
+#[derive(Deserialize)]
+struct DummyPbsUser {
+ pub email: Option<String>,
+}
+
+/// Extract the root user's email address from the PBS user config.
+fn lookup_mail_address(content: &str, username: &str) -> Option<String> {
+ let mut config = SectionConfig::new(&DUMMY_ID_SCHEMA).allow_unknown_sections(true);
+ let user_plugin = SectionConfigPlugin::new(
+ "user".to_string(),
+ Some("userid".to_string()),
+ &DUMMY_USER_SCHEMA,
+ );
+ config.register_plugin(user_plugin);
+
+ match config.parse(PBS_USER_CFG_FILENAME, content) {
+ Ok(parsed) => {
+ parsed.sections.get(username)?;
+ match parsed.lookup::<DummyPbsUser>("user", username) {
+ Ok(user) => common::normalize_for_return(user.email.as_deref()),
+ Err(err) => {
+ log::error!("unable to parse {} - {}", PBS_USER_CFG_FILENAME, err);
+ None
+ }
+ }
+ }
+ Err(err) => {
+ log::error!("unable to parse {} - {}", PBS_USER_CFG_FILENAME, err);
+ None
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct PBSContext;
+
+pub static PBS_CONTEXT: PBSContext = PBSContext;
+
+impl Context for PBSContext {
+ fn lookup_email_for_user(&self, user: &str) -> Option<String> {
+ let content = common::attempt_file_read(PBS_USER_CFG_FILENAME);
+ content.and_then(|content| lookup_mail_address(&content, user))
+ }
+
+ fn default_sendmail_author(&self) -> String {
+ "Proxmox Backup Server".into()
+ }
+
+ fn default_sendmail_from(&self) -> String {
+ let content = common::attempt_file_read(PBS_NODE_CFG_FILENAME);
+ content
+ .and_then(|content| common::lookup_datacenter_config_key(&content, "email-from"))
+ .unwrap_or_else(|| String::from("root"))
+ }
+
+ fn http_proxy_config(&self) -> Option<String> {
+ let content = common::attempt_file_read(PBS_NODE_CFG_FILENAME);
+ content.and_then(|content| common::lookup_datacenter_config_key(&content, "http-proxy"))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const USER_CONFIG: &str = "
+user: root@pam
+ email root@example.com
+
+user: test@pbs
+ enable true
+ expire 0
+ ";
+
+ #[test]
+ fn test_parse_mail() {
+ assert_eq!(
+ lookup_mail_address(USER_CONFIG, "root@pam"),
+ Some("root@example.com".to_string())
+ );
+ assert_eq!(lookup_mail_address(USER_CONFIG, "test@pbs"), None);
+ }
+
+ const NODE_CONFIG: &str = "
+default-lang: de
+email-from: root@example.com
+http-proxy: http://localhost:1234
+ ";
+
+ #[test]
+ fn test_parse_node_config() {
+ assert_eq!(
+ common::lookup_datacenter_config_key(NODE_CONFIG, "email-from"),
+ Some("root@example.com".to_string())
+ );
+ assert_eq!(
+ common::lookup_datacenter_config_key(NODE_CONFIG, "http-proxy"),
+ Some("http://localhost:1234".to_string())
+ );
+ assert_eq!(
+ common::lookup_datacenter_config_key(NODE_CONFIG, "foo"),
+ None
+ );
+ }
+}
diff --git a/proxmox-notify/src/context/pve.rs b/proxmox-notify/src/context/pve.rs
new file mode 100644
index 0000000..7acf059
--- /dev/null
+++ b/proxmox-notify/src/context/pve.rs
@@ -0,0 +1,82 @@
+use crate::context::{common, Context};
+
+fn lookup_mail_address(content: &str, user: &str) -> Option<String> {
+ common::normalize_for_return(content.lines().find_map(|line| {
+ let fields: Vec<&str> = line.split(':').collect();
+ #[allow(clippy::get_first)] // to keep expression style consistent
+ match fields.get(0)?.trim() == "user" && fields.get(1)?.trim() == user {
+ true => fields.get(6).copied(),
+ false => None,
+ }
+ }))
+}
+
+#[derive(Debug)]
+pub struct PVEContext;
+
+impl Context for PVEContext {
+ fn lookup_email_for_user(&self, user: &str) -> Option<String> {
+ let content = common::attempt_file_read("/etc/pve/user.cfg");
+ content.and_then(|content| lookup_mail_address(&content, user))
+ }
+
+ fn default_sendmail_author(&self) -> String {
+ "Proxmox VE".into()
+ }
+
+ fn default_sendmail_from(&self) -> String {
+ let content = common::attempt_file_read("/etc/pve/datacenter.cfg");
+ content
+ .and_then(|content| common::lookup_datacenter_config_key(&content, "mail_from"))
+ .unwrap_or_else(|| String::from("root"))
+ }
+
+ fn http_proxy_config(&self) -> Option<String> {
+ let content = common::attempt_file_read("/etc/pve/datacenter.cfg");
+ content.and_then(|content| common::lookup_datacenter_config_key(&content, "http_proxy"))
+ }
+}
+
+pub static PVE_CONTEXT: PVEContext = PVEContext;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const USER_CONFIG: &str = "
+user:root@pam:1:0:::root@example.com:::
+user:test@pve:1:0:::test@example.com:::
+user:no-mail@pve:1:0::::::
+ ";
+
+ #[test]
+ fn test_parse_mail() {
+ assert_eq!(
+ lookup_mail_address(USER_CONFIG, "root@pam"),
+ Some("root@example.com".to_string())
+ );
+ assert_eq!(
+ lookup_mail_address(USER_CONFIG, "test@pve"),
+ Some("test@example.com".to_string())
+ );
+ assert_eq!(lookup_mail_address(USER_CONFIG, "no-mail@pve"), None);
+ }
+
+ const DC_CONFIG: &str = "
+email_from: user@example.com
+http_proxy: http://localhost:1234
+keyboard: en-us
+";
+ #[test]
+ fn test_parse_dc_config() {
+ assert_eq!(
+ common::lookup_datacenter_config_key(DC_CONFIG, "email_from"),
+ Some("user@example.com".to_string())
+ );
+ assert_eq!(
+ common::lookup_datacenter_config_key(DC_CONFIG, "http_proxy"),
+ Some("http://localhost:1234".to_string())
+ );
+ assert_eq!(common::lookup_datacenter_config_key(DC_CONFIG, "foo"), None);
+ }
+}
--
2.39.2
next prev parent reply other threads:[~2023-08-31 11:07 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-08-31 11:06 [pve-devel] [PATCH many 00/11] notifications: feed system mails into proxmox_notify Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH debcargo-conf 01/11] package mail-parser 0.8.2 Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH proxmox 02/11] sys: email: add `forward` Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH proxmox 03/11] notify: introduce Error::Generic Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH proxmox 04/11] notify: add mechanisms for email message forwarding Lukas Wagner
2023-08-31 11:06 ` Lukas Wagner [this message]
2023-08-31 11:06 ` [pve-devel] [PATCH proxmox-perl-rs 06/11] notify: construct Notification via constructor Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH proxmox-perl-rs 07/11] pve-rs: notify: remove notify_context for PVE Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH pve-cluster 08/11] datacenter config: add new parameters for system mail forwarding Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH pve-manager 09/11] ui: notify: add system-mail settings, configuring " Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH proxmox-mail-forward 10/11] feed forwarded mails into proxmox_notify Lukas Wagner
2023-08-31 11:06 ` [pve-devel] [PATCH pve-docs 11/11] notification: add docs for system mail forwarding Lukas Wagner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230831110621.340832-6-l.wagner@proxmox.com \
--to=l.wagner@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal