diff --git a/Cargo.toml b/Cargo.toml index d7381b6..1d67fb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,5 @@ include = [ "lib/**/*.php" ] [dependencies] regex = "1.1.9" +uuid = { version = "0.8.1", features = ["v4"] } +serde_json = "1.0.59" diff --git a/src/constants.rs b/src/constants.rs index 2b48f8c..ff532ae 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -25,3 +25,13 @@ pub const SSH_FXP_NAME: u8 = 104; pub const SSH_FXP_ATTRS: u8 = 105; pub const SSH_FXP_EXTENDED: u8 = 200; pub const SSH_FXP_EXTENDED_REPLY: u8 = 201; +// Status codes +pub const SSH_FX_OK: u32 = 0; +pub const SSH_FX_EOF: u32 = 1; +pub const SSH_FX_NO_SUCH_FILE: u32 = 2; +pub const SSH_FX_PERMISSION_DENIED: u32 = 3; +pub const SSH_FX_FAILURE: u32 = 4; +pub const SSH_FX_BAD_MESSAGE: u32 = 5; +pub const SSH_FX_NO_CONNECTION: u32 = 6; +pub const SSH_FX_CONNECTION_LOST: u32 = 7; +pub const SSH_FX_OP_UNSUPPORTED: u32 = 8; diff --git a/src/handle.rs b/src/handle.rs new file mode 100644 index 0000000..b40a1b6 --- /dev/null +++ b/src/handle.rs @@ -0,0 +1,20 @@ +pub struct DirectoryHandle { + child_paths: Vec, + finished: bool, +} +impl DirectoryHandle { + pub fn get_files(&mut self) -> Result, String> { + if self.finished { + Err("Could not find more files.".to_string()) + } else { + self.finished = true; + Ok(self.child_paths.clone()) + } + } + pub fn new(childs: Vec) -> DirectoryHandle { + DirectoryHandle { + child_paths: childs, + finished: false, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6b5866f..a90c948 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,3 +2,4 @@ pub mod packet; pub mod constants; pub mod sftp; pub mod php; +pub mod handle; diff --git a/src/main.rs b/src/main.rs index 121d25f..67b2ed8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -use fake_sftp::packet::dispatch_packet; - fn main() { let arguments: Vec = std::env::args().into_iter().collect(); if arguments.len() < 3 { @@ -8,10 +6,5 @@ fn main() { } let nextcloud_folder = &arguments[1]; let user = &arguments[2]; - fake_sftp::php::ls("/", user, nextcloud_folder); - loop { - let packet = fake_sftp::packet::Packet::read_packet(); - dispatch_packet(packet) - } + fake_sftp::packet::dispatch::loop_of_dispatch(nextcloud_folder, user); } - diff --git a/src/packet.rs b/src/packet.rs index 3b20779..a66d9e7 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,68 +1,11 @@ pub mod data; pub mod header; pub mod types; +pub mod dispatch; -use crate::constants::SSH_FXP_INIT; -use crate::constants::SSH_FXP_REALPATH; -use crate::constants::SSH_FXP_VERSION; -use crate::constants::SSH_FXP_NAME; - -use std::io::{BufReader, BufWriter, Read, Write}; +use std::io::{BufReader, Read}; use std::process::exit; -pub fn dispatch_packet(packet: Packet) { - let mut buff_stdout = BufWriter::new(std::io::stdout()); - let mut response: Packet = match packet.packet_header.type_packet { - SSH_FXP_INIT => dispatch_version_request(packet), - SSH_FXP_REALPATH => dispatch_realpath_request(packet), - _ => panic!("{} not implemented.", packet.packet_header.type_packet), - }; - let serialized_response = response.serialize(); - eprintln!("Writing response."); - eprintln!("{:#?}", &serialized_response); - buff_stdout - .write(&serialized_response) - .expect("Unable to write to stdout."); - buff_stdout.flush().expect("Unable to flush stdout."); -} - -fn dispatch_realpath_request(packet: Packet) -> Packet { - let realpath_packet_data: data::realpath::RealpathData = - data::realpath::RealpathData::deserialize(&packet.data); - let path = crate::sftp::realpath(&realpath_packet_data.path); - let id = realpath_packet_data.id; - Packet { - packet_header: header::PacketHeader { - length: 0, - type_packet: SSH_FXP_NAME, - }, - data: crate::packet::data::name::NameData::new(id, &[path]).serialize(), - } -} - -fn dispatch_version_request(packet: Packet) -> Packet { - let init_packet_data: data::version::VersionData = - match data::version::VersionData::deserialize(&packet.data) { - Ok(ok) => ok, - Err(err) => panic!(err), - }; - eprintln!("The client version is {}.", init_packet_data.version); - if init_packet_data.version < 3 { - panic!("Unsupported version, minimal client version 3."); - } - let version_packet_data = data::version::VersionData { - version: 3, - extension_data: Vec::new(), - }; - Packet { - packet_header: header::PacketHeader { - length: 0, - type_packet: SSH_FXP_VERSION, - }, - data: version_packet_data.serialize(), - } -} - pub struct Packet { pub packet_header: header::PacketHeader, pub data: Vec, diff --git a/src/packet/data.rs b/src/packet/data.rs index a38e915..dc98ae7 100644 --- a/src/packet/data.rs +++ b/src/packet/data.rs @@ -1,3 +1,6 @@ pub mod version; pub mod realpath; pub mod name; +pub mod opendir; +pub mod status; +pub mod handle; diff --git a/src/packet/data/handle.rs b/src/packet/data/handle.rs new file mode 100644 index 0000000..af64b4b --- /dev/null +++ b/src/packet/data/handle.rs @@ -0,0 +1,22 @@ +pub struct HandleData { + pub id: u32, + pub handle: String, +} + +impl HandleData { + pub fn new(id: u32, handle: String) -> HandleData { + HandleData { + id: id, + handle: handle, + } + } + pub fn serialize(&self) -> Vec { + let mut serialized_data: Vec = Vec::new(); + serialized_data.extend(&self.id.to_be_bytes()); + let handle = crate::packet::types::string::String { + content: self.handle.clone(), + }; + serialized_data.extend(handle.serialize()); + serialized_data + } +} diff --git a/src/packet/data/opendir.rs b/src/packet/data/opendir.rs new file mode 100644 index 0000000..ce46cad --- /dev/null +++ b/src/packet/data/opendir.rs @@ -0,0 +1,20 @@ +use std::io::Read; + +pub struct OpendirData { + pub id: u32, + pub path: String, +} +impl OpendirData { + pub fn deserialize(mut u8_array_data: &[u8]) -> OpendirData { + if u8_array_data.len() < 9 { + panic!("Opendir data is expected to be bigger."); + } + let mut id: [u8; 4] = [0; 4]; + u8_array_data + .read(&mut id) + .expect("Failed to read id from opendir packet."); + let id = u32::from_be_bytes(id); + let path = crate::packet::types::string::String::deserialize(u8_array_data).content; + OpendirData { id: id, path: path } + } +} diff --git a/src/packet/data/realpath.rs b/src/packet/data/realpath.rs index 763afc4..ab45d6e 100644 --- a/src/packet/data/realpath.rs +++ b/src/packet/data/realpath.rs @@ -7,7 +7,7 @@ pub struct RealpathData { impl RealpathData { pub fn deserialize(mut u8_array_data: &[u8]) -> RealpathData { - if u8_array_data.len() < 5 { + if u8_array_data.len() < 9 { panic!("Realpath data is expected to be bigger."); } let mut id: [u8; 4] = [0; 4]; diff --git a/src/packet/data/status.rs b/src/packet/data/status.rs new file mode 100644 index 0000000..836087c --- /dev/null +++ b/src/packet/data/status.rs @@ -0,0 +1,20 @@ +pub struct StatusData { + pub id: u32, + pub status_code: u32, + pub error_message: String, + pub lang: String, +} +impl StatusData { + pub fn serialize(&self) -> Vec { + let mut serialized_data: Vec = Vec::new(); + serialized_data.extend(&self.id.to_be_bytes()); + serialized_data.extend(&self.status_code.to_be_bytes()); + let error_message = crate::packet::types::string::String { + content: self.error_message.clone(), + }; + let lang = crate::packet::types::string::String { content: self.lang.clone() }; + serialized_data.extend(error_message.serialize()); + serialized_data.extend(lang.serialize()); + serialized_data + } +} diff --git a/src/packet/dispatch.rs b/src/packet/dispatch.rs new file mode 100644 index 0000000..2d0251a --- /dev/null +++ b/src/packet/dispatch.rs @@ -0,0 +1,149 @@ +use uuid::Uuid; + +use crate::constants::SSH_FXP_HANDLE; +use crate::constants::SSH_FXP_INIT; +use crate::constants::SSH_FXP_NAME; +use crate::constants::SSH_FXP_OPENDIR; +use crate::constants::SSH_FXP_REALPATH; +use crate::constants::SSH_FXP_STATUS; +use crate::constants::SSH_FXP_VERSION; +use crate::constants::SSH_FX_FAILURE; +use crate::constants::SSH_FX_NO_SUCH_FILE; +use crate::handle::DirectoryHandle; +use crate::packet::data::name::NameData; +use crate::packet::data::opendir::OpendirData; +use crate::packet::data::realpath::RealpathData; +use crate::packet::data::status::StatusData; +use crate::packet::data::version::VersionData; +use crate::packet::data::handle::HandleData; +use crate::packet::header::PacketHeader; +use crate::packet::Packet; +use crate::php; +use crate::sftp::realpath; + +use std::collections::HashMap; +use std::io::{BufWriter, Write}; + +pub fn loop_of_dispatch(nextcloud_folder: &str, user: &str) { + let mut directory_handles: HashMap = HashMap::new(); + loop { + let packet = Packet::read_packet(); + dispatch_packet(packet, &mut directory_handles, nextcloud_folder, user) + } +} + +pub fn dispatch_packet( + packet: Packet, + directory_handles: &mut HashMap, + nextcloud_folder: &str, + user: &str, +) { + let mut buff_stdout = BufWriter::new(std::io::stdout()); + let mut response: Packet = match packet.packet_header.type_packet { + SSH_FXP_INIT => dispatch_version_request(packet), + SSH_FXP_REALPATH => dispatch_realpath_request(packet), + SSH_FXP_OPENDIR => { + dispatch_opendir_request(packet, directory_handles, nextcloud_folder, user) + } + _ => panic!("{} not implemented.", packet.packet_header.type_packet), + }; + let serialized_response = response.serialize(); + eprintln!("Writing response."); + eprintln!("{:#?}", &serialized_response); + buff_stdout + .write(&serialized_response) + .expect("Unable to write to stdout."); + buff_stdout.flush().expect("Unable to flush stdout."); +} + +fn dispatch_opendir_request( + packet: Packet, + directory_handles: &mut HashMap, + nextcloud_folder: &str, + user: &str, +) -> Packet { + let opendir_packet_data = OpendirData::deserialize(&packet.data); + let path = opendir_packet_data.path; + if !php::check_if_exists(&path, &user, &nextcloud_folder) { + return Packet { + packet_header: PacketHeader { + length: 0, + type_packet: SSH_FXP_STATUS, + }, + data: StatusData { + id: opendir_packet_data.id, + status_code: SSH_FX_NO_SUCH_FILE, + error_message: "No such file or directory.".to_string(), + lang: "en_US".to_string(), + } + .serialize(), + }; + } + if !php::check_if_is_dir(&path, &user, &nextcloud_folder) { + return Packet { + packet_header: PacketHeader { + length: 0, + type_packet: SSH_FXP_STATUS, + }, + data: StatusData { + id: opendir_packet_data.id, + status_code: SSH_FX_FAILURE, + error_message: "The file is not a directory.".to_string(), + lang: "en_US".to_string(), + } + .serialize(), + }; + } + let str_handle: String = Uuid::new_v4().to_string(); + eprintln!("{}", str_handle); + directory_handles.insert( + str_handle.clone(), + DirectoryHandle::new(php::ls(&path, &user, &nextcloud_folder)), + ); + Packet { + packet_header: PacketHeader { + length: 0, + type_packet: SSH_FXP_HANDLE, + }, + data: HandleData { + id: opendir_packet_data.id, + handle: str_handle, + } + .serialize(), + } +} + +fn dispatch_realpath_request(packet: Packet) -> Packet { + let realpath_packet_data: RealpathData = RealpathData::deserialize(&packet.data); + let path = realpath(&realpath_packet_data.path); + let id = realpath_packet_data.id; + Packet { + packet_header: PacketHeader { + length: 0, + type_packet: SSH_FXP_NAME, + }, + data: NameData::new(id, &[path]).serialize(), + } +} + +fn dispatch_version_request(packet: Packet) -> Packet { + let init_packet_data: VersionData = match VersionData::deserialize(&packet.data) { + Ok(ok) => ok, + Err(err) => panic!(err), + }; + eprintln!("The client version is {}.", init_packet_data.version); + if init_packet_data.version < 3 { + panic!("Unsupported version, minimal client version 3."); + } + let version_packet_data = VersionData { + version: 3, + extension_data: Vec::new(), + }; + Packet { + packet_header: PacketHeader { + length: 0, + type_packet: SSH_FXP_VERSION, + }, + data: version_packet_data.serialize(), + } +} diff --git a/src/php.rs b/src/php.rs index 0720e7e..800cd31 100644 --- a/src/php.rs +++ b/src/php.rs @@ -1,4 +1,8 @@ +use serde_json::Value::Bool; +use serde_json::Value; + use std::io::Write; + fn execute_php(command: &str, arguments: &[&str]) -> String { let mut execute_array: Vec<&str> = Vec::new(); execute_array.extend(&["-r", &command, "--"]); @@ -15,12 +19,51 @@ fn execute_php(command: &str, arguments: &[&str]) -> String { return String::from_utf8(output.stdout).expect("Unable to parse php response"); } -pub fn ls(path: &str, user: &str, nextcloud_location: &str) { +pub fn ls(path: &str, user: &str, nextcloud_location: &str) -> Vec { let php_script: String = [ + &start_script(), + "$path = $argv[3];", + "function get_name($node) { return $node->getName(); }", + "echo(json_encode(array_map('get_name', $view->getDirectoryContent($path))));", + ] + .join("\n"); + let result = execute_php(&php_script, &[user, nextcloud_location, path]); + let result: Vec = serde_json::from_str(&result).expect("Invalid json reading ls."); + result +} + +pub fn check_if_exists(path: &str, user: &str, nextcloud_location: &str) -> bool { + let php_script: String = [ + &start_script(), + "$path = $argv[3];", + "echo json_encode($view->file_exists($path));", + ] + .join("\n"); + json_get_boolean(&execute_php(&php_script, &[user, nextcloud_location, path])) +} + +pub fn check_if_is_dir(path: &str, user: &str, nextcloud_location: &str) -> bool { + let php_script: String = [ + &start_script(), + "$path = $argv[3];", + "echo json_encode($view->is_dir($path));", + ] + .join("\n"); + json_get_boolean(&execute_php(&php_script, &[user, nextcloud_location, path])) +} +fn json_get_boolean(json: &str) -> bool { + let json_value: Value = serde_json::from_str(json).expect("Unable to parse json at json_get_boolean."); + match json_value { + Bool(x) => x, + _ => false, + } +} + +pub fn start_script() -> String { + [ "define('OC_CONSOLE', 1);", "$user = $argv[1];", "$nextcloud_location = $argv[2];", - "$path = $argv[3];", "include $nextcloud_location . '/lib/base.php';", "use Symfony\\Component\\Console\\Application as SymfonyApplication;", "use \\OC\\Files\\View;", @@ -30,13 +73,6 @@ pub fn ls(path: &str, user: &str, nextcloud_location: &str) { );", "\\OC_Util::setupFS($user);", "$view = new View('/'. $user . '/files');", - "foreach ($view->getDirectoryContent($path) as $node) { - print $node->getName() . \"\\n\"; - }", ] - .join("\n"); - eprintln!( - "{}", - execute_php(&php_script, &[user, nextcloud_location, path]) - ); + .join("\n") }