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