-
Notifications
You must be signed in to change notification settings - Fork 11
/
tftpd-targz.rs
113 lines (93 loc) · 3.06 KB
/
tftpd-targz.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use anyhow::Result;
use structopt::StructOpt;
use async_compression::futures::bufread::GzipDecoder;
use async_std::fs::File;
use async_std::io::{BufReader, Sink};
use async_std::path::{Path, PathBuf};
use async_std::stream::StreamExt;
use async_std::task::block_on;
use async_tar::{Archive, Entry};
use async_tftp::packet;
use async_tftp::server::{Handler, TftpServerBuilder};
use std::net::SocketAddr;
struct TftpdTarGzHandler {
archive_path: PathBuf,
}
impl TftpdTarGzHandler {
fn new(path: impl AsRef<Path>) -> Self {
TftpdTarGzHandler {
archive_path: path.as_ref().to_owned(),
}
}
}
// Sometimes paths within archives start with `/` or `./`, strip both.
fn strip_path_prefixes(path: &Path) -> &Path {
path.strip_prefix("/").or_else(|_| path.strip_prefix("./")).unwrap_or(path)
}
impl Handler for TftpdTarGzHandler {
type Reader = Entry<Archive<GzipDecoder<BufReader<File>>>>;
type Writer = Sink;
async fn read_req_open(
&mut self,
_client: &SocketAddr,
path: &std::path::Path,
) -> Result<(Self::Reader, Option<u64>), packet::Error> {
let req_path = strip_path_prefixes(path.into()).to_owned();
let file = File::open(self.archive_path.clone()).await?;
let archive = Archive::new(GzipDecoder::new(BufReader::new(file)));
let mut entries = archive.entries()?;
while let Some(Ok(entry)) = entries.next().await {
if entry
.path()
.map(|p| strip_path_prefixes(&p) == req_path)
.unwrap_or(false)
{
// We manage to find the entry.
// Check if it is a regular file.
if entry.header().entry_type() != async_tar::EntryType::Regular
{
break;
}
return Ok((entry, None));
}
}
Err(packet::Error::FileNotFound)
}
async fn write_req_open(
&mut self,
_client: &SocketAddr,
_path: &std::path::Path,
_size: Option<u64>,
) -> Result<Self::Writer, packet::Error> {
Err(packet::Error::IllegalOperation)
}
}
#[derive(Debug, StructOpt)]
struct Opt {
archive_path: PathBuf,
}
fn main() -> Result<()> {
// Parse args
let opt = Opt::from_args();
fern::Dispatch::new()
.level(log::LevelFilter::Info)
.level_for("async_tftp", log::LevelFilter::Trace)
.chain(std::io::stdout())
.apply()
.expect("Failed to initialize logger");
block_on(async move {
// We will serve files from a tar.gz through tftp
let handler = TftpdTarGzHandler::new(&opt.archive_path);
// Build server
let tftpd = TftpServerBuilder::with_handler(handler)
.bind("0.0.0.0:6969".parse().unwrap())
// Workaround to handle cases where client is behind VPN
.block_size_limit(1024)
.build()
.await?;
// Serve
log::info!("Listening on: {}", tftpd.listen_addr()?);
tftpd.serve().await?;
Ok(())
})
}