Add a separate client binary, with some common code to the server.

The cargo workspace thing was a PITA to figure out, but it seems to be working.  Could maybe be simplified?
This commit is contained in:
Barry Pederson
2024-11-02 15:58:35 -07:00
parent 46ad77f19e
commit 076e48f457
16 changed files with 380 additions and 255 deletions
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "server"
version = "0.2.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.20", features = ["derive"] }
common = { path = "../common" }
+94
View File
@@ -0,0 +1,94 @@
use std::fs::File;
use std::io::{BufRead, BufReader};
use crate::options::OPTIONS;
use common::Request;
#[derive(Clone)]
pub struct FileHandler;
impl FileHandler {
pub fn new() -> FileHandler {
FileHandler {}
}
pub fn check_auth(&self, request: &Request) -> bool {
// Don't allow empty userids or passwords
//
if request.userid.is_empty() || request.userid.contains('/') || request.password.is_empty()
{
return false;
}
let mut file_name = OPTIONS.password_dir.clone();
file_name.push(&request.userid);
if let Ok(file) = File::open(file_name) {
for line in BufReader::new(file).lines().map_while(Result::ok) {
if !line.starts_with('#') && line.trim() == request.password {
return true;
}
}
}
false
}
}
#[cfg(test)]
mod tests {
use crate::handler::FileHandler;
use common::Request;
#[test]
fn it_checks_good_password() {
let handler = FileHandler::new();
let request = Request {
userid: "bp".to_string(),
password: "some-rad-password".to_string(),
service: "ignore-this".to_string(),
realm: "also-ignore-this".to_string(),
};
assert_eq!(handler.check_auth(&request), true);
}
#[test]
fn it_fails_bad_password() {
let handler = FileHandler::new();
let request = Request {
userid: "bp".to_string(),
password: "not-correct".to_string(),
service: "ignore-this".to_string(),
realm: "also-ignore-this".to_string(),
};
assert_eq!(handler.check_auth(&request), false);
}
#[test]
fn it_fails_bad_userid() {
let handler = FileHandler::new();
let request = Request {
userid: "bp-xxx".to_string(),
password: "some-rad-password".to_string(),
service: "ignore-this".to_string(),
realm: "also-ignore-this".to_string(),
};
assert_eq!(handler.check_auth(&request), false);
}
#[test]
fn it_ignores_comments() {
let handler = FileHandler::new();
let request = Request {
userid: "bp".to_string(),
password: "commented-out".to_string(),
service: "ignore-this".to_string(),
realm: "also-ignore-this".to_string(),
};
assert_eq!(handler.check_auth(&request), false);
}
}
+54
View File
@@ -0,0 +1,54 @@
use crate::handler::FileHandler;
use crate::options::OPTIONS;
use crate::server::Server;
use std::fs::{metadata, set_permissions};
use std::io::Error;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::UnixListener;
use std::thread;
pub struct Listener {
listener: UnixListener,
handler: FileHandler,
}
impl Listener {
pub fn new(handler: FileHandler) -> Result<Listener, Error> {
if OPTIONS.socket_name.exists() {
std::fs::remove_file(&OPTIONS.socket_name)?;
}
let listener = UnixListener::bind(&OPTIONS.socket_name)?;
let mut perms = metadata(&OPTIONS.socket_name)?.permissions();
perms.set_mode(0o0777);
set_permissions(&OPTIONS.socket_name, perms)?;
Ok(Listener { handler, listener })
}
pub fn run(&mut self) -> Result<(), Error> {
// accept connections and process them, spawning a new thread for each one
for stream in self.listener.incoming() {
match stream {
Ok(stream) => {
/* connection succeeded */
let handler = self.handler.clone();
thread::spawn(|| Server::new(stream, handler).unwrap().handle_client());
}
Err(_err) => {
/* connection failed */
break;
}
}
}
Ok(())
}
}
impl Drop for Listener {
fn drop(&mut self) {
let _result = std::fs::remove_file(&OPTIONS.socket_name);
println!("Shutting down");
}
}
+12
View File
@@ -0,0 +1,12 @@
mod handler;
mod listener;
pub mod options;
mod server;
use crate::handler::FileHandler;
use crate::listener::Listener;
fn main() -> std::io::Result<()> {
let mut listener = Listener::new(FileHandler::new())?;
listener.run()
}
+20
View File
@@ -0,0 +1,20 @@
use std::path::PathBuf;
use clap::Parser;
use std::sync::LazyLock;
#[derive(Parser, Clone, Debug)]
/// saslauthd server/client
pub struct Opt {
#[arg(
short = 'f',
long = "socket-name",
default_value = "/tmp/saslauthd.sock"
)]
pub socket_name: PathBuf,
#[arg(short = 'p', long = "password-dir", default_value = "./passwords")]
pub password_dir: PathBuf,
}
pub static OPTIONS: LazyLock<Opt> = LazyLock::new(Opt::parse);
+34
View File
@@ -0,0 +1,34 @@
use crate::handler::FileHandler;
use common::{Request, RESPONSE_NO, RESPONSE_OK};
use std::io::{Error, Write};
use std::os::unix::net::UnixStream;
pub struct Server {
stream: UnixStream,
handler: FileHandler,
}
impl Server {
pub fn new(stream: UnixStream, handler: FileHandler) -> Result<Server, Error> {
Ok(Server { stream, handler })
}
fn communicate(&mut self) -> Result<(), Error> {
let request = Request::from_stream(&mut self.stream)?;
println!("userid: {}", request.userid);
if self.handler.check_auth(&request) {
self.stream.write_all(&RESPONSE_OK)?
} else {
self.stream.write_all(&RESPONSE_NO)?
}
Ok(())
}
pub fn handle_client(&mut self) {
if let Err(error) = self.communicate() {
eprint!("{error}");
}
}
}