From 266bfa2392e82ee53f786fd1bde774a665fa1094 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 27 Jun 2022 12:36:15 +0200 Subject: [PATCH] implicit ftps --- src/async_ftp/mod.rs | 85 ++++++++++++++++++++++++++++++++++++++---- src/sync_ftp/mod.rs | 89 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 160 insertions(+), 14 deletions(-) diff --git a/src/async_ftp/mod.rs b/src/async_ftp/mod.rs index c0baf24..4b6b421 100644 --- a/src/async_ftp/mod.rs +++ b/src/async_ftp/mod.rs @@ -98,13 +98,9 @@ impl FtpStream { } } - /// Switch to a secure mode if possible, using a provided SSL configuration. + /// Switch to secure mode if possible (FTPS), 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 @@ -154,6 +150,60 @@ impl FtpStream { Ok(secured_ftp_tream) } + /// Switch to implicit secure mode (FTPS), using a provided SSL configuration. + /// + /// + /// ## Example + /// + /// ```rust,no_run + /// use suppaftp::FtpStream; + /// use suppaftp::async_native_tls::{TlsConnector, TlsStream}; + /// use std::path::Path; + /// + /// // Create a TlsConnector + /// // NOTE: For custom options see + /// let mut ctx = TlsConnector::new(); + /// let mut ftp_stream = FtpStream::connect("127.0.0.1:21").await.unwrap(); + /// let mut ftp_stream = ftp_stream.into_secure(ctx, "localhost:990", "localhost").await.unwrap(); + /// ``` + #[cfg(feature = "async-secure")] + pub async fn into_secure_implicit( + mut self, + tls_connector: TlsConnector, + addr: A, + domain: &str, + ) -> FtpResult { + debug!("Switching to FTPS (implicit)"); + self.reader = TcpStream::connect(addr) + .await + .map_err(FtpError::ConnectionError) + .map(|stream| BufReader::new(DataStream::Tcp(stream)))?; + debug!("Established connection with server"); + debug!("TLS OK; initializing ssl stream"); + let stream = tls_connector + .connect(domain, self.reader.into_inner().into_tcp_stream()) + .await + .map_err(|e| FtpError::SecureError(format!("{}", e)))?; + debug!("TLS Steam OK"); + let mut stream = FtpStream { + reader: BufReader::new(DataStream::Ssl(stream)), + mode: self.mode, + tls_ctx: Some(tls_connector), + domain: Some(String::from(domain)), + welcome_msg: self.welcome_msg, + }; + debug!("Reading server response..."); + match stream.read_response(Status::Ready).await { + Ok(response) => { + debug!("Server READY; response: {}", response.body); + self.welcome_msg = Some(response.body); + } + Err(err) => return Err(err), + } + + Ok(stream) + } + /// Enable active mode for data channel pub fn active_mode(mut self) -> Self { self.mode = Mode::Active; @@ -611,7 +661,7 @@ impl FtpStream { let msb = addr.port() / 256; let lsb = addr.port() % 256; - let ip_port = format!("{},{},{}", ip.to_string().replace(".", ","), msb, lsb); + let ip_port = format!("{},{},{}", ip.to_string().replace('.', ","), msb, lsb); debug!("Active mode, listening on {}:{}", ip, addr.port()); debug!("Running PORT command"); @@ -743,7 +793,7 @@ mod test { #[async_attributes::test] #[cfg(feature = "async-secure")] #[serial] - async fn connect_ssl() { + async fn should_connect_ssl() { crate::log_init(); let ftp_stream = FtpStream::connect("test.rebex.net:21").await.unwrap(); let mut ftp_stream = ftp_stream @@ -761,6 +811,27 @@ mod test { assert!(ftp_stream.quit().await.is_ok()); } + #[async_attributes::test] + #[serial] + #[cfg(feature = "async-secure")] + async fn should_connect_ssl_implicit() { + crate::log_init(); + let ftp_stream = FtpStream::connect("test.rebex.net:21").await.unwrap(); + let mut ftp_stream = ftp_stream + .into_secure_implicit(TlsConnector::new(), "test.rebex.net:990", "test.rebex.net") + .await + .ok() + .unwrap(); + // Set timeout (to test ref to ssl) + assert!(ftp_stream.get_ref().await.set_ttl(255).is_ok()); + // Login + assert!(ftp_stream.login("demo", "password").await.is_ok()); + // PWD + assert_eq!(ftp_stream.pwd().await.ok().unwrap().as_str(), "/"); + // Quit + assert!(ftp_stream.quit().await.is_ok()); + } + #[async_attributes::test] #[cfg(feature = "async-secure")] #[serial] diff --git a/src/sync_ftp/mod.rs b/src/sync_ftp/mod.rs index e216cb3..0f62da6 100644 --- a/src/sync_ftp/mod.rs +++ b/src/sync_ftp/mod.rs @@ -113,13 +113,9 @@ impl FtpStream { self.mode = mode; } - /// Switch to a secure mode if possible, using a provided SSL configuration. + /// Switch to explicit secure mode if possible (FTPS), 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 @@ -160,6 +156,58 @@ impl FtpStream { Ok(secured_ftp_tream) } + /// Switch to implicit secure mode (FTPS), using a provided SSL configuration. + /// + /// + /// ## Example + /// + /// ```rust,no_run + /// use suppaftp::FtpStream; + /// use suppaftp::native_tls::{TlsConnector, TlsStream}; + /// use std::path::Path; + /// + /// // 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 = ftp_stream.into_secure_implicit(ctx, "localhost:990", "localhost").unwrap(); + /// ``` + #[cfg(feature = "secure")] + pub fn into_secure_implicit( + mut self, + tls_connector: TlsConnector, + addr: A, + domain: &str, + ) -> FtpResult { + debug!("Switching to FTPS (implicit)"); + self.reader = TcpStream::connect(addr) + .map_err(FtpError::ConnectionError) + .map(|stream| BufReader::new(DataStream::Tcp(stream)))?; + debug!("Established connection with server"); + debug!("TLS OK; initializing ssl stream"); + let stream = tls_connector + .connect(domain, self.reader.into_inner().into_tcp_stream()) + .map_err(|e| FtpError::SecureError(format!("{}", e)))?; + debug!("TLS Steam OK"); + let mut stream = FtpStream { + reader: BufReader::new(DataStream::Ssl(stream.into())), + mode: self.mode, + tls_ctx: Some(tls_connector), + domain: Some(String::from(domain)), + welcome_msg: self.welcome_msg, + }; + debug!("Reading server response..."); + match stream.read_response(Status::Ready) { + Ok(response) => { + debug!("Server READY; response: {}", response.body); + self.welcome_msg = Some(response.body); + } + Err(err) => return Err(err), + } + + Ok(stream) + } + /// Returns welcome message retrieved from server (if available) pub fn get_welcome_msg(&self) -> Option<&str> { self.welcome_msg.as_deref() @@ -676,7 +724,7 @@ impl FtpStream { let msb = addr.port() / 256; let lsb = addr.port() % 256; - let ip_port = format!("{},{},{}", ip.to_string().replace(".", ","), msb, lsb); + let ip_port = format!("{},{},{}", ip.to_string().replace('.', ","), msb, lsb); debug!("Active mode, listening on {}:{}", ip, addr.port()); debug!("Running PORT command"); @@ -751,7 +799,7 @@ mod test { #[test] #[serial] #[cfg(feature = "secure")] - fn connect_ssl() { + fn should_connect_ssl() { crate::log_init(); let ftp_stream = FtpStream::connect("test.rebex.net:21").unwrap(); let mut ftp_stream = ftp_stream @@ -792,6 +840,33 @@ mod test { assert!(ftp_stream.quit().is_ok()); } + #[test] + #[serial] + #[cfg(feature = "secure")] + fn should_connect_ssl_implicit() { + crate::log_init(); + let ftp_stream = FtpStream::connect("test.rebex.net:21").unwrap(); + let mut ftp_stream = ftp_stream + .into_secure_implicit( + TlsConnector::new().unwrap(), + "test.rebex.net:990", + "test.rebex.net", + ) + .ok() + .unwrap(); + // Set timeout (to test ref to ssl) + assert!(ftp_stream + .get_ref() + .set_read_timeout(Some(Duration::from_secs(10))) + .is_ok()); + // Login + assert!(ftp_stream.login("demo", "password").is_ok()); + // PWD + assert_eq!(ftp_stream.pwd().ok().unwrap().as_str(), "/"); + // Quit + assert!(ftp_stream.quit().is_ok()); + } + #[test] #[serial] fn should_change_mode() {