From 110876b09ba20673d6984589bdb2778a06916530 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Sun, 24 Mar 2019 16:11:14 +0100 Subject: chroot to destination directory when having sufficient permissions --- src/tftpd.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/tftpd.rs b/src/tftpd.rs index c45f6e1..a833998 100644 --- a/src/tftpd.rs +++ b/src/tftpd.rs @@ -13,7 +13,7 @@ use std::path::{Path, PathBuf}; use std::time::Duration; extern crate nix; -use nix::unistd::{setresgid, setresuid, Gid, Uid}; +use nix::unistd::{chroot, setresgid, setresuid, Gid, Uid, ROOT}; extern crate getopts; use getopts::Options; @@ -218,7 +218,7 @@ impl Tftpd { } fn drop_privs(&self, uid: u32, gid: u32) -> Result<(), Box> { - let root_uid = Uid::from_raw(0); + let root_uid = ROOT; let root_gid = Gid::from_raw(0); let unpriv_uid = Uid::from_raw(uid); let unpriv_gid = Gid::from_raw(gid); @@ -243,6 +243,22 @@ impl Tftpd { Ok(()) } + fn chroot_destdir(&mut self) -> Result<(), nix::Error> { + /* chroot will only succeed if we have required permissions; + either running as root or having CAP_SYS_CHROOT. + propagate error only if chroot should have succeeded. */ + match chroot(&self.conf.dir) { + Ok(_) => { + /* configured dir is now new root directory */ + self.conf.dir = PathBuf::from("/"); + Ok(()) + }, + Err(err) if err == nix::Error::from_errno(nix::errno::Errno::EPERM) => Ok(()), + Err(err) if Uid::effective() == ROOT => Err(err), + Err(_) => Ok(()), + } + } + pub fn start(&mut self) { let socket = match UdpSocket::bind(format!("[::]:{}", self.conf.port)) { Ok(s) => s, @@ -251,6 +267,13 @@ impl Tftpd { return; } }; + match self.chroot_destdir() { + Ok(_) => {}, + Err(err) => { + eprintln!("Changing root directory failed ({}).", err); + return; + } + } match self.drop_privs(self.conf.uid, self.conf.gid) { Ok(_) => (), Err(err) => { @@ -262,7 +285,7 @@ impl Tftpd { match env::set_current_dir(&self.conf.dir) { Ok(_) => (), Err(err) => { - eprintln!("Changing directory to {} failed ({}).", &self.conf.dir.display(), err); + eprintln!("Changing directory failed ({}).", err); return; } } -- cgit v1.2.3 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) --- README | 3 +++ src/lib.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/README b/README index 08b08c7..44d584b 100644 --- a/README +++ b/README @@ -10,6 +10,9 @@ Currently supported: - RFC 2348 (Blocksize Option) - RFC 2349 (Timeout Interval and Transfer Size Options) +Non-standard options: +- blksize2: block size as a power of 2 + Use cargo to build the binaries (output dir is target/release/): $ cargo build --release 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(-) 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(-) 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(-) 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(-) 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(-) 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 From 16c6dd8f7116271508974d8acf04da1f1bf0afc9 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Thu, 29 Aug 2019 21:48:44 +0200 Subject: Drop -d parameter and allow appending directory at end of command line --- README | 4 +--- src/tftpd.rs | 13 +++---------- test.sh | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/README b/README index 3045fcd..e6a5f44 100644 --- a/README +++ b/README @@ -39,12 +39,10 @@ Server: $ ./rtftpd --help RusTFTP - ./rtftpd [options] + ./rtftpd [options] [directory] Options: -h, --help display usage information - -d, --directory DIRECTORY - directory to serve (default: current directory) -p, --port PORT port to listen on (default: 69) -u, --uid UID user id to run as (default: 65534) -g, --gid GID group id to run as (default: 65534) diff --git a/src/tftpd.rs b/src/tftpd.rs index 5342145..102063d 100644 --- a/src/tftpd.rs +++ b/src/tftpd.rs @@ -306,7 +306,7 @@ fn usage(opts: &Options, program: &str, error: Option) { println!("{}\n", err); } let version = rtftp::VERSION.unwrap_or(""); - println!("{}", opts.usage(format!("RusTFTP {}\n\n{} [options]", version, program).as_str())); + println!("{}", opts.usage(format!("RusTFTP {}\n\n{} [options] [directory]", version, program).as_str())); } fn parse_commandline(args: &[String]) -> Option { @@ -314,7 +314,6 @@ fn parse_commandline(args: &[String]) -> Option { let mut conf: Configuration = Default::default(); let mut opts = Options::new(); opts.optflag("h", "help", "display usage information"); - opts.optopt("d", "directory", "directory to serve (default: current directory)", "DIRECTORY"); opts.optopt("p", "port", format!("port to listen on (default: {})", conf.port).as_ref(), "PORT"); opts.optopt("u", "uid", format!("user id to run as (default: {})", conf.uid).as_ref(), "UID"); opts.optopt("g", "gid", format!("group id to run as (default: {})", conf.gid).as_ref(), "GID"); @@ -341,14 +340,8 @@ fn parse_commandline(args: &[String]) -> Option { 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 None; - } - }; + if matches.free.len() > 0 { + conf.dir = Path::new(&matches.free[0]).to_path_buf(); } Some(conf) diff --git a/test.sh b/test.sh index 61fa6d0..7ee1800 100755 --- a/test.sh +++ b/test.sh @@ -52,7 +52,7 @@ tftpc() { } rtftpd() { - $SSD --background --exec "$RTFTPD" --start -- -p $PORT -d "$SERVERDIR" 1>/dev/null + $SSD --background --exec "$RTFTPD" --start -- -p $PORT "$SERVERDIR" 1>/dev/null } rtftpc() { -- cgit v1.2.3 From b8d25fdb7bf38fcfb6ae65820e59fc157cf028c7 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Sun, 15 Sep 2019 14:37:31 +0200 Subject: Check for minimum length of request --- src/tftpd.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tftpd.rs b/src/tftpd.rs index 102063d..6513367 100644 --- a/src/tftpd.rs +++ b/src/tftpd.rs @@ -181,6 +181,11 @@ impl Tftpd { socket.set_read_timeout(Some(Duration::from_secs(5)))?; socket.connect(cl)?; + if buf.len() < 2 { + self.tftp.send_error(&socket, 0, "Invalid request length")?; + return Err(io::Error::new(io::ErrorKind::Other, "invalid request length")); + } + match u16::from_be_bytes([buf[0], buf[1]]) { // opcode o if o == rtftp::Opcode::RRQ as u16 => { if self.conf.wo { -- cgit v1.2.3 From 8afc2bda04ffe147c591fb8e5517572037289695 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Sat, 5 Oct 2019 15:40:44 +0200 Subject: Add changelog --- CHANGES | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 CHANGES diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..f21fa28 --- /dev/null +++ b/CHANGES @@ -0,0 +1,14 @@ +1.1.0 (2019-10-05) + * Server: + - chroot to destination directory if permissions are sufficient + - drop -d parameter, but allow directory at the end of the command line + * Support for some non-standard options: + - blksize2 + - utimeout + +1.0.0 (2019-03-12) + * Initial release + * RFC 1350 (TFTP revision 2) + * RFC 2347 (Option Extension) + * RFC 2348 (Blocksize Option) + * RFC 2349 (Timeout Interval and Transfer Size Options) -- cgit v1.2.3 From 24f2968663c8fe5551fb0c2269ea1ae5975518b7 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Sat, 5 Oct 2019 15:41:54 +0200 Subject: Update dependencies to versions in Debian unstable --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eb4bc6a..534e2bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ lto = true panic = 'abort' [dependencies] -nix = "0.13.0" -getopts = "0.2.18" +nix = "0.15.0" +getopts = "0.2.19" threadpool = "1.7.1" [[bin]] -- cgit v1.2.3 From 8b9882c5337cc40f0d2a4e056d937f449c06a500 Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Sat, 5 Oct 2019 15:43:08 +0200 Subject: Bump version to 1.1.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 534e2bf..a1adf0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rtftp" -version = "1.0.0" +version = "1.1.0" authors = ["Reiner Herrmann "] edition = "2018" license = "GPL-3.0-or-later" -- cgit v1.2.3