first commit
This commit is contained in:
commit
699cb26587
2634
Cargo.lock
generated
Normal file
2634
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
Normal file
10
Cargo.toml
Normal 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
23
README.md
Normal 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
2
src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod server;
|
||||||
|
pub mod ui;
|
107
src/main.rs
Normal file
107
src/main.rs
Normal 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
80
src/server/mod.rs
Normal 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
95
src/ui/mod.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user