diff --git a/src/api2/access.rs b/src/api2/access.rs index 13692a19..25b23321 100644 --- a/src/api2/access.rs +++ b/src/api2/access.rs @@ -25,14 +25,7 @@ fn authenticate_user(username: &str, password: &str) -> Result<(), Error> { } } - if username == "root@pam" { - let mut auth = pam::Authenticator::with_password("proxmox-backup-auth").unwrap(); - auth.get_handler().set_credentials("root", password); - auth.authenticate()?; - return Ok(()); - } - - bail!("inavlid credentials"); + crate::auth::authenticate_user(username, password) } #[api( diff --git a/src/api2/config/user.rs b/src/api2/config/user.rs index 47ca5869..d59788f2 100644 --- a/src/api2/config/user.rs +++ b/src/api2/config/user.rs @@ -108,7 +108,7 @@ pub fn list_users( }, )] /// Create new user. -pub fn create_user(userid: String, param: Value) -> Result<(), Error> { +pub fn create_user(userid: String, password: Option, param: Value) -> Result<(), Error> { let _lock = crate::tools::open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?; @@ -120,13 +120,17 @@ pub fn create_user(userid: String, param: Value) -> Result<(), Error> { bail!("user '{}' already exists.", userid); } - // fixme: check/store password - // check domain + let (username, realm) = crate::auth::parse_userid(&userid)?; + let authenticator = crate::auth::lookup_authenticator(&realm)?; config.set_data(&userid, "user", &user)?; user::save_config(&config)?; + if let Some(password) = password { + authenticator.store_password(&username, &password)?; + } + Ok(()) } @@ -236,7 +240,9 @@ pub fn update_user( } if let Some(password) = password { - unimplemented!(); + let (username, realm) = crate::auth::parse_userid(&userid)?; + let authenticator = crate::auth::lookup_authenticator(&realm)?; + authenticator.store_password(&username, &password)?; } if let Some(firstname) = firstname { diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 00000000..6a5d8a96 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,165 @@ +//! Proxmox Backup Server Authentication +//! +//! This library contains helper to authenticate users. + +use std::process::{Command, Stdio}; +use std::io::Write; +use std::ffi::{CString, CStr}; + +use base64; +use failure::*; +use serde_json::json; + +pub trait ProxmoxAuthenticator { + fn authenticate_user(&self, username: &str, password: &str) -> Result<(), Error>; + fn store_password(&self, username: &str, password: &str) -> Result<(), Error>; +} + +pub struct PAM(); + +impl ProxmoxAuthenticator for PAM { + + fn authenticate_user(&self, username: &str, password: &str) -> Result<(), Error> { + let mut auth = pam::Authenticator::with_password("proxmox-backup-auth").unwrap(); + auth.get_handler().set_credentials(username, password); + auth.authenticate()?; + return Ok(()); + + } + + fn store_password(&self, username: &str, password: &str) -> Result<(), Error> { + let mut child = Command::new("passwd") + .arg(username) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .or_else(|err| Err(format_err!("unable to set password for '{}' - execute passwd failed: {}", username, err)))?; + + // Note: passwd reads password twice from stdin (for verify) + writeln!(child.stdin.as_mut().unwrap(), "{}\n{}", password, password)?; + + let output = child.wait_with_output() + .or_else(|err| Err(format_err!("unable to set password for '{}' - wait failed: {}", username, err)))?; + + if !output.status.success() { + bail!("unable to set password for '{}' - {}", username, String::from_utf8_lossy(&output.stderr)); + } + + Ok(()) + } +} + +pub struct PBS(); + +pub fn crypt(password: &[u8], salt: &str) -> Result { + + #[link(name="crypt")] + extern "C" { + #[link_name = "crypt"] + fn __crypt(key: *const libc::c_char, salt: *const libc::c_char) -> * mut libc::c_char; + } + + let salt = CString::new(salt)?; + let password = CString::new(password)?; + + let res = unsafe { + CStr::from_ptr( + __crypt( + password.as_c_str().as_ptr(), + salt.as_c_str().as_ptr() + ) + ) + }; + Ok(String::from(res.to_str()?)) +} + + +pub fn encrypt_pw(password: &str) -> Result { + + let salt = proxmox::sys::linux::random_data(8)?; + let salt = format!("$5${}$", base64::encode_config(&salt, base64::CRYPT)); + + crypt(password.as_bytes(), &salt) +} + +pub fn verify_crypt_pw(password: &str, enc_password: &str) -> Result<(), Error> { + let verify = crypt(password.as_bytes(), enc_password)?; + if &verify != enc_password { + bail!("invalid credentials"); + } + Ok(()) +} + +const SHADOW_CONFIG_FILENAME: &str = "/etc/proxmox-backup/shadow.json"; + +impl ProxmoxAuthenticator for PBS { + + fn authenticate_user(&self, username: &str, password: &str) -> Result<(), Error> { + let data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; + match data[username].as_str() { + None => bail!("no password set"), + Some(enc_password) => verify_crypt_pw(password, enc_password)?, + } + Ok(()) + } + + fn store_password(&self, username: &str, password: &str) -> Result<(), Error> { + let enc_password = encrypt_pw(password)?; + let mut data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?; + data[username] = enc_password.into(); + + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); + let options = proxmox::tools::fs::CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(nix::unistd::Gid::from_raw(0)); + + let data = serde_json::to_vec_pretty(&data)?; + proxmox::tools::fs::replace_file(SHADOW_CONFIG_FILENAME, &data, options)?; + + Ok(()) + } +} + +pub fn parse_userid(userid: &str) -> Result<(String, String), Error> { + let data: Vec<&str> = userid.rsplitn(2, '@').collect(); + + if data.len() != 2 { + bail!("userid '{}' has no realm", userid); + } + Ok((data[1].to_owned(), data[0].to_owned())) +} + +pub fn lookup_authenticator(realm: &str) -> Result, Error> { + match realm { + "pam" => Ok(Box::new(PAM())), + "pbs" => Ok(Box::new(PBS())), + _ => bail!("unknown realm '{}'", realm), + } +} + +pub fn authenticate_user(userid: &str, password: &str) -> Result<(), Error> { + let (username, realm) = parse_userid(userid)?; + + let (user_config, _digest) = crate::config::user::config()?; + let user: Result = user_config.lookup("user", userid); + match user { + Ok(user) => { + if let Some(false) = user.enable { + bail!("account disabled"); + } + if let Some(expire) = user.expire { + if expire > 0 { + let now = unsafe { libc::time(std::ptr::null_mut()) }; + if expire <= now { + bail!("account expired"); + } + } + } + }, + Err(_) => bail!("no such user"), + } + + lookup_authenticator(&realm)? + .authenticate_user(&username, password) +} diff --git a/src/lib.rs b/src/lib.rs index 73977505..c0844464 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,3 +19,5 @@ pub mod api2; pub mod client; pub mod auth_helpers; + +pub mod auth;