feat: clone from https://github.com/miquels/webdav-server-rs
This commit is contained in:
156
src/auth.rs
Normal file
156
src/auth.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user