]>
Commit | Line | Data |
---|---|---|
7d817b03 DM |
1 | //! Proxmox Backup Server Authentication |
2 | //! | |
3 | //! This library contains helper to authenticate users. | |
4 | ||
5 | use std::process::{Command, Stdio}; | |
6 | use std::io::Write; | |
7d817b03 | 7 | |
f7d4e4b5 | 8 | use anyhow::{bail, format_err, Error}; |
7d817b03 DM |
9 | use serde_json::json; |
10 | ||
af06decd | 11 | use pbs_buildcfg::configdir; |
6227654a | 12 | use pbs_api_types::{Userid, UsernameRef, RealmRef}; |
e7cb4dc5 | 13 | |
7d817b03 | 14 | pub trait ProxmoxAuthenticator { |
e7cb4dc5 WB |
15 | fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error>; |
16 | fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error>; | |
a4e871f5 | 17 | fn remove_password(&self, username: &UsernameRef) -> Result<(), Error>; |
7d817b03 DM |
18 | } |
19 | ||
1cb08a0a | 20 | struct PAM(); |
7d817b03 DM |
21 | |
22 | impl ProxmoxAuthenticator for PAM { | |
23 | ||
e7cb4dc5 | 24 | fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
7d817b03 | 25 | let mut auth = pam::Authenticator::with_password("proxmox-backup-auth").unwrap(); |
e7cb4dc5 | 26 | auth.get_handler().set_credentials(username.as_str(), password); |
7d817b03 | 27 | auth.authenticate()?; |
54aec2fa | 28 | Ok(()) |
7d817b03 DM |
29 | } |
30 | ||
e7cb4dc5 | 31 | fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
7d817b03 | 32 | let mut child = Command::new("passwd") |
e7cb4dc5 | 33 | .arg(username.as_str()) |
7d817b03 DM |
34 | .stdin(Stdio::piped()) |
35 | .stderr(Stdio::piped()) | |
36 | .spawn() | |
e7cb4dc5 WB |
37 | .map_err(|err| format_err!( |
38 | "unable to set password for '{}' - execute passwd failed: {}", | |
39 | username.as_str(), | |
40 | err, | |
41 | ))?; | |
7d817b03 DM |
42 | |
43 | // Note: passwd reads password twice from stdin (for verify) | |
44 | writeln!(child.stdin.as_mut().unwrap(), "{}\n{}", password, password)?; | |
45 | ||
e7cb4dc5 WB |
46 | let output = child |
47 | .wait_with_output() | |
48 | .map_err(|err| format_err!( | |
49 | "unable to set password for '{}' - wait failed: {}", | |
50 | username.as_str(), | |
51 | err, | |
52 | ))?; | |
7d817b03 DM |
53 | |
54 | if !output.status.success() { | |
e7cb4dc5 WB |
55 | bail!( |
56 | "unable to set password for '{}' - {}", | |
57 | username.as_str(), | |
58 | String::from_utf8_lossy(&output.stderr), | |
59 | ); | |
7d817b03 DM |
60 | } |
61 | ||
62 | Ok(()) | |
63 | } | |
a4e871f5 DC |
64 | |
65 | // do not remove password for pam users | |
66 | fn remove_password(&self, _username: &UsernameRef) -> Result<(), Error> { | |
67 | Ok(()) | |
68 | } | |
7d817b03 DM |
69 | } |
70 | ||
1cb08a0a | 71 | struct PBS(); |
7d817b03 | 72 | |
e7cb4dc5 | 73 | const SHADOW_CONFIG_FILENAME: &str = configdir!("/shadow.json"); |
7d817b03 DM |
74 | |
75 | impl ProxmoxAuthenticator for PBS { | |
76 | ||
e7cb4dc5 | 77 | fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
7d817b03 | 78 | let data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; |
e7cb4dc5 | 79 | match data[username.as_str()].as_str() { |
7d817b03 | 80 | None => bail!("no password set"), |
d5790a9f | 81 | Some(enc_password) => proxmox_sys::crypt::verify_crypt_pw(password, enc_password)?, |
7d817b03 DM |
82 | } |
83 | Ok(()) | |
84 | } | |
85 | ||
e7cb4dc5 | 86 | fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
d5790a9f | 87 | let enc_password = proxmox_sys::crypt::encrypt_pw(password)?; |
7d817b03 | 88 | let mut data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; |
e7cb4dc5 | 89 | data[username.as_str()] = enc_password.into(); |
7d817b03 DM |
90 | |
91 | let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); | |
92 | let options = proxmox::tools::fs::CreateOptions::new() | |
93 | .perm(mode) | |
94 | .owner(nix::unistd::ROOT) | |
95 | .group(nix::unistd::Gid::from_raw(0)); | |
96 | ||
97 | let data = serde_json::to_vec_pretty(&data)?; | |
e0a19d33 | 98 | proxmox::tools::fs::replace_file(SHADOW_CONFIG_FILENAME, &data, options, true)?; |
7d817b03 DM |
99 | |
100 | Ok(()) | |
101 | } | |
a4e871f5 DC |
102 | |
103 | fn remove_password(&self, username: &UsernameRef) -> Result<(), Error> { | |
104 | let mut data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; | |
105 | if let Some(map) = data.as_object_mut() { | |
106 | map.remove(username.as_str()); | |
107 | } | |
108 | ||
109 | let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); | |
110 | let options = proxmox::tools::fs::CreateOptions::new() | |
111 | .perm(mode) | |
112 | .owner(nix::unistd::ROOT) | |
113 | .group(nix::unistd::Gid::from_raw(0)); | |
114 | ||
115 | let data = serde_json::to_vec_pretty(&data)?; | |
e0a19d33 | 116 | proxmox::tools::fs::replace_file(SHADOW_CONFIG_FILENAME, &data, options, true)?; |
a4e871f5 DC |
117 | |
118 | Ok(()) | |
119 | } | |
7d817b03 DM |
120 | } |
121 | ||
4b40148c | 122 | /// Lookup the autenticator for the specified realm |
e7cb4dc5 WB |
123 | pub fn lookup_authenticator(realm: &RealmRef) -> Result<Box<dyn ProxmoxAuthenticator>, Error> { |
124 | match realm.as_str() { | |
7d817b03 DM |
125 | "pam" => Ok(Box::new(PAM())), |
126 | "pbs" => Ok(Box::new(PBS())), | |
e7cb4dc5 | 127 | _ => bail!("unknown realm '{}'", realm.as_str()), |
7d817b03 DM |
128 | } |
129 | } | |
130 | ||
4b40148c | 131 | /// Authenticate users |
e7cb4dc5 | 132 | pub fn authenticate_user(userid: &Userid, password: &str) -> Result<(), Error> { |
7d817b03 | 133 | |
e7cb4dc5 WB |
134 | lookup_authenticator(userid.realm())? |
135 | .authenticate_user(userid.name(), password) | |
7d817b03 | 136 | } |