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:
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "server"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.20", features = ["derive"] }
|
||||
common = { path = "../common" }
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user