diff options
| author | Reiner Herrmann <reiner@reiner-h.de> | 2019-02-27 18:42:28 +0100 |
|---|---|---|
| committer | Reiner Herrmann <reiner@reiner-h.de> | 2019-02-27 18:49:15 +0100 |
| commit | 7cf4ef5de82cf01aaeb443b35d4794e175bd3dc2 (patch) | |
| tree | f32b1826c1a01aeb30c20f9c23e52b4d3f0701d6 /src | |
| parent | 72448ffc1fad9a3dc3b4b018f98fc478a4b37321 (diff) | |
Move generic TFTP functionality into separate module and server parts into struct
Diffstat (limited to 'src')
| -rw-r--r-- | src/tftp.rs | 294 | ||||
| -rw-r--r-- | src/tftpd.rs | 574 |
2 files changed, 457 insertions, 411 deletions
diff --git a/src/tftp.rs b/src/tftp.rs new file mode 100644 index 0000000..67dcd1a --- /dev/null +++ b/src/tftp.rs @@ -0,0 +1,294 @@ +/* + * Copyright 2019 Reiner Herrmann <reiner@reiner-h.de> + * License: GPL-3+ + */ + +use std::net::UdpSocket; +use std::fs::OpenOptions; +use std::fs::File; +use std::collections::HashMap; +use std::path::{Path,PathBuf}; +use std::io; +use std::io::prelude::*; + +pub struct TftpOptions { + +} + +pub struct Tftp { +} + +impl Tftp { + pub fn new() -> Tftp { + Tftp{} + } + + fn get_tftp_str(&self, buf: &[u8]) -> Option<(String, usize)> { + 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, + }; + + return Some((val, len)); + } + + fn wait_for_ack(&self, sock: &UdpSocket, expected_block: u16) -> Result<bool, io::Error> { + let mut buf = [0; 4]; + match sock.recv(&mut buf) { + Ok(_) => (), + Err(ref error) if [io::ErrorKind::WouldBlock, io::ErrorKind::TimedOut].contains(&error.kind()) => { + return Ok(false); + } + Err(err) => return Err(err), + }; + + let opcode = u16::from_be_bytes([buf[0], buf[1]]); + let block_nr = u16::from_be_bytes([buf[2], buf[3]]); + + if opcode == 4 && block_nr == expected_block { + return Ok(true) + } + + Ok(false) + } + + fn ack_options(&self, sock: &UdpSocket, options: &HashMap<String, String>, ackwait: bool) -> Result<(), io::Error> { + if options.is_empty() { + return Ok(()) + } + + let mut buf = Vec::with_capacity(512); + buf.extend([0x00, 0x06].iter()); // opcode + + for (key, val) in options { + buf.extend(key.bytes()); + buf.push(0x00); + buf.extend(val.bytes()); + buf.push(0x00); + } + + for _ in 1..5 { + sock.send(&buf)?; + if !ackwait { + return Ok(()); + } + match self.wait_for_ack(&sock, 0) { + Ok(true) => return Ok(()), + Ok(false) => continue, + Err(e) => return Err(e), + }; + } + + Err(io::Error::new(io::ErrorKind::TimedOut, "ack timeout")) + } + + pub fn init_tftp_options(&self, sock: &UdpSocket, options: &mut HashMap<String, String>, ackwait: bool) -> Result<TftpOptions, io::Error> { + let tftpopts = TftpOptions {}; + + options.retain(|key, _val| { + match key.as_str() { + "placeholder_option" => { + true + } + _ => false + } + }); + + self.ack_options(&sock, &options, ackwait)?; + + return Ok(tftpopts); + } + + fn parse_options(&self, buf: &[u8]) -> HashMap<String, String> { + let mut options = HashMap::new(); + + let mut pos = 0; + loop { + let (key, len) = match self.get_tftp_str(&buf[pos ..]) { + Some(args) => args, + None => break, + }; + pos += len + 1; + + let (val, len) = match self.get_tftp_str(&buf[pos ..]) { + Some(args) => args, + None => break, + }; + pos += len + 1; + + options.insert(key, val); + } + + return options; + } + + pub fn parse_file_mode_options(&self, buf: &[u8]) -> Result<(PathBuf, String, HashMap<String, String>), io::Error> { + let dataerr = io::Error::new(io::ErrorKind::InvalidData, "invalid data received"); + + let mut pos = 0; + let (filename, len) = match self.get_tftp_str(&buf[pos ..]) { + Some(args) => args, + None => return Err(dataerr), + }; + pos += len + 1; + + let filename = Path::new(&filename); + + let (mode, len) = match self.get_tftp_str(&buf[pos ..]) { + Some(args) => args, + None => return Err(dataerr), + }; + pos += len + 1; + + let options = self.parse_options(&buf[pos ..]); + + Ok((filename.to_path_buf(), mode, options)) + } + + pub fn send_error(&self, socket: &UdpSocket, code: u16, msg: &str) -> Result<(), io::Error> { + let mut buf = vec![0x00, 0x05]; // opcode + buf.extend(code.to_be_bytes().iter()); + buf.extend(msg.as_bytes()); + + socket.send(&buf)?; + Ok(()) + } + + pub fn send_ack(&self, sock: &UdpSocket, block_nr: u16) -> Result<(), io::Error> { + let mut buf = vec![0x00, 0x04]; // opcode + buf.extend(block_nr.to_be_bytes().iter()); + + sock.send(&buf)?; + + Ok(()) + } + + pub fn send_file(&self, socket: &UdpSocket, path: &Path) -> Result<(), io::Error> { + let mut file = match File::open(path) { + Ok(f) => f, + Err(ref error) if error.kind() == io::ErrorKind::NotFound => { + self.send_error(&socket, 1, "File not found")?; + return Err(io::Error::new(io::ErrorKind::NotFound, "file not found")); + }, + Err(_) => { + self.send_error(&socket, 2, "Permission denied")?; + return Err(io::Error::new(io::ErrorKind::PermissionDenied, "permission denied")); + } + }; + if !file.metadata()?.is_file() { + self.send_error(&socket, 1, "File not found")?; + return Err(io::Error::new(io::ErrorKind::NotFound, "file not found")); + } + + let mut block_nr: u16 = 1; + + loop { + let mut filebuf = [0; 512]; + let len = match file.read(&mut filebuf) { + Ok(n) => n, + Err(ref error) if error.kind() == io::ErrorKind::Interrupted => continue, /* retry */ + Err(err) => { + self.send_error(&socket, 0, "File reading error")?; + return Err(err); + } + }; + + let mut sendbuf = Vec::with_capacity(4 + len); + sendbuf.extend([0x00, 0x03].iter()); // opcode + sendbuf.extend(block_nr.to_be_bytes().iter()); + sendbuf.extend(filebuf[0..len].iter()); + + let mut acked = false; + for _ in 1..5 { + /* try a couple of times to send data, in case of timeouts + or re-ack of previous data */ + socket.send(&sendbuf)?; + match self.wait_for_ack(&socket, block_nr) { + Ok(true) => { + acked = true; + break; + }, + Ok(false) => continue, + Err(e) => return Err(e), + }; + } + if !acked { + return Err(io::Error::new(io::ErrorKind::TimedOut, "ack timeout")) + } + + if len < 512 { + /* this was the last block */ + break; + } + + /* increment with rollover on overflow */ + block_nr = block_nr.wrapping_add(1); + } + Ok(()) + } + + pub fn recv_file(&self, sock: &UdpSocket, path: &PathBuf) -> Result<(), io::Error> { + let mut file = match OpenOptions::new().write(true).create_new(true).open(path) { + Ok(f) => f, + Err(ref err) if err.kind() == io::ErrorKind::AlreadyExists => { + return Err(io::Error::new(err.kind(), "already exists")); + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::PermissionDenied, "permission denied")), + }; + + let mut block_nr = 0; + + loop { + let mut buf = [0; 1024]; + let mut len = 0; + + for _ in 1..5 { + self.send_ack(&sock, block_nr)?; + len = match sock.recv(&mut buf) { + Ok(n) => n, + Err(ref error) if [io::ErrorKind::WouldBlock, io::ErrorKind::TimedOut].contains(&error.kind()) => { + /* re-ack and try to recv again */ + continue; + } + Err(err) => return Err(err), + }; + break; + } + if len > 516 || len < 4 { + /* max size: 2 + 2 + 512 */ + return Err(io::Error::new(io::ErrorKind::InvalidInput, "unexpected size")); + } + + let _opcode = match u16::from_be_bytes([buf[0], buf[1]]) { + 3 /* DATA */ => (), + _ => return Err(io::Error::new(io::ErrorKind::Other, "unexpected opcode")), + }; + let nr = u16::from_be_bytes([buf[2], buf[3]]); + if nr != block_nr.wrapping_add(1) { + /* already received or packets were missed, re-acknowledge */ + continue; + } + block_nr = nr; + + let databuf = &buf[4..len]; + file.write_all(databuf)?; + + if len < 516 { + break; + } + } + + file.flush()?; + + self.send_ack(&sock, block_nr)?; + + Ok(()) + } + +} diff --git a/src/tftpd.rs b/src/tftpd.rs index ce773a3..afb8205 100644 --- a/src/tftpd.rs +++ b/src/tftpd.rs @@ -2,15 +2,12 @@ * Copyright 2019 Reiner Herrmann <reiner@reiner-h.de> * License: GPL-3+ */ + use std::net::{SocketAddr,UdpSocket}; -use std::fs::OpenOptions; -use std::fs::File; use std::path::{Path,PathBuf}; use std::error::Error; -use std::collections::HashMap; use std::env; use std::io; -use std::io::prelude::*; use std::time::Duration; extern crate nix; @@ -19,427 +16,220 @@ use nix::unistd::{Gid,Uid,setresgid,setresuid}; extern crate getopts; use getopts::Options; +mod tftp; + struct Configuration { - port: u16, - uid: u32, - gid: u32, - ro: bool, - wo: bool, - dir: PathBuf, + pub port: u16, + pub uid: u32, + pub gid: u32, + pub ro: bool, + pub wo: bool, + pub dir: PathBuf, } -struct TftpOptions { - +struct Tftpd { + tftp: tftp::Tftp, + conf: Configuration, } -fn wait_for_ack(sock: &UdpSocket, expected_block: u16) -> Result<bool, io::Error> { - let mut buf = [0; 4]; - match sock.recv(&mut buf) { - Ok(_) => (), - Err(ref error) if [io::ErrorKind::WouldBlock, io::ErrorKind::TimedOut].contains(&error.kind()) => { - return Ok(false); +impl Tftpd { + pub fn new(conf: Configuration) -> Tftpd { + Tftpd{ + tftp: tftp::Tftp::new(), + conf: conf, } - Err(err) => return Err(err), - }; - - let opcode = u16::from_be_bytes([buf[0], buf[1]]); - let block_nr = u16::from_be_bytes([buf[2], buf[3]]); - - if opcode == 4 && block_nr == expected_block { - return Ok(true) - } - - Ok(false) -} - -fn ack_options(sock: &UdpSocket, options: &HashMap<String, String>, waitack: bool) -> Result<(), io::Error> { - if options.is_empty() { - return Ok(()) } - let mut buf = Vec::with_capacity(512); - buf.extend([0x00, 0x06].iter()); // opcode - - for (key, val) in options { - buf.extend(key.bytes()); - buf.push(0x00); - buf.extend(val.bytes()); - buf.push(0x00); - } - - for _ in 1..5 { - sock.send(&buf)?; - if !waitack { - return Ok(()); - } - match wait_for_ack(&sock, 0) { - Ok(true) => return Ok(()), - Ok(false) => continue, - Err(e) => return Err(e), + fn file_allowed(&self, filename: &Path) -> Option<PathBuf> { + /* get parent to check dir where file should be read/written */ + let path = Path::new(".").join(filename); + let path = match path.parent() { + Some(p) => p, + None => return None, + }; + let path = match path.canonicalize() { + Ok(p) => p, + Err(_) => return None, }; - } - - Err(io::Error::new(io::ErrorKind::TimedOut, "ack timeout")) -} - -fn init_tftp_options(sock: &UdpSocket, options: &mut HashMap<String, String>, waitack: bool) -> Result<TftpOptions, io::Error> { - let tftpopts = TftpOptions {}; - - options.retain(|key, _val| { - match key.as_str() { - "placeholder_option" => { - true - } - _ => false - } - }); - - ack_options(&sock, &options, waitack)?; - - return Ok(tftpopts); -} - -fn get_tftp_str(buf: &[u8]) -> Option<(String, usize)> { - 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, - }; - - return Some((val, len)); -} - -fn parse_options(buf: &[u8]) -> HashMap<String, String> { - let mut options = HashMap::new(); - let mut pos = 0; - loop { - let (key, len) = match get_tftp_str(&buf[pos ..]) { - Some(args) => args, - None => break, + /* get last component to append to canonicalized path */ + let filename = match filename.file_name() { + Some(f) => f, + None => return None, }; - pos += len + 1; + let path = path.join(filename); - let (val, len) = match get_tftp_str(&buf[pos ..]) { - Some(args) => args, - None => break, + let cwd = match env::current_dir() { + Ok(p) => p, + Err(_) => return None, }; - pos += len + 1; - options.insert(key, val); + match path.strip_prefix(cwd) { + Ok(p) => Some(p.to_path_buf()), + Err(_) => return None, + } } - return options; -} - -fn parse_file_mode_options(buf: &[u8]) -> Result<(PathBuf, String, HashMap<String, String>), io::Error> { - let dataerr = io::Error::new(io::ErrorKind::InvalidData, "invalid data received"); - let mut pos = 0; - let (filename, len) = match get_tftp_str(&buf[pos ..]) { - Some(args) => args, - None => return Err(dataerr), - }; - pos += len + 1; - - let filename = Path::new(&filename); - - let (mode, len) = match get_tftp_str(&buf[pos ..]) { - Some(args) => args, - None => return Err(dataerr), - }; - pos += len + 1; - - let options = parse_options(&buf[pos ..]); + fn handle_wrq(&self, socket: &UdpSocket, cl: &SocketAddr, buf: &[u8]) -> Result<(), io::Error> { + let (filename, mode, mut options) = self.tftp.parse_file_mode_options(buf)?; + let _opts = self.tftp.init_tftp_options(&socket, &mut options, false); - Ok((filename.to_path_buf(), mode, options)) -} - -fn send_file(socket: &UdpSocket, path: &Path) -> Result<(), io::Error> { - let mut file = match File::open(path) { - Ok(f) => f, - Err(ref error) if error.kind() == io::ErrorKind::NotFound => { - send_error(&socket, 1, "File not found")?; - return Err(io::Error::new(io::ErrorKind::NotFound, "file not found")); - }, - Err(_) => { - send_error(&socket, 2, "Permission denied")?; - return Err(io::Error::new(io::ErrorKind::PermissionDenied, "permission denied")); + match mode.as_ref() { + "octet" => (), + _ => { + self.tftp.send_error(&socket, 0, "Unsupported mode")?; + return Err(io::Error::new(io::ErrorKind::Other, "unsupported mode")); + } } - }; - if !file.metadata()?.is_file() { - send_error(&socket, 1, "File not found")?; - return Err(io::Error::new(io::ErrorKind::NotFound, "file not found")); - } - - let mut block_nr: u16 = 1; - loop { - let mut filebuf = [0; 512]; - let len = match file.read(&mut filebuf) { - Ok(n) => n, - Err(ref error) if error.kind() == io::ErrorKind::Interrupted => continue, /* retry */ - Err(err) => { - send_error(&socket, 0, "File reading error")?; - return Err(err); + let path = match self.file_allowed(&filename) { + Some(p) => p, + None => { + println!("Sending {} to {} failed (permission check failed).", filename.display(), cl); + self.tftp.send_error(&socket, 2, "Permission denied")?; + return Err(io::Error::new(io::ErrorKind::PermissionDenied, "permission denied")); } }; - let mut sendbuf = Vec::with_capacity(4 + len); - sendbuf.extend([0x00, 0x03].iter()); // opcode - sendbuf.extend(block_nr.to_be_bytes().iter()); - sendbuf.extend(filebuf[0..len].iter()); - - for _ in 1..5 { - /* try a couple of times to send data, in case of timeouts - or re-ack of previous data */ - socket.send(&sendbuf)?; - match wait_for_ack(&socket, block_nr) { - Ok(true) => break, - Ok(false) => continue, - Err(e) => return Err(e), - }; - } - - if len < 512 { - /* this was the last block */ - break; + match self.tftp.recv_file(&socket, &path) { + Ok(_) => println!("Received {} from {}.", path.display(), cl), + Err(ref err) => { + println!("Receiving {} from {} failed ({}).", path.display(), cl, err.to_string()); + match err.kind() { + io::ErrorKind::PermissionDenied => self.tftp.send_error(&socket, 2, "Permission denied")?, + io::ErrorKind::AlreadyExists => self.tftp.send_error(&socket, 6, "File already exists")?, + _ => self.tftp.send_error(&socket, 0, "Receiving error")?, + } + return Err(io::Error::new(err.kind(), err.to_string())); + } } - - /* increment with rollover on overflow */ - block_nr = block_nr.wrapping_add(1); + Ok(()) } - Ok(()) -} - -fn recv_file(sock: &UdpSocket, path: &PathBuf) -> Result<(), io::Error> { - let mut file = match OpenOptions::new().write(true).create_new(true).open(path) { - Ok(f) => f, - Err(ref err) if err.kind() == io::ErrorKind::AlreadyExists => { - return Err(io::Error::new(err.kind(), "already exists")); - }, - Err(_) => return Err(io::Error::new(io::ErrorKind::PermissionDenied, "permission denied")), - }; - - let mut block_nr = 0; - loop { - let mut buf = [0; 1024]; - let mut len = 0; + fn handle_rrq(&self, socket: &UdpSocket, cl: &SocketAddr, buf: &[u8]) -> Result<(), io::Error> { + let (filename, mode, mut options) = self.tftp.parse_file_mode_options(buf)?; + let _opts = self.tftp.init_tftp_options(&socket, &mut options, true); - for _ in 1..5 { - send_ack(&sock, block_nr)?; - len = match sock.recv(&mut buf) { - Ok(n) => n, - Err(ref error) if [io::ErrorKind::WouldBlock, io::ErrorKind::TimedOut].contains(&error.kind()) => { - /* re-ack and try to recv again */ - continue; - } - Err(err) => return Err(err), - }; - break; - } - if len > 516 || len < 4 { - /* max size: 2 + 2 + 512 */ - return Err(io::Error::new(io::ErrorKind::InvalidInput, "unexpected size")); + match mode.as_ref() { + "octet" => (), + _ => { + self.tftp.send_error(&socket, 0, "Unsupported mode")?; + return Err(io::Error::new(io::ErrorKind::Other, "unsupported mode")); + } } - let _opcode = match u16::from_be_bytes([buf[0], buf[1]]) { - 3 /* DATA */ => (), - _ => return Err(io::Error::new(io::ErrorKind::Other, "unexpected opcode")), + let path = match self.file_allowed(&filename) { + Some(p) => p, + None => { + println!("Sending {} to {} failed (permission check failed).", filename.display(), cl); + self.tftp.send_error(&socket, 2, "Permission denied")?; + return Err(io::Error::new(io::ErrorKind::PermissionDenied, "permission denied")); + } }; - let nr = u16::from_be_bytes([buf[2], buf[3]]); - if nr != block_nr.wrapping_add(1) { - /* already received or packets were missed, re-acknowledge */ - continue; - } - block_nr = nr; - - let databuf = &buf[4..len]; - file.write_all(databuf)?; - if len < 516 { - break; + match self.tftp.send_file(&socket, &path) { + Ok(_) => println!("Sent {} to {}.", path.display(), cl), + Err(err) => println!("Sending {} to {} failed ({}).", path.display(), cl, err.to_string()), } + Ok(()) } - file.flush()?; + pub fn handle_client(&self, conf: &Configuration, cl: &SocketAddr, buf: &[u8]) -> Result<(), io::Error> { + let socket = UdpSocket::bind("0.0.0.0:0")?; + socket.connect(cl)?; + socket.set_read_timeout(Some(Duration::from_secs(5)))?; - send_ack(&sock, block_nr)?; - - Ok(()) -} - -fn file_allowed(filename: &Path) -> Option<PathBuf> { - /* get parent to check dir where file should be read/written */ - let path = Path::new(".").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 cwd = match env::current_dir() { - Ok(p) => p, - Err(_) => return None, - }; - - match path.strip_prefix(cwd) { - Ok(p) => Some(p.to_path_buf()), - Err(_) => return None, - } -} - -fn handle_wrq(socket: &UdpSocket, cl: &SocketAddr, buf: &[u8]) -> Result<(), io::Error> { - let (filename, mode, mut options) = parse_file_mode_options(buf)?; - let _opts = init_tftp_options(&socket, &mut options, false); - - match mode.as_ref() { - "octet" => (), - _ => { - send_error(&socket, 0, "Unsupported mode")?; - return Err(io::Error::new(io::ErrorKind::Other, "unsupported mode")); - } - } - - let path = match file_allowed(&filename) { - Some(p) => p, - None => { - println!("Sending {} to {} failed (permission check failed).", filename.display(), cl); - send_error(&socket, 2, "Permission denied")?; - return Err(io::Error::new(io::ErrorKind::PermissionDenied, "permission denied")); - } - }; - - match recv_file(&socket, &path) { - Ok(_) => println!("Received {} from {}.", path.display(), cl), - Err(ref err) => { - println!("Receiving {} from {} failed ({}).", path.display(), cl, err.to_string()); - match err.kind() { - io::ErrorKind::PermissionDenied => send_error(&socket, 2, "Permission denied")?, - io::ErrorKind::AlreadyExists => send_error(&socket, 6, "File already exists")?, - _ => send_error(&socket, 0, "Receiving error")?, + let _opcode = match u16::from_be_bytes([buf[0], buf[1]]) { + 1 /* RRQ */ => { + if conf.wo { + self.tftp.send_error(&socket, 4, "reading not allowed")?; + return Err(io::Error::new(io::ErrorKind::Other, "unallowed mode")); + } else { + self.handle_rrq(&socket, &cl, &buf[2..])?; + } + }, + 2 /* WRQ */ => { + if conf.ro { + self.tftp.send_error(&socket, 4, "writing not allowed")?; + return Err(io::Error::new(io::ErrorKind::Other, "unallowed mode")); + } else { + self.handle_wrq(&socket, &cl, &buf[2..])?; + } + }, + 5 /* ERROR */ => println!("Received ERROR from {}", cl), + _ => { + self.tftp.send_error(&socket, 4, "Unexpected opcode")?; + return Err(io::Error::new(io::ErrorKind::Other, "unexpected opcode")); } - return Err(io::Error::new(err.kind(), err.to_string())); - } + }; + Ok(()) } - Ok(()) -} + fn drop_privs(&self, uid: u32, gid: u32) -> Result<(), Box<Error>> { + let root_uid = Uid::from_raw(0); + let root_gid = Gid::from_raw(0); + let unpriv_uid = Uid::from_raw(uid); + let unpriv_gid = Gid::from_raw(gid); -fn handle_rrq(socket: &UdpSocket, cl: &SocketAddr, buf: &[u8]) -> Result<(), io::Error> { - let (filename, mode, mut options) = parse_file_mode_options(buf)?; - let _opts = init_tftp_options(&socket, &mut options, true); + if Gid::current() != root_gid && Gid::effective() != root_gid + && Uid::current() != root_uid && Uid::effective() != root_uid { + /* already unprivileged user */ + return Ok(()); + } - match mode.as_ref() { - "octet" => (), - _ => { - send_error(&socket, 0, "Unsupported mode")?; - return Err(io::Error::new(io::ErrorKind::Other, "unsupported mode")); + if Gid::current() == root_gid || Gid::effective() == root_gid { + setresgid(unpriv_gid, unpriv_gid, unpriv_gid)?; } - } - let path = match file_allowed(&filename) { - Some(p) => p, - None => { - println!("Sending {} to {} failed (permission check failed).", filename.display(), cl); - send_error(&socket, 2, "Permission denied")?; - return Err(io::Error::new(io::ErrorKind::PermissionDenied, "permission denied")); + if Uid::current() == root_uid || Uid::effective() == root_uid { + setresuid(unpriv_uid, unpriv_uid, unpriv_uid)?; } - }; - match send_file(&socket, &path) { - Ok(_) => println!("Sent {} to {}.", path.display(), cl), - Err(err) => println!("Sending {} to {} failed ({}).", path.display(), cl, err.to_string()), + Ok(()) } - Ok(()) -} - -fn send_error(socket: &UdpSocket, code: u16, msg: &str) -> Result<(), io::Error> { - let mut buf = vec![0x00, 0x05]; // opcode - buf.extend(code.to_be_bytes().iter()); - buf.extend(msg.as_bytes()); - - socket.send(&buf)?; - Ok(()) -} - -fn send_ack(sock: &UdpSocket, block_nr: u16) -> Result<(), io::Error> { - let mut buf = vec![0x00, 0x04]; // opcode - buf.extend(block_nr.to_be_bytes().iter()); - - sock.send(&buf)?; - - Ok(()) -} -fn handle_client(conf: &Configuration, cl: &SocketAddr, buf: &[u8]) -> Result<(), io::Error> { - let socket = UdpSocket::bind("0.0.0.0:0")?; - socket.connect(cl)?; - socket.set_read_timeout(Some(Duration::from_secs(5)))?; - - let _opcode = match u16::from_be_bytes([buf[0], buf[1]]) { - 1 /* RRQ */ => { - if conf.wo { - send_error(&socket, 4, "reading not allowed")?; - return Err(io::Error::new(io::ErrorKind::Other, "unallowed mode")); - } else { - handle_rrq(&socket, &cl, &buf[2..])?; + pub fn start(&self) { + let socket = match UdpSocket::bind(format!("0.0.0.0:{}", self.conf.port)) { + Ok(s) => s, + Err(err) => { + println!("Binding a socket failed: {}", err); + return; } - }, - 2 /* WRQ */ => { - if conf.ro { - send_error(&socket, 4, "writing not allowed")?; - return Err(io::Error::new(io::ErrorKind::Other, "unallowed mode")); - } else { - handle_wrq(&socket, &cl, &buf[2..])?; + }; + match self.drop_privs(self.conf.uid, self.conf.gid) { + Ok(_) => (), + Err(err) => { + println!("Dropping privileges failed: {}", err); + return; } - }, - 5 /* ERROR */ => println!("Received ERROR from {}", cl), - _ => { - send_error(&socket, 4, "Unexpected opcode")?; - return Err(io::Error::new(io::ErrorKind::Other, "unexpected opcode")); - } - }; - Ok(()) -} + }; -fn drop_privs(uid: u32, gid: u32) -> Result<(), Box<Error>> { - let root_uid = Uid::from_raw(0); - let root_gid = Gid::from_raw(0); - let unpriv_uid = Uid::from_raw(uid); - let unpriv_gid = Gid::from_raw(gid); + match env::set_current_dir(&self.conf.dir) { + Ok(_) => (), + Err(err) => { + println!("Changing directory to {} failed ({}).", &self.conf.dir.display(), err); + return; + } + } - if Gid::current() != root_gid && Gid::effective() != root_gid - && Uid::current() != root_uid && Uid::effective() != root_uid { - /* already unprivileged user */ - return Ok(()); - } + loop { + let mut buf = [0; 2048]; + let (n, src) = match socket.recv_from(&mut buf) { + Ok(args) => args, + Err(err) => { + println!("Receiving data from socket failed: {}", err); + break; + } + }; - if Gid::current() == root_gid || Gid::effective() == root_gid { - setresgid(unpriv_gid, unpriv_gid, unpriv_gid)?; - } + match self.handle_client(&self.conf, &src, &buf[0..n]) { + /* errors intentionally ignored */ + _ => (), + } + } - if Uid::current() == root_uid || Uid::effective() == root_uid { - setresuid(unpriv_uid, unpriv_uid, unpriv_uid)?; } - - Ok(()) } fn usage(opts: Options, error: Option<String>) { @@ -522,48 +312,10 @@ fn parse_commandline<'a>(args: &'a Vec<String>) -> Result<Configuration, &'a str fn main() { let args: Vec<String> = env::args().collect(); - let conf = match parse_commandline(&args) { Ok(c) => c, Err(_) => return, }; - let socket = match UdpSocket::bind(format!("0.0.0.0:{}", conf.port)) { - Ok(s) => s, - Err(err) => { - println!("Binding a socket failed: {}", err); - return; - } - }; - match drop_privs(conf.uid, conf.gid) { - Ok(_) => (), - Err(err) => { - println!("Dropping privileges failed: {}", err); - return; - } - }; - - match env::set_current_dir(&conf.dir) { - Ok(_) => (), - Err(err) => { - println!("Changing directory to {} failed ({}).", &conf.dir.display(), err); - return; - } - } - - loop { - let mut buf = [0; 2048]; - let (n, src) = match socket.recv_from(&mut buf) { - Ok(args) => args, - Err(err) => { - println!("Receiving data from socket failed: {}", err); - break; - } - }; - - match handle_client(&conf, &src, &buf[0..n]) { - /* errors intentionally ignored */ - _ => (), - } - } + Tftpd::new(conf).start(); } |
