157 lines
4.9 KiB
Rust
157 lines
4.9 KiB
Rust
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<hyper::Body>;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Auth {
|
|
config: Arc<Config>,
|
|
#[cfg(feature = "pam")]
|
|
pam_auth: pam_sandboxed::PamAuth,
|
|
}
|
|
|
|
impl Auth {
|
|
pub fn new(config: Arc<Config>) -> io::Result<Auth> {
|
|
// 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<String, StatusCode>
|
|
{
|
|
// we must have a login/pass
|
|
let basic = match req.headers().typed_get::<Authorization<Basic>>() {
|
|
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<String, StatusCode>
|
|
{
|
|
// 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<String, StatusCode>
|
|
{
|
|
// 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)
|
|
})
|
|
}
|
|
}
|