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