diff --git a/client/src/main.rs b/client/src/main.rs index f10da92..a955f12 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,7 +1,8 @@ mod options; use crate::options::OPTIONS; -use common::{read_string, Request}; +use common::io::read_string; +use common::request::Request; use std::os::unix::net::UnixStream; fn main() -> std::io::Result<()> { diff --git a/common/src/io.rs b/common/src/io.rs new file mode 100644 index 0000000..6018db7 --- /dev/null +++ b/common/src/io.rs @@ -0,0 +1,36 @@ +use std::io::{Error, ErrorKind, Read, Write}; + +/// The saslauthd protocol transmits strings as +/// 16-bit unsigned network-byte-order lengths followed +/// by the string itself. +/// +pub fn read_string(stream: &mut impl Read) -> Result { + let mut length_buffer = [0u8, 0u8]; + + stream.read_exact(&mut length_buffer)?; + let length = u16::from_be_bytes(length_buffer); + + let mut string_buffer = vec![0u8; length as usize]; + + 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")), + } +} + +pub fn write_string(stream: &mut impl Write, 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]; + + stream.write_all(&length_buffer)?; + stream.write_all(s.as_bytes())?; + + Ok(()) +} diff --git a/common/src/lib.rs b/common/src/lib.rs index e1b8af9..fd1292a 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,71 +1,2 @@ -use std::io::{Error, ErrorKind, Read, Write}; - -pub const RESPONSE_NO: [u8; 4] = [0x0, 0x2, b'N', b'O']; -pub const RESPONSE_OK: [u8; 4] = [0x0, 0x2, b'O', b'K']; - -pub struct Request { - pub userid: String, - pub password: String, - pub service: String, - pub realm: String, -} - -/// The saslauthd protocol transmits strings as -/// 16-bit unsigned network-byte-order lengths followed -/// by the string itself. -/// -pub fn read_string(stream: &mut impl Read) -> Result { - let mut length_buffer = [0u8, 0u8]; - - stream.read_exact(&mut length_buffer)?; - let length = u16::from_be_bytes(length_buffer); - - let mut string_buffer = vec![0u8; length as usize]; - - 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(stream: &mut impl Write, 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]; - - stream.write_all(&length_buffer)?; - stream.write_all(s.as_bytes())?; - - Ok(()) -} - -impl Request { - pub fn from_stream(stream: &mut impl Read) -> Result { - let userid = read_string(stream)?; - let password = read_string(stream)?; - let service = read_string(stream)?; - let realm = read_string(stream)?; - - Ok(Request { - userid, - password, - service, - realm, - }) - } - - pub fn write_to_stream(&self, stream: &mut impl Write) -> Result<(), Error> { - write_string(stream, &self.userid)?; - write_string(stream, &self.password)?; - write_string(stream, &self.service)?; - write_string(stream, &self.realm)?; - - Ok(()) - } -} +pub mod io; +pub mod request; diff --git a/common/src/request.rs b/common/src/request.rs new file mode 100644 index 0000000..d02f615 --- /dev/null +++ b/common/src/request.rs @@ -0,0 +1,43 @@ +use crate::io::{read_string, write_string}; +use std::io::{Error, Read, Write}; + +#[derive(Debug, PartialEq)] +pub struct Request { + pub userid: String, + pub password: String, + pub service: String, + pub realm: String, +} + +impl Request { + pub fn new(userid: &str, password: &str, service: &str, realm: &str) -> Self { + Request { + userid: userid.to_string(), + password: password.to_string(), + service: service.to_string(), + realm: realm.to_string(), + } + } + pub fn from_stream(stream: &mut impl Read) -> Result { + let userid = read_string(stream)?; + let password = read_string(stream)?; + let service = read_string(stream)?; + let realm = read_string(stream)?; + + Ok(Request { + userid, + password, + service, + realm, + }) + } + + pub fn write_to_stream(&self, stream: &mut impl Write) -> Result<(), Error> { + write_string(stream, &self.userid)?; + write_string(stream, &self.password)?; + write_string(stream, &self.service)?; + write_string(stream, &self.realm)?; + + Ok(()) + } +} diff --git a/server/src/handler.rs b/server/src/handler.rs index fe4f1a8..49dc409 100644 --- a/server/src/handler.rs +++ b/server/src/handler.rs @@ -1,16 +1,19 @@ use crate::repository::PasswordDirectory; -use common::{Request, RESPONSE_NO, RESPONSE_OK}; +use common::request::Request; use std::io::{Error, Write}; use std::os::unix::net::UnixStream; +pub const RESPONSE_NO: [u8; 4] = [0x0, 0x2, b'N', b'O']; +pub const RESPONSE_OK: [u8; 4] = [0x0, 0x2, b'O', b'K']; + pub struct Handler { stream: UnixStream, handler: PasswordDirectory, } impl Handler { - pub fn new(stream: UnixStream, handler: PasswordDirectory) -> Result { - Ok(Handler { stream, handler }) + pub fn new(stream: UnixStream, handler: PasswordDirectory) -> Handler { + Handler { stream, handler } } fn communicate(&mut self) -> Result<(), Error> { diff --git a/server/src/repository.rs b/server/src/repository.rs index 032854f..3cc0821 100644 --- a/server/src/repository.rs +++ b/server/src/repository.rs @@ -1,4 +1,4 @@ -use common::Request; +use common::request::Request; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; @@ -41,7 +41,7 @@ impl PasswordDirectory { #[cfg(test)] mod tests { use crate::repository::PasswordDirectory; - use common::Request; + use common::request::Request; const TEST_PATH: &str = "./test_passwords"; diff --git a/server/src/server.rs b/server/src/server.rs index b22ff5b..b937b24 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -9,12 +9,12 @@ use std::thread; pub struct Server { listener: UnixListener, - handler: PasswordDirectory, + repository: PasswordDirectory, socket_name: PathBuf, } impl Server { - pub fn new(socket_name: &str, handler: PasswordDirectory) -> Result { + pub fn new(socket_name: &str, repository: PasswordDirectory) -> Result { let socket_name = PathBuf::from(socket_name); if socket_name.exists() { @@ -28,7 +28,7 @@ impl Server { set_permissions(&socket_name, perms)?; Ok(Server { - handler, + repository, listener, socket_name, }) @@ -40,8 +40,8 @@ impl Server { match stream { Ok(stream) => { /* connection succeeded */ - let handler = self.handler.clone(); - thread::spawn(|| Handler::new(stream, handler).unwrap().handle_client()); + let repository = self.repository.clone(); + thread::spawn(|| Handler::new(stream, repository).handle_client()); } Err(_err) => { /* connection failed */ diff --git a/tests/common_io_test.rs b/tests/common_io_test.rs new file mode 100644 index 0000000..4624496 --- /dev/null +++ b/tests/common_io_test.rs @@ -0,0 +1,24 @@ +use common::io::{read_string, write_string}; +use std::io::Cursor; + +#[test] +fn it_reads_strings() { + let buffer = [0x0, 0x3, b'F', b'o', b'o']; + + assert_eq!( + read_string(&mut buffer.as_ref()).unwrap(), + "Foo".to_string() + ); +} + +#[test] +fn it_writes_strings() { + let mut buffer = Cursor::new(Vec::new()); + + write_string(&mut buffer, "Foo").unwrap(); + + // Should have written exactly 5 bytes + assert_eq!(buffer.get_ref().len(), 5usize); + + assert_eq!(&buffer.get_ref()[0..5], [0x0, 0x3, b'F', b'o', b'o']); +} diff --git a/tests/common_request_test.rs b/tests/common_request_test.rs new file mode 100644 index 0000000..42e000a --- /dev/null +++ b/tests/common_request_test.rs @@ -0,0 +1,28 @@ +use common::request::Request; +use std::io::Cursor; + +const SAMPLE: [u8; 26] = [ + 0x0, 0x3, b'F', b'o', b'o', 0x0, 0x4, b'B', b'a', b'z', b'0', 0x0, 0x5, b'x', b'x', b'x', b'y', + b'y', 0x0, 0x6, b'1', b'7', b'0', b'1', b'-', b'a', +]; + +#[test] +fn it_reads_requests() { + let request = Request::from_stream(&mut Cursor::new(SAMPLE)).unwrap(); + + assert_eq!(request, Request::new("Foo", "Baz0", "xxxyy", "1701-a")); +} + +#[test] +fn it_writes_requests() { + let request = Request::new("Foo", "Baz0", "xxxyy", "1701-a"); + + let mut buffer = Cursor::new(Vec::new()); + + request.write_to_stream(&mut buffer).unwrap(); + + // Should have written exactly 5 bytes + assert_eq!(buffer.get_ref().len(), 26usize); + + assert_eq!(&buffer.get_ref()[0..26], SAMPLE); +}