use std::io; use std::net::SocketAddr; use std::sync::Arc; use crate::config::{AuthType, Config, Location}; use headers::{authorization::Basic, Authorization, HeaderMapExt}; use http::status::StatusCode; type HttpRequest = http::Request; #[derive(Clone)] pub struct Auth { config: Arc, #[cfg(feature = "pam")] pam_auth: pam_sandboxed::PamAuth, } impl Auth { pub fn new(config: Arc) -> io::Result { // initialize pam. #[cfg(feature = "pam")] let pam_auth = { // set cache timeouts. if let Some(timeout) = config.pam.cache_timeout { crate::cache::cached::set_pamcache_timeout(timeout); } pam_sandboxed::PamAuth::new(config.pam.threads.clone())? }; Ok(Auth { #[cfg(feature = "pam")] pam_auth, config, }) } // authenticate user. pub async fn auth<'a>( &'a self, req: &'a HttpRequest, location: &Location, _remote_ip: SocketAddr, ) -> Result { // we must have a login/pass let basic = match req.headers().typed_get::>() { Some(Authorization(basic)) => basic, _ => return Err(StatusCode::UNAUTHORIZED), }; let user = basic.username(); let pass = basic.password(); // match the auth type. let auth_type = location .accounts .auth_type .as_ref() .or(self.config.accounts.auth_type.as_ref()); match auth_type { #[cfg(feature = "pam")] Some(&AuthType::Pam) => self.auth_pam(req, user, pass, _remote_ip).await, Some(&AuthType::HtPasswd(ref ht)) => self.auth_htpasswd(user, pass, ht.as_str()).await, None => { debug!("need authentication, but auth-type is not set"); Err(StatusCode::UNAUTHORIZED) }, } } // authenticate user using PAM. #[cfg(feature = "pam")] async fn auth_pam<'a>( &'a self, req: &'a HttpRequest, user: &'a str, pass: &'a str, remote_ip: SocketAddr, ) -> Result { // stringify the remote IP address. let ip = remote_ip.ip(); let ip_string = if ip.is_loopback() { // if it's loopback, take the value from the x-forwarded-for // header, if present. req.headers() .get("x-forwarded-for") .and_then(|s| s.to_str().ok()) .and_then(|s| s.split(',').next()) .map(|s| s.trim().to_owned()) } else { Some(match ip { std::net::IpAddr::V4(ip) => ip.to_string(), std::net::IpAddr::V6(ip) => ip.to_string(), }) }; let ip_ref = ip_string.as_ref().map(|s| s.as_str()); // authenticate. let service = self.config.pam.service.as_str(); let pam_auth = self.pam_auth.clone(); match crate::cache::cached::pam_auth(pam_auth, service, user, pass, ip_ref).await { Ok(_) => Ok(user.to_string()), Err(_) => { debug!( "auth_pam({}): authentication for {} ({:?}) failed", service, user, ip_ref ); Err(StatusCode::UNAUTHORIZED) }, } } // authenticate user using htpasswd. async fn auth_htpasswd<'a>( &'a self, user: &'a str, pass: &'a str, section: &'a str, ) -> Result { // Get the htpasswd.WHATEVER section from the config file. let file = match self.config.htpasswd.get(section) { Some(section) => section.htpasswd.as_str(), None => return Err(StatusCode::UNAUTHORIZED), }; // Read the file and split it into a bunch of lines. tokio::task::block_in_place(move || { let data = match std::fs::read_to_string(file) { Ok(data) => data, Err(e) => { debug!("{}: {}", file, e); return Err(StatusCode::UNAUTHORIZED); }, }; let lines = data .split('\n') .map(|s| s.trim()) .filter(|s| !s.starts_with("#") && !s.is_empty()); // Check each line for a match. for line in lines { let mut fields = line.split(':'); if let (Some(htuser), Some(htpass)) = (fields.next(), fields.next()) { if htuser == user && pwhash::unix::verify(pass, htpass) { return Ok(user.to_string()); } } } debug!("auth_htpasswd: authentication for {} failed", user); Err(StatusCode::UNAUTHORIZED) }) } }