Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
make 'pam' and 'fsquota' optional features
- Loading branch information
Showing
6 changed files
with
195 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
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 { | ||
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 { | ||
IpAddr::V4(ip) => ip.to_string(), | ||
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 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) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.