Disclaimer: The first thing you should know is that RFC959 was written a while after FTP became popular and there is still some broken software based on the lack of specification there was before (and some time after) RFC959 was published. Many older (and more stable) FTP libraries have some special handling for some servers to make sure it works the way you want 99.9% of the time. This is especially more common with handling of extensions to the FTP protocol.
The rest of my answer assumes the server is RFC959 compliant.
Also keep in mind that bypassing your FTP client library's higher-level request/response management means you'll need to re-implement a part of this library yourself. This means you should be comfortable with the specification since you'll need to refer to it. Where possible, I'll refer to the appropriate sections so that you can get around.
In an case, I strongly recommend that you debug your problems by stepping into PHP's FTP client library rather than implementing all of this yourself. It it's possible, you should really request that the library output all the commands it's using. All that being said, I will still guide you through the procedure to help diagnose your problem.
Managing the FTP data connection is somewhat of a pain. It's not as easy as it looks at first glance if you want to support all optional parts of the specification. Exactly how you transfer files mostly depends on the current state of the following options:
- the data type (section 3.1.1): transferring files is usually safest and most efficient using the image/binary data type. This is not the default and some FTP commands (such as directory listings) require setting it to ASCII, so make sure you always set it before a transfer.
- the data structure (section 3.1.2): the file structure is usually what you want, but some older computers and mainframes might have to convert to and from this mode.
- the transmission mode (section 3.4): the stream mode is most commonly used, but the block mode supports resuming interrupted transfers and compressed mode is of little interest.
- the connection mode (sections 3.2 and 3.3): either the client or the server may establish the data connection by connecting to the its peer. This must be negotiated using either:
- default: the client listens on port 20; or
- a custom port: the client tells the server it's listing on another port; or
- passive mode: the client asks on which port the server will listen.
Pay attention to the specification because some configurations allow you to keep the data connection open while other may require you to close it (e.g. stream mode). If the data connection is already open, then you don't need to reconnect to the server on each transfer.
All this seems really complicated, but it's just informative. It might come in handy while you're debugging. There are really only two popular ways to transfer files using FTP:
The server connects to the client on a second port and sends the file in the image (binary) data type using the file data structure.
Configure the data type (required):
TYPE I
Configure the data structure (optional, default):
STRU F
Configure the transfer mode (optional, default):
MODE S
Choose an available port (this may be somewhat more subtle than you think) and start listening. If you choose the default port (20), skip the next step.
choose port, create socket, listen on selected port.
Tell the server we'll be listening on a given port (optional if default port is selected, but it doesn't hurt to be careful):
PORT your-public-ip-address, selected-port
Tell the server to expect a file transfer:
STOR remote-file-name
Wait for incoming connection from the server.
Send the file contents
open file, send contents, close file
Close the data connection
close socket.
The client connects to the server on a second port and sends the file in the image (binary) data type using the file data structure.
Configure the data type (required):
TYPE I
Configure the data structure (optional, default):
STRU F
Configure the transfer mode (optional, default):
MODE S
Tell the server we'll be listening on a given port (required):
PASV
Read the PASV command response containing IP address and port number the server is listening on.
Tell the server to expect a file transfer:
STOR remote-file-name
Establish connection:
connect to server on IP address and port number from PASV response
Send the file contents
open file, send contents, close file
Close the data connection
close socket.
There are some inconvenient issues with the first method since choosing a port is a little tricky (after using a port, you need to wait for a short period before using it again) and your firewall or ISP may block incoming connections on some ports, etc. The second method is easiest and should be preferred unless the server rejects it.