Search code examples
c++socketsftp

Simple FTP Client C++


I'm attempting to create a simple FTP client in C/C++ that will do simple operations (connect, retrieve file). What I have working so far is the connection and login. I connect using sockets onto port 21, like any regular FTP client. The trouble I'm having is connecting to the port that is specified when the command PASV is entered. I get the message, parse it, then calculate the port from the replay message when PASV is entered.

227 Entering Passive Mode (a1, a2, a3, a4, p1, p2) 
DataPort = (p1 * 256) + p2

Once I have the port, I try to create another socket and connecting to it the same way. That's where my issues are. My code so far is posted below. I don't know if I need to get the server address again the same way. I'm not getting a response back from the server (if I'm actually suppose to get one, I don't know) Please ask any questions or concerns, thanks.

const int FTP_PORT = 21;        // Server Port
const int SIZE = 1024;          // Size of Buffers
char receiveBuff[SIZE];         // Buffer to send to the server
char sendBuff[SIZE];            // Buffer to receive from server
char pasvBuff[] = "pasv";       // Buffer to see if PASV Command was entered
char quitBuff[] = "QUIT";       // Buffer to see if QUIT Command was entered
char pasvMessage[100];          // String for PASV information

int main(int argc, char *argv[])
{
    int length = 0, i=0;
    int a1, a2, a3, a4, p1, p2, dataPort;       //PASV Information

    /* Get Server Name from User */
    if (argc != 2)
    {
        cerr << "Usage: " << argv[0] << " server" << endl;
        return 1;
    }

    /* Obtain Host (Server) Info  */
    struct hostent *host;
    host = gethostbyname(argv[1]);
    if (host == (struct hostent *)NULL)
    {
        perror("Client: gethostbyname");
        return 2;
    }

    /* Add Server Information */
    struct sockaddr_in servAdr;             // Internet address of server
    memset(&servAdr, 0, sizeof(servAdr));   // Clear structure
    servAdr.sin_family = AF_INET;           // Set address typedef
    memcpy(&servAdr.sin_addr, host->h_addr, host->h_length);
    servAdr.sin_port = htons(FTP_PORT);     // Use FTP port

    /* Create Socket to Connect to FTP Server */
    int origSock;                   // Original socket in client
    if ((origSock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("Client: generate error");
        return 3;
    }

    /* Connect to FTP Server on Port 21 */
    if (connect(origSock, (struct sockaddr *)&servAdr, sizeof(servAdr)) < 0)
    {
        perror("Client: connect error");
        return 4;
    }

    /* Get Conenct Message and Print to Screen */
    read(origSock, receiveBuff, sizeof(receiveBuff) - 1);
    write(fileno(stdout), receiveBuff, sizeof(receiveBuff) - 1);

    do
    {
        /* Clear Buffers */
        memset(receiveBuff, 0, SIZE);
        memset(sendBuff, 0, SIZE);

        write(fileno(stdout), "Please enter a FTP Command: ", 28);      // Write User Interface
        read(fileno(stdin), sendBuff, SIZE);                            // Read Command from User
        send(origSock, sendBuff, strlen(sendBuff) , 0);                 // Send Command to Server

        read(origSock, receiveBuff, sizeof(receiveBuff) - 1);           // Read Response from Server
        write(fileno(stdout), receiveBuff, sizeof(receiveBuff) - 1);    // Print Response from Server to screen

        /* If PASV Command was Entered */
        if (strncmp(sendBuff, pasvBuff, 4) == 0)
        {
            sscanf(receiveBuff, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)", &a1,&a2,&a3,&a4,&p1,&p2);
            dataPort = (p1 * 256) + p2;

            struct sockaddr_in servAdr2;                // Internet address of server
            memset(&servAdr2, 0, sizeof(servAdr2));     // Clear structure
            servAdr2.sin_family = AF_INET;              // Set address typedef
            memcpy(&servAdr2.sin_addr, host->h_addr, host->h_length);
            servAdr.sin_port = htons(dataPort);         // Use FTP port

            /* Create Socket to Connect to FTP Server */
            int dataSock;                               // Data socket in client
            if ((dataSock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
            {
                perror("Client: generate error");
                return 3;
            }

            /* Connect to FTP Server on Data Port */
            if (connect(dataSock, (struct sockaddr *)&servAdr, sizeof(servAdr)) < 0)
            {
                perror("Client: connect error");
                return 4;
            }

            read(dataSock, receiveBuff, sizeof(receiveBuff) - 1);
            write(fileno(stdout), receiveBuff, sizeof(receiveBuff) - 1);
        }

    } while (strncmp(sendBuff, quitBuff, 4) != 0);      // Go until QUIT Command is entered

    close(origSock);

    return 0;
}

Solution

  • When you are parsing the PASV reply, you are populating the servAdr2 variable, except for its sin_port field. You are assigning the reported port to the servAdr.sin_port field instead. You are then connecting the data socket using servAdr instead of servAdr2. So, you are effectively connecting the data socket to the original IP address of the server on the reported port, instead of connecting to the reported IP address (which can be different than the server IP). a1-a4 are the IPv4 octets of the IP address you should be connecting to.

    That said, if the server supports the EPSV command, you really should use that instead. It is much easier to parse then PASV, as PASV does not have a standardized format (so be prepared to parse multiple vendor-specific formats). EPSV solves that problem by standardizing the format in a machine-parsable manner.

    As for why you are not getting any response, it is because you are not telling the server to send any files over the open data connection. Sending PASV merely opens the server's data port. After you connect to it, you then have to send a STOR or RETR command on the control socket to actually perform a file transfer over the data socket. You also have to read the server's final response on the control socket after the transfer is finished, before you can then send any new commands.