diff options
-rw-r--r-- | libftpconn/xfer.c | 176 |
1 files changed, 161 insertions, 15 deletions
diff --git a/libftpconn/xfer.c b/libftpconn/xfer.c index 07985406..ae4b3377 100644 --- a/libftpconn/xfer.c +++ b/libftpconn/xfer.c @@ -20,36 +20,179 @@ #include <unistd.h> #include <errno.h> +#include <string.h> +#include <netinet/in.h> #include <ftpconn.h> #include "priv.h" -/* Open a data connection, returning the file descriptor in DATA. */ +/* Open an active data connection, returning the file descriptor in DATA. */ static error_t -ftp_conn_open_data (struct ftp_conn *conn, int *data) +ftp_conn_start_open_actv_data (struct ftp_conn *conn, int *data) { - struct sockaddr *addr; - error_t err = ftp_conn_get_pasv_addr (conn, &addr); + error_t err = 0; - if (! err) + if (conn->actv_data_conn_queue < 0) { - int dsock = socket (PF_INET, SOCK_STREAM, 0); + /* DCQ is a socket on which we listen for data connections from the + server. */ + int dcq; + struct sockaddr *addr = conn->actv_data_addr; + size_t addr_len = sizeof *addr; - if (dsock < 0) - err = errno; - else if (connect (dsock, addr, addr->sa_len) < 0) + if (! addr) + /* Generate an address for the data connection (which we must know, + so we can tell the server). */ { - err = errno; - close (dsock); + addr = conn->actv_data_addr = malloc (sizeof (struct sockaddr_in)); + if (! addr) + return ENOMEM; + + /* Get the local address chosen by the system. */ + if (conn->control < 0) + err = EBADF; + else if (getsockname (conn->control, addr, &addr_len) < 0) + err = errno; + + if (err == EBADF || err == EPIPE) + /* Control connection has closed; reopen it and try again. */ + { + err = ftp_conn_open (conn); + if (!err && getsockname (conn->control, addr, &addr_len) < 0) + err = errno; + } + + if (err) + { + free (addr); + conn->actv_data_addr = 0; + return err; + } } + + dcq = socket (AF_INET, SOCK_STREAM, 0); + if (dcq < 0) + return errno; + +#if 0 + if (setsockopt (dcq, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof on) < 0) + err = errno; +#endif + + /* Let the system choose a port for us. */ + ((struct sockaddr_in *)addr)->sin_port = 0; + + /* Use ADDR as the data socket's local address. */ + if (!err && bind (dcq, addr, addr_len) < 0) + err = errno; + + /* See what port was chosen by the system. */ + if (!err && getsockname (dcq, addr, &addr_len) < 0) + err = errno; + + /* Set the incoming connection queue length. */ + if (!err && listen (dcq, 1) < 0) + err = errno; + + if (err) + close (dcq); else - *data = dsock; + conn->actv_data_conn_queue = dcq; + } + + if (! err) + /* By my reading of rfc959, we shouldn't have to re-send the data + connection address to the server after the first time (the servers + should always use the last-specified address), but bsd-derived ftp + servers don't work unless this is done. Aren't standards wonderful? */ + err = ftp_conn_send_actv_addr (conn, conn->actv_data_addr); + + if (! err) + *data = conn->actv_data_conn_queue; + + return err; +} - free (addr); +/* Finish opening the active data connection *DATA opened with + ftp_conn_start_open_actv_data, following the sending of the command that + uses the connection to the server. This function closes the file + descriptor in *DATA, and returns a new file descriptor for the actual data + connection. */ +static error_t +ftp_conn_finish_open_actv_data (struct ftp_conn *conn, int *data) +{ + struct sockaddr_in rmt_addr; + size_t rmt_addr_len = sizeof rmt_addr; + int real = accept (*data, &rmt_addr, &rmt_addr_len); + + if (real < 0) + return errno; + + *data = real; + + return 0; +} + +/* Return a data connection, which may not be in a completely open state; + this call should be followed by the command that uses the connection, and + a call to ftp_conn_finish_open_data, if that succeeds. */ +static error_t +ftp_conn_start_open_data (struct ftp_conn *conn, int *data) +{ + error_t err; + + if (conn->use_passive) + /* First try a passive connection. */ + { + struct sockaddr *addr; + + /* Tell the server we wan't to use passive mode, for which it should + give us an address to connect to. */ + err = ftp_conn_get_pasv_addr (conn, &addr); + + if (! err) + { + int dsock = socket (PF_INET, SOCK_STREAM, 0); + + if (dsock < 0) + err = errno; + else if (connect (dsock, addr, addr->sa_len) < 0) + { + err = errno; + close (dsock); + } + else + *data = dsock; + + free (addr); + } + } + else + err = EAGAIN; + + if (err) + /* Using a passive connection didn't work, try an active one. */ + { + conn->use_passive = 0; /* Don't try again. */ + err = ftp_conn_start_open_actv_data (conn, data); } return err; } + +/* Finish opening the data connection *DATA opened with + ftp_conn_start_open_data, following the sending of the command that uses + the connection to the server. This function may change *DATA, in which + case the old file descriptor is closed. */ +static error_t +ftp_conn_finish_open_data (struct ftp_conn *conn, int *data) +{ + if (conn->use_passive) + /* Passive connections should already have been completely opened. */ + return 0; + else + return ftp_conn_finish_open_actv_data (conn, data); +} /* Start a transfer command CMD/ARG, returning a file descriptor in DATA. POSS_ERRS is a list of errnos to try matching against any resulting error @@ -60,7 +203,7 @@ ftp_conn_start_transfer (struct ftp_conn *conn, const error_t *poss_errs, int *data) { - error_t err = ftp_conn_open_data (conn, data); + error_t err = ftp_conn_start_open_data (conn, data); if (! err) { @@ -69,7 +212,10 @@ ftp_conn_start_transfer (struct ftp_conn *conn, err = ftp_conn_cmd (conn, cmd, arg, &reply, &txt); if (!err && !REPLY_IS_PRELIM (reply)) - err = unexpected_reply (conn, reply, txt, poss_errs); + err = unexpected_reply (conn, reply, txt, poss_errs); + + if (! err) + err = ftp_conn_finish_open_data (conn, data); if (err) close (*data); |