pub mod prot { //! # Protocol //! //! The authentication protocol is dead simple for now, it is a client-server model in which //! both parts send/receive data in the same format. //! //! ## Communication //! //! It simply works by expecting a set of data, and if, anything is unexpected, the communication aborts. //! //! ### Data De/Serialization //! //! Integer primitives are sent as bytes in Big Endian order. //! //! Strings are sent in 2 steps, first it sends the byte lenth. The byte length is flexible and //! depends on the protocol, is sent first, then, that numbers of bytes is expected, and to be //! in utf-8 format. //! //! The byte lenth type associated still be annotated by `String` where N is the data type //! that holds the length. //! //! ### Protocol //! //! The communication lacks versioning in the current state, as soon as the connection opens, //! the client sends the user as a `String` and the password as a `String`. Numbers are //! this small because higher users or passwords are rare or even impossible and this prevent //! memory allocation abuse. //! //! Then the server responds with a single [`u8`], the given value is then mapped to the enum //! [`ServerResponse`]. /// Represents the status after a login attempt #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum ServerResponse { /// Something went wrong in the server, e.g. the server failed pam client initialization ServerError = 0, /// The user is locked due to too many failed locin attempts. One must not rely on these, /// it's common for lockers to not report the lock status and just report as a [`Failed`] /// state. /// /// [`Failed`]: #variant.Failed Locked = 1, /// The authentication likely went through but failed Failed = 2, /// The authentication succeeded Succeeded = 3, } impl ServerResponse { #[must_use] pub const fn as_u8(self) -> u8 { self as u8 } #[must_use] pub const fn from_u8(b: u8) -> Option { match b { 0 => Some(Self::ServerError), 1 => Some(Self::Locked), 2 => Some(Self::Failed), 3 => Some(Self::Succeeded), _ => None, } } } #[must_use] pub fn attempt_login( mut stream: std::os::unix::net::UnixStream, user: &str, passwd: &str, ) -> Option { ucom::write_str(&mut stream, user)?; ucom::write_str(&mut stream, passwd)?; ServerResponse::from_u8(ucom::read_u8(&mut stream)?) } pub mod ucom { //! # µcom //! //! Small module to serialize basic primitives in a very basic form. use std::io::{Read, Write}; pub fn read_duple(from: &mut impl Read) -> Option<(String, String)> { let user = read_str(from)?; let pass = read_str(from)?; Some((user, pass)) } pub fn read_str(from: &mut impl Read) -> Option { let size = read_u8(from)? as usize; let mut buf = vec![0; size]; from.read_exact(&mut buf).ok()?; String::from_utf8(buf).ok() } pub fn read_u8(from: &mut impl Read) -> Option { let mut size_buf = [0; 1]; from.read_exact(&mut size_buf).ok()?; Some(u8::from_be_bytes(size_buf)) } pub fn write_duple(into: &mut impl Write, user: &str, pass: &str) -> Option<()> { write_str(into, user)?; write_str(into, pass)?; Some(()) } pub fn write_str(into: &mut impl Write, str: &str) -> Option<()> { let buf = str.as_bytes(); write_u8(into, buf.len().try_into().ok()?)?; into.write_all(buf).ok() } pub fn write_u8(into: &mut impl Write, u8: u8) -> Option<()> { let size_buf = u8.to_be_bytes(); into.write_all(&size_buf).ok() } } }