use constant_time_eq::constant_time_eq; use diesel::expression::AsExpression; use diesel::pg::PgConnection; use diesel::prelude::*; use diesel::sql_types::Bool; use pamsm::{Pam, PamError, PamFlags, PamLibExt, PamResult, PamServiceModule, pam_module}; use self::db::*; mod db; struct PamTokenPg; fn authenticate(pam: Pam, flags: PamFlags, args: &[String]) -> PamResult<()> { let mut dsn = None; let mut table = None; let mut schema = None; macro_rules! parse_options { ($args:expr, $($opt:ident),+$(,)?) => { for arg in $args { if false { unreachable!() } $(else if arg.starts_with(concat!(stringify!($opt), "=")) { let value = &arg[(stringify!($opt).len() + 1)..]; if $opt.is_none() { $opt = Some(value); } else { pam.syslog(pamsm::LogLvl::ERR, concat!( "improperly configured: option '", stringify!($opt), "' specified multiple times", ))?; return Err(PamError::SYSTEM_ERR); } })+ else { pam.syslog( pamsm::LogLvl::ERR, &format!("improperly configured: invalid option '{}'", arg), )?; return Err(PamError::SYSTEM_ERR); } } } } parse_options!(args, dsn, table, schema); let Some(dsn) = dsn else { pam.syslog( pamsm::LogLvl::ERR, "improperly configured: missing required option 'dsn'", )?; return Err(PamError::SYSTEM_ERR); }; let Some(table) = table else { pam.syslog( pamsm::LogLvl::ERR, "improperly configured: missing required option 'table'", )?; return Err(PamError::SYSTEM_ERR); }; let table = db::Table::new(table, schema); let pam_user = pam .get_user(None) .and_then(|user| user.ok_or(PamError::AUTH_ERR))? .to_str() .map_err(|_| PamError::SERVICE_ERR)?; let pam_authtok = pam .get_authtok(None) .and_then(|authtok| authtok.ok_or(PamError::AUTH_ERR))? .to_bytes(); let mut conn = PgConnection::establish(dsn).map_err(|_| PamError::AUTHINFO_UNAVAIL)?; let user_tokens = table .select(token) .filter(username.eq(pam_user)) .filter(token.ne("").or(AsExpression::::as_expression( !flags.contains(PamFlags::DISALLOW_NULL_AUTHTOK), ))) .load::(&mut conn) .map_err(|_| PamError::AUTHINFO_UNAVAIL)?; for user_token in user_tokens { let user_token = user_token.as_bytes(); if constant_time_eq(user_token, pam_authtok) { return Ok(()); } } Err(PamError::AUTH_ERR) } impl PamServiceModule for PamTokenPg { fn authenticate(pam: Pam, flags: PamFlags, args: Vec) -> PamError { authenticate(pam, flags, &args) .err() .unwrap_or(PamError::SUCCESS) } } pam_module!(PamTokenPg); #[cfg(test)] mod tests { use super::*; }