127 lines
4.1 KiB
Rust
127 lines
4.1 KiB
Rust
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()
|
|
}
|
|
}
|
|
}
|