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