intial version

This commit is contained in:
2026-02-24 18:05:44 +01:00
commit 50ae9d8128
8 changed files with 720 additions and 0 deletions

126
src/lib.rs Normal file
View File

@@ -0,0 +1,126 @@
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<N>` 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<u8>` and the password as a `String<u8>`. 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<Self> {
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<ServerResponse> {
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<String> {
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<u8> {
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()
}
}
}