[QUESTION] - TITLE - FTPS - TLS session of data connection not resumed error while using FileZilla server
Is this bug solved by Suppaftp https://github.com/mattnenterprise/rust-ftp/issues/96 ? The library is not maintained so I thought to use Suppaftp
I wrote a simple program with suppaftp to connect to filezilla ftps server version 1.9.2 and list directory but I get below error
<Date/Time> Info [Type] Message
<02-11-2024 12:10:21> FTP Session 106 127.0.0.1 testuser [Error] TLS session of data connection not resumed error.
Following is the script I tried to run
use suppaftp::{NativeTlsFtpStream, NativeTlsConnector};
use suppaftp::native_tls::TlsConnector;
fn main() {
// Connect to the FTP server in passive mode
let ftp_stream = NativeTlsFtpStream::connect("127.0.0.1:21").unwrap();
let tls_connector = TlsConnector::builder()
.danger_accept_invalid_certs(true) // Only for testing; not recommended in production
.build()
.unwrap();
// Convert the connector into the type expected by suppaftp
let native_tls_connector = NativeTlsConnector::from(tls_connector);
// Switch to secure mode using the connector with modified validation settings
let mut ftp_stream = ftp_stream.into_secure(native_tls_connector, "127.0.0.1").unwrap();
// Log in to the FTP server
ftp_stream.login("testuser", "test$123456").unwrap();
// List files in the current directory
match ftp_stream.list(None) {
Ok(files) => {
for file in files {
println!("{}", file);
}
}
Err(e) => {
eprintln!("Failed to list files: {}", e);
}
}
// Quit the FTP session
assert!(ftp_stream.quit().is_ok());
}
My cargo.toml looks like this
[package]
name = "ftp_client"
version = "0.1.0"
edition = "2021"
[dependencies]
suppaftp = { version = "^6", features = ["native-tls"] }
native-tls = "0.2.12"
openssl = { version = "0.10.68", features = ["vendored"] }
The login to the server happens fine. The error occurs when you call commands like to list directory or any other file actions. Before raising a bug , I wanted ask a question, if this is solved or not yet.
Note: The server is working fine because the same when I tried with node js code am able to do the directory listing, so it looks like to me the library lacks support for this feature. Can you confirm or am I doing something wrong? Thanks
I'm not sure if this is related to fervand1's issue or not, but I am also having issues with FileZilla.
When retrieving files, sometimes errors such as: Invalid response: [226] 226 Operation successful and Connection error: No connection could be made because the target machine actively refused it. (os error 10061) are returned.
This seems to happen when FileZilla responds with 150 About to start data transfer.
I think suppaftp is supposed to chill out on this response.
However, immediately afterward suppaftp sends PASV which seemingly messes things up?
FileZilla complains about this and says A command is already being processed. Queuing the new one until the current one is finished.
I believe that at this point suppaftp returns Connection error: No connection could be made to my program
@leontoeides I spent few days on this trying to understand why it is happening. And seems like the issue is with session resumption support. Am not an expert on this topic but here is what I found and hopefully might help you as well Suppaftp supports FTP via TLS through native-tls and rustls. After researching a bit I found out that the native-tls crate doesn't have support for session resumption and hence we are seeing the behaviour where upon successful login it disconnects when we try to list or do any other file/dir related actions.
Rustls on the other hand has the support for this function which filezilla server is applying by default refer https://docs.rs/rustls/latest/rustls/client/struct.ClientConfig.html#structfield.resumption . Following is the script that worked for me. FYI ignore the implementation of bypass with NoVerifier (will implement it proper to accept a proper certificate or rootstore)
use std::sync::Arc;
use suppaftp::{RustlsFtpStream, RustlsConnector};
use suppaftp::rustls::{ClientConfig};
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
#[derive(Debug)]
struct NoVerifier;
fn main(){
let config = ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoVerifier))
.with_no_client_auth();
// Create a connection to an FTP server and authenticate to it.
let rc_config = Arc::new(config);
let mut ftp_stream = RustlsFtpStream::connect("127.0.0.1:21")
.unwrap()
.into_secure(RustlsConnector::from(rc_config), "127.0.0.1")
.unwrap();
ftp_stream.login("testuser", "test$123456")
.expect("Failed to log in to FTP server");
println!("Successfully connected and logged in!");
match ftp_stream.pwd() {
Ok(current_dir) => println!("Current directory: {}", current_dir),
Err(e) => eprintln!("Error retrieving current directory: {}", e),
}
match ftp_stream.list(Some("/")) {
Ok(dir_list) => {
println!("Directory listing:");
for entry in dir_list {
println!("{}", entry); // Print each entry
}
}
Err(e) => eprintln!("Error listing directory: {}", e), // Handle any errors
}
// Terminate the connection to the server.
let _ = ftp_stream.quit();
}
/// DANGER: This custom implementation of the SeverCertVerifier
/// is really dangerous and return success for all and everything.
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
vec![
rustls::SignatureScheme::RSA_PKCS1_SHA1,
rustls::SignatureScheme::ECDSA_SHA1_Legacy,
rustls::SignatureScheme::RSA_PKCS1_SHA256,
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::RSA_PKCS1_SHA384,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::RSA_PKCS1_SHA512,
rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
rustls::SignatureScheme::RSA_PSS_SHA256,
rustls::SignatureScheme::RSA_PSS_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA512,
rustls::SignatureScheme::ED25519,
rustls::SignatureScheme::ED448,
]
}
}
In cargo.toml, enable the rustls feature
suppaftp = { version = "^6", features = ["rustls"] }
See if this helps. Thanks