]>
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 | ||
9 | use base64; | |
f7d4e4b5 | 10 | use anyhow::{bail, format_err, Error}; |
7d817b03 DM |
11 | use serde_json::json; |
12 | ||
e7cb4dc5 WB |
13 | use crate::api2::types::{Userid, UsernameRef, RealmRef}; |
14 | ||
7d817b03 | 15 | pub trait ProxmoxAuthenticator { |
e7cb4dc5 WB |
16 | fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error>; |
17 | fn store_password(&self, username: &UsernameRef, password: &str) -> 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 DM |
27 | auth.authenticate()?; |
28 | return Ok(()); | |
29 | ||
30 | } | |
31 | ||
e7cb4dc5 | 32 | fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
7d817b03 | 33 | let mut child = Command::new("passwd") |
e7cb4dc5 | 34 | .arg(username.as_str()) |
7d817b03 DM |
35 | .stdin(Stdio::piped()) |
36 | .stderr(Stdio::piped()) | |
37 | .spawn() | |
e7cb4dc5 WB |
38 | .map_err(|err| format_err!( |
39 | "unable to set password for '{}' - execute passwd failed: {}", | |
40 | username.as_str(), | |
41 | err, | |
42 | ))?; | |
7d817b03 DM |
43 | |
44 | // Note: passwd reads password twice from stdin (for verify) | |
45 | writeln!(child.stdin.as_mut().unwrap(), "{}\n{}", password, password)?; | |
46 | ||
e7cb4dc5 WB |
47 | let output = child |
48 | .wait_with_output() | |
49 | .map_err(|err| format_err!( | |
50 | "unable to set password for '{}' - wait failed: {}", | |
51 | username.as_str(), | |
52 | err, | |
53 | ))?; | |
7d817b03 DM |
54 | |
55 | if !output.status.success() { | |
e7cb4dc5 WB |
56 | bail!( |
57 | "unable to set password for '{}' - {}", | |
58 | username.as_str(), | |
59 | String::from_utf8_lossy(&output.stderr), | |
60 | ); | |
7d817b03 DM |
61 | } |
62 | ||
63 | Ok(()) | |
64 | } | |
65 | } | |
66 | ||
67 | pub struct PBS(); | |
68 | ||
69 | pub fn crypt(password: &[u8], salt: &str) -> Result<String, Error> { | |
70 | ||
71 | #[link(name="crypt")] | |
72 | extern "C" { | |
73 | #[link_name = "crypt"] | |
74 | fn __crypt(key: *const libc::c_char, salt: *const libc::c_char) -> * mut libc::c_char; | |
75 | } | |
76 | ||
77 | let salt = CString::new(salt)?; | |
78 | let password = CString::new(password)?; | |
79 | ||
80 | let res = unsafe { | |
81 | CStr::from_ptr( | |
82 | __crypt( | |
83 | password.as_c_str().as_ptr(), | |
84 | salt.as_c_str().as_ptr() | |
85 | ) | |
86 | ) | |
87 | }; | |
88 | Ok(String::from(res.to_str()?)) | |
89 | } | |
90 | ||
91 | ||
92 | pub fn encrypt_pw(password: &str) -> Result<String, Error> { | |
93 | ||
94 | let salt = proxmox::sys::linux::random_data(8)?; | |
95 | let salt = format!("$5${}$", base64::encode_config(&salt, base64::CRYPT)); | |
96 | ||
97 | crypt(password.as_bytes(), &salt) | |
98 | } | |
99 | ||
100 | pub fn verify_crypt_pw(password: &str, enc_password: &str) -> Result<(), Error> { | |
101 | let verify = crypt(password.as_bytes(), enc_password)?; | |
102 | if &verify != enc_password { | |
103 | bail!("invalid credentials"); | |
104 | } | |
105 | Ok(()) | |
106 | } | |
107 | ||
e7cb4dc5 | 108 | const SHADOW_CONFIG_FILENAME: &str = configdir!("/shadow.json"); |
7d817b03 DM |
109 | |
110 | impl ProxmoxAuthenticator for PBS { | |
111 | ||
e7cb4dc5 | 112 | fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
7d817b03 | 113 | let data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; |
e7cb4dc5 | 114 | match data[username.as_str()].as_str() { |
7d817b03 DM |
115 | None => bail!("no password set"), |
116 | Some(enc_password) => verify_crypt_pw(password, enc_password)?, | |
117 | } | |
118 | Ok(()) | |
119 | } | |
120 | ||
e7cb4dc5 | 121 | fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error> { |
7d817b03 DM |
122 | let enc_password = encrypt_pw(password)?; |
123 | let mut data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; | |
e7cb4dc5 | 124 | data[username.as_str()] = enc_password.into(); |
7d817b03 DM |
125 | |
126 | let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); | |
127 | let options = proxmox::tools::fs::CreateOptions::new() | |
128 | .perm(mode) | |
129 | .owner(nix::unistd::ROOT) | |
130 | .group(nix::unistd::Gid::from_raw(0)); | |
131 | ||
132 | let data = serde_json::to_vec_pretty(&data)?; | |
133 | proxmox::tools::fs::replace_file(SHADOW_CONFIG_FILENAME, &data, options)?; | |
134 | ||
135 | Ok(()) | |
136 | } | |
137 | } | |
138 | ||
4b40148c | 139 | /// Lookup the autenticator for the specified realm |
e7cb4dc5 WB |
140 | pub fn lookup_authenticator(realm: &RealmRef) -> Result<Box<dyn ProxmoxAuthenticator>, Error> { |
141 | match realm.as_str() { | |
7d817b03 DM |
142 | "pam" => Ok(Box::new(PAM())), |
143 | "pbs" => Ok(Box::new(PBS())), | |
e7cb4dc5 | 144 | _ => bail!("unknown realm '{}'", realm.as_str()), |
7d817b03 DM |
145 | } |
146 | } | |
147 | ||
4b40148c | 148 | /// Authenticate users |
e7cb4dc5 | 149 | pub fn authenticate_user(userid: &Userid, password: &str) -> Result<(), Error> { |
7d817b03 | 150 | |
e7cb4dc5 WB |
151 | lookup_authenticator(userid.realm())? |
152 | .authenticate_user(userid.name(), password) | |
7d817b03 | 153 | } |