veeso / suppaftp

a super FTP/FTPS client library for Rust with support for both passive and active mode
Apache License 2.0
121 stars 31 forks source link

[QUESTION] - Passive mode with custom provided TcpStream #89

Closed markhaehnel closed 1 month ago

markhaehnel commented 1 month ago

Hi, i'm trying to connect to a FTP server over a SOCKS5 proxy. Connecting works fine.

I just connect with FtpStream::connect_with_stream(stream.into_inner())? where stream is my SOCKS5 TcpStream.

The problem is now: In passive mode, when i want to retrieve a file, the connection to the server is not done via the proxy (provided TcpStream). It instantiates a new TcpStream internally.

Is there a way to achieve connecting over a custom provided TcpStream when doing retr_as_stream?

Best regards, Mark

veeso commented 1 month ago

Hi,

I'm not sure how it should work though. The TcpStream is instantiated using the addr returned by pasv, so how can you know the address prior to this? If you could provide more details with a full flow it would be great for me to understand exactly how it should be implemented.

markhaehnel commented 1 month ago

The current flow is as follows:

let stream = Socks5Stream::connect("localhost:1080", address)?;
let mut client = FtpStream::connect_with_stream(stream.into_inner())?;

client.set_mode(Mode::Passive);
client.login(username, password)?;

let mut file_to_write = std::fs::File::create(format!("dl/{}", filename))?;
let mut retr_stream = client.retr_as_stream(filename)?;
std::io::copy(&mut retr_stream, &mut file_to_write)?;
client.finalize_retr_stream(retr_stream)?;

client.quit()?;

For solving this my idea would be that one could provide a optional function that creates a TcpStream for the client. Kind of like this:

let stream = Socks5Stream::connect("localhost:1080", address)?;
let mut client = FtpStream::connect_with_stream(stream.into_inner())?;

client.stream_builder(|address| {
    let stream = TcpStream::connect(address)?;
    Ok(stream)

    // or if you want to use a socks5 proxy
    let stream = Socks5Stream::connect("localhost:1080", address)?;
    Ok(stream.into_inner())
});

client.set_mode(Mode::Passive);
client.login(username, password)?;

let mut file_to_write = std::fs::File::create(format!("dl/{}", filename))?;
let mut retr_stream = client.retr_as_stream(filename)?;
std::io::copy(&mut retr_stream, &mut file_to_write)?;
client.finalize_retr_stream(retr_stream)?;

client.quit()?;

This function would be called whenever the client needs to create a new TcpStream for the data connection. For example here.

What do you think of this?

BTW, i would be happy to provide the implementation in a PR once we agree on a solution.

veeso commented 1 month ago

I guess it could work. I'll try to implement this.

markhaehnel commented 1 month ago

Mind if I submit a Pull Request with my proposed changes?

veeso commented 1 month ago

I actually was already implementing it 😅

markhaehnel commented 1 month ago

No problem, go on! 😄