use std::{
fmt::Debug,
fs, io,
io::BufRead,
path::{Path, PathBuf},
};
use log::warn;
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "socall")]
mod socall;
#[cfg(feature = "socall")]
pub use socall::handle_reexec;
#[cfg(feature = "socall")]
pub use socall::PamMotdResolutionStrategy;
#[cfg(feature = "socall")]
pub use socall::Resolver;
#[cfg(not(feature = "socall"))]
mod reimpl;
#[cfg(not(feature = "socall"))]
pub use reimpl::Resolver;
const PAM_DIR: [&str; 2] = ["/etc", "pam.d"];
macro_rules! merr {
($($arg:tt)*) => {{
Error::Err { msg: format!($($arg)*) }
}}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum ArgResolutionStrategy {
Exact(Vec<String>),
MatchServices(Vec<String>),
Auto,
}
impl ArgResolutionStrategy {
fn resolve(self) -> Result<Vec<String>, Error> {
match self {
ArgResolutionStrategy::Exact(args) => Ok(args),
ArgResolutionStrategy::Auto => ArgResolutionStrategy::MatchServices(vec![
String::from("ssh"),
String::from("login"),
])
.resolve(),
ArgResolutionStrategy::MatchServices(services) => {
let mut args = vec![];
for service in services.into_iter() {
let mut service_path = PathBuf::new();
for part in PAM_DIR.iter() {
service_path.push(part);
}
service_path.push(service);
args.extend(Self::slurp_args(service_path)?);
}
args.sort_unstable();
args.dedup();
args.push(String::from("noupdate"));
Ok(args)
}
}
}
fn slurp_args<P: AsRef<Path> + Debug>(service_file: P) -> Result<Vec<String>, Error> {
if !service_file.as_ref().is_file() {
return Ok(vec![]);
}
let file = fs::File::open(&service_file)
.map_err(|e| merr!("opening {:?} to parse args: {:?}", &service_file, e))?;
let reader = io::BufReader::new(file);
let mut args = vec![];
for line in reader.lines() {
let line = line.map_err(|e| merr!("reading line from {:?}: {:?}", &service_file, e))?;
let line = line.trim();
if line.starts_with('#') || line.is_empty() {
continue;
}
if line.starts_with("@include") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() != 2 {
warn!(
"expect exactly 1 argument to @include, got {}",
parts.len() - 1
);
}
let mut included_service_path = PathBuf::new();
for part in PAM_DIR.iter() {
included_service_path.push(part);
}
included_service_path.push(parts[1]);
args.extend(Self::slurp_args(included_service_path)?);
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 3 {
warn!("expect at least 3 parts for a pam module config");
continue;
}
let module = parts[2];
if module != "pam_motd.so" {
continue;
}
for arg in &parts[3..] {
if *arg != "noupdate" {
args.push(String::from(*arg));
}
}
}
Ok(args)
}
}
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
Err {
msg: String,
},
__NonExhaustive,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Error::Err { msg } => write!(f, "{}", msg)?,
_ => write!(f, "{:?}", self)?,
}
Ok(())
}
}
impl std::error::Error for Error {}