SMB support (#184)

* feat: smb client

* fix: smb connection

* fix: smbclient deps

* feat: SMB mentions to user manual

* feat: changelog

* dlib macos

* fix: removed smb support from macos :(

* fix: restored libsmbclient build

* fix: strange lint message

* fix: macos build smb

* fix: macos build smb

* fix: macos tests

* fix: macos lint

* feat: SMB windows support

* fix: windows tests
This commit is contained in:
Christian Visintin
2023-05-13 15:00:16 +02:00
committed by GitHub
parent a13663e5e9
commit b7369162d2
54 changed files with 1256 additions and 154 deletions

View File

@@ -7,9 +7,16 @@ use std::path::PathBuf;
use remotefs::RemoteFs;
use remotefs_aws_s3::AwsS3Fs;
use remotefs_ftp::FtpFs;
#[cfg(smb_unix)]
use remotefs_smb::SmbOptions;
#[cfg(smb)]
use remotefs_smb::{SmbCredentials, SmbFs};
use remotefs_ssh::{ScpFs, SftpFs, SshConfigParseRule, SshOpts};
#[cfg(not(smb))]
use super::params::{AwsS3Params, GenericProtocolParams};
#[cfg(smb)]
use super::params::{AwsS3Params, GenericProtocolParams, SmbParams};
use super::{FileTransferProtocol, ProtocolParams};
use crate::system::config_client::ConfigClient;
use crate::system::sshkey_storage::SshKeyStorage;
@@ -39,6 +46,10 @@ impl Builder {
(FileTransferProtocol::Sftp, ProtocolParams::Generic(params)) => {
Box::new(Self::sftp_client(params, config_client))
}
#[cfg(smb)]
(FileTransferProtocol::Smb, ProtocolParams::Smb(params)) => {
Box::new(Self::smb_client(params))
}
(protocol, params) => {
error!("Invalid params for protocol '{:?}'", protocol);
panic!("Invalid protocol '{protocol:?}' with parameters of type {params:?}")
@@ -98,6 +109,50 @@ impl Builder {
Self::build_ssh_opts(params, config_client).into()
}
#[cfg(smb_unix)]
fn smb_client(params: SmbParams) -> SmbFs {
let mut credentials = SmbCredentials::default()
.server(format!("smb://{}:{}", params.address, params.port))
.share(params.share);
if let Some(username) = params.username {
credentials = credentials.username(username);
}
if let Some(password) = params.password {
credentials = credentials.password(password);
}
if let Some(workgroup) = params.workgroup {
credentials = credentials.workgroup(workgroup);
}
match SmbFs::try_new(
credentials,
SmbOptions::default()
.one_share_per_server(true)
.case_sensitive(false),
) {
Ok(fs) => fs,
Err(e) => {
error!("Invalid params for protocol SMB: {e}");
panic!("Invalid params for protocol SMB: {e}")
}
}
}
#[cfg(windows)]
fn smb_client(params: SmbParams) -> SmbFs {
let mut credentials = SmbCredentials::new(params.address, params.share);
if let Some(username) = params.username {
credentials = credentials.username(username);
}
if let Some(password) = params.password {
credentials = credentials.password(password);
}
SmbFs::new(credentials)
}
/// Build ssh options from generic protocol params and client configuration
fn build_ssh_opts(params: GenericProtocolParams, config_client: &ConfigClient) -> SshOpts {
let mut opts = SshOpts::new(params.address)
@@ -187,6 +242,14 @@ mod test {
let _ = Builder::build(FileTransferProtocol::Sftp, params, &config_client);
}
#[test]
#[cfg(smb)]
fn should_build_smb_fs() {
let params = ProtocolParams::Smb(SmbParams::new("localhost", "share"));
let config_client = get_config_client();
let _ = Builder::build(FileTransferProtocol::Smb, params, &config_client);
}
#[test]
#[should_panic]
fn should_not_build_fs() {

View File

@@ -13,10 +13,11 @@ pub use params::{FileTransferParams, ProtocolParams};
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum FileTransferProtocol {
Sftp,
Scp,
Ftp(bool), // Bool is for secure (true => ftps)
AwsS3,
Ftp(bool), // Bool is for secure (true => ftps)
Scp,
Sftp,
Smb,
}
// Traits
@@ -24,13 +25,14 @@ pub enum FileTransferProtocol {
impl std::string::ToString for FileTransferProtocol {
fn to_string(&self) -> String {
String::from(match self {
FileTransferProtocol::AwsS3 => "S3",
FileTransferProtocol::Ftp(secure) => match secure {
true => "FTPS",
false => "FTP",
},
FileTransferProtocol::Scp => "SCP",
FileTransferProtocol::Sftp => "SFTP",
FileTransferProtocol::AwsS3 => "S3",
FileTransferProtocol::Smb => "SMB",
})
}
}
@@ -41,9 +43,10 @@ impl std::str::FromStr for FileTransferProtocol {
match s.to_ascii_uppercase().as_str() {
"FTP" => Ok(FileTransferProtocol::Ftp(false)),
"FTPS" => Ok(FileTransferProtocol::Ftp(true)),
"S3" => Ok(FileTransferProtocol::AwsS3),
"SCP" => Ok(FileTransferProtocol::Scp),
"SFTP" => Ok(FileTransferProtocol::Sftp),
"S3" => Ok(FileTransferProtocol::AwsS3),
"SMB" => Ok(FileTransferProtocol::Smb),
_ => Err(s.to_string()),
}
}
@@ -104,6 +107,14 @@ mod tests {
FileTransferProtocol::from_str("scp").ok().unwrap(),
FileTransferProtocol::Scp
);
assert_eq!(
FileTransferProtocol::from_str("SMB").ok().unwrap(),
FileTransferProtocol::Smb
);
assert_eq!(
FileTransferProtocol::from_str("smb").ok().unwrap(),
FileTransferProtocol::Smb
);
assert_eq!(
FileTransferProtocol::from_str("S3").ok().unwrap(),
FileTransferProtocol::AwsS3
@@ -126,5 +137,6 @@ mod tests {
assert_eq!(FileTransferProtocol::Scp.to_string(), String::from("SCP"));
assert_eq!(FileTransferProtocol::Sftp.to_string(), String::from("SFTP"));
assert_eq!(FileTransferProtocol::AwsS3.to_string(), String::from("S3"));
assert_eq!(FileTransferProtocol::Smb.to_string(), String::from("SMB"));
}
}

View File

@@ -19,6 +19,7 @@ pub struct FileTransferParams {
pub enum ProtocolParams {
Generic(GenericProtocolParams),
AwsS3(AwsS3Params),
Smb(SmbParams),
}
/// Protocol params used by most common protocols
@@ -44,6 +45,19 @@ pub struct AwsS3Params {
pub new_path_style: bool,
}
/// Connection parameters for SMB protocol
#[derive(Debug, Clone)]
pub struct SmbParams {
pub address: String,
#[cfg(unix)]
pub port: u16,
pub share: String,
pub username: Option<String>,
pub password: Option<String>,
#[cfg(unix)]
pub workgroup: Option<String>,
}
impl FileTransferParams {
/// Instantiates a new `FileTransferParams`
pub fn new(protocol: FileTransferProtocol, params: ProtocolParams) -> Self {
@@ -66,6 +80,7 @@ impl FileTransferParams {
match &self.params {
ProtocolParams::AwsS3(params) => params.password_missing(),
ProtocolParams::Generic(params) => params.password_missing(),
ProtocolParams::Smb(params) => params.password_missing(),
}
}
@@ -74,6 +89,7 @@ impl FileTransferParams {
match &mut self.params {
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
ProtocolParams::Generic(params) => params.set_default_secret(secret),
ProtocolParams::Smb(params) => params.set_default_secret(secret),
}
}
}
@@ -117,6 +133,15 @@ impl ProtocolParams {
_ => None,
}
}
#[cfg(test)]
/// Retrieve SMB parameters if any
pub fn smb_params(&self) -> Option<&SmbParams> {
match self {
ProtocolParams::Smb(params) => Some(params),
_ => None,
}
}
}
// -- Generic protocol params
@@ -235,6 +260,61 @@ impl AwsS3Params {
}
}
// -- SMB params
impl SmbParams {
/// Instantiates a new `AwsS3Params` struct
pub fn new<S: AsRef<str>>(address: S, share: S) -> Self {
Self {
address: address.as_ref().to_string(),
#[cfg(unix)]
port: 445,
share: share.as_ref().to_string(),
username: None,
password: None,
#[cfg(unix)]
workgroup: None,
}
}
#[cfg(unix)]
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn username(mut self, username: Option<impl ToString>) -> Self {
self.username = username.map(|x| x.to_string());
self
}
pub fn password(mut self, password: Option<impl ToString>) -> Self {
self.password = password.map(|x| x.to_string());
self
}
#[cfg(unix)]
pub fn workgroup(mut self, workgroup: Option<impl ToString>) -> Self {
self.workgroup = workgroup.map(|x| x.to_string());
self
}
/// Returns whether a password is supposed to be required for this protocol params.
/// The result true is returned ONLY if the supposed secret is MISSING!!!
pub fn password_missing(&self) -> bool {
self.password.is_none()
}
/// Set password
#[cfg(unix)]
pub fn set_default_secret(&mut self, secret: String) {
self.password = Some(secret);
}
#[cfg(windows)]
pub fn set_default_secret(&mut self, _secret: String) {}
}
#[cfg(test)]
mod test {
@@ -304,6 +384,53 @@ mod test {
assert_eq!(params.new_path_style, true);
}
#[test]
fn should_init_smb_params() {
let params = SmbParams::new("localhost", "temp");
assert_eq!(&params.address, "localhost");
#[cfg(unix)]
assert_eq!(params.port, 445);
assert_eq!(&params.share, "temp");
#[cfg(unix)]
assert!(params.username.is_none());
#[cfg(unix)]
assert!(params.password.is_none());
#[cfg(unix)]
assert!(params.workgroup.is_none());
}
#[test]
#[cfg(unix)]
fn should_init_smb_params_with_optionals() {
let params = SmbParams::new("localhost", "temp")
.port(3456)
.username(Some("foo"))
.password(Some("bar"))
.workgroup(Some("baz"));
assert_eq!(&params.address, "localhost");
assert_eq!(params.port, 3456);
assert_eq!(&params.share, "temp");
assert_eq!(params.username.as_deref().unwrap(), "foo");
assert_eq!(params.password.as_deref().unwrap(), "bar");
assert_eq!(params.workgroup.as_deref().unwrap(), "baz");
}
#[test]
#[cfg(windows)]
fn should_init_smb_params_with_optionals() {
let params = SmbParams::new("localhost", "temp")
.username(Some("foo"))
.password(Some("bar"));
assert_eq!(&params.address, "localhost");
assert_eq!(&params.share, "temp");
assert_eq!(params.username.as_deref().unwrap(), "foo");
assert_eq!(params.password.as_deref().unwrap(), "bar");
}
#[test]
fn references() {
let mut params =