//! Authentication methods.
use anyhow::{anyhow, Context, Result};
use log::{error, info};
use nix::unistd::gethostname;
use crate::{
pam::PamPassword,
tui::{prompt, EchoInput},
};
/// Authenticates a user.
pub trait Authenticator {
fn authenticate(&mut self) -> Result<()>;
fn username(&self) -> &str; // For lack of trait fields.
}
/// Prompts user to log in.
pub fn login() -> Result<impl Authenticator> {
let hostname = || -> Result<String> {
let mut buf = [0u8; 64];
let hostname = gethostname(&mut buf).context("Couldn't call gethostname")?;
let hostname = hostname.to_str().context("Hostname isn't valid UTF-8")?;
Ok(hostname.to_owned())
};
let mut attempts = 0;
let max_attempts = 3;
let hostname = hostname().context("Couldn't get hostname")?;
let username_prompt = format!("{} login", hostname);
loop {
// TODO: login(1) eats SIGINTs. This function should too.
let username = prompt(&username_prompt, EchoInput::Echo)?;
if username.is_empty() {
continue;
}
// TODO: Here's the inflection point between various auth possibilities.
// It can't be outside this function because the username is the determiner.
// 1. PAM password: Status quo.
// 2. PAM autologin: Identical authenticate() except no `let password`.
// 3. !PAM password: I'll have to bone up on scrypt or something.
// 4. !PAM autologin: Probably a `getgrouplist` call.
let mut pam = PamPassword::new(username)?;
match pam.authenticate() {
Err(e) => {
error!("Couldn't authenticate {}: {}", pam.username(), e);
}
Ok(_) => {
info!("Authenticated {}", pam.username());
return Ok(pam);
}
}
attempts += 1;
if attempts == max_attempts {
return Err(anyhow!("Too many login attempts"));
}
}
}