From 244661874ca29515ebd50917e8c45c2dfb676ec6 Mon Sep 17 00:00:00 2001 From: Elrendio Date: Sat, 19 Dec 2020 18:17:58 +0100 Subject: [PATCH] FIX - Re-add openssl --- Cargo.toml | 11 +- examples/connecting.rs | 5 +- src/data_stream.rs | 15 +- src/ftp.rs | 371 ++++++++++++++++++++++++++--------------- src/lib.rs | 37 +++- src/types.rs | 32 ++++ 6 files changed, 316 insertions(+), 155 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6042dd5b5..ed8ce1838 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,17 +19,18 @@ path = "src/lib.rs" [features] # Enable support of FTPS which requires openssl -secure = ["native-tls"] +secure = ["openssl"] # Add debug output (to STDOUT) of commands sent to the server # and lines read from the server debug_print = [] [dependencies] -lazy_static = "1.4.0" -regex = "1.4.2" -chrono = "0.4.19" +lazy_static = "1" +regex = "1" +chrono = "0.4" +openssl = { version = "0.10", optional = true } [dependencies.native-tls] -version = "^0.2" +version = "0.2" optional = true diff --git a/examples/connecting.rs b/examples/connecting.rs index 5da70f6e1..d93c23405 100644 --- a/examples/connecting.rs +++ b/examples/connecting.rs @@ -1,11 +1,10 @@ extern crate ftp; use ftp::{FtpError, FtpStream}; -use std::io::Cursor; -use std::str; +use std::{io::Cursor, str}; fn test_ftp(addr: &str, user: &str, pass: &str) -> Result<(), FtpError> { - let mut ftp_stream = FtpStream::connect((addr, 21)).unwrap(); + let (mut ftp_stream, _welcome_msg) = FtpStream::connect((addr, 21)).unwrap(); ftp_stream.login(user, pass).unwrap(); println!("current dir: {}", ftp_stream.pwd().unwrap()); diff --git a/src/data_stream.rs b/src/data_stream.rs index b78af4adf..ed02a6f03 100644 --- a/src/data_stream.rs +++ b/src/data_stream.rs @@ -1,13 +1,20 @@ -#[cfg(feature = "secure")] +#[cfg(all(feature = "secure", feature = "native-tls"))] use native_tls::TlsStream; -use std::io::{Read, Result, Write}; -use std::net::TcpStream; +#[cfg(all(feature = "secure", not(feature = "native-tls")))] +use openssl::ssl::SslStream; + +use std::{ + io::{Read, Result, Write}, + net::TcpStream, +}; /// Data Stream used for communications #[derive(Debug)] pub enum DataStream { Tcp(TcpStream), - #[cfg(feature = "secure")] + #[cfg(all(feature = "secure", not(feature = "native-tls")))] + Ssl(SslStream), + #[cfg(all(feature = "secure", feature = "native-tls"))] Ssl(TlsStream), } diff --git a/src/ftp.rs b/src/ftp.rs index 170db72c5..b5dfda20f 100644 --- a/src/ftp.rs +++ b/src/ftp.rs @@ -1,19 +1,26 @@ //! FTP module. -use super::data_stream::DataStream; -use super::status; -use super::types::{FileType, FtpError, Line, Result}; -use chrono::offset::TimeZone; -use chrono::{DateTime, Utc}; -#[cfg(feature = "secure")] +use super::{ + data_stream::DataStream, + status, + types::{FileType, FtpError, Line}, +}; + +use { + chrono::{offset::TimeZone, DateTime, Utc}, + regex::Regex, + std::{ + borrow::Cow, + io::{copy, BufRead, BufReader, BufWriter, Cursor, Read, Write}, + net::{SocketAddr, TcpStream, ToSocketAddrs}, + str::FromStr, + }, +}; + +#[cfg(all(feature = "secure", feature = "native-tls"))] use native_tls::TlsConnector; -use regex::Regex; -use std::borrow::Cow; -use std::io::{copy, BufRead, BufReader, BufWriter, Cursor, Read, Write}; -use std::net::ToSocketAddrs; -use std::net::{SocketAddr, TcpStream}; -use std::str::FromStr; -use std::string::String; +#[cfg(all(feature = "secure", not(feature = "native-tls")))] +use openssl::ssl::{Ssl, SslContext}; lazy_static! { // This regex extracts IP and Port details from PASV command response. @@ -31,54 +38,52 @@ lazy_static! { #[derive(Debug)] pub struct FtpStream { reader: BufReader, - welcome_msg: Option, - #[cfg(feature = "secure")] + #[cfg(all(feature = "secure", feature = "native-tls"))] tls_ctx: Option, - #[cfg(feature = "secure")] + #[cfg(all(feature = "secure", feature = "native-tls"))] domain: Option, + #[cfg(all(feature = "secure", not(feature = "native-tls")))] + ssl_cfg: Option, } impl FtpStream { - /// Creates an FTP Stream. + /// Creates an FTP Stream and returns the welcome message #[cfg(not(feature = "secure"))] - pub fn connect(addr: A) -> Result { - TcpStream::connect(addr) - .map_err(|e| FtpError::ConnectionError(e)) - .and_then(|stream| { - let mut ftp_stream = FtpStream { - reader: BufReader::new(DataStream::Tcp(stream)), - welcome_msg: None, - }; - match ftp_stream.read_response(status::READY) { - Ok(line) => { - ftp_stream.welcome_msg = Some(line.1); - Ok(ftp_stream) - } - Err(err) => Err(err), - } - }) + pub fn connect(addr: A) -> crate::Result<(FtpStream, String)> { + let mut ftp_stream = FtpStream { + reader: BufReader::new(DataStream::Tcp(TcpStream::connect(addr)?)), + }; + + let line = ftp_stream.read_response(status::READY)?; + + Ok((ftp_stream, line.1)) } - /// Creates an FTP Stream. - #[cfg(feature = "secure")] - pub fn connect(addr: A) -> Result { - TcpStream::connect(addr) - .map_err(|e| FtpError::ConnectionError(e)) - .and_then(|stream| { - let mut ftp_stream = FtpStream { - reader: BufReader::new(DataStream::Tcp(stream)), - welcome_msg: None, - tls_ctx: None, - domain: None, - }; - match ftp_stream.read_response(status::READY) { - Ok(line) => { - ftp_stream.welcome_msg = Some(line.1); - Ok(ftp_stream) - } - Err(err) => Err(err), - } - }) + /// Creates an FTP Stream and returns the welcome message + #[cfg(all(feature = "secure", feature = "native-tls"))] + pub fn connect(addr: A) -> crate::Result<(FtpStream, String)> { + let mut ftp_stream = FtpStream { + reader: BufReader::new(DataStream::Tcp(TcpStream::connect(addr)?)), + tls_ctx: None, + domain: None, + }; + + let line = ftp_stream.read_response(status::READY)?; + + Ok((ftp_stream, line.1)) + } + + /// Creates an FTP Stream and returns the welcome message + #[cfg(all(feature = "secure", not(feature = "native-tls")))] + pub fn connect(addr: A) -> crate::Result<(FtpStream, String)> { + let mut ftp_stream = FtpStream { + reader: BufReader::new(DataStream::Tcp(TcpStream::connect(addr)?)), + ssl_cfg: None, + }; + + let line = ftp_stream.read_response(status::READY)?; + + Ok((ftp_stream, line.1)) } /// Switch to a secure mode if possible, using a provided SSL configuration. @@ -98,22 +103,25 @@ impl FtpStream { /// // Create a TlsConnector /// // NOTE: For custom options see /// let mut ctx = TlsConnector::new().unwrap(); - /// let mut ftp_stream = FtpStream::connect("127.0.0.1:21").unwrap(); + /// let mut (ftp_stream, _welcome_msg) = FtpStream::connect("127.0.0.1:21").unwrap(); /// let mut ftp_stream = ftp_stream.into_secure(ctx, "localhost").unwrap(); /// ``` - #[cfg(feature = "secure")] - pub fn into_secure(mut self, tls_connector: TlsConnector, domain: &str) -> Result { + #[cfg(all(feature = "secure", feature = "native-tls"))] + pub fn into_secure( + mut self, + tls_connector: TlsConnector, + domain: &str, + ) -> crate::Result { // Ask the server to start securing data. self.write_str("AUTH TLS\r\n")?; self.read_response(status::AUTH_OK)?; - let stream = tls_connector - .connect(domain, self.reader.into_inner().into_tcp_stream()) - .map_err(|e| FtpError::SecureError(format!("{}", e)))?; + let mut secured_ftp_tream = FtpStream { - reader: BufReader::new(DataStream::Ssl(stream)), + reader: BufReader::new(DataStream::Ssl( + tls_connector.connect(domain, self.reader.into_inner().into_tcp_stream())?, + )), tls_ctx: Some(tls_connector), domain: Some(String::from(domain)), - welcome_msg: self.welcome_msg.clone(), }; // Set protection buffer size secured_ftp_tream.write_str("PBSZ 0\r\n")?; @@ -121,6 +129,7 @@ impl FtpStream { // Change the level of data protectio to Private secured_ftp_tream.write_str("PROT P\r\n")?; secured_ftp_tream.read_response(status::COMMAND_OK)?; + Ok(secured_ftp_tream) } @@ -137,7 +146,7 @@ impl FtpStream { /// /// // Create an TlsConnector /// let mut ctx = TlsConnector::new().unwrap(); - /// let mut ftp_stream = FtpStream::connect("127.0.0.1:21").unwrap(); + /// let mut (ftp_stream, _welcome_msg) = FtpStream::connect("127.0.0.1:21").unwrap(); /// let mut ftp_stream = ftp_stream.into_secure(ctx, "localhost").unwrap(); /// // Do all secret things /// // Switch back to the insecure mode @@ -145,8 +154,8 @@ impl FtpStream { /// // Do all public things /// let _ = ftp_stream.quit(); /// ``` - #[cfg(feature = "secure")] - pub fn into_insecure(mut self) -> Result { + #[cfg(all(feature = "secure", feature = "native-tls"))] + pub fn into_insecure(mut self) -> crate::Result { // Ask the server to stop securing data self.write_str("CCC\r\n")?; self.read_response(status::COMMAND_OK)?; @@ -154,40 +163,134 @@ impl FtpStream { reader: BufReader::new(DataStream::Tcp(self.reader.into_inner().into_tcp_stream())), tls_ctx: None, domain: None, - welcome_msg: self.welcome_msg.clone(), }; Ok(plain_ftp_stream) } - /// ### get_welcome_msg + /// Switch to a secure mode if possible, using a provided SSL configuration. + /// This method does nothing if the connect is already secured. + /// + /// ## Panics + /// + /// Panics if the plain TCP connection cannot be switched to TLS mode. + /// + /// ## Example + /// + /// ```rust,no_run + /// use std::path::Path; + /// use ftp::FtpStream; + /// use ftp::openssl::ssl::{ SslContext, SslMethod }; + /// + /// // Create an SslContext with a custom cert. + /// let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + /// let _ = ctx.set_ca_file(Path::new("/path/to/a/cert.pem")).unwrap(); + /// let ctx = ctx.build(); + /// let (mut ftp_stream, _welcome_msg) = FtpStream::connect("127.0.0.1:21").unwrap(); + /// let mut ftp_stream = ftp_stream.into_secure(ctx).unwrap(); + /// ``` + #[cfg(all(feature = "secure", not(feature = "native-tls")))] + pub fn into_secure(mut self, ssl_context: SslContext) -> crate::Result { + // Ask the server to start securing data. + self.write_str("AUTH TLS\r\n")?; + self.read_response(status::AUTH_OK)?; + + let mut secured_ftp_tream = FtpStream { + reader: BufReader::new(DataStream::Ssl( + Ssl::new(&ssl_context)? + .connect(self.reader.into_inner().into_tcp_stream()) + .map_err(|e| FtpError::SecureError(e.to_string()))?, + )), + ssl_cfg: Some(ssl_context), + }; + // Set protection buffer size + secured_ftp_tream.write_str("PBSZ 0\r\n")?; + secured_ftp_tream.read_response(status::COMMAND_OK)?; + // Change the level of data protectio to Private + secured_ftp_tream.write_str("PROT P\r\n")?; + secured_ftp_tream.read_response(status::COMMAND_OK)?; + + Ok(secured_ftp_tream) + } + + /// Switch to insecure mode. + /// + /// ## Example + /// + /// ```rust,no_run + /// use std::path::Path; + /// use ftp::FtpStream; + /// + /// use ftp::openssl::ssl::{ SslContext, SslMethod }; /// - /// Returns welcome message retrieved from server (if available) - pub fn get_welcome_msg(&self) -> Option { - self.welcome_msg.clone() + /// // Create an SslContext with a custom cert. + /// let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + /// let _ = ctx.set_ca_file(Path::new("/path/to/a/cert.pem")).unwrap(); + /// let ctx = ctx.build(); + /// let mut ftp_stream = FtpStream::connect("127.0.0.1:21").unwrap(); + /// let mut ftp_stream = ftp_stream.into_secure(ctx).unwrap(); + /// // Do all secret things + /// // Switch back to the insecure mode + /// let mut ftp_stream = ftp_stream.into_insecure().unwrap(); + /// // Do all public things + /// let _ = ftp_stream.quit(); + /// ``` + #[cfg(all(feature = "secure", not(feature = "native-tls")))] + pub fn into_insecure(mut self) -> crate::Result { + // Ask the server to stop securing data + self.write_str("CCC\r\n")?; + self.read_response(status::COMMAND_OK)?; + + let plain_ftp_stream = FtpStream { + reader: BufReader::new(DataStream::Tcp(self.reader.into_inner().into_tcp_stream())), + ssl_cfg: None, + }; + + Ok(plain_ftp_stream) } /// Execute command which send data back in a separate stream #[cfg(not(feature = "secure"))] - fn data_command(&mut self, cmd: &str) -> Result { - self.pasv() - .and_then(|addr| self.write_str(cmd).map(|_| addr)) - .and_then(|addr| TcpStream::connect(addr).map_err(|e| FtpError::ConnectionError(e))) - .map(|stream| DataStream::Tcp(stream)) + fn data_command(&mut self, cmd: &str) -> crate::Result { + let addr = self.pasv()?; + self.write_str(cmd)?; + Ok(DataStream::Tcp(TcpStream::connect(addr)?)) } /// Execute command which send data back in a separate stream - #[cfg(feature = "secure")] - fn data_command(&mut self, cmd: &str) -> Result { - self.pasv() - .and_then(|addr| self.write_str(cmd).map(|_| addr)) - .and_then(|addr| TcpStream::connect(addr).map_err(|e| FtpError::ConnectionError(e))) - .and_then(|stream| match self.tls_ctx { - Some(ref tls_ctx) => tls_ctx - .connect(self.domain.as_ref().unwrap(), stream) - .map(|stream| DataStream::Ssl(stream)) - .map_err(|e| FtpError::SecureError(format!("{}", e))), - None => Ok(DataStream::Tcp(stream)), - }) + #[cfg(all(feature = "secure", feature = "native-tls"))] + fn data_command(&mut self, cmd: &str) -> crate::Result { + let addr = self.pasv()?; + self.write_str(cmd)?; + let stream = TcpStream::connect(addr)?; + + Ok(match self.tls_ctx { + Some(ref tls_ctx) => { + DataStream::Ssl(tls_ctx.connect(self.domain.as_ref().unwrap(), stream)?) + } + None => DataStream::Tcp(stream), + }) + } + + /// Execute command which send data back in a separate stream + #[cfg(all(feature = "secure", not(feature = "native-tls")))] + fn data_command(&mut self, cmd: &str) -> crate::Result { + let addr = self.pasv()?; + self.write_str(cmd)?; + let stream = TcpStream::connect(addr)?; + + Ok(match self.ssl_cfg { + Some(ref ssl_cfg) => { + let mut ssl = Ssl::new(ssl_cfg)?; + if let DataStream::Ssl(ssl_stream) = self.reader.get_ref() { + unsafe { + // SAFETY: ssl_stream was also using the context from self.ssl_cfg + ssl.set_session(ssl_stream.ssl().session().unwrap())? + } + }; + DataStream::Ssl(ssl.connect(stream)?) + } + None => DataStream::Tcp(stream), + }) } /// Returns a reference to the underlying TcpStream. @@ -208,63 +311,57 @@ impl FtpStream { } /// Log in to the FTP server. - pub fn login(&mut self, user: &str, password: &str) -> Result<()> { + pub fn login(&mut self, user: &str, password: &str) -> crate::Result<()> { self.write_str(format!("USER {}\r\n", user))?; - self.read_response_in(&[status::LOGGED_IN, status::NEED_PASSWORD]) - .and_then(|Line(code, _)| { - if code == status::NEED_PASSWORD { - self.write_str(format!("PASS {}\r\n", password))?; - self.read_response(status::LOGGED_IN)?; - } - Ok(()) - }) + let Line(code, _) = self.read_response_in(&[status::LOGGED_IN, status::NEED_PASSWORD])?; + if code == status::NEED_PASSWORD { + self.write_str(format!("PASS {}\r\n", password))?; + self.read_response(status::LOGGED_IN)?; + } + Ok(()) } /// Change the current directory to the path specified. - pub fn cwd(&mut self, path: &str) -> Result<()> { + pub fn cwd(&mut self, path: &str) -> crate::Result<()> { self.write_str(format!("CWD {}\r\n", path))?; - self.read_response(status::REQUESTED_FILE_ACTION_OK) - .map(|_| ()) + self.read_response(status::REQUESTED_FILE_ACTION_OK)?; + Ok(()) } /// Move the current directory to the parent directory. - pub fn cdup(&mut self) -> Result<()> { + pub fn cdup(&mut self) -> crate::Result<()> { self.write_str("CDUP\r\n")?; - self.read_response_in(&[status::COMMAND_OK, status::REQUESTED_FILE_ACTION_OK]) - .map(|_| ()) + self.read_response_in(&[status::COMMAND_OK, status::REQUESTED_FILE_ACTION_OK])?; + Ok(()) } /// Gets the current directory - pub fn pwd(&mut self) -> Result { + pub fn pwd(&mut self) -> crate::Result { self.write_str("PWD\r\n")?; - self.read_response(status::PATH_CREATED) - .and_then( - |Line(_, content)| match (content.find('"'), content.rfind('"')) { - (Some(begin), Some(end)) if begin < end => { - Ok(content[begin + 1..end].to_string()) - } - _ => { - let cause = format!("Invalid PWD Response: {}", content); - Err(FtpError::InvalidResponse(cause)) - } - }, - ) + let Line(_, content) = self.read_response(status::PATH_CREATED)?; + match (content.find('"'), content.rfind('"')) { + (Some(begin), Some(end)) if begin < end => Ok(content[begin + 1..end].to_string()), + _ => { + let cause = format!("Invalid PWD Response: {}", content); + Err(FtpError::InvalidResponse(cause)) + } + } } /// This does nothing. This is usually just used to keep the connection open. - pub fn noop(&mut self) -> Result<()> { + pub fn noop(&mut self) -> crate::Result<()> { self.write_str("NOOP\r\n")?; self.read_response(status::COMMAND_OK).map(|_| ()) } /// This creates a new directory on the server. - pub fn mkdir(&mut self, pathname: &str) -> Result<()> { + pub fn mkdir(&mut self, pathname: &str) -> crate::Result<()> { self.write_str(format!("MKD {}\r\n", pathname))?; self.read_response(status::PATH_CREATED).map(|_| ()) } /// Runs the PASV command. - fn pasv(&mut self) -> Result { + fn pasv(&mut self) -> crate::Result { self.write_str("PASV\r\n")?; // PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). let Line(_, line) = self.read_response(status::PASSIVE_MODE)?; @@ -294,14 +391,14 @@ impl FtpStream { /// Sets the type of file to be transferred. That is the implementation /// of `TYPE` command. - pub fn transfer_type(&mut self, file_type: FileType) -> Result<()> { + pub fn transfer_type(&mut self, file_type: FileType) -> crate::Result<()> { let type_command = format!("TYPE {}\r\n", file_type.to_string()); self.write_str(&type_command)?; self.read_response(status::COMMAND_OK).map(|_| ()) } /// Quits the current FTP session. - pub fn quit(&mut self) -> Result<()> { + pub fn quit(&mut self) -> crate::Result<()> { self.write_str("QUIT\r\n")?; self.read_response(status::CLOSING).map(|_| ()) } @@ -310,7 +407,7 @@ impl FtpStream { /// This method is a more complicated way to retrieve a file. /// The reader returned should be dropped. /// Also you will have to read the response to make sure it has the correct value. - pub fn get(&mut self, file_name: &str) -> Result> { + pub fn get(&mut self, file_name: &str) -> crate::Result> { let retr_command = format!("RETR {}\r\n", file_name); let data_stream = BufReader::new(self.data_command(&retr_command)?); self.read_response_in(&[status::ABOUT_TO_SEND, status::ALREADY_OPEN])?; @@ -318,7 +415,7 @@ impl FtpStream { } /// Renames the file from_name to to_name - pub fn rename(&mut self, from_name: &str, to_name: &str) -> Result<()> { + pub fn rename(&mut self, from_name: &str, to_name: &str) -> crate::Result<()> { self.write_str(format!("RNFR {}\r\n", from_name))?; self.read_response(status::REQUEST_FILE_PENDING) .and_then(|_| { @@ -348,9 +445,9 @@ impl FtpStream { /// }).is_ok()); /// # assert!(conn.rm("retr.txt").is_ok()); /// ``` - pub fn retr(&mut self, filename: &str, reader: F) -> Result + pub fn retr(&mut self, filename: &str, reader: F) -> crate::Result where - F: Fn(&mut dyn Read) -> Result, + F: Fn(&mut dyn Read) -> crate::Result, { let retr_command = format!("RETR {}\r\n", filename); { @@ -382,7 +479,7 @@ impl FtpStream { /// assert_eq!(cursor.into_inner(), "hello, world!".as_bytes()); /// # assert!(conn.rm("simple_retr.txt").is_ok()); /// ``` - pub fn simple_retr(&mut self, file_name: &str) -> Result>> { + pub fn simple_retr(&mut self, file_name: &str) -> crate::Result>> { self.retr(file_name, |reader| { let mut buffer = Vec::new(); reader @@ -394,20 +491,20 @@ impl FtpStream { } /// Removes the remote pathname from the server. - pub fn rmdir(&mut self, pathname: &str) -> Result<()> { + pub fn rmdir(&mut self, pathname: &str) -> crate::Result<()> { self.write_str(format!("RMD {}\r\n", pathname))?; self.read_response(status::REQUESTED_FILE_ACTION_OK) .map(|_| ()) } /// Remove the remote file from the server. - pub fn rm(&mut self, filename: &str) -> Result<()> { + pub fn rm(&mut self, filename: &str) -> crate::Result<()> { self.write_str(format!("DELE {}\r\n", filename))?; self.read_response(status::REQUESTED_FILE_ACTION_OK) .map(|_| ()) } - fn put_file(&mut self, filename: &str, r: &mut R) -> Result<()> { + fn put_file(&mut self, filename: &str, r: &mut R) -> crate::Result<()> { let stor_command = format!("STOR {}\r\n", filename); let mut data_stream = BufWriter::new(self.data_command(&stor_command)?); self.read_response_in(&[status::ALREADY_OPEN, status::ABOUT_TO_SEND])?; @@ -417,7 +514,7 @@ impl FtpStream { } /// This stores a file on the server. - pub fn put(&mut self, filename: &str, r: &mut R) -> Result<()> { + pub fn put(&mut self, filename: &str, r: &mut R) -> crate::Result<()> { self.put_file(filename, r)?; self.read_response_in(&[ status::CLOSING_DATA_CONNECTION, @@ -432,7 +529,7 @@ impl FtpStream { cmd: Cow<'static, str>, open_code: u32, close_code: &[u32], - ) -> Result> { + ) -> crate::Result> { let data_stream = BufReader::new(self.data_command(&cmd)?); self.read_response_in(&[open_code, status::ALREADY_OPEN])?; let lines = Self::get_lines_from_stream(data_stream); @@ -440,7 +537,7 @@ impl FtpStream { lines } - fn get_lines_from_stream(data_stream: BufReader) -> Result> { + fn get_lines_from_stream(data_stream: BufReader) -> crate::Result> { let mut lines: Vec = Vec::new(); let mut lines_stream = data_stream.lines(); @@ -468,7 +565,7 @@ impl FtpStream { /// Execute `LIST` command which returns the detailed file listing in human readable format. /// If `pathname` is omited then the list of files in the current directory will be /// returned otherwise it will the list of files on `pathname`. - pub fn list(&mut self, pathname: Option<&str>) -> Result> { + pub fn list(&mut self, pathname: Option<&str>) -> crate::Result> { let command = pathname.map_or("LIST\r\n".into(), |path| { format!("LIST {}\r\n", path).into() }); @@ -486,7 +583,7 @@ impl FtpStream { /// Execute `NLST` command which returns the list of file names only. /// If `pathname` is omited then the list of files in the current directory will be /// returned otherwise it will the list of files on `pathname`. - pub fn nlst(&mut self, pathname: Option<&str>) -> Result> { + pub fn nlst(&mut self, pathname: Option<&str>) -> crate::Result> { let command = pathname.map_or("NLST\r\n".into(), |path| { format!("NLST {}\r\n", path).into() }); @@ -503,7 +600,7 @@ impl FtpStream { /// Retrieves the modification time of the file at `pathname` if it exists. /// In case the file does not exist `None` is returned. - pub fn mdtm(&mut self, pathname: &str) -> Result>> { + pub fn mdtm(&mut self, pathname: &str) -> crate::Result>> { self.write_str(format!("MDTM {}\r\n", pathname))?; let Line(_, content) = self.read_response(status::FILE)?; @@ -529,7 +626,7 @@ impl FtpStream { /// Retrieves the size of the file in bytes at `pathname` if it exists. /// In case the file does not exist `None` is returned. - pub fn size(&mut self, pathname: &str) -> Result> { + pub fn size(&mut self, pathname: &str) -> crate::Result> { self.write_str(format!("SIZE {}\r\n", pathname))?; let Line(_, content) = self.read_response(status::FILE)?; @@ -539,7 +636,7 @@ impl FtpStream { } } - fn write_str>(&mut self, command: S) -> Result<()> { + fn write_str>(&mut self, command: S) -> crate::Result<()> { if cfg!(feature = "debug_print") { print!("CMD {}", command.as_ref()); } @@ -550,12 +647,12 @@ impl FtpStream { .map_err(|send_err| FtpError::ConnectionError(send_err)) } - pub fn read_response(&mut self, expected_code: u32) -> Result { + pub fn read_response(&mut self, expected_code: u32) -> crate::Result { self.read_response_in(&[expected_code]) } /// Retrieve single line response - pub fn read_response_in(&mut self, expected_code: &[u32]) -> Result { + pub fn read_response_in(&mut self, expected_code: &[u32]) -> crate::Result { let mut line = String::new(); self.reader .read_line(&mut line) diff --git a/src/lib.rs b/src/lib.rs index f54802cb5..647fc45e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ //! //! ```rust //! use ftp::FtpStream; -//! let mut ftp_stream = FtpStream::connect("127.0.0.1:21").unwrap_or_else(|err| +//! let mut (ftp_stream, _welcome_msg) = FtpStream::connect("127.0.0.1:21").unwrap_or_else(|err| //! panic!("{}", err) //! ); //! let _ = ftp_stream.quit(); @@ -18,21 +18,41 @@ //! ### FTPS //! //! The client supports FTPS on demand. To enable it the client should be -//! compiled with feature `openssl` enabled what requires -//! [openssl](https://crates.io/crates/openssl) dependency. +//! compiled with feature `secure`. This crate supports two implementation of FTPS, one with [openssl]( +//! https://crates.io/crates/openssl) and one with [native-tls](https://crates.io/crates/native-tls). By default it uses +//! [openssl](https://crates.io/crates/openssl) and you can switch to [native-tls](https://crates.io/crates/native-tls) +//! with features `secure` and `native-tls` //! //! The client uses explicit mode for connecting FTPS what means you should //! connect the server as usually and then switch to the secure mode (TLS is used). //! For better security it's the good practice to switch to the secure mode //! before authentication. //! -//! ### FTPS Usage +//! ## FTPS Usage +//! +//! ```rust,no_run +//! use ftp::FtpStream; +//! use ftp::openssl::ssl::{ SslContext, SslMethod }; +//! +//! let (ftp_stream, _welcome_msg) = FtpStream::connect("127.0.0.1:21").unwrap(); +//! let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); +//! // Switch to the secure mode +//! let mut ftp_stream = ftp_stream.into_secure(ctx).unwrap(); +//! ftp_stream.login("anonymous", "anonymous").unwrap(); +//! // Do other secret stuff +//! // Switch back to the insecure mode (if required) +//! let mut ftp_stream = ftp_stream.into_insecure().unwrap(); +//! // Do all public stuff +//! let _ = ftp_stream.quit(); +//! ``` +//! +//! ## FTPS Usage with native-tls //! //! ```rust,no_run //! use ftp::FtpStream; //! use ftp::native_tls::{TlsConnector, TlsStream}; //! -//! let ftp_stream = FtpStream::connect("127.0.0.1:21").unwrap(); +//! let (ftp_stream, _welcome_msg) = FtpStream::connect("127.0.0.1:21").unwrap(); //! let mut ctx = TlsConnector::new().unwrap(); //! // Switch to the secure mode //! let mut ftp_stream = ftp_stream.into_secure(ctx, "localhost").unwrap(); @@ -50,8 +70,10 @@ extern crate lazy_static; extern crate chrono; extern crate regex; -#[cfg(feature = "secure")] +#[cfg(all(feature = "secure", feature = "native-tls"))] pub extern crate native_tls; +#[cfg(all(feature = "secure", not(feature = "native-tls")))] +pub extern crate openssl; mod data_stream; mod ftp; @@ -60,3 +82,6 @@ pub mod types; pub use self::ftp::FtpStream; pub use self::types::FtpError; + +/// A shorthand for a Result whose error type is always an FtpError. +pub type Result = ::std::result::Result; diff --git a/src/types.rs b/src/types.rs index 69c25401a..a5dde0cc1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -11,11 +11,43 @@ pub type Result = ::std::result::Result; #[derive(Debug)] pub enum FtpError { ConnectionError(::std::io::Error), + #[cfg(feature = "secure")] SecureError(String), InvalidResponse(String), InvalidAddress(::std::net::AddrParseError), } +impl From<::std::io::Error> for FtpError { + fn from(err: ::std::io::Error) -> Self { + FtpError::ConnectionError(err) + } +} + +#[cfg(all(feature = "secure", feature = "native-tls"))] +impl From> for FtpError { + fn from(err: native_tls::HandshakeError) -> Self { + FtpError::SecureError(err.to_string()) + } +} +#[cfg(all(feature = "secure", not(feature = "native-tls")))] +impl From for FtpError { + fn from(err: openssl::error::ErrorStack) -> Self { + FtpError::SecureError(err.to_string()) + } +} +#[cfg(all(feature = "secure", not(feature = "native-tls")))] +impl From> for FtpError { + fn from(err: openssl::ssl::HandshakeError) -> Self { + FtpError::SecureError(err.to_string()) + } +} + +impl From<::std::net::AddrParseError> for FtpError { + fn from(err: ::std::net::AddrParseError) -> Self { + FtpError::InvalidAddress(err) + } +} + /// Text Format Control used in `TYPE` command #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum FormatControl {