diff --git a/Cargo.lock b/Cargo.lock index d0ec0d7..5956ec6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "tftpd" -version = "0.1.5" +version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index e9e22c3..593acd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tftpd" -version = "0.1.5" +version = "0.1.6" authors = ["Altuğ Bakan "] edition = "2021" description = "Multithreaded TFTP server daemon" diff --git a/README.md b/README.md index 62e5287..e079f35 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This server implements [RFC 1350](https://www.rfc-editor.org/rfc/rfc1350), The T - [RFC 2348](https://www.rfc-editor.org/rfc/rfc2348) Blocksize Option - [RFC 2349](https://www.rfc-editor.org/rfc/rfc2349) Timeout Interval Option - [RFC 2349](https://www.rfc-editor.org/rfc/rfc2349) Transfer Size Option +- [RFC 7440](https://www.rfc-editor.org/rfc/rfc7440) Windowsize Option # Security diff --git a/src/lib.rs b/src/lib.rs index 82a114d..8099d4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ //! - [RFC 2348](https://www.rfc-editor.org/rfc/rfc2348) Blocksize Option //! - [RFC 2349](https://www.rfc-editor.org/rfc/rfc2349) Timeout Interval Option //! - [RFC 2349](https://www.rfc-editor.org/rfc/rfc2349) Transfer Size Option +//! - [RFC 7440](https://www.rfc-editor.org/rfc/rfc7440) Windowsize Option //! //! # Security //! @@ -19,6 +20,7 @@ mod convert; mod message; mod packet; mod server; +mod window; mod worker; pub use config::Config; @@ -30,4 +32,5 @@ pub use packet::OptionType; pub use packet::Packet; pub use packet::TransferOption; pub use server::Server; +pub use window::Window; pub use worker::Worker; diff --git a/src/packet.rs b/src/packet.rs index 48ab7c6..8adaa8e 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,5 +1,5 @@ use crate::Convert; -use std::{error::Error, fmt}; +use std::{error::Error, fmt, str::FromStr}; /// Packet `enum` represents the valid TFTP packet types. /// @@ -127,7 +127,7 @@ impl Opcode { /// Converts a [`u16`] to a [`u8`] array with 2 elements. pub const fn as_bytes(self) -> [u8; 2] { - return (self as u16).to_be_bytes(); + (self as u16).to_be_bytes() } } @@ -199,14 +199,18 @@ impl OptionType { OptionType::Timeout => "timeout", } } +} + +impl FromStr for OptionType { + type Err = &'static str; /// Converts a [`str`] to an [`OptionType`]. - pub fn from_str(value: &str) -> Result { + fn from_str(value: &str) -> Result { match value { "blksize" => Ok(OptionType::BlockSize), "tsize" => Ok(OptionType::TransferSize), "timeout" => Ok(OptionType::Timeout), - _ => Err("Invalid option type".into()), + _ => Err("Invalid option type"), } } } @@ -263,7 +267,7 @@ impl ErrorCode { /// Converts an [`ErrorCode`] to a [`u8`] array with 2 elements. pub fn as_bytes(self) -> [u8; 2] { - return (self as u16).to_be_bytes(); + (self as u16).to_be_bytes() } } @@ -360,7 +364,7 @@ fn serialize_error(code: &ErrorCode, msg: &String) -> Vec { [ &Opcode::Error.as_bytes()[..], &code.as_bytes()[..], - &msg.as_bytes()[..], + msg.as_bytes(), &[0x00], ] .concat() @@ -384,9 +388,9 @@ mod tests { fn parses_read_request() { let buf = [ &Opcode::Rrq.as_bytes()[..], - &"test.png".as_bytes(), + ("test.png".as_bytes()), &[0x00], - &"octet".as_bytes(), + ("octet".as_bytes()), &[0x00], ] .concat(); @@ -409,17 +413,17 @@ mod tests { fn parses_read_request_with_options() { let buf = [ &Opcode::Rrq.as_bytes()[..], - &"test.png".as_bytes(), + ("test.png".as_bytes()), &[0x00], - &"octet".as_bytes(), + ("octet".as_bytes()), &[0x00], - &OptionType::TransferSize.as_str().as_bytes(), + (OptionType::TransferSize.as_str().as_bytes()), &[0x00], - &"0".as_bytes(), + ("0".as_bytes()), &[0x00], - &OptionType::Timeout.as_str().as_bytes(), + (OptionType::Timeout.as_str().as_bytes()), &[0x00], - &"5".as_bytes(), + ("5".as_bytes()), &[0x00], ] .concat(); @@ -456,9 +460,9 @@ mod tests { fn parses_write_request() { let buf = [ &Opcode::Wrq.as_bytes()[..], - &"test.png".as_bytes(), + ("test.png".as_bytes()), &[0x00], - &"octet".as_bytes(), + ("octet".as_bytes()), &[0x00], ] .concat(); @@ -481,17 +485,17 @@ mod tests { fn parses_write_request_with_options() { let buf = [ &Opcode::Wrq.as_bytes()[..], - &"test.png".as_bytes(), + ("test.png".as_bytes()), &[0x00], - &"octet".as_bytes(), + ("octet".as_bytes()), &[0x00], - &OptionType::TransferSize.as_str().as_bytes(), + (OptionType::TransferSize.as_str().as_bytes()), &[0x00], - &"12341234".as_bytes(), + ("12341234".as_bytes()), &[0x00], - &OptionType::BlockSize.as_str().as_bytes(), + (OptionType::BlockSize.as_str().as_bytes()), &[0x00], - &"1024".as_bytes(), + ("1024".as_bytes()), &[0x00], ] .concat(); diff --git a/src/server.rs b/src/server.rs index dcbb4a1..e3cb6d4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,7 +2,7 @@ use crate::{Config, Message, Worker}; use crate::{ErrorCode, Packet, TransferOption}; use std::error::Error; use std::net::{SocketAddr, UdpSocket}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; /// Server `struct` is used for handling incoming TFTP requests. /// @@ -79,11 +79,11 @@ impl Server { fn handle_rrq( &self, filename: String, - options: &mut Vec, + options: &mut [TransferOption], to: &SocketAddr, ) -> Result<(), Box> { - let file_path = &self.directory.join(&filename); - match check_file_exists(&file_path, &self.directory) { + let file_path = &self.directory.join(filename); + match check_file_exists(file_path, &self.directory) { ErrorCode::FileNotFound => Message::send_error_to( &self.socket, to, @@ -96,12 +96,15 @@ impl Server { ErrorCode::AccessViolation, "file access violation", ), - ErrorCode::FileExists => Ok(Worker::send( - self.socket.local_addr()?, - *to, - file_path.to_path_buf(), - options.to_vec(), - )), + ErrorCode::FileExists => { + Worker::send( + self.socket.local_addr()?, + *to, + file_path.to_path_buf(), + options.to_vec(), + ); + Ok(()) + } _ => Err("unexpected error code when checking file".into()), } } @@ -109,11 +112,11 @@ impl Server { fn handle_wrq( &self, filename: String, - options: &mut Vec, + options: &mut [TransferOption], to: &SocketAddr, ) -> Result<(), Box> { - let file_path = &self.directory.join(&filename); - match check_file_exists(&file_path, &self.directory) { + let file_path = &self.directory.join(filename); + match check_file_exists(file_path, &self.directory) { ErrorCode::FileExists => Message::send_error_to( &self.socket, to, @@ -126,18 +129,21 @@ impl Server { ErrorCode::AccessViolation, "file access violation", ), - ErrorCode::FileNotFound => Ok(Worker::receive( - self.socket.local_addr()?, - *to, - file_path.to_path_buf(), - options.to_vec(), - )), + ErrorCode::FileNotFound => { + Worker::receive( + self.socket.local_addr()?, + *to, + file_path.to_path_buf(), + options.to_vec(), + ); + Ok(()) + } _ => Err("unexpected error code when checking file".into()), } } } -fn check_file_exists(file: &PathBuf, directory: &PathBuf) -> ErrorCode { +fn check_file_exists(file: &Path, directory: &PathBuf) -> ErrorCode { if !validate_file_path(file, directory) { return ErrorCode::AccessViolation; } @@ -149,7 +155,7 @@ fn check_file_exists(file: &PathBuf, directory: &PathBuf) -> ErrorCode { ErrorCode::FileExists } -fn validate_file_path(file: &PathBuf, directory: &PathBuf) -> bool { +fn validate_file_path(file: &Path, directory: &PathBuf) -> bool { !file.to_str().unwrap().contains("..") && file.ancestors().any(|a| a == directory) } diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..2d203d4 --- /dev/null +++ b/src/window.rs @@ -0,0 +1,43 @@ +use std::{collections::VecDeque, error::Error, fs::File, io::Read}; + +pub struct Window { + elements: VecDeque>, + size: usize, + chunk_size: usize, + file: File, +} + +impl Window { + pub fn new(size: usize, chunk_size: usize, file: File) -> Window { + Window { + elements: VecDeque::new(), + size, + chunk_size, + file, + } + } + + pub fn fill(&mut self) -> Result<(), Box> { + for _ in self.elements.len()..self.size { + let mut chunk = vec![0; self.chunk_size]; + let size = self.file.read(&mut chunk)?; + self.elements.push_back(chunk); + + if size != self.chunk_size { + break; + } + } + + Ok(()) + } + + pub fn remove(&mut self, amount: usize) -> Result<(), Box> { + if amount > self.elements.len() { + return Err("amount cannot be larger than size".into()); + } + + drop(self.elements.drain(0..amount)); + + Ok(()) + } +} diff --git a/src/worker.rs b/src/worker.rs index 4e4f730..62ce3d8 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -180,7 +180,7 @@ fn receive_file( }) => { if received_block_number == block_number.wrapping_add(1) { block_number = received_block_number; - file.write(&data)?; + file.write_all(&data)?; size = data.len(); break; } @@ -216,7 +216,7 @@ fn accept_request( options: &Vec, work_type: &WorkType, ) -> Result<(), Box> { - if options.len() > 0 { + if !options.is_empty() { Message::send_oack(socket, options.to_vec())?; } else if *work_type == WorkType::Receive { Message::send_ack(socket, 0)? @@ -226,13 +226,9 @@ fn accept_request( } fn check_response(socket: &UdpSocket) -> Result<(), Box> { - if let Packet::Ack(received_block_number) = Message::recv(&socket)? { + if let Packet::Ack(received_block_number) = Message::recv(socket)? { if received_block_number != 0 { - Message::send_error( - &socket, - ErrorCode::IllegalOperation, - "invalid oack response", - )?; + Message::send_error(socket, ErrorCode::IllegalOperation, "invalid oack response")?; } }