feat: Adding support to opendir requests and directory handles.

Using a dumb implementation where you have to fetch the entire dir.
Not very recommendable with long folders.
This commit is contained in:
Sergiotarxz 2020-11-12 18:58:44 +01:00
parent 8c03e6420c
commit d05e1f842f
13 changed files with 297 additions and 78 deletions

View File

@ -9,3 +9,5 @@ include = [ "lib/**/*.php" ]
[dependencies]
regex = "1.1.9"
uuid = { version = "0.8.1", features = ["v4"] }
serde_json = "1.0.59"

View File

@ -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;

20
src/handle.rs Normal file
View File

@ -0,0 +1,20 @@
pub struct DirectoryHandle {
child_paths: Vec<String>,
finished: bool,
}
impl DirectoryHandle {
pub fn get_files(&mut self) -> Result<Vec<String>, 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<String>) -> DirectoryHandle {
DirectoryHandle {
child_paths: childs,
finished: false,
}
}
}

View File

@ -2,3 +2,4 @@ pub mod packet;
pub mod constants;
pub mod sftp;
pub mod php;
pub mod handle;

View File

@ -1,5 +1,3 @@
use fake_sftp::packet::dispatch_packet;
fn main() {
let arguments: Vec<String> = 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);
}

View File

@ -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<u8>,

View File

@ -1,3 +1,6 @@
pub mod version;
pub mod realpath;
pub mod name;
pub mod opendir;
pub mod status;
pub mod handle;

22
src/packet/data/handle.rs Normal file
View File

@ -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<u8> {
let mut serialized_data: Vec<u8> = 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
}
}

View File

@ -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 }
}
}

View File

@ -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];

20
src/packet/data/status.rs Normal file
View File

@ -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<u8> {
let mut serialized_data: Vec<u8> = 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
}
}

149
src/packet/dispatch.rs Normal file
View File

@ -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<String, DirectoryHandle> = 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<String, DirectoryHandle>,
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<String, DirectoryHandle>,
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(),
}
}

View File

@ -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<String> {
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<String> = 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")
}