intial commit
This commit is contained in:
62
src/args.rs
Normal file
62
src/args.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use std::{
|
||||
os::{linux::net::SocketAddrExt, unix::net::SocketAddr},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
/// OpenID Connect server with unix backend
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
#[command(styles = get_clap_styles())]
|
||||
pub struct Args {
|
||||
#[arg(short, default_value = "/etc/authy-oidc.toml")]
|
||||
pub conf: PathBuf,
|
||||
|
||||
#[cfg(feature = "pamsock")]
|
||||
#[arg(long, default_value = "pam")]
|
||||
#[arg(value_parser = parse_as_abstract_name)]
|
||||
pub pamsock_abstract_name: std::os::unix::net::SocketAddr,
|
||||
}
|
||||
|
||||
fn parse_as_abstract_name(name: &str) -> std::io::Result<SocketAddr> {
|
||||
SocketAddr::from_abstract_name(name.as_bytes())
|
||||
}
|
||||
|
||||
const fn get_clap_styles() -> clap::builder::Styles {
|
||||
clap::builder::Styles::styled()
|
||||
.usage(
|
||||
anstyle::Style::new()
|
||||
.bold()
|
||||
.underline()
|
||||
.fg_color(Some(anstyle::Color::Ansi256(anstyle::Ansi256Color(208)))),
|
||||
)
|
||||
.header(
|
||||
anstyle::Style::new()
|
||||
.bold()
|
||||
.underline()
|
||||
.fg_color(Some(anstyle::Color::Ansi256(anstyle::Ansi256Color(208)))),
|
||||
)
|
||||
.literal(
|
||||
anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))),
|
||||
)
|
||||
.invalid(
|
||||
anstyle::Style::new()
|
||||
.bold()
|
||||
.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))),
|
||||
)
|
||||
.error(
|
||||
anstyle::Style::new()
|
||||
.bold()
|
||||
.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))),
|
||||
)
|
||||
.valid(
|
||||
anstyle::Style::new()
|
||||
.bold()
|
||||
.underline()
|
||||
.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))),
|
||||
)
|
||||
.placeholder(
|
||||
anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Blue))),
|
||||
)
|
||||
}
|
||||
18
src/auth/mod.rs
Normal file
18
src/auth/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticateResponse<F: Display + Debug> {
|
||||
/// All rightie
|
||||
Success,
|
||||
|
||||
/// The message will be displayed to the frontend, must give a reason of why the user login
|
||||
/// failed
|
||||
Failed(F),
|
||||
}
|
||||
|
||||
#[cfg(feature = "pamsock")]
|
||||
pub mod pamsock;
|
||||
#[cfg(feature = "pamsock")]
|
||||
use pamsock as auth;
|
||||
|
||||
pub use auth::authenticate;
|
||||
40
src/auth/pamsock.rs
Normal file
40
src/auth/pamsock.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
//! Horrible error handling here btw
|
||||
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use pamsock::prot::ServerResponse;
|
||||
|
||||
use super::AuthenticateResponse;
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<AuthenticateResponse<&'static str>> for ServerResponse {
|
||||
fn into(self) -> AuthenticateResponse<&'static str> {
|
||||
use AuthenticateResponse as AR;
|
||||
|
||||
match self {
|
||||
Self::ServerError => AR::Failed("unknown server error"),
|
||||
Self::Locked => AR::Failed("account locked, too many login attempts"),
|
||||
Self::Failed => AR::Failed("wrong credentials"),
|
||||
Self::Succeeded => AR::Success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn authenticate(
|
||||
cfg: &crate::args::Args,
|
||||
user: &str,
|
||||
passwd: &str,
|
||||
) -> Option<AuthenticateResponse<impl Display + Debug>> {
|
||||
use std::os::unix::net::UnixStream;
|
||||
use tokio::net::UnixStream as AsyncUnixStream;
|
||||
|
||||
let std_sock = UnixStream::connect_addr(&cfg.pamsock_abstract_name).ok()?;
|
||||
std_sock.set_nonblocking(true).ok();
|
||||
let async_sock = AsyncUnixStream::from_std(std_sock).ok()?;
|
||||
|
||||
Some(
|
||||
pamsock::prot::attempt_login_async(async_sock, user, passwd)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
64
src/conf.rs
Normal file
64
src/conf.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
fs::File,
|
||||
io::{self, Read, Seek},
|
||||
};
|
||||
|
||||
use libc::gid_t;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Either<T, U> {
|
||||
Left(T),
|
||||
Right(U),
|
||||
}
|
||||
|
||||
impl<T: Debug, U: Debug> Debug for Either<T, U> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Left(l) => l.fmt(f),
|
||||
Self::Right(r) => r.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Group = Either<String, gid_t>;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Unix {
|
||||
groups: Vec<Group>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
unix: Unix,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ConfigParseError {
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Parse(#[from] toml::de::Error),
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// # Errors
|
||||
///
|
||||
/// For IO errors reading the file or parsing errors.
|
||||
pub fn from_toml_file(f: &mut File) -> Result<Self, ConfigParseError> {
|
||||
let mut buf = Vec::new();
|
||||
if let Ok(len) = f.stream_len() {
|
||||
// There's no way a config file passes machine's memory limitations
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
buf.reserve_exact(len as usize);
|
||||
}
|
||||
f.read_to_end(&mut buf)?;
|
||||
|
||||
Ok(toml::from_slice(&buf)?)
|
||||
}
|
||||
}
|
||||
16
src/ext/mod.rs
Normal file
16
src/ext/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use std::{fs::File, io, path::Path};
|
||||
|
||||
pub trait FileExt {
|
||||
/// Tries to [`File::open`] a file, but wrapped around an option if the file doesn't exist.
|
||||
fn try_open<P: AsRef<Path>>(path: P) -> Option<io::Result<File>>;
|
||||
}
|
||||
|
||||
impl FileExt for File {
|
||||
fn try_open<P: AsRef<Path>>(path: P) -> Option<io::Result<File>> {
|
||||
match Self::open(path) {
|
||||
Ok(f) => Some(Ok(f)),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/main.rs
Normal file
29
src/main.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#![feature(seek_stream_len)]
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::ext::FileExt;
|
||||
|
||||
pub mod args;
|
||||
pub mod auth;
|
||||
pub mod conf;
|
||||
pub mod ext;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let args = args::Args::parse();
|
||||
let conf = if let Some(conf) = File::try_open(&args.conf) {
|
||||
conf::Config::from_toml_file(&mut conf?)?
|
||||
} else {
|
||||
conf::Config::default()
|
||||
};
|
||||
|
||||
println!("{conf:#?}");
|
||||
|
||||
let res = auth::authenticate(&args, "javalsai", "test").await;
|
||||
println!("{res:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user