first commit

This commit is contained in:
Yakumo Hokori 2025-09-02 22:01:08 +08:00
commit 699cb26587
7 changed files with 2951 additions and 0 deletions

2634
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "ftphack"
version = "0.1.0"
edition = "2024"
[dependencies]
gtk = "0.18.2"
libunftp = "0.21.0"
tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
unftp-sbe-fs = "0.3.0"

23
README.md Normal file
View File

@ -0,0 +1,23 @@
# ftphack
一个用于FTP安全测试的Rust工具项目
## 项目简介
ftphack 是一个用Rust编写的FTP协议安全测试工具集旨在帮助安全研究人员进行FTP服务的安全评估。
## 安装方法
```bash
cargo build
```
## 使用方法
```bash
cargo run
```
## 许可证
本项目采用 MIT 许可证。

2
src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod server;
pub mod ui;

107
src/main.rs Normal file
View File

@ -0,0 +1,107 @@
mod server;
mod ui;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::prelude::*;
fn main() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
// Create UI
let ui = ui::FtpUi::new();
// Create server instance
let server = Rc::new(RefCell::new(None::<server::FtpServer>));
// Connect signals
let ui_clone = ui.clone();
let server_clone = server.clone();
let rt_clone = rt.handle().clone();
ui.start_button.connect_clicked(move |_| {
// Get settings from UI
let ip = ui_clone.ip_entry.text();
let port: u16 = ui_clone.port_entry.text().parse().unwrap_or(2121);
let dir = ui_clone.dir_entry.text();
// Validate directory
let dir_path = PathBuf::from(&dir);
if !dir_path.exists() {
ui_clone.status_label.set_text("Error: Directory does not exist");
return;
}
// Update server address
let addr: SocketAddr = format!("{}:{}", ip, port).parse().unwrap();
// Create and start server
match server::FtpServer::new(addr, dir_path) {
Ok(mut srv) => {
// First start the server (initialize it)
if let Err(e) = srv.start() {
ui_clone.status_label.set_text(&format!("Error: {}", e));
return;
}
// Then run the server in the Tokio runtime
let handle = rt_clone.clone();
match handle.block_on(async { srv.run() }) {
Ok(()) => {
server_clone.borrow_mut().replace(srv);
ui_clone.status_label.set_text("Status: Running");
ui_clone.start_button.set_sensitive(false);
ui_clone.stop_button.set_sensitive(true);
}
Err(e) => {
ui_clone.status_label.set_text(&format!("Error: {}", e));
}
}
}
Err(e) => {
ui_clone.status_label.set_text(&format!("Error: {}", e));
}
}
});
let ui_clone = ui.clone();
let server_clone2 = server.clone();
ui.stop_button.connect_clicked(move |_| {
let mut server_ref = server_clone2.borrow_mut();
if let Some(mut srv) = server_ref.take() {
srv.stop();
}
ui_clone.status_label.set_text("Status: Stopped");
ui_clone.start_button.set_sensitive(true);
ui_clone.stop_button.set_sensitive(false);
});
// Directory browse button
let ui_clone = ui.clone();
ui.dir_button.connect_clicked(move |_| {
let dialog = gtk::FileChooserDialog::with_buttons(
Some("选择目录"),
Some(&ui_clone.window),
gtk::FileChooserAction::SelectFolder,
&[("取消", gtk::ResponseType::Cancel), ("确定", gtk::ResponseType::Accept)]
);
if dialog.run() == gtk::ResponseType::Accept {
if let Some(filename) = dialog.filename() {
ui_clone.dir_entry.set_text(&filename.to_string_lossy());
}
}
dialog.close();
});
// Show window
ui.window.show_all();
// Start GTK main loop
gtk::main();
}

80
src/server/mod.rs Normal file
View File

@ -0,0 +1,80 @@
use std::net::SocketAddr;
use libunftp::{Server, ServerBuilder};
use unftp_sbe_fs::Filesystem;
use std::sync::Arc;
use libunftp::auth::{AnonymousAuthenticator, DefaultUser};
use std::sync::atomic::{AtomicBool, Ordering};
pub struct FtpServer {
server: Option<Server<Filesystem, DefaultUser>>,
addr: SocketAddr,
root_dir: std::path::PathBuf,
shutdown_flag: Arc<AtomicBool>,
}
impl FtpServer {
pub fn new(addr: SocketAddr, root_dir: std::path::PathBuf) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
if !root_dir.exists() {
return Err(format!("Directory does not exist: {:?}", root_dir).into());
}
Ok(Self {
server: None,
addr,
root_dir,
shutdown_flag: Arc::new(AtomicBool::new(false)),
})
}
pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let root_dir = self.root_dir.clone();
let server = ServerBuilder::new(Box::new(move || -> Filesystem { Filesystem::new(&root_dir).unwrap() }))
.greeting("Welcome to FTP server")
.passive_ports(50000..=60000)
.authenticator(Arc::new(AnonymousAuthenticator))
.build()?;
// Store the server instance
self.server = Some(server);
Ok(())
}
pub fn run(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Start the server
let addr = self.addr;
let shutdown_flag = self.shutdown_flag.clone();
// We need to move the server out of self.server to use it in the async task
let server = self.server.take().unwrap();
tokio::task::spawn(async move {
// Create a future that listens for the shutdown signal
let shutdown_future = async {
loop {
if shutdown_flag.load(Ordering::Relaxed) {
break;
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
};
// Run the server and shutdown future concurrently
tokio::select! {
result = server.listen(addr.to_string()) => {
if let Err(e) = result {
eprintln!("Server error: {}", e);
}
}
_ = shutdown_future => {
println!("Server shutdown requested");
}
}
});
Ok(())
}
pub fn stop(&mut self) {
// Set the shutdown flag to true
self.shutdown_flag.store(true, Ordering::Relaxed);
}
}

95
src/ui/mod.rs Normal file
View File

@ -0,0 +1,95 @@
use gtk::prelude::*;
#[derive(Clone)]
pub struct FtpUi {
pub window: gtk::ApplicationWindow,
pub start_button: gtk::Button,
pub stop_button: gtk::Button,
pub status_label: gtk::Label,
pub ip_entry: gtk::Entry,
pub port_entry: gtk::Entry,
pub dir_entry: gtk::Entry,
pub dir_button: gtk::Button,
}
impl FtpUi {
pub fn new() -> Self {
// Initialize GTK
if gtk::init().is_err() {
eprintln!("Failed to initialize GTK.");
std::process::exit(1);
}
let window = gtk::ApplicationWindow::builder()
.title("FTP Server")
.default_width(400)
.default_height(300)
.build();
// Create grid
let grid = gtk::Grid::builder()
.row_spacing(10)
.column_spacing(10)
.margin_top(10)
.margin_bottom(10)
.margin_start(10)
.margin_end(10)
.build();
// IP Address
let ip_label = gtk::Label::new(Some("IP 地址:"));
let ip_entry = gtk::Entry::builder()
.text("0.0.0.0")
.build();
// Port
let port_label = gtk::Label::new(Some("端口:"));
let port_entry = gtk::Entry::builder()
.text("2121")
.build();
// Directory
let dir_label = gtk::Label::new(Some("目录:"));
let dir_entry = gtk::Entry::builder()
.text(std::env::temp_dir().to_str().unwrap())
.build();
let dir_button = gtk::Button::with_label("浏览");
// Status
let status_label = gtk::Label::new(Some("Status: Stopped"));
// Buttons
let start_button = gtk::Button::with_label("启动");
let stop_button = gtk::Button::with_label("停止");
stop_button.set_sensitive(false);
// Layout
grid.attach(&ip_label, 0, 0, 1, 1);
grid.attach(&ip_entry, 1, 0, 1, 1);
grid.attach(&port_label, 0, 1, 1, 1);
grid.attach(&port_entry, 1, 1, 1, 1);
grid.attach(&dir_label, 0, 2, 1, 1);
grid.attach(&dir_entry, 1, 2, 1, 1);
grid.attach(&dir_button, 2, 2, 1, 1);
grid.attach(&status_label, 0, 3, 3, 1);
grid.attach(&start_button, 0, 4, 1, 1);
grid.attach(&stop_button, 1, 4, 1, 1);
window.add(&grid);
Self {
window,
start_button,
stop_button,
status_label,
ip_entry,
port_entry,
dir_entry,
dir_button,
}
}
}