Refactor Listener and Server into separate files.

This commit is contained in:
Barry Pederson
2022-03-01 09:07:29 -08:00
parent bb65edd766
commit a244e714ea
3 changed files with 160 additions and 148 deletions
+54
View File
@@ -0,0 +1,54 @@
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;
use crate::options::Opt;
use crate::server::Server;
pub struct Listener {
options: Opt,
listener: UnixListener,
}
impl Listener {
pub fn new(options: Opt) -> Result<Listener, Error> {
if options.socket.exists() {
std::fs::remove_file(&options.socket)?;
}
let listener = UnixListener::bind(&options.socket)?;
let mut perms = metadata(&options.socket)?.permissions();
perms.set_mode(0o0777);
set_permissions(&options.socket, perms)?;
Ok(Listener { options, 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 options = self.options.clone();
thread::spawn(|| Server::new(options, stream).unwrap().handle_client());
}
Err(_err) => {
/* connection failed */
break;
}
}
}
Ok(())
}
}
impl Drop for Listener {
fn drop(&mut self) {
let _result = std::fs::remove_file(&self.options.socket);
println!("Shutting down");
}
}
+5 -148
View File
@@ -1,155 +1,12 @@
use std::fs::{metadata, set_permissions, File};
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Write};
use std::net::Shutdown;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::{UnixListener, UnixStream};
use std::thread;
mod listener;
mod options;
mod server;
use structopt::StructOpt;
use crate::listener::Listener;
use crate::options::Opt;
mod options;
const RESPONSE_NO: [u8; 4] = [0x0, 0x2, b'N', b'O'];
const RESPONSE_OK: [u8; 4] = [0x0, 0x2, b'O', b'K'];
struct Listener {
options: Opt,
listener: UnixListener,
}
struct Server {
options: Opt,
stream: UnixStream,
}
impl Listener {
pub fn new(options: Opt) -> Result<Listener, Error> {
if options.socket.exists() {
std::fs::remove_file(&options.socket)?;
}
let listener = UnixListener::bind(&options.socket)?;
let mut perms = metadata(&options.socket)?.permissions();
perms.set_mode(0o0777);
set_permissions(&options.socket, perms)?;
Ok(Listener { options, 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 options = self.options.clone();
thread::spawn(|| Server::new(options, stream).unwrap().handle_client());
}
Err(_err) => {
/* connection failed */
break;
}
}
}
Ok(())
}
}
impl Drop for Listener {
fn drop(&mut self) {
let _result = std::fs::remove_file(&self.options.socket);
println!("Shutting down");
}
}
impl Server {
pub fn new(options: Opt, stream: UnixStream) -> Result<Server, Error> {
Ok(Server { options, stream })
}
/// The saslauthd protocol transmits strings as
/// 16-bit unsigned network-byte-order lengths followed
/// by the string itself.
///
fn read_string(&mut self) -> Result<String, Error> {
let mut length_buffer = [0u8, 0u8];
self.stream.read_exact(&mut length_buffer)?;
let length = u16::from_be_bytes(length_buffer);
let mut string_buffer = vec![0u8; length as usize];
self.stream.read_exact(&mut string_buffer)?;
match String::from_utf8(string_buffer) {
Ok(str) => Ok(str),
Err(_err) => Err(Error::new(ErrorKind::Other, "Invalid UTF-8 sent")),
}
}
fn write_string(&mut self, s: &str) -> Result<(), Error> {
let length = s.len();
if length > 0xffff {
return Err(Error::new(ErrorKind::Other, "String too long to write"));
}
let length_buffer = [(length >> 8) as u8, (length & 0xff) as u8];
self.stream.write_all(&length_buffer)?;
self.stream.write_all(s.as_bytes())?;
Ok(())
}
fn check_auth(&mut self, userid: &str, password: &str) -> Result<bool, Error> {
// Don't allow empty userids or passwords
//
if userid.is_empty() || password.is_empty() {
return Ok(false);
}
let mut password_file = self.options.passwords.clone();
password_file.push(&userid);
let file = File::open(password_file)?;
for line in BufReader::new(file).lines().flatten() {
if !line.starts_with('#') && line.trim() == password {
return Ok(true);
}
}
Ok(false)
}
fn communicate(&mut self) -> Result<(), Error> {
let userid = self.read_string()?;
let password = self.read_string()?;
let service = self.read_string()?;
let _realm = self.read_string()?;
match self.check_auth(&userid, &password) {
Ok(true) => self.stream.write_all(&RESPONSE_OK)?,
Ok(false) => self.stream.write_all(&RESPONSE_NO)?,
Err(e) => {
self.stream.write_all(&RESPONSE_NO)?;
self.stream.shutdown(Shutdown::Both)?;
return Err(e);
}
}
Ok(())
}
fn handle_client(&mut self) {
if let Err(error) = self.communicate() {
eprint!("{}", error);
}
}
}
fn main() -> std::io::Result<()> {
let mut server = Listener::new(Opt::from_args())?;
+101
View File
@@ -0,0 +1,101 @@
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Write};
use std::net::Shutdown;
use std::os::unix::net::UnixStream;
use crate::options::Opt;
const RESPONSE_NO: [u8; 4] = [0x0, 0x2, b'N', b'O'];
const RESPONSE_OK: [u8; 4] = [0x0, 0x2, b'O', b'K'];
pub struct Server {
options: Opt,
stream: UnixStream,
}
impl Server {
pub fn new(options: Opt, stream: UnixStream) -> Result<Server, Error> {
Ok(Server { options, stream })
}
/// The saslauthd protocol transmits strings as
/// 16-bit unsigned network-byte-order lengths followed
/// by the string itself.
///
fn read_string(&mut self) -> Result<String, Error> {
let mut length_buffer = [0u8, 0u8];
self.stream.read_exact(&mut length_buffer)?;
let length = u16::from_be_bytes(length_buffer);
let mut string_buffer = vec![0u8; length as usize];
self.stream.read_exact(&mut string_buffer)?;
match String::from_utf8(string_buffer) {
Ok(str) => Ok(str),
Err(_err) => Err(Error::new(ErrorKind::Other, "Invalid UTF-8 sent")),
}
}
fn write_string(&mut self, s: &str) -> Result<(), Error> {
let length = s.len();
if length > 0xffff {
return Err(Error::new(ErrorKind::Other, "String too long to write"));
}
let length_buffer = [(length >> 8) as u8, (length & 0xff) as u8];
self.stream.write_all(&length_buffer)?;
self.stream.write_all(s.as_bytes())?;
Ok(())
}
fn check_auth(&mut self, userid: &str, password: &str) -> Result<bool, Error> {
// Don't allow empty userids or passwords
//
if userid.is_empty() || password.is_empty() {
return Ok(false);
}
let mut password_file = self.options.passwords.clone();
password_file.push(&userid);
let file = File::open(password_file)?;
for line in BufReader::new(file).lines().flatten() {
if !line.starts_with('#') && line.trim() == password {
return Ok(true);
}
}
Ok(false)
}
fn communicate(&mut self) -> Result<(), Error> {
let userid = self.read_string()?;
let password = self.read_string()?;
let _service = self.read_string()?;
let _realm = self.read_string()?;
match self.check_auth(&userid, &password) {
Ok(true) => self.stream.write_all(&RESPONSE_OK)?,
Ok(false) => self.stream.write_all(&RESPONSE_NO)?,
Err(e) => {
self.stream.write_all(&RESPONSE_NO)?;
self.stream.shutdown(Shutdown::Both)?;
return Err(e);
}
}
Ok(())
}
pub fn handle_client(&mut self) {
if let Err(error) = self.communicate() {
eprint!("{}", error);
}
}
}