diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/connect.c | 28 | ||||
-rw-r--r-- | lib/connect.h | 5 | ||||
-rw-r--r-- | lib/ftp.c | 364 | ||||
-rw-r--r-- | lib/ftp.h | 3 | ||||
-rw-r--r-- | lib/multi.c | 7 | ||||
-rw-r--r-- | lib/progress.c | 4 | ||||
-rw-r--r-- | lib/progress.h | 1 | ||||
-rw-r--r-- | lib/strerror.c | 8 | ||||
-rw-r--r-- | lib/url.c | 9 | ||||
-rw-r--r-- | lib/urldata.h | 4 |
10 files changed, 337 insertions, 96 deletions
diff --git a/lib/connect.c b/lib/connect.c index bcd538406..cc835808b 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -99,6 +99,34 @@ singleipconnect(struct connectdata *conn, bool *connected); /* + * Curl_timeleft_accept() returns the amount of milliseconds left allowed for + * waiting server to connect. If the value is negative, the timeout time has + * already elapsed. + * + * The start time is stored in progress.t_acceptdata - as set with + * Curl_pgrsTime(..., TIMER_STARTACCEPT); + * + */ +long Curl_timeleft_accept(struct SessionHandle *data) +{ + long timeout_ms = DEFAULT_ACCEPT_TIMEOUT; + struct timeval now; + + if(data->set.accepttimeout > 0) + timeout_ms = data->set.accepttimeout; + + now = Curl_tvnow(); + + /* subtract elapsed time */ + timeout_ms -= Curl_tvdiff(now, data->progress.t_acceptdata); + if(!timeout_ms) + /* avoid returning 0 as that means no timeout! */ + return -1; + + return timeout_ms; +} + +/* * Curl_timeleft() returns the amount of milliseconds left allowed for the * transfer/connection. If the value is negative, the timeout time has already * elapsed. diff --git a/lib/connect.h b/lib/connect.h index f84361f2e..4e905bdf5 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -43,7 +43,12 @@ long Curl_timeleft(struct SessionHandle *data, struct timeval *nowp, bool duringconnect); +/* function that returns how much time there's left to wait for incoming + server connect */ +long Curl_timeleft_accept(struct SessionHandle *data); + #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */ +#define DEFAULT_ACCEPT_TIMEOUT 60000 /* milliseconds == one minute */ /* * Used to extract socket and connectdata struct for the most recent @@ -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; @@ -146,6 +146,9 @@ struct ftp_conn { int count2; /* general purpose counter for the state machine */ int count3; /* general purpose counter for the state machine */ ftpstate state; /* always use ftp.c:state() to change state! */ + ftpstate state_saved; /* transfer type saved to be reloaded after + data connection is established */ + curl_off_t retr_size_saved; /* Size of retrieved file saved */ char * server_os; /* The target server operating system. */ curl_off_t known_filesize; /* file size is different from -1, if wildcard LIST parsing was done and wc_statemach set diff --git a/lib/multi.c b/lib/multi.c index e5073432e..e408ab184 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -1388,6 +1388,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; case CURLM_STATE_DO_DONE: + + if(easy->easy_conn->bits.wait_data_conn == TRUE) { + multistate(easy, CURLM_STATE_DO_MORE); + result = CURLM_OK; + break; + } + /* Move ourselves from the send to recv pipeline */ moveHandleFromSendToRecvPipeline(data, easy->easy_conn); /* Check if we can move pending requests to send pipe */ diff --git a/lib/progress.c b/lib/progress.c index 6abe72e56..1eeb78052 100644 --- a/lib/progress.c +++ b/lib/progress.c @@ -169,6 +169,10 @@ void Curl_pgrsTime(struct SessionHandle *data, timerid timer) data->progress.t_startsingle = now; break; + case TIMER_STARTACCEPT: + data->progress.t_acceptdata = Curl_tvnow(); + break; + case TIMER_NAMELOOKUP: data->progress.t_nslookup = Curl_tvdiff_secs(now, data->progress.t_startsingle); diff --git a/lib/progress.h b/lib/progress.h index 95944f0ba..f5cc5403a 100644 --- a/lib/progress.h +++ b/lib/progress.h @@ -34,6 +34,7 @@ typedef enum { TIMER_STARTTRANSFER, TIMER_POSTRANSFER, TIMER_STARTSINGLE, + TIMER_STARTACCEPT, TIMER_REDIRECT, TIMER_LAST /* must be last */ } timerid; diff --git a/lib/strerror.c b/lib/strerror.c index fcb617cf2..4aa125735 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -81,6 +81,12 @@ curl_easy_strerror(CURLcode error) case CURLE_REMOTE_ACCESS_DENIED: return "Access denied to remote resource"; + case CURLE_FTP_ACCEPT_FAILED: + return "FTP: The server failed to connect to data port"; + + case CURLE_FTP_ACCEPT_TIMEOUT: + return "FTP: Accepting server connect has timed out"; + case CURLE_FTP_PRET_FAILED: return "FTP: The server did not accept the PRET command."; @@ -284,8 +290,6 @@ curl_easy_strerror(CURLcode error) return "Chunk callback failed"; /* error codes not used by current libcurl */ - case CURLE_OBSOLETE10: - case CURLE_OBSOLETE12: case CURLE_OBSOLETE16: case CURLE_OBSOLETE20: case CURLE_OBSOLETE24: @@ -1677,6 +1677,13 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, data->set.connecttimeout = va_arg(param, long); break; + case CURLOPT_ACCEPTTIMEOUT_MS: + /* + * The maximum time you allow curl to wait for server connect + */ + data->set.accepttimeout = va_arg(param, long); + break; + case CURLOPT_USERPWD: /* * user:password to use in the operation @@ -5457,7 +5464,7 @@ CURLcode Curl_do_more(struct connectdata *conn) if(conn->handler->do_more) result = conn->handler->do_more(conn); - if(result == CURLE_OK) + if(result == CURLE_OK && conn->bits.wait_data_conn == FALSE) /* do_complete must be called after the protocol-specific DO function */ do_complete(conn); diff --git a/lib/urldata.h b/lib/urldata.h index 53df18cab..f7c35e367 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -412,6 +412,8 @@ struct ConnectBits { bool do_more; /* this is set TRUE if the ->curl_do_more() function is supposed to be called, after ->curl_do() */ + bool wait_data_conn; /* this is set TRUE if data connection is waited */ + bool tcpconnect[2]; /* the TCP layer (or similar) is connected, this is set the first time on the first connect function call */ bool protoconnstart;/* the protocol layer has STARTED its operation after @@ -1038,6 +1040,7 @@ struct Progress { struct timeval start; struct timeval t_startsingle; + struct timeval t_acceptdata; #define CURR_TIME (5+1) /* 6 entries for 5 seconds */ curl_off_t speeder[ CURR_TIME ]; @@ -1407,6 +1410,7 @@ struct UserDefined { void *ioctl_client; /* pointer to pass to the ioctl callback */ long timeout; /* in milliseconds, 0 means no timeout */ long connecttimeout; /* in milliseconds, 0 means no timeout */ + long accepttimeout; /* in milliseconds, 0 means no timeout */ long server_response_timeout; /* in milliseconds, 0 means no timeout */ long tftp_blksize ; /* in bytes, 0 means use default */ curl_off_t infilesize; /* size of file to upload, -1 means unknown */ |