From b35a7b90949e19afca22c8cf5699bdc112ae2f6a Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Sun, 24 Mar 2019 16:33:33 +0100 Subject: Support blksize2 option (non-standard) --- src/lib.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 9299eea..ec092a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,20 @@ fn octet_to_netascii(buf: &[u8]) -> Vec { out } +fn blksize2(mut size: usize) -> usize { + if size == 0 { + return 0; + } + + let mut msb = 0; + while size > 0 { + size >>= 1; + msb += 1; + } + 1 << (msb - 1) +} + + impl Default for Tftp { fn default() -> Tftp { Tftp { @@ -309,6 +323,14 @@ impl Tftp { } _ => false, }, + "blksize2" => match val.parse() { + Ok(b) if b >= 8 && b <= 32768 => { + /* select 2^x lower or equal the requested size */ + self.options.blksize = blksize2(b); + true + } + _ => false, + }, "timeout" => match val.parse() { Ok(t) if t >= 1 => { self.options.timeout = t; @@ -663,4 +685,13 @@ mod tests { assert_eq!(octet_to_netascii(b"\r\0\r\n"), b"\r\0\0\r\0\r\n"); assert_eq!(octet_to_netascii(b""), b""); } + + #[test] + fn test_blksize2() { + assert_eq!(blksize2(16), 16); + assert_eq!(blksize2(17), 16); + assert_eq!(blksize2(15), 8); + assert_eq!(blksize2(1), 1); + assert_eq!(blksize2(0), 0); + } } -- cgit v1.2.3 From 37a011424d9f19e1801b25460d7a2db95220948e Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Sun, 24 Mar 2019 16:43:22 +0100 Subject: Support utimeout option (non-standard) --- README | 1 + src/lib.rs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src/lib.rs') diff --git a/README b/README index 44d584b..3045fcd 100644 --- a/README +++ b/README @@ -12,6 +12,7 @@ Currently supported: Non-standard options: - blksize2: block size as a power of 2 +- utimeout: timeout in microseconds Use cargo to build the binaries (output dir is target/release/): diff --git a/src/lib.rs b/src/lib.rs index ec092a4..61e7059 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,7 +39,7 @@ pub enum Mode { #[derive(Clone, Copy)] pub struct TftpOptions { blksize: usize, - timeout: u8, + timeout: Duration, tsize: u64, } @@ -53,7 +53,7 @@ pub struct Tftp { fn default_options() -> TftpOptions { TftpOptions { blksize: 512, - timeout: 3, + timeout: Duration::from_secs(3), tsize: 0, } } @@ -333,7 +333,14 @@ impl Tftp { }, "timeout" => match val.parse() { Ok(t) if t >= 1 => { - self.options.timeout = t; + self.options.timeout = Duration::from_secs(t); + true + } + _ => false, + }, + "utimeout" => match val.parse() { + Ok(t) if t >= 1 => { + self.options.timeout = Duration::from_micros(t); true } _ => false, @@ -349,7 +356,7 @@ impl Tftp { } }); - sock.set_read_timeout(Some(Duration::from_secs(u64::from(self.options.timeout))))?; + sock.set_read_timeout(Some(self.options.timeout))?; Ok(()) } -- cgit v1.2.3 From 01838bd4e8a208ef0d76181e1b5875421b7803a2 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Mon, 25 Mar 2019 00:17:11 +0100 Subject: Simplify blksize2 calculation --- src/lib.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 61e7059..d2374ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,17 +93,8 @@ fn octet_to_netascii(buf: &[u8]) -> Vec { out } -fn blksize2(mut size: usize) -> usize { - if size == 0 { - return 0; - } - - let mut msb = 0; - while size > 0 { - size >>= 1; - msb += 1; - } - 1 << (msb - 1) +fn blksize2(size: usize) -> usize { + (size + 1).next_power_of_two() >> 1 } -- cgit v1.2.3 From c790e9b6013cfe86c81f5d8068feefff4339abf7 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Mon, 25 Mar 2019 01:40:14 +0100 Subject: Simplify error handling in some places --- src/lib.rs | 20 ++++++++------------ src/tftpc.rs | 60 +++++++++++++++--------------------------------------------- src/tftpd.rs | 23 ++++++----------------- 3 files changed, 29 insertions(+), 74 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index d2374ab..f049d05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,18 +145,14 @@ impl Tftp { } fn get_tftp_str(&self, buf: &[u8]) -> Option { - let mut iter = buf.iter(); - - let len = match iter.position(|&x| x == 0) { - Some(l) => l, - None => return None, - }; - let val = match String::from_utf8(buf[0..len].to_vec()) { - Ok(v) => v, - Err(_) => return None, - }; - - Some(val) + /* make sure the null-terminator exists */ + buf.iter().find(|&x| *x == 0)?; + + /* build string from buffer */ + String::from_utf8(buf.iter() + .take_while(|&x| *x != 0) + .map(|&x| x) + .collect()).ok() } /// Read::read can possibly return less bytes than the requested buffer size, diff --git a/src/tftpc.rs b/src/tftpc.rs index 24073d2..240ef2c 100644 --- a/src/tftpc.rs +++ b/src/tftpc.rs @@ -65,35 +65,23 @@ impl Tftpc { fn wait_for_option_ack(&mut self, sock: &UdpSocket) -> Option { let mut buf = [0; 512]; - match sock.peek_from(&mut buf) { - Ok(_) => (), - Err(_) => return None, - }; + sock.peek_from(&mut buf).ok()?; let opcode = u16::from_be_bytes([buf[0], buf[1]]); if opcode != rtftp::Opcode::OACK as u16 { return None; } - let (len, remote) = match sock.recv_from(&mut buf) { - Ok(args) => args, - Err(_) => return None, - }; + let (len, remote) = sock.recv_from(&mut buf).ok()?; let mut options = self.tftp.parse_options(&buf[2..len]); - match self.tftp.init_tftp_options(&sock, &mut options) { - Ok(_) => {} - Err(_) => return None, - } + self.tftp.init_tftp_options(&sock, &mut options).ok()?; Some(remote) } fn wait_for_response(&self, sock: &UdpSocket, expected_opcode: rtftp::Opcode, expected_block: u16, expected_remote: Option) -> Result, std::io::Error> { let mut buf = [0; 4]; - let (len, remote) = match sock.peek_from(&mut buf) { - Ok(args) => args, - Err(err) => return Err(err), - }; + let (len, remote) = sock.peek_from(&mut buf)?; if let Some(rem) = expected_remote { /* verify we got a response from the same client that sent @@ -140,24 +128,14 @@ impl Tftpc { } fn handle_wrq(&mut self, sock: &UdpSocket) -> Result { - let mut file = match File::open(self.conf.filename.as_path()) { - Ok(f) => f, - Err(err) => return Err(err), - }; - let err_invalidpath = io::Error::new(io::ErrorKind::InvalidInput, "Invalid path/filename"); - let filename = match self.conf.filename.file_name() { - Some(f) => match f.to_str() { - Some(s) => s, - None => return Err(err_invalidpath), - }, - None => return Err(err_invalidpath), - }; - let metadata = match file.metadata() { - Ok(m) => m, - Err(_) => return Err(err_invalidpath), - }; + let mut file = File::open(self.conf.filename.as_path())?; + let err_invalidpath = || io::Error::new(io::ErrorKind::InvalidInput, "Invalid path/filename"); + + let filename = self.conf.filename.file_name().ok_or(err_invalidpath())? + .to_str().ok_or(err_invalidpath())?; + let metadata = file.metadata().map_err(|_| err_invalidpath())?; if !metadata.is_file() { - return Err(err_invalidpath); + return Err(err_invalidpath()); } let tsize = self.tftp.transfersize(&mut file)?; @@ -192,19 +170,11 @@ impl Tftpc { } fn handle_rrq(&mut self, sock: &UdpSocket) -> Result { - let filename = match self.conf.filename.file_name() { - Some(f) => f, - None => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid path/filename")), - }; + let err_invalidpath = || io::Error::new(io::ErrorKind::InvalidInput, "Invalid path/filename"); + let filename = self.conf.filename.file_name().ok_or(err_invalidpath())?; let outpath = env::current_dir().expect("Can't get current directory").join(filename); - let mut file = match File::create(outpath) { - Ok(f) => f, - Err(err) => return Err(err), - }; - let filename = match self.conf.filename.to_str() { - Some(f) => f, - None => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid path/filename")), - }; + let mut file = File::create(outpath)?; + let filename = self.conf.filename.to_str().ok_or(err_invalidpath())?; let buf = self.init_req(rtftp::Opcode::RRQ, filename, 0); diff --git a/src/tftpd.rs b/src/tftpd.rs index a833998..d10287b 100644 --- a/src/tftpd.rs +++ b/src/tftpd.rs @@ -64,24 +64,13 @@ impl Tftpd { fn file_allowed(&self, filename: &Path) -> Option { /* get parent to check dir where file should be read/written */ - let path = self.conf.dir.join(filename); - let path = match path.parent() { - Some(p) => p, - None => return None, - }; - let path = match path.canonicalize() { - Ok(p) => p, - Err(_) => return None, - }; - - /* get last component to append to canonicalized path */ - let filename = match filename.file_name() { - Some(f) => f, - None => return None, - }; - let path = path.join(filename); + let path = self.conf.dir.join(filename) + .parent()? + .canonicalize() + .ok()?; - match path.strip_prefix(&self.conf.dir) { + /* check last component of given filename appended to canonicalized path */ + match path.join(filename.file_name()?).strip_prefix(&self.conf.dir) { Ok(p) if p != PathBuf::new() => Some(p.to_path_buf()), _ => None, } -- cgit v1.2.3 From ab017b1e36170e4bd9e5d8e0bd6454d966259e1c Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Tue, 26 Mar 2019 01:15:58 +0100 Subject: Simplify command line argument parsing --- src/lib.rs | 18 +++++------------ src/tftpc.rs | 56 ++++++++++++++++++++++----------------------------- src/tftpd.rs | 66 ++++++++++++++++++------------------------------------------ 3 files changed, 49 insertions(+), 91 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index f049d05..64291cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,7 +151,7 @@ impl Tftp { /* build string from buffer */ String::from_utf8(buf.iter() .take_while(|&x| *x != 0) - .map(|&x| x) + .cloned() .collect()).ok() } @@ -372,26 +372,18 @@ impl Tftp { } pub fn parse_file_mode_options(&self, buf: &[u8]) -> Result<(PathBuf, String, HashMap), io::Error> { - let dataerr = io::Error::new(io::ErrorKind::InvalidData, "invalid data received"); + let dataerr = || io::Error::new(io::ErrorKind::InvalidData, "invalid data received"); let mut pos = 0; - let filename = match self.get_tftp_str(&buf[pos..]) { - Some(f) => f, - None => return Err(dataerr), - }; + let filename = self.get_tftp_str(&buf[pos..]).ok_or_else(dataerr)?; pos += filename.len() + 1; - let filename = Path::new(&filename); - - let mode = match self.get_tftp_str(&buf[pos..]) { - Some(m) => m.to_lowercase(), - None => return Err(dataerr), - }; + let mode = self.get_tftp_str(&buf[pos..]).ok_or_else(dataerr)?.to_lowercase(); pos += mode.len() + 1; let options = self.parse_options(&buf[pos..]); - Ok((filename.to_path_buf(), mode, options)) + Ok((Path::new(&filename).to_path_buf(), mode, options)) } pub fn send_error(&self, socket: &UdpSocket, code: u16, msg: &str) -> Result<(), io::Error> { diff --git a/src/tftpc.rs b/src/tftpc.rs index 240ef2c..a134293 100644 --- a/src/tftpc.rs +++ b/src/tftpc.rs @@ -131,8 +131,8 @@ impl Tftpc { let mut file = File::open(self.conf.filename.as_path())?; let err_invalidpath = || io::Error::new(io::ErrorKind::InvalidInput, "Invalid path/filename"); - let filename = self.conf.filename.file_name().ok_or(err_invalidpath())? - .to_str().ok_or(err_invalidpath())?; + let filename = self.conf.filename.file_name().ok_or_else(err_invalidpath)? + .to_str().ok_or_else(err_invalidpath)?; let metadata = file.metadata().map_err(|_| err_invalidpath())?; if !metadata.is_file() { return Err(err_invalidpath()); @@ -171,10 +171,10 @@ impl Tftpc { fn handle_rrq(&mut self, sock: &UdpSocket) -> Result { let err_invalidpath = || io::Error::new(io::ErrorKind::InvalidInput, "Invalid path/filename"); - let filename = self.conf.filename.file_name().ok_or(err_invalidpath())?; + let filename = self.conf.filename.file_name().ok_or_else(err_invalidpath)?; let outpath = env::current_dir().expect("Can't get current directory").join(filename); let mut file = File::create(outpath)?; - let filename = self.conf.filename.to_str().ok_or(err_invalidpath())?; + let filename = self.conf.filename.to_str().ok_or_else(err_invalidpath)?; let buf = self.init_req(rtftp::Opcode::RRQ, filename, 0); @@ -226,7 +226,7 @@ impl Tftpc { } } -fn usage(opts: Options, program: String, error: Option) { +fn usage(opts: &Options, program: &str, error: Option) { if let Some(err) = error { println!("{}\n", err); } @@ -234,7 +234,7 @@ fn usage(opts: Options, program: String, error: Option) { println!("{}", opts.usage(format!("RusTFTP {}\n\n{} [options] [:port]", version, program).as_str())); } -fn parse_commandline(args: &[String]) -> Result { +fn parse_commandline(args: &[String]) -> Option { let program = args[0].clone(); let mut operation = None; let mut mode = rtftp::Mode::OCTET; @@ -247,16 +247,14 @@ fn parse_commandline(args: &[String]) -> Result { opts.optopt("p", "put", "upload file to remote server", "FILE"); opts.optopt("b", "blksize", format!("negotiate a different block size (default: {})", blksize).as_ref(), "SIZE"); opts.optflag("n", "netascii","use netascii mode (instead of octet)"); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(err) => { - usage(opts, program, Some(err.to_string())); - return Err("Parsing error"); - } - }; + + let getopts_fail = |err: getopts::Fail| { usage(&opts, &program, Some(err.to_string())) }; + let conv_error = |err: std::num::ParseIntError| { usage(&opts, &program, Some(err.to_string())) }; + + let matches = opts.parse(&args[1..]).map_err(getopts_fail).ok()?; if matches.opt_present("h") || matches.free.len() != 1 { - usage(opts, program, None); - return Err("usage"); + usage(&opts, &program, None); + return None; } if let Some(f) = matches.opt_str("g") { @@ -269,8 +267,8 @@ fn parse_commandline(args: &[String]) -> Result { } if operation.is_none() || (matches.opt_present("g") && matches.opt_present("p")) { - usage(opts, program, Some("Exactly one of g (get) and p (put) required".to_string())); - return Err("get put"); + usage(&opts, &program, Some("Exactly one of g (get) and p (put) required".to_string())); + return None; } if matches.opt_present("n") { @@ -279,25 +277,19 @@ fn parse_commandline(args: &[String]) -> Result { let remote_in = matches.free[0].as_str(); let remote = match remote_in.to_socket_addrs() { - Ok(mut i) => i.next(), + Ok(i) => i, Err(_) => match (remote_in, 69).to_socket_addrs() { - Ok(mut j) => j.next(), + Ok(j) => j, Err(_) => { - usage(opts, program, Some("Failed to parse and lookup specified remote".to_string())); - return Err("lookup"); + usage(&opts, &program, Some("Failed to parse and lookup specified remote".to_string())); + return None; } }, - }; + }.next(); - blksize = match matches.opt_get_default::("b", blksize) { - Ok(b) => b, - Err(err) => { - usage(opts, program, Some(err.to_string())); - return Err("blksize"); - } - }; + blksize = matches.opt_get_default::("b", blksize).map_err(conv_error).ok()?; - Ok(Configuration { + Some(Configuration { operation: operation.unwrap(), mode, filename: filename.unwrap(), @@ -309,8 +301,8 @@ fn parse_commandline(args: &[String]) -> Result { fn main() { let args: Vec = env::args().collect(); let conf = match parse_commandline(&args) { - Ok(c) => c, - Err(_) => return, + Some(c) => c, + None => return, }; Tftpc::new(conf).start(); diff --git a/src/tftpd.rs b/src/tftpd.rs index d10287b..638c5d5 100644 --- a/src/tftpd.rs +++ b/src/tftpd.rs @@ -301,7 +301,7 @@ impl Tftpd { } } -fn usage(opts: Options, program: String, error: Option) { +fn usage(opts: &Options, program: &str, error: Option) { if let Some(err) = error { println!("{}\n", err); } @@ -309,7 +309,7 @@ fn usage(opts: Options, program: String, error: Option) { println!("{}", opts.usage(format!("RusTFTP {}\n\n{} [options]", version, program).as_str())); } -fn parse_commandline(args: &[String]) -> Result { +fn parse_commandline(args: &[String]) -> Option { let program = args[0].clone(); let mut conf: Configuration = Default::default(); let mut opts = Options::new(); @@ -321,70 +321,44 @@ fn parse_commandline(args: &[String]) -> Result { opts.optflag("r", "read-only", "allow only reading/downloading of files (RRQ)"); opts.optflag("w", "write-only", "allow only writing/uploading of files (WRQ)"); opts.optopt("t", "threads", format!("number of worker threads (default: {})", conf.threads).as_ref(), "N"); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(err) => { - usage(opts, program, Some(err.to_string())); - return Err("Parsing error"); - } - }; + + let getopts_fail = |err: getopts::Fail| { usage(&opts, &program, Some(err.to_string())) }; + let conv_error = |err: std::num::ParseIntError| { usage(&opts, &program, Some(err.to_string())) }; + + let matches = opts.parse(&args[1..]).map_err(getopts_fail).ok()?; if matches.opt_present("h") { - usage(opts, program, None); - return Err("usage"); + usage(&opts, &program, None); + return None; } - conf.port = match matches.opt_get_default("p", conf.port) { - Ok(p) => p, - Err(err) => { - usage(opts, program, Some(err.to_string())); - return Err("port"); - } - }; - conf.uid = match matches.opt_get_default("u", conf.uid) { - Ok(u) => u, - Err(err) => { - usage(opts, program, Some(err.to_string())); - return Err("uid"); - } - }; - conf.gid = match matches.opt_get_default("g", conf.gid) { - Ok(g) => g, - Err(err) => { - usage(opts, program, Some(err.to_string())); - return Err("gid"); - } - }; - conf.threads = match matches.opt_get_default("t", conf.threads) { - Ok(t) => t, - Err(err) => { - usage(opts, program, Some(err.to_string())); - return Err("threads"); - } - }; + conf.port = matches.opt_get_default("p", conf.port).map_err(conv_error).ok()?; + conf.uid = matches.opt_get_default("u", conf.uid).map_err(conv_error).ok()?; + conf.gid = matches.opt_get_default("g", conf.gid).map_err(conv_error).ok()?; + conf.threads = matches.opt_get_default("t", conf.threads).map_err(conv_error).ok()?; conf.ro = matches.opt_present("r"); conf.wo = matches.opt_present("w"); if conf.ro && conf.wo { - usage(opts, program, Some(String::from("Only one of r (read-only) and w (write-only) allowed"))); - return Err("ro and wo"); + usage(&opts, &program, Some(String::from("Only one of r (read-only) and w (write-only) allowed"))); + return None; } if matches.opt_present("d") { conf.dir = match matches.opt_str("d") { Some(d) => Path::new(&d).to_path_buf(), None => { - usage(opts, program, None); - return Err("directory"); + usage(&opts, &program, None); + return None; } }; } - Ok(conf) + Some(conf) } fn main() { let args: Vec = env::args().collect(); let conf = match parse_commandline(&args) { - Ok(c) => c, - Err(_) => return, + Some(c) => c, + None => return, }; Tftpd::new(conf).start(); -- cgit v1.2.3 From 50e6a82f0955bf5d489d311f2c9600c379006c37 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Thu, 29 Aug 2019 21:23:58 +0200 Subject: Use dyn keyword for trait objects to fix deprecation warnings --- src/lib.rs | 2 +- src/tftpd.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 64291cb..6250722 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,7 +163,7 @@ impl Tftp { /// This function will always fill the buffer completely (like expected from /// read_exact), but also works with EOF, by filling the buffer partially and /// returning the amount of bytes read. - fn read_exact(&self, reader: &mut Read, buf: &mut [u8]) -> Result { + fn read_exact(&self, reader: &mut dyn Read, buf: &mut [u8]) -> Result { let maxlen = buf.len(); let mut outbuf = Vec::with_capacity(maxlen); let mut len = 0; diff --git a/src/tftpd.rs b/src/tftpd.rs index 638c5d5..5342145 100644 --- a/src/tftpd.rs +++ b/src/tftpd.rs @@ -206,7 +206,7 @@ impl Tftpd { } } - fn drop_privs(&self, uid: u32, gid: u32) -> Result<(), Box> { + fn drop_privs(&self, uid: u32, gid: u32) -> Result<(), Box> { let root_uid = ROOT; let root_gid = Gid::from_raw(0); let unpriv_uid = Uid::from_raw(uid); -- cgit v1.2.3