]> git.proxmox.com Git - proxmox-backup.git/blame - src/auth.rs
fix #2871: close FDs when scanning backup group
[proxmox-backup.git] / src / auth.rs
CommitLineData
7d817b03
DM
1//! Proxmox Backup Server Authentication
2//!
3//! This library contains helper to authenticate users.
4
5use std::process::{Command, Stdio};
6use std::io::Write;
7use std::ffi::{CString, CStr};
8
9use base64;
f7d4e4b5 10use anyhow::{bail, format_err, Error};
7d817b03
DM
11use serde_json::json;
12
13pub 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
18pub struct PAM();
19
20impl 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
52pub struct PBS();
53
54pub 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
77pub 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
85pub 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
93const SHADOW_CONFIG_FILENAME: &str = "/etc/proxmox-backup/shadow.json";
94
95impl 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
124pub 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
134pub 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
143pub 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}