FTP over SSH – Transferring Files from Third Server

ftpsftpsshssh-tunneltunneling

I have a machine A.
From it, I can SSH to machine B.
Machine B can FTP to machine C.
I want to download files from C, onto A.
I do not have access from A to C directly.

                 Port 20 and 21
┌───┐     ┌───┐  FTP Control    ┌─────────────────┐
│   │ SSH │   ├────────────────►│                 │
│ A ├────►│ B │                 │ C - with files  │
│   │     │   ├────────────────►│     I want      │
└───┘     └───┘  FTP data       │                 │
                 Random ports   └─────────────────┘

I know there's a million questions on this site about SFTP.
As far as I can tell, this is not the same as SFTP.
With SFTP it seems the SSH server must be the same server that has the files to be downloaded, which is not the problem here.
Is this correct? Or is there any way to do this with a simple SFTP command and some additional arguments?

Are there any easy ways to do this?
The files are large, and the disk and memory on B is tiny, so if possible I'd like to stream the data straight through. (Compared to doing a two-step FTP download onto the disk of B.)

In general SSH can tunnel anything. But apparently FTP uses more than just port 20 and 21. It uses a whole bunch of random and unpredictable ports, new ones for each file operation. The possible range of such FTP ports is so large that with one client I tried, I wasn't able to just port-forward the whole range with SSH.
(Note that I'm trying passive FTP, where all network connections are initiated from client the server. As opposed to active FTP where C would initiate a connection back to B, which isn't possible because of a NAT between them.)

I've tried to write a script in python hacking together the FTP standard library and a 3rd-party SSH tunnel library. It's a pretty convoluted and janky solution, that results in me openning up a new port for every new file, but never closing it. Also a recent upgrade to one library broke some dependencies so now the script doesn't work with the latest version of these libraries. I'm tempted to re-write the solution with the underlying Paramiko library. But I fear that's a deep rabbit-hole. This stuff is really fiddly. (Let me know if you need to see my attempt. I'm trying to omit it to avoid the X/Y problem.)

Is there a simpler way to do this tunneling?

I would prefer a solution with Python, but at this point I'm desperate enough to use any tool.

Best Answer

The "conventional" solution for that is to use an FTP client that has SOCKS 5 support and rather than forwarding specific ports over ssh, configure dynamic port forwarding (the ssh client will act as SOCKS 5 server)

Then on host A:

ssh -D localhost:10108 user@host-B 

And configure your FTP client to use a SOCKS5 proxy on that port, for example in WinSCP https://winscp.net/eng/docs/ui_login_proxy or edit the ncftp configuration file $HOME/.ncftp/firewall or use

curl --socks5-hostname localhost:10108 ftp://host-c/path/to/file