async_tftp/server/handlers/
dir.rs1use blocking::{unblock, Unblock};
2use log::trace;
3use std::fs::{self, File};
4use std::io;
5use std::net::SocketAddr;
6use std::path::Component;
7use std::path::{Path, PathBuf};
8
9use crate::error::{Error, Result};
10use crate::packet;
11
12pub struct DirHandler {
14 dir: PathBuf,
15 serve_rrq: bool,
16 serve_wrq: bool,
17}
18
19pub enum DirHandlerMode {
20 ReadOnly,
22 WriteOnly,
24 ReadWrite,
26}
27
28impl DirHandler {
29 pub fn new<P>(dir: P, flags: DirHandlerMode) -> Result<Self>
31 where
32 P: AsRef<Path>,
33 {
34 let dir = fs::canonicalize(dir.as_ref())?;
35
36 if !dir.is_dir() {
37 return Err(Error::NotDir(dir));
38 }
39
40 trace!("TFTP directory: {}", dir.display());
41
42 let serve_rrq = match flags {
43 DirHandlerMode::ReadOnly => true,
44 DirHandlerMode::WriteOnly => false,
45 DirHandlerMode::ReadWrite => true,
46 };
47
48 let serve_wrq = match flags {
49 DirHandlerMode::ReadOnly => false,
50 DirHandlerMode::WriteOnly => true,
51 DirHandlerMode::ReadWrite => true,
52 };
53
54 Ok(DirHandler {
55 dir,
56 serve_rrq,
57 serve_wrq,
58 })
59 }
60}
61
62impl crate::server::Handler for DirHandler {
63 type Reader = Unblock<File>;
64 type Writer = Unblock<File>;
65
66 async fn read_req_open(
67 &mut self,
68 _client: &SocketAddr,
69 path: &Path,
70 ) -> Result<(Self::Reader, Option<u64>), packet::Error> {
71 if !self.serve_rrq {
72 return Err(packet::Error::IllegalOperation);
73 }
74
75 let path = secure_path(&self.dir, path)?;
76
77 if !path.is_file() {
79 return Err(packet::Error::FileNotFound);
80 }
81
82 let path_clone = path.clone();
83 let (file, len) = unblock(move || open_file_ro(path_clone)).await?;
84 let reader = Unblock::new(file);
85
86 trace!("TFTP sending file: {}", path.display());
87
88 Ok((reader, len))
89 }
90
91 async fn write_req_open(
92 &mut self,
93 _client: &SocketAddr,
94 path: &Path,
95 size: Option<u64>,
96 ) -> Result<Self::Writer, packet::Error> {
97 if !self.serve_wrq {
98 return Err(packet::Error::IllegalOperation);
99 }
100
101 let path = secure_path(&self.dir, path)?;
102
103 let path_clone = path.clone();
104 let file = unblock(move || open_file_wo(path_clone, size)).await?;
105 let writer = Unblock::new(file);
106
107 trace!("TFTP receiving file: {}", path.display());
108
109 Ok(writer)
110 }
111}
112
113fn secure_path(
114 restricted_dir: &Path,
115 path: &Path,
116) -> Result<PathBuf, packet::Error> {
117 let path = path
119 .strip_prefix("/")
120 .or_else(|_| path.strip_prefix("./"))
121 .unwrap_or(path);
122
123 if path.components().any(|x| x == Component::ParentDir) {
125 return Err(packet::Error::PermissionDenied);
126 }
127
128 match path.components().next() {
131 Some(Component::Normal(_)) => {}
132 _ => return Err(packet::Error::PermissionDenied),
133 }
134
135 Ok(restricted_dir.join(path))
136}
137
138fn open_file_ro(path: PathBuf) -> io::Result<(File, Option<u64>)> {
139 let file = File::open(path)?;
140 let len = file.metadata().ok().map(|m| m.len());
141 Ok((file, len))
142}
143
144fn open_file_wo(path: PathBuf, size: Option<u64>) -> io::Result<File> {
145 let file = File::create(path)?;
146
147 if let Some(size) = size {
148 file.set_len(size)?;
149 }
150
151 Ok(file)
152}