diff options
author | Gokhan Sengun <gokhansengun@gmail.com> | 2011-12-19 14:35:20 +0100 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2011-12-20 20:30:02 +0100 |
commit | c834213ad52c52431e9ca597862dc81839cabe84 (patch) | |
tree | 678dfd82d3129b8aef8a0222defc55244003203b /lib/ftp.c | |
parent | 5527417afae062d7a2b12ee80730a0ab22b86eab (diff) |
FTP: perform active connections non-blocking
1- Two new error codes are introduced.
CURLE_FTP_ACCEPT_FAILED to be set whenever ACCEPTing fails because of
FTP server connected.
CURLE_FTP_ACCEPT_TIMEOUT to be set whenever ACCEPTing timeouts.
Neither of these errors are considered fatal and control connection
remains OK because it could just be a firewall blocking server to
connect to the client.
2- One new setopt option was introduced.
CURLOPT_ACCEPTTIMEOUT_MS
It sets the maximum amount of time FTP client is going to wait for a
server to connect. Internal default accept timeout is 60 seconds.
Diffstat (limited to 'lib/ftp.c')
-rw-r--r-- | lib/ftp.c | 364 |
1 files changed, 271 insertions, 93 deletions
@@ -108,6 +108,8 @@ #endif /* Local API functions */ +static void state(struct connectdata *conn, + ftpstate newstate); static CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote); static CURLcode ftp_quit(struct connectdata *conn); @@ -150,6 +152,11 @@ static void wc_data_dtor(void *ptr); static CURLcode ftp_state_post_retr_size(struct connectdata *conn, curl_off_t filesize); +static CURLcode ftp_readresp(curl_socket_t sockfd, + struct pingpong *pp, + int *ftpcode, + size_t *size); + /* easy-to-use macro: */ #define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z)) != CURLE_OK) \ return result @@ -310,19 +317,16 @@ static bool isBadFtpString(const char *string) /*********************************************************************** * - * AllowServerConnect() + * AcceptServerConnect() * - * When we've issue the PORT command, we have told the server to connect - * to us. This function will sit and wait here until the server has - * connected. + * After connection request is received from the server this function is + * called to accept the connection and close the listening socket * */ -static CURLcode AllowServerConnect(struct connectdata *conn) +static CURLcode AcceptServerConnect(struct connectdata *conn) { struct SessionHandle *data = conn->data; curl_socket_t sock = conn->sock[SECONDARYSOCKET]; - long timeout_ms; - long interval_ms; curl_socket_t s = CURL_SOCKET_BAD; #ifdef ENABLE_IPV6 struct Curl_sockaddr_storage add; @@ -331,48 +335,220 @@ static CURLcode AllowServerConnect(struct connectdata *conn) #endif curl_socklen_t size = (curl_socklen_t) sizeof(add); - for(;;) { - timeout_ms = Curl_timeleft(data, NULL, TRUE); + if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { + size = sizeof(add); + + s=accept(sock, (struct sockaddr *) &add, &size); + } + Curl_closesocket(conn, sock); /* close the first socket */ + + if(CURL_SOCKET_BAD == s) { + failf(data, "Error accept()ing server connect"); + return CURLE_FTP_PORT_FAILED; + } + infof(data, "Connection accepted from server\n"); + + conn->sock[SECONDARYSOCKET] = s; + curlx_nonblock(s, TRUE); /* enable non-blocking */ + conn->sock_accepted[SECONDARYSOCKET] = TRUE; + return CURLE_OK; + +} + +/*********************************************************************** + * + * ReceivedServerConnect() + * + * After allowing server to connect to us from data port, this function + * checks both data connection for connection establishment and ctrl + * connection for a negative response regarding a failure in connecting + * + */ +static CURLcode ReceivedServerConnect(struct connectdata* conn, bool* received) +{ + struct SessionHandle *data = conn->data; + curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; + curl_socket_t data_sock = conn->sock[SECONDARYSOCKET]; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + int result; + long timeout_ms; + ssize_t nread; + int ftpcode; + + *received = FALSE; + + timeout_ms = Curl_timeleft_accept(data); + infof(data, "Checking for server connect\n"); + if(timeout_ms < 0) { + /* if a timeout was already reached, bail out */ + failf(data, "Accept timeout occurred while waiting server connect"); + return CURLE_FTP_ACCEPT_TIMEOUT; + } + + /* First check whether there is a cached response from server */ + if(pp->cache_size && pp->cache && pp->cache[0] > '3') { + /* Data connection could not be established, let's return */ + infof(data, "There is negative response in cache while serv connect"); + Curl_GetFTPResponse(&nread, conn, &ftpcode); + return CURLE_FTP_ACCEPT_FAILED; + } + + result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); + + /* see if the connection request is already here */ + switch (result) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_ACCEPT_FAILED; + case 0: /* Server connect is not received yet */ + break; /* loop */ + default: + + if(result & CURL_CSELECT_IN2) { + infof(data, "Ready to accept data connection from server\n"); + *received = TRUE; + } + else if(result & CURL_CSELECT_IN) { + infof(data, "Ctrl conn has data while waiting for data conn\n"); + Curl_GetFTPResponse(&nread, conn, &ftpcode); + + if(ftpcode/100 > 3) + return CURLE_FTP_ACCEPT_FAILED; + + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + break; + } /* switch() */ + + return CURLE_OK; +} + + +/*********************************************************************** + * + * InitiateTransfer() + * + * After connection from server is accepted this function is called to + * setup transfer parameters and initiate the data transfer. + * + */ +static CURLcode InitiateTransfer(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct FTP *ftp = data->state.proto.ftp; + CURLcode result = CURLE_OK; + + if(conn->ssl[SECONDARYSOCKET].use) { + /* since we only have a plaintext TCP connection here, we must now + * do the TLS stuff */ + infof(data, "Doing the SSL/TLS handshake on the data stream\n"); + result = Curl_ssl_connect(conn, SECONDARYSOCKET); + if(result) + return result; + } + + if(conn->proto.ftpc.state_saved == FTP_STOR) { + *(ftp->bytecountp)=0; + + /* When we know we're uploading a specified file, we can get the file + size prior to the actual upload. */ + + Curl_pgrsSetUploadSize(data, data->set.infilesize); + + /* set the SO_SNDBUF for the secondary socket for those who need it */ + Curl_sndbufset(conn->sock[SECONDARYSOCKET]); + + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ + SECONDARYSOCKET, ftp->bytecountp); + } + else { + /* FTP download: */ + Curl_setup_transfer(conn, SECONDARYSOCKET, + conn->proto.ftpc.retr_size_saved, FALSE, + ftp->bytecountp, -1, NULL); /* no upload here */ + } + + conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ + state(conn, FTP_STOP); + + return CURLE_OK; +} + +/*********************************************************************** + * + * AllowServerConnect() + * + * When we've issue the PORT command, we have told the server to connect + * to us. This function + * - will sit and wait here until the server has connected for easy interface + * - will check whether data connection is established if so it is accepted + * for multi interface + * + */ +static CURLcode AllowServerConnect(struct connectdata *conn, bool *connected) +{ + struct SessionHandle *data = conn->data; + long timeout_ms; + long interval_ms; + CURLcode ret = CURLE_OK; + + *connected = FALSE; + infof(data, "Preparing for accepting server on data port\n"); + + /* Save the time we start accepting server connect */ + Curl_pgrsTime(data, TIMER_STARTACCEPT); + for(;;) { + timeout_ms = Curl_timeleft_accept(data); if(timeout_ms < 0) { /* if a timeout was already reached, bail out */ - failf(data, "Timeout while waiting for server connect"); - return CURLE_OPERATION_TIMEDOUT; + failf(data, "Accept timeout occurred while waiting server connect"); + return CURLE_FTP_ACCEPT_TIMEOUT; } - interval_ms = 1000; /* use 1 second timeout intervals */ - if(timeout_ms < interval_ms) - interval_ms = timeout_ms; + /* see if the connection request is already here */ + ret = ReceivedServerConnect(conn, connected); + if(ret) + return ret; - switch (Curl_socket_ready(sock, CURL_SOCKET_BAD, interval_ms)) { - case -1: /* error */ - /* let's die here */ - failf(data, "Error while waiting for server connect"); - return CURLE_FTP_PORT_FAILED; - case 0: /* timeout */ - break; /* loop */ - default: - /* we have received data here */ - if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { - size = sizeof(add); + if(*connected) { + ret = AcceptServerConnect(conn); + if(ret) + return ret; - s=accept(sock, (struct sockaddr *) &add, &size); - } - Curl_closesocket(conn, sock); /* close the first socket */ + ret = InitiateTransfer(conn); + if(ret) + return ret; - if(CURL_SOCKET_BAD == s) { - failf(data, "Error accept()ing server connect"); - return CURLE_FTP_PORT_FAILED; + break; /* connection is accepted, break the loop */ + } + else { + if(data->state.used_interface == Curl_if_easy) { + interval_ms = 1000; + if(timeout_ms < interval_ms) + interval_ms = timeout_ms; + + /* sleep for 1 second and then continue */ + Curl_socket_ready(CURL_SOCKET_BAD, CURL_SOCKET_BAD, interval_ms); } - infof(data, "Connection accepted from server\n"); + else { + /* Add timeout to multi handle and break out of the loop */ + if(ret == CURLE_OK && *connected == FALSE) { + if(data->set.accepttimeout > 0) + Curl_expire(data, data->set.accepttimeout); + else + Curl_expire(data, DEFAULT_ACCEPT_TIMEOUT); + } - conn->sock[SECONDARYSOCKET] = s; - curlx_nonblock(s, TRUE); /* enable non-blocking */ - conn->sock_accepted[SECONDARYSOCKET] = TRUE; - return CURLE_OK; - } /* switch() */ + break; /* connection was not accepted immediately */ + } + } } - /* never reaches this point */ + + return ret; } /* macro to check for a three-digit ftp status code at the start of the @@ -668,6 +844,10 @@ static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks, } socks[0] = conn->sock[SECONDARYSOCKET]; + if(conn->bits.wait_data_conn) { + socks[1] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0) | GETSOCK_READSOCK(1); + } return GETSOCK_READSOCK(0); } @@ -2153,11 +2333,10 @@ static CURLcode ftp_state_rest_resp(struct connectdata *conn, } static CURLcode ftp_state_stor_resp(struct connectdata *conn, - int ftpcode) + int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; - struct FTP *ftp = data->state.proto.ftp; if(ftpcode>=400) { failf(data, "Failed FTP upload: %0d", ftpcode); @@ -2165,41 +2344,26 @@ static CURLcode ftp_state_stor_resp(struct connectdata *conn, return CURLE_UPLOAD_FAILED; } + conn->proto.ftpc.state_saved = instate; + + /* PORT means we are now awaiting the server to connect to us. */ if(data->set.ftp_use_port) { - /* BLOCKING */ - /* PORT means we are now awaiting the server to connect to us. */ - result = AllowServerConnect(conn); - if(result) - return result; - } + bool connected; - if(conn->ssl[SECONDARYSOCKET].use) { - /* since we only have a plaintext TCP connection here, we must now - do the TLS stuff */ - infof(data, "Doing the SSL/TLS handshake on the data stream\n"); - /* BLOCKING */ - result = Curl_ssl_connect(conn, SECONDARYSOCKET); + result = AllowServerConnect(conn, &connected); if(result) return result; - } - - *(ftp->bytecountp)=0; - - /* When we know we're uploading a specified file, we can get the file - size prior to the actual upload. */ - Curl_pgrsSetUploadSize(data, data->set.infilesize); - - /* set the SO_SNDBUF for the secondary socket for those who need it */ - Curl_sndbufset(conn->sock[SECONDARYSOCKET]); - - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ - SECONDARYSOCKET, ftp->bytecountp); - state(conn, FTP_STOP); - - conn->proto.ftpc.pp.pending_resp = TRUE; /* expect a server response */ + if(!connected) { + infof(data, "Data conn was not available immediately\n"); + state(conn, FTP_STOP); + conn->bits.wait_data_conn = TRUE; + } - return result; + return CURLE_OK; + } + else + return InitiateTransfer(conn); } /* for LIST and RETR responses */ @@ -2280,22 +2444,6 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn, else if(ftp->downloadsize > -1) size = ftp->downloadsize; - if(data->set.ftp_use_port) { - /* BLOCKING */ - result = AllowServerConnect(conn); - if(result) - return result; - } - - if(conn->ssl[SECONDARYSOCKET].use) { - /* since we only have a plaintext TCP connection here, we must now - do the TLS stuff */ - infof(data, "Doing the SSL/TLS handshake on the data stream\n"); - result = Curl_ssl_connect(conn, SECONDARYSOCKET); - if(result) - return result; - } - if(size > data->req.maxdownload && data->req.maxdownload > 0) size = data->req.size = data->req.maxdownload; else if((instate != FTP_LIST) && (data->set.prefer_ascii)) @@ -2307,11 +2455,24 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn, infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size); /* FTP download: */ - Curl_setup_transfer(conn, SECONDARYSOCKET, size, FALSE, - ftp->bytecountp, -1, NULL); /* no upload here */ + conn->proto.ftpc.state_saved = instate; + conn->proto.ftpc.retr_size_saved = size; + + if(data->set.ftp_use_port) { + bool connected; + + result = AllowServerConnect(conn, &connected); + if(result) + return result; - conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ - state(conn, FTP_STOP); + if(!connected) { + infof(data, "Data conn was not available immediately\n"); + state(conn, FTP_STOP); + conn->bits.wait_data_conn = TRUE; + } + } + else + return InitiateTransfer(conn); } else { if((instate == FTP_LIST) && (ftpcode == 450)) { @@ -2459,7 +2620,6 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) if(pp->sendleft) return Curl_pp_flushsend(pp); - /* we read a piece of response */ result = ftp_readresp(sock, pp, &ftpcode, &nread); if(result) return result; @@ -2865,7 +3025,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) break; case FTP_STOR: - result = ftp_state_stor_resp(conn, ftpcode); + result = ftp_state_stor_resp(conn, ftpcode, ftpc->state); break; case FTP_QUIT: @@ -3081,6 +3241,8 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, case CURLE_BAD_DOWNLOAD_RESUME: case CURLE_FTP_WEIRD_PASV_REPLY: case CURLE_FTP_PORT_FAILED: + case CURLE_FTP_ACCEPT_FAILED: + case CURLE_FTP_ACCEPT_TIMEOUT: case CURLE_FTP_COULDNT_SET_TYPE: case CURLE_FTP_COULDNT_RETR_FILE: case CURLE_UPLOAD_FAILED: @@ -3474,7 +3636,24 @@ static CURLcode ftp_nextconnect(struct connectdata *conn) /* a transfer is about to take place, or if not a file name was given so we'll do a SIZE on it later and then we need the right TYPE first */ - if(data->set.upload) { + if(conn->bits.wait_data_conn == TRUE) { + bool serv_conned; + + result = ReceivedServerConnect(conn, &serv_conned); + if(result) + return result; /* Failed to accept data connection */ + + if(serv_conned) { + /* It looks data connection is established */ + result = AcceptServerConnect(conn); + conn->bits.wait_data_conn = FALSE; + if(result == CURLE_OK) + result = InitiateTransfer(conn); + } + + return result; + } + else if(data->set.upload) { result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_STOR_TYPE); if(result) return result; @@ -3513,7 +3692,6 @@ static CURLcode ftp_nextconnect(struct connectdata *conn) too! */ Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - /* end of transfer */ DEBUGF(infof(data, "DO-MORE phase ends with %d\n", (int)result)); return result; |