]>
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; | |
7 | use std::ffi::{CString, CStr}; | |
8 | ||
f7d4e4b5 | 9 | use anyhow::{bail, format_err, Error}; |
7d817b03 DM |
10 | use serde_json::json; |
11 | ||
e7cb4dc5 WB |
12 | use crate::api2::types::{Userid, UsernameRef, RealmRef}; |
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 | ||
20 | pub struct PAM(); | |
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 | ||
71 | pub struct PBS(); | |
72 | ||
73 | pub fn crypt(password: &[u8], salt: &str) -> Result<String, Error> { | |
74 | ||
75 | #[link(name="crypt")] | |
76 | extern "C" { | |
77 | #[link_name = "crypt"] | |
78 | fn __crypt(key: *const libc::c_char, salt: *const libc::c_char) -> * mut libc::c_char; | |
79 | } | |
80 | ||
81 | let salt = CString::new(salt)?; | |
82 | let password = CString::new(password)?; | |
83 | ||
84 | let res = unsafe { | |
85 | CStr::from_ptr( | |
86 | __crypt( | |
87 | password.as_c_str().as_ptr(), | |
88 | salt.as_c_str().as_ptr() | |
89 | ) | |
90 | ) | |
91 | }; | |
92 | Ok(String::from(res.to_str()?)) | |
93 | } | |
94 | ||
95 | ||
96 | pub fn encrypt_pw(password: &str) -> Result<String, Error> { | |
97 | ||
98 | let salt = proxmox::sys::linux::random_data(8)?; | |
99 | let salt = format!("$5${}$", base64::encode_config(&salt, base64::CRYPT)); | |
100 | ||
101 | crypt(password.as_bytes(), &salt) | |
102 | } | |
103 | ||
104 | pub fn verify_crypt_pw(password: &str, enc_password: &str) -> Result<(), Error> { | |
105 | let verify = crypt(password.as_bytes(), enc_password)?; | |
54aec2fa | 106 | if verify != enc_password { |
7d817b03 DM |
107 | bail!("invalid credentials"); |
108 | } | |
109 | Ok(()) | |
110 | } | |
111 | ||
e7cb4dc5 | 112 | const SHADOW_CONFIG_FILENAME: &str = configdir!("/shadow.json"); |
7d817b03 DM |
113 | |
114 | impl ProxmoxAuthenticator for PBS { | |
115 | ||
e7cb4dc5 | 116 | fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
7d817b03 | 117 | let data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; |
e7cb4dc5 | 118 | match data[username.as_str()].as_str() { |
7d817b03 DM |
119 | None => bail!("no password set"), |
120 | Some(enc_password) => verify_crypt_pw(password, enc_password)?, | |
121 | } | |
122 | Ok(()) | |
123 | } | |
124 | ||
e7cb4dc5 | 125 | fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
7d817b03 DM |
126 | let enc_password = encrypt_pw(password)?; |
127 | let mut data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; | |
e7cb4dc5 | 128 | data[username.as_str()] = enc_password.into(); |
7d817b03 DM |
129 | |
130 | let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); | |
131 | let options = proxmox::tools::fs::CreateOptions::new() | |
132 | .perm(mode) | |
133 | .owner(nix::unistd::ROOT) | |
134 | .group(nix::unistd::Gid::from_raw(0)); | |
135 | ||
136 | let data = serde_json::to_vec_pretty(&data)?; | |
137 | proxmox::tools::fs::replace_file(SHADOW_CONFIG_FILENAME, &data, options)?; | |
138 | ||
139 | Ok(()) | |
140 | } | |
a4e871f5 DC |
141 | |
142 | fn remove_password(&self, username: &UsernameRef) -> Result<(), Error> { | |
143 | let mut data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; | |
144 | if let Some(map) = data.as_object_mut() { | |
145 | map.remove(username.as_str()); | |
146 | } | |
147 | ||
148 | let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); | |
149 | let options = proxmox::tools::fs::CreateOptions::new() | |
150 | .perm(mode) | |
151 | .owner(nix::unistd::ROOT) | |
152 | .group(nix::unistd::Gid::from_raw(0)); | |
153 | ||
154 | let data = serde_json::to_vec_pretty(&data)?; | |
155 | proxmox::tools::fs::replace_file(SHADOW_CONFIG_FILENAME, &data, options)?; | |
156 | ||
157 | Ok(()) | |
158 | } | |
7d817b03 DM |
159 | } |
160 | ||
4b40148c | 161 | /// Lookup the autenticator for the specified realm |
e7cb4dc5 WB |
162 | pub fn lookup_authenticator(realm: &RealmRef) -> Result<Box<dyn ProxmoxAuthenticator>, Error> { |
163 | match realm.as_str() { | |
7d817b03 DM |
164 | "pam" => Ok(Box::new(PAM())), |
165 | "pbs" => Ok(Box::new(PBS())), | |
e7cb4dc5 | 166 | _ => bail!("unknown realm '{}'", realm.as_str()), |
7d817b03 DM |
167 | } |
168 | } | |
169 | ||
4b40148c | 170 | /// Authenticate users |
e7cb4dc5 | 171 | pub fn authenticate_user(userid: &Userid, password: &str) -> Result<(), Error> { |
7d817b03 | 172 | |
e7cb4dc5 WB |
173 | lookup_authenticator(userid.realm())? |
174 | .authenticate_user(userid.name(), password) | |
7d817b03 | 175 | } |