From 6a2e21ec8cbaf7c719902e06953d9dbec629ad4f Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 9 Feb 2005 13:06:40 +0000 Subject: [PATCH] FTP code turned into state machine. Not completely yet, but a good start. The tag 'before_ftp_statemachine' was set just before this commit in case of future need. --- include/curl/curl.h | 6 +- lib/dict.c | 6 +- lib/dict.h | 14 +- lib/file.c | 6 +- lib/file.h | 14 +- lib/ftp.c | 4035 ++++++++++++++++++++++++---------------- lib/ftp.h | 29 +- lib/hostares.c | 12 +- lib/hostasyn.c | 5 - lib/hostip.c | 5 - lib/hostip.h | 10 +- lib/hostip6.c | 5 - lib/hostsyn.c | 15 +- lib/hostthre.c | 18 +- lib/http.c | 13 +- lib/http.h | 6 +- lib/ldap.c | 5 +- lib/ldap.h | 14 +- lib/multi.c | 191 +- lib/multiif.h | 1 - lib/sendf.h | 2 +- lib/telnet.c | 6 +- lib/telnet.h | 14 +- lib/transfer.c | 13 +- lib/url.c | 158 +- lib/url.h | 21 +- lib/urldata.h | 82 +- src/main.c | 4 +- tests/data/test113 | 2 +- tests/data/test114 | 2 +- tests/data/test190 | 3 +- tests/data/test195 | 2 +- tests/data/test196 | 3 +- tests/libtest/lib511.c | 3 +- 34 files changed, 2970 insertions(+), 1755 deletions(-) diff --git a/include/curl/curl.h b/include/curl/curl.h index 019ef08a0..a8fb925b5 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -245,7 +245,9 @@ typedef enum { CURLE_COULDNT_RESOLVE_HOST, /* 6 */ CURLE_COULDNT_CONNECT, /* 7 */ CURLE_FTP_WEIRD_SERVER_REPLY, /* 8 */ - CURLE_FTP_ACCESS_DENIED, /* 9 */ + CURLE_FTP_ACCESS_DENIED, /* 9 a service was denied by the FTP server + due to lack of access - when login fails + this is not returned. */ CURLE_FTP_USER_PASSWORD_INCORRECT, /* 10 */ CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */ CURLE_FTP_WEIRD_USER_REPLY, /* 12 */ @@ -305,6 +307,8 @@ typedef enum { CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind that failed */ CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ + CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not + accepted and we failed to login */ CURL_LAST /* never use! */ } CURLcode; diff --git a/lib/dict.c b/lib/dict.c index ef82e0b85..1c687292f 100644 --- a/lib/dict.c +++ b/lib/dict.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -85,7 +85,7 @@ #define _MPRINTF_REPLACE /* use our functions only */ #include -CURLcode Curl_dict(struct connectdata *conn) +CURLcode Curl_dict(struct connectdata *conn, bool *done) { char *word; char *ppath; @@ -100,6 +100,8 @@ CURLcode Curl_dict(struct connectdata *conn) char *path = conn->path; curl_off_t *bytecount = &conn->bytecount; + *done = TRUE; /* unconditionally */ + if(conn->bits.user_passwd) { /* AUTH is missing */ } diff --git a/lib/dict.h b/lib/dict.h index 4301f0176..d3da1936f 100644 --- a/lib/dict.h +++ b/lib/dict.h @@ -2,18 +2,18 @@ #define __DICT_H /*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. - * + * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. @@ -24,7 +24,7 @@ * $Id$ ***************************************************************************/ #ifndef CURL_DISABLE_DICT -CURLcode Curl_dict(struct connectdata *conn); +CURLcode Curl_dict(struct connectdata *conn, bool *done); CURLcode Curl_dict_done(struct connectdata *conn); #endif #endif diff --git a/lib/file.c b/lib/file.c index 267a99ba0..5bebf3287 100644 --- a/lib/file.c +++ b/lib/file.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -266,7 +266,7 @@ static CURLcode file_upload(struct connectdata *conn) * opposed to sockets) we instead perform the whole do-operation in this * function. */ -CURLcode Curl_file(struct connectdata *conn) +CURLcode Curl_file(struct connectdata *conn, bool *done) { /* This implementation ignores the host name in conformance with RFC 1738. Only local files (reachable via the standard file system) @@ -286,6 +286,8 @@ CURLcode Curl_file(struct connectdata *conn) int fd; struct timeval now = Curl_tvnow(); + *done = TRUE; /* unconditionally */ + Curl_readwrite_init(conn); Curl_initinfo(data); Curl_pgrsStartNow(data); diff --git a/lib/file.h b/lib/file.h index 689b8aeca..f78acfe63 100644 --- a/lib/file.h +++ b/lib/file.h @@ -2,18 +2,18 @@ #define __FILE_H /*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. - * + * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. @@ -24,7 +24,7 @@ * $Id$ ***************************************************************************/ #ifndef CURL_DISABLE_FILE -CURLcode Curl_file(struct connectdata *); +CURLcode Curl_file(struct connectdata *, bool *done); CURLcode Curl_file_done(struct connectdata *, CURLcode); CURLcode Curl_file_connect(struct connectdata *); #endif diff --git a/lib/ftp.c b/lib/ftp.c index b29ae5363..45ec88ba4 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -124,11 +124,20 @@ static CURLcode ftp_3rdparty_pretransfer(struct connectdata *conn); static CURLcode ftp_3rdparty_transfer(struct connectdata *conn); static CURLcode ftp_parse_url_path(struct connectdata *conn); static CURLcode ftp_cwd_and_create_path(struct connectdata *conn); -static CURLcode ftp_regular_transfer(struct connectdata *conn); +static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done); static CURLcode ftp_3rdparty(struct connectdata *conn); +static void ftp_pasv_verbose(struct connectdata *conn, + Curl_addrinfo *ai, + char *newhost, /* ascii version */ + int port); +static CURLcode ftp_state_post_rest(struct connectdata *conn); +static CURLcode ftp_state_post_cwd(struct connectdata *conn); +static CURLcode ftp_state_quote(struct connectdata *conn, + bool init, ftpstate instate); /* easy-to-use macro: */ #define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z))) return result +#define NBFTPSENDF(x,y,z) if((result = Curl_nbftpsendf(x,y,z))) return result static void freedirs(struct FTP *ftp) { @@ -226,6 +235,166 @@ static CURLcode AllowServerConnect(struct connectdata *conn) } +static CURLcode ftp_readresp(curl_socket_t sockfd, + struct connectdata *conn, + int *ftpcode, /* return the ftp-code if done */ + size_t *size) /* size of the response */ +{ + int perline; /* count bytes per line */ + bool keepon=TRUE; + ssize_t gotbytes; + char *ptr; + struct SessionHandle *data = conn->data; + char *line_start; + char *buf = data->state.buffer; + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + int code = 0; + + if (ftpcode) + *ftpcode = 0; /* 0 for errors or not done */ + + ptr=buf; + line_start = buf; + + perline=0; + keepon=TRUE; + + while((ftp->nread_respcache) { + /* we had data in the "cache", copy that instead of doing an actual + * read + * + * ftp->cache_size is cast to int here. This should be safe, + * because it would have been populated with something of size + * int to begin with, even though its datatype may be larger + * than an int. + */ + memcpy(ptr, ftp->cache, (int)ftp->cache_size); + gotbytes = (int)ftp->cache_size; + free(ftp->cache); /* free the cache */ + ftp->cache = NULL; /* clear the pointer */ + ftp->cache_size = 0; /* zero the size just in case */ + } + else { + int res = Curl_read(conn, sockfd, ptr, BUFSIZE-ftp->nread_resp, + &gotbytes); + if(res < 0) + /* EWOULDBLOCK */ + return CURLE_OK; /* return */ + + if(CURLE_OK != res) + keepon = FALSE; + } + + if(!keepon) + ; + else if(gotbytes <= 0) { + keepon = FALSE; + result = CURLE_RECV_ERROR; + failf(data, "FTP response reading failed"); + } + else { + /* we got a whole chunk of data, which can be anything from one + * byte to a set of lines and possible just a piece of the last + * line */ + int i; + + conn->headerbytecount += gotbytes; + + ftp->nread_resp += gotbytes; + for(i = 0; i < gotbytes; ptr++, i++) { + perline++; + if(*ptr=='\n') { + /* a newline is CRLF in ftp-talk, so the CR is ignored as + the line isn't really terminated until the LF comes */ + + /* output debug output if that is requested */ + if(data->set.verbose) + Curl_debug(data, CURLINFO_HEADER_IN, line_start, perline, conn); + + /* + * We pass all response-lines to the callback function registered + * for "headers". The response lines can be seen as a kind of + * headers. + */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, + line_start, perline); + if(result) + return result; + +#define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \ + isdigit((int)line[2]) && (' ' == line[3])) + + if(perline>3 && lastline(line_start)) { + /* This is the end of the last line, copy the last line to the + start of the buffer and zero terminate, for old times sake (and + krb4)! */ + char *meow; + int n; + for(meow=line_start, n=0; meownread_resp; /* size of the response */ + ftp->nread_resp = 0; /* restart */ + break; + } + perline=0; /* line starts over here */ + line_start = ptr+1; + } + } + if(!keepon && (i != gotbytes)) { + /* We found the end of the response lines, but we didn't parse the + full chunk of data we have read from the server. We therefore need + to store the rest of the data to be checked on the next invoke as + it may actually contain another end of response already! */ + ftp->cache_size = gotbytes - i; + ftp->cache = (char *)malloc((int)ftp->cache_size); + if(ftp->cache) + memcpy(ftp->cache, line_start, (int)ftp->cache_size); + else + return CURLE_OUT_OF_MEMORY; /**BANG**/ + } + } /* there was data */ + + } /* while there's buffer left and loop is requested */ + + if(!result) + code = atoi(buf); + +#ifdef HAVE_KRB4 + /* handle the security-oriented responses 6xx ***/ + /* FIXME: some errorchecking perhaps... ***/ + switch(code) { + case 631: + Curl_sec_read_msg(conn, buf, prot_safe); + break; + case 632: + Curl_sec_read_msg(conn, buf, prot_private); + break; + case 633: + Curl_sec_read_msg(conn, buf, prot_confidential); + break; + default: + /* normal ftp stuff we pass through! */ + break; + } +#endif + + *ftpcode=code; /* return the initial number like this */ + + + /* store the latest code for later retrieval */ + conn->data->info.httpcode=code; + + return result; +} + /* --- parse FTP server responses --- */ /* @@ -238,9 +407,9 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ struct connectdata *conn, int *ftpcode) /* return the ftp-code */ { - /* Brand new implementation. - * We cannot read just one byte per read() and then go back to select() - * as it seems that the OpenSSL read() stuff doesn't grok that properly. + /* + * We cannot read just one byte per read() and then go back to select() as + * the OpenSSL read() doesn't grok that properly. * * Alas, read as much as possible, split up into lines, use the ending * line in a response or continue reading. */ @@ -450,22 +619,2078 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ return result; } +/* This is the ONLY way to change FTP state! */ +static void state(struct connectdata *conn, + ftpstate state) +{ +#ifdef CURLDEBUG + /* for debug purposes */ + const char *names[]={ + "STOP", + "WAIT220", + "AUTH", + "USER", + "PASS", + "ACCT", + "PBSZ", + "PROT", + "PWD", + "QUOTE", + "RETR_PREQUOTE", + "STOR_PREQUOTE", + "POSTQUOTE", + "CWD", + "MKD", + "MDTM", + "TYPE", + "LIST_TYPE", + "RETR_TYPE", + "STOR_TYPE", + "SIZE", + "RETR_SIZE", + "STOR_SIZE", + "REST", + "RETR_REST", + "PORT", + "PASV", + "LIST", + "RETR", + "STOR", + "QUIT" + }; +#endif + struct FTP *ftp = conn->proto.ftp; +#ifdef CURLDEBUG + if(ftp->state != state) + infof(conn->data, "FTP %p state change from %s to %s\n", + ftp, names[ftp->state], names[state]); +#endif + ftp->state = state; +} + +static CURLcode ftp_state_user(struct connectdata *conn) +{ + CURLcode result; + struct FTP *ftp = conn->proto.ftp; + /* send USER */ + NBFTPSENDF(conn, "USER %s", ftp->user?ftp->user:""); + + state(conn, FTP_USER); + + return CURLE_OK; +} + +static CURLcode ftp_state_pwd(struct connectdata *conn) +{ + CURLcode result; + + /* send PWD to discover our entry point */ + NBFTPSENDF(conn, "PWD", NULL); + state(conn, FTP_PWD); + + return CURLE_OK; +} + +/* For the FTP "protocol connect" and "doing" phases only */ +CURLcode Curl_ftp_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) +{ + struct FTP *ftp = conn->proto.ftp; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + + if(ftp->sendleft) + /* write mode */ + FD_SET(sockfd, write_fd_set); + else + /* read mode */ + FD_SET(sockfd, read_fd_set); + + if((int)sockfd > *max_fdp) + *max_fdp = (int)sockfd; + + return CURLE_OK; +} + +/* This is called after the FTP_QUOTE state is passed. + + ftp_state_cwd() sends the range of PWD commands to the server to change to + the correct directory. It may also need to send MKD commands to create + missing ones, if that option is enabled. +*/ +static CURLcode ftp_state_cwd(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + + if(ftp->cwddone) + /* already done and fine */ + result = ftp_state_post_cwd(conn); + else { + ftp->count2 = 0; + if (conn->bits.reuse && ftp->entrypath) { + /* This is a re-used connection. Since we change directory to where the + transfer is taking place, we must first get back to the original dir + where we ended up after login: */ + ftp->count1 = 0; /* we count this as the first path, then we add one + for all upcoming ones in the ftp->dirs[] array */ + NBFTPSENDF(conn, "CWD %s", ftp->entrypath); + state(conn, FTP_CWD); + } + else { + if(ftp->dirdepth) { + ftp->count1 = 1; + /* issue the first CWD, the rest is sent when the CWD responses are + received... */ + NBFTPSENDF(conn, "CWD %s", ftp->dirs[ftp->count1 -1]); + state(conn, FTP_CWD); + } + else { + /* No CWD necessary */ + result = ftp_state_post_cwd(conn); + } + } + } + return result; +} + +typedef enum { EPRT, LPRT, PORT, DONE } ftpport; + +static CURLcode ftp_state_use_port(struct connectdata *conn, + ftpport fcmd) /* start with this */ + +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data=conn->data; + curl_socket_t portsock= CURL_SOCKET_BAD; + +#ifdef ENABLE_IPV6 + /****************************************************************** + * IPv6-specific section + */ + + struct addrinfo *res, *ai; + struct sockaddr_storage ss; + socklen_t sslen; + char hbuf[NI_MAXHOST]; + struct sockaddr *sa=(struct sockaddr *)&ss; + unsigned char *ap; + unsigned char *pp; + char portmsgbuf[1024], tmp[1024]; + const char *mode[] = { "EPRT", "LPRT", "PORT", NULL }; + int rc; + int error; + char *host=NULL; + struct Curl_dns_entry *h=NULL; + + if(data->set.ftpport && (strlen(data->set.ftpport) > 1)) { + /* attempt to get the address of the given interface name */ + if(!Curl_if2ip(data->set.ftpport, hbuf, sizeof(hbuf))) + /* not an interface, use the given string as host name instead */ + host = data->set.ftpport; + else + host = hbuf; /* use the hbuf for host name */ + } /* data->set.ftpport */ + + if(!host) { + /* not an interface and not a host name, get default by extracting + the IP from the control connection */ + + sslen = sizeof(ss); + rc = getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen); + if(rc < 0) { + failf(data, "getsockname() returned %d\n", rc); + return CURLE_FTP_PORT_FAILED; + } + + rc = getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL, + 0, NIFLAGS); + if(rc) { + failf(data, "getnameinfo() returned %d\n", rc); + return CURLE_FTP_PORT_FAILED; + } + host = hbuf; /* use this host name */ + } + + rc = Curl_resolv(conn, host, 0, &h); + if(rc == CURLRESOLV_PENDING) + rc = Curl_wait_for_resolv(conn, &h); + if(h) { + res = h->addr; + /* when we return from this function, we can forget about this entry + to we can unlock it now already */ + Curl_resolv_unlock(data, h); + } /* (h) */ + else + res = NULL; /* failure! */ + + portsock = CURL_SOCKET_BAD; + error = 0; + for (ai = res; ai; ai = ai->ai_next) { + /* + * Workaround for AIX5 getaddrinfo() problem (it doesn't set ai_socktype): + */ + if (ai->ai_socktype == 0) + ai->ai_socktype = SOCK_STREAM; + + portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (portsock == CURL_SOCKET_BAD) { + error = Curl_ourerrno(); + continue; + } + + if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) { + error = Curl_ourerrno(); + sclose(portsock); + portsock = CURL_SOCKET_BAD; + continue; + } + + if (listen(portsock, 1) < 0) { + error = Curl_ourerrno(); + sclose(portsock); + portsock = CURL_SOCKET_BAD; + continue; + } + + break; + } + + if (portsock == CURL_SOCKET_BAD) { + failf(data, "%s", Curl_strerror(conn,error)); + return CURLE_FTP_PORT_FAILED; + } + + sslen = sizeof(ss); + if (getsockname(portsock, sa, &sslen) < 0) { + failf(data, "%s", Curl_strerror(conn,Curl_ourerrno())); + return CURLE_FTP_PORT_FAILED; + } + +#ifdef PF_INET6 + if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) + /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the + request and enable EPRT again! */ + conn->bits.ftp_use_eprt = TRUE; +#endif + + for (; fcmd != DONE; fcmd++) { + int lprtaf, eprtaf; + int alen=0, plen=0; + + if(!conn->bits.ftp_use_eprt && (EPRT == fcmd)) + /* if disabled, goto next */ + continue; + + if(!conn->bits.ftp_use_lprt && (LPRT == fcmd)) + /* if disabled, goto next */ + continue; + + switch (sa->sa_family) { + case AF_INET: + ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr; + alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr); + pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port; + plen = sizeof(((struct sockaddr_in *)&ss)->sin_port); + lprtaf = 4; + eprtaf = 1; + break; + case AF_INET6: + ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr; + alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr); + pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port; + plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port); + lprtaf = 6; + eprtaf = 2; + break; + default: + ap = pp = NULL; + lprtaf = eprtaf = -1; + break; + } + + if (EPRT == fcmd) { + if (eprtaf < 0) + continue; + if (getnameinfo((struct sockaddr *)&ss, sslen, + portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp), + NIFLAGS)) + continue; + + /* do not transmit IPv6 scope identifier to the wire */ + if (sa->sa_family == AF_INET6) { + char *q = strchr(portmsgbuf, '%'); + if (q) + *q = '\0'; + } + + result = Curl_nbftpsendf(conn, "%s |%d|%s|%s|", mode[fcmd], eprtaf, + portmsgbuf, tmp); + if(result) + return result; + break; + } + else if ((LPRT == fcmd) || (PORT == fcmd)) { + int i; + + if ((LPRT == fcmd) && lprtaf < 0) + continue; + if ((PORT == fcmd) && sa->sa_family != AF_INET) + continue; + + portmsgbuf[0] = '\0'; + if (LPRT == fcmd) { + snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen); + if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= + sizeof(portmsgbuf)) { + continue; + } + } + + for (i = 0; i < alen; i++) { + if (portmsgbuf[0]) + snprintf(tmp, sizeof(tmp), ",%u", ap[i]); + else + snprintf(tmp, sizeof(tmp), "%u", ap[i]); + + if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= + sizeof(portmsgbuf)) { + continue; + } + } + + if (LPRT == fcmd) { + snprintf(tmp, sizeof(tmp), ",%d", plen); + + if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf)) + continue; + } + + for (i = 0; i < plen; i++) { + snprintf(tmp, sizeof(tmp), ",%u", pp[i]); + + if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= + sizeof(portmsgbuf)) { + continue; + } + } + + result = Curl_nbftpsendf(conn, "%s %s", mode[fcmd], portmsgbuf); + if(result) + return result; + break; + } + } + + /* store which command was sent */ + ftp->count1 = fcmd; + + /* we set the secondary socket variable to this for now, it is only so that + the cleanup function will close it in case we fail before the true + secondary stuff is made */ + if(-1 != conn->sock[SECONDARYSOCKET]) + sclose(conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = portsock; + +#else + /****************************************************************** + * IPv4-specific section + */ + struct sockaddr_in sa; + unsigned short porttouse; + char myhost[256] = ""; + bool sa_filled_in = FALSE; + Curl_addrinfo *addr = NULL; + unsigned short ip[4]; + (void)fcmd; /* not used in the IPv4 code */ + if(data->set.ftpport) { + in_addr_t in; + + /* First check if the given name is an IP address */ + in=inet_addr(data->set.ftpport); + + if(in != CURL_INADDR_NONE) + /* this is an IPv4 address */ + addr = Curl_ip2addr(in, data->set.ftpport, 0); + else { + if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) { + /* The interface to IP conversion provided a dotted address */ + in=inet_addr(myhost); + addr = Curl_ip2addr(in, myhost, 0); + } + else if(strlen(data->set.ftpport)> 1) { + /* might be a host name! */ + struct Curl_dns_entry *h=NULL; + int rc = Curl_resolv(conn, myhost, 0, &h); + if(rc == CURLRESOLV_PENDING) + /* BLOCKING */ + rc = Curl_wait_for_resolv(conn, &h); + if(h) { + addr = h->addr; + /* when we return from this function, we can forget about this entry + so we can unlock it now already */ + Curl_resolv_unlock(data, h); + } /* (h) */ + } /* strlen */ + } /* CURL_INADDR_NONE */ + } /* data->set.ftpport */ + + if(!addr) { + /* pick a suitable default here */ + + socklen_t sslen; + + sslen = sizeof(sa); + if (getsockname(conn->sock[FIRSTSOCKET], + (struct sockaddr *)&sa, &sslen) < 0) { + failf(data, "getsockname() failed"); + return CURLE_FTP_PORT_FAILED; + } + + sa_filled_in = TRUE; /* the sa struct is filled in */ + } + + if (addr || sa_filled_in) { + portsock = socket(AF_INET, SOCK_STREAM, 0); + if(CURL_SOCKET_BAD != portsock) { + socklen_t size; + + /* we set the secondary socket variable to this for now, it + is only so that the cleanup function will close it in case + we fail before the true secondary stuff is made */ + if(-1 != conn->sock[SECONDARYSOCKET]) + sclose(conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = portsock; + + if(!sa_filled_in) { + memcpy(&sa, addr->ai_addr, sizeof(sa)); + sa.sin_addr.s_addr = INADDR_ANY; + } + + sa.sin_port = 0; + size = sizeof(sa); + + if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) { + /* we succeeded to bind */ + struct sockaddr_in add; + socklen_t socksize = sizeof(add); + + if(getsockname(portsock, (struct sockaddr *) &add, + &socksize)<0) { + failf(data, "getsockname() failed"); + return CURLE_FTP_PORT_FAILED; + } + porttouse = ntohs(add.sin_port); + + if ( listen(portsock, 1) < 0 ) { + failf(data, "listen(2) failed on socket"); + return CURLE_FTP_PORT_FAILED; + } + } + else { + failf(data, "bind(2) failed on socket"); + return CURLE_FTP_PORT_FAILED; + } + } + else { + failf(data, "socket(2) failed (%s)"); + return CURLE_FTP_PORT_FAILED; + } + } + else { + failf(data, "could't find IP address to use"); + return CURLE_FTP_PORT_FAILED; + } + + if(sa_filled_in) + Curl_inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr, + myhost, sizeof(myhost)); + else + Curl_printable_address(addr, myhost, sizeof(myhost)); + + if(4 == sscanf(myhost, "%hu.%hu.%hu.%hu", + &ip[0], &ip[1], &ip[2], &ip[3])) { + + infof(data, "Telling server to connect to %d.%d.%d.%d:%d\n", + ip[0], ip[1], ip[2], ip[3], porttouse); + + result=Curl_nbftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d", + ip[0], ip[1], ip[2], ip[3], + porttouse >> 8, porttouse & 255); + if(result) + return result; + } + else + return CURLE_FTP_PORT_FAILED; + + Curl_freeaddrinfo(addr); + + ftp->count1 = PORT; + +#endif /* end of ipv4-specific code */ + + state(conn, FTP_PORT); + return result; +} + +static CURLcode ftp_state_use_pasv(struct connectdata *conn) +{ + struct FTP *ftp = conn->proto.ftp; + CURLcode result = CURLE_OK; + /* + Here's the excecutive summary on what to do: + + PASV is RFC959, expect: + 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) + + LPSV is RFC1639, expect: + 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) + + EPSV is RFC2428, expect: + 229 Entering Extended Passive Mode (|||port|) + + */ + + const char *mode[] = { "EPSV", "PASV", NULL }; + int modeoff; + +#ifdef PF_INET6 + if(!conn->bits.ftp_use_epsv && conn->bits.ipv6) + /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the + request and enable EPSV again! */ + conn->bits.ftp_use_epsv = TRUE; +#endif + + modeoff = conn->bits.ftp_use_epsv?0:1; + + result = Curl_nbftpsendf(conn, "%s", mode[modeoff]); + if(result) + return result; + + ftp->count1 = modeoff; + state(conn, FTP_PASV); + infof(conn->data, "Connect data stream passively\n"); + + return result; +} + +/* REST is the last command in the chain of commands when a "head"-like + request is made. Thus, if an actual transfer is to be made this is where + we take off for real. */ +static CURLcode ftp_state_post_rest(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data = conn->data; + + if(ftp->no_transfer || conn->bits.no_body) { + /* then we're done with a "head"-like request, goto STOP */ + state(conn, FTP_STOP); + + /* doesn't transfer any data */ + ftp->no_transfer = TRUE; + } + else if(data->set.ftp_use_port) { + /* We have chosen to use the PORT (or similar) command */ + result = ftp_state_use_port(conn, EPRT); + } + else { + /* We have chosen (this is default) to use the PASV (or similar) command */ + result = ftp_state_use_pasv(conn); + } + return result; +} + +static CURLcode ftp_state_post_size(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + + if(ftp->no_transfer) { + /* if a "head"-like request is being made */ + + /* Determine if server can respond to REST command and therefore + whether it supports range */ + NBFTPSENDF(conn, "REST %d", 0); + + state(conn, FTP_REST); + } + else + result = ftp_state_post_rest(conn); + + return result; +} + +static CURLcode ftp_state_post_type(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + + if(ftp->no_transfer) { + /* if a "head"-like request is being made */ + + /* we know ftp->file is a valid pointer to a file name */ + NBFTPSENDF(conn, "SIZE %s", ftp->file); + + state(conn, FTP_SIZE); + } + else + result = ftp_state_post_size(conn); + + return result; +} + +static CURLcode ftp_state_post_listtype(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + /* If this output is to be machine-parsed, the NLST command might be better + to use, since the LIST command output is not specified or standard in any + way. It has turned out that the NLST list output is not the same on all + servers either... */ + + NBFTPSENDF(conn, "%s", + data->set.customrequest?data->set.customrequest: + (data->set.ftp_list_only?"NLST":"LIST")); + + state(conn, FTP_LIST); + + return result; +} + +static CURLcode ftp_state_post_retrtype(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + /* We've sent the TYPE, now we must send the list of prequote strings */ + + result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE); + + return result; +} + +static CURLcode ftp_state_post_stortype(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + /* We've sent the TYPE, now we must send the list of prequote strings */ + + result = ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE); + + return result; +} + +static CURLcode ftp_state_post_mdtm(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data = conn->data; + + /* If we have selected NOBODY and HEADER, it means that we only want file + information. Which in FTP can't be much more than the file size and + date. */ + if(conn->bits.no_body && data->set.include_header && ftp->file) { + /* The SIZE command is _not_ RFC 959 specified, and therefor many servers + may not support it! It is however the only way we have to get a file's + size! */ + + ftp->no_transfer = TRUE; /* this means no actual transfer will be made */ + + /* Some servers return different sizes for different modes, and thus we + must set the proper type before we check the size */ + NBFTPSENDF(conn, "TYPE %c", + data->set.ftp_ascii?'A':'I'); + state(conn, FTP_TYPE); + } + else + result = ftp_state_post_type(conn); + + return result; +} + +/* This is called after the CWD commands have been done in the beginning of + the DO phase */ +static CURLcode ftp_state_post_cwd(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data = conn->data; + + /* Requested time of file or time-depended transfer? */ + if((data->set.get_filetime || data->set.timecondition) && ftp->file) { + + /* we have requested to get the modified-time of the file, this is a white + spot as the MDTM is not mentioned in RFC959 */ + NBFTPSENDF(conn, "MDTM %s", ftp->file); + + state(conn, FTP_MDTM); + } + else + result = ftp_state_post_mdtm(conn); + + return result; +} + + +/* This is called after the TYPE and possible quote commands have been sent */ +static CURLcode ftp_state_ul_setup(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data = conn->data; + curl_off_t passed=0; + + if(conn->resume_from) { + /* we're about to continue the uploading of a file */ + /* 1. get already existing file's size. We use the SIZE command for this + which may not exist in the server! The SIZE command is not in + RFC959. */ + + /* 2. This used to set REST. But since we can do append, we + don't another ftp command. We just skip the source file + offset and then we APPEND the rest on the file instead */ + + /* 3. pass file-size number of bytes in the source file */ + /* 4. lower the infilesize counter */ + /* => transfer as usual */ + + if(conn->resume_from < 0 ) { + /* Got no given size to start from, figure it out */ + NBFTPSENDF(conn, "SIZE %s", ftp->file); + state(conn, FTP_STOR_SIZE); + return result; + } + + /* enable append */ + data->set.ftp_append = TRUE; + + /* Let's read off the proper amount of bytes from the input. If we knew it + was a proper file we could've just fseek()ed but we only have a stream + here */ + + /* TODO: allow the ioctlfunction to provide a fast forward function that + can be used here and use this method only as a fallback! */ + do { + curl_off_t readthisamountnow = (conn->resume_from - passed); + curl_off_t actuallyread; + + if(readthisamountnow > BUFSIZE) + readthisamountnow = BUFSIZE; + + actuallyread = (curl_off_t) + conn->fread(data->state.buffer, 1, (size_t)readthisamountnow, + conn->fread_in); + + passed += actuallyread; + if(actuallyread != readthisamountnow) { + failf(data, "Could only read %" FORMAT_OFF_T + " bytes from the input", passed); + return CURLE_FTP_COULDNT_USE_REST; + } + } while(passed != conn->resume_from); + + /* now, decrease the size of the read */ + if(data->set.infilesize>0) { + data->set.infilesize -= conn->resume_from; + + if(data->set.infilesize <= 0) { + infof(data, "File already completely uploaded\n"); + + /* no data to transfer */ + result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + /* Set no_transfer so that we won't get any error in + * Curl_ftp_done() because we didn't transfer anything! */ + ftp->no_transfer = TRUE; + + state(conn, FTP_STOP); + return CURLE_OK; + } + } + /* we've passed, proceed as normal */ + } /* resume_from */ + + NBFTPSENDF(conn, data->set.ftp_append?"APPE %s":"STOR %s", + ftp->file); + + state(conn, FTP_STOR); + + return result; +} + +static CURLcode ftp_state_quote(struct connectdata *conn, + bool init, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data = conn->data; + bool quote=FALSE; + struct curl_slist *item; + + switch(instate) { + case FTP_QUOTE: + default: + item = data->set.quote; + break; + case FTP_RETR_PREQUOTE: + case FTP_STOR_PREQUOTE: + item = data->set.prequote; + break; + case FTP_POSTQUOTE: + item = data->set.postquote; + break; + } + + if(init) + ftp->count1 = 0; + else + ftp->count1++; + + if(item) { + int i = 0; + + /* Skip count1 items in the linked list */ + while((i< ftp->count1) && item) { + item = item->next; + i++; + } + if(item) { + NBFTPSENDF(conn, "%s", item->data); + state(conn, instate); + quote = TRUE; + } + } + + if(!quote) { + /* No more quote to send, continue to ... */ + switch(instate) { + case FTP_QUOTE: + default: + result = ftp_state_cwd(conn); + break; + case FTP_RETR_PREQUOTE: + NBFTPSENDF(conn, "SIZE %s", ftp->file); + state(conn, FTP_RETR_SIZE); + break; + case FTP_STOR_PREQUOTE: + result = ftp_state_ul_setup(conn); + break; + case FTP_POSTQUOTE: + break; + } + } + + return result; +} + +static CURLcode ftp_state_pasv_resp(struct connectdata *conn, + int ftpcode) +{ + struct FTP *ftp = conn->proto.ftp; + CURLcode result; + struct SessionHandle *data=conn->data; + Curl_addrinfo *conninfo; + struct Curl_dns_entry *addr=NULL; + int rc; + unsigned short connectport; /* the local port connect() should use! */ + unsigned short newport=0; /* remote port */ + bool connected; + + /* newhost must be able to hold a full IP-style address in ASCII, which + in the IPv6 case means 5*8-1 = 39 letters */ +#define NEWHOST_BUFSIZE 48 + char newhost[NEWHOST_BUFSIZE]; + char *str=&data->state.buffer[4]; /* start on the first letter */ + + if((ftp->count1 == 0) && + (ftpcode == 229)) { + /* positive EPSV response */ + char *ptr = strchr(str, '('); + if(ptr) { + unsigned int num; + char separator[4]; + ptr++; + if(5 == sscanf(ptr, "%c%c%c%u%c", + &separator[0], + &separator[1], + &separator[2], + &num, + &separator[3])) { + char sep1 = separator[0]; + int i; + + /* The four separators should be identical, or else this is an oddly + formatted reply and we bail out immediately. */ + for(i=1; i<4; i++) { + if(separator[i] != sep1) { + ptr=NULL; /* set to NULL to signal error */ + break; + } + } + if(ptr) { + newport = num; + + /* use the same IP we are already connected to */ + snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str, newhost); + } + } + else + ptr=NULL; + } + if(!ptr) { + failf(data, "Weirdly formatted EPSV reply"); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + } + else if((ftp->count1 == 1) && + (ftpcode == 227)) { + /* positive PASV response */ + int ip[4]; + int port[2]; + + /* + * Scan for a sequence of six comma-separated numbers and use them as + * IP+port indicators. + * + * Found reply-strings include: + * "227 Entering Passive Mode (127,0,0,1,4,51)" + * "227 Data transfer will passively listen to 127,0,0,1,4,51" + * "227 Entering passive mode. 127,0,0,1,4,51" + */ + while(*str) { + if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d", + &ip[0], &ip[1], &ip[2], &ip[3], + &port[0], &port[1])) + break; + str++; + } + + if(!*str) { + failf(data, "Couldn't interpret the 227-response"); + return CURLE_FTP_WEIRD_227_FORMAT; + } + + snprintf(newhost, sizeof(newhost), + "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + newport = (port[0]<<8) + port[1]; + } + else if(ftp->count1 == 0) { + /* EPSV failed, move on to PASV */ + + /* disable it for next transfer */ + conn->bits.ftp_use_epsv = FALSE; + infof(data, "disabling EPSV usage\n"); + + NBFTPSENDF(conn, "PASV", NULL); + ftp->count1++; + /* remain in the FTP_PASV state */ + return result; + } + else { + failf(data, "Bad PASV/EPSV response: %03d", ftpcode); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + + /* we got OK from server */ + + if(data->change.proxy && *data->change.proxy) { + /* + * This is a tunnel through a http proxy and we need to connect to the + * proxy again here. + * + * We don't want to rely on a former host lookup that might've expired + * now, instead we remake the lookup here and now! + */ + rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr); + if(rc == CURLRESOLV_PENDING) + /* BLOCKING */ + rc = Curl_wait_for_resolv(conn, &addr); + + connectport = + (unsigned short)conn->port; /* we connect to the proxy's port */ + + } + else { + /* normal, direct, ftp connection */ + rc = Curl_resolv(conn, newhost, newport, &addr); + if(rc == CURLRESOLV_PENDING) + /* BLOCKING */ + rc = Curl_wait_for_resolv(conn, &addr); + + if(!addr) { + failf(data, "Can't resolve new host %s:%d", newhost, newport); + return CURLE_FTP_CANT_GET_HOST; + } + connectport = newport; /* we connect to the remote port */ + } + + result = Curl_connecthost(conn, + addr, + &conn->sock[SECONDARYSOCKET], + &conninfo, + &connected); + + Curl_resolv_unlock(data, addr); /* we're done using this address */ + + if(result) + return result; + + conn->bits.tcpconnect = connected; /* simply TRUE or FALSE */ + + /* + * When this is used from the multi interface, this might've returned with + * the 'connected' set to FALSE and thus we are now awaiting a non-blocking + * connect to connect and we should not be "hanging" here waiting. + */ + + if(data->set.verbose) + /* this just dumps information about this second connection */ + ftp_pasv_verbose(conn, conninfo, newhost, connectport); + +#ifndef CURL_DISABLE_HTTP + if(conn->bits.tunnel_proxy) { + /* FIX: this MUST wait for a proper connect first if 'connected' is + * FALSE */ + + /* BLOCKING */ + /* We want "seamless" FTP operations through HTTP proxy tunnel */ + result = Curl_ConnectHTTPProxyTunnel(conn, SECONDARYSOCKET, + newhost, newport); + if(CURLE_OK != result) + return result; + } +#endif /* CURL_DISABLE_HTTP */ + + state(conn, FTP_STOP); /* this phase is completed */ + + return result; +} + +static CURLcode ftp_state_port_resp(struct connectdata *conn, + int ftpcode) +{ + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data = conn->data; + ftpport fcmd = ftp->count1; + CURLcode result = CURLE_OK; + + if(ftpcode != 200) { + /* the command failed */ + + if (EPRT == fcmd) { + infof(data, "disabling EPRT usage\n"); + conn->bits.ftp_use_eprt = FALSE; + } + else if (LPRT == fcmd) { + infof(data, "disabling LPRT usage\n"); + conn->bits.ftp_use_lprt = FALSE; + } + fcmd++; + + if(fcmd == DONE) { + failf(data, "Failed to do PORT"); + result = CURLE_FTP_PORT_FAILED; + } + else + /* try next */ + result = ftp_state_use_port(conn, fcmd); + } + else { + infof(data, "Connect data stream actively\n"); + state(conn, FTP_STOP); /* end of DO phase */ + } + + return result; +} + +static CURLcode ftp_state_mdtm_resp(struct connectdata *conn, + int ftpcode) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data=conn->data; + + switch(ftpcode) { + case 213: + { + /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the + last .sss part is optional and means fractions of a second */ + int year, month, day, hour, minute, second; + char *buf = data->state.buffer; + if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d", + &year, &month, &day, &hour, &minute, &second)) { + /* we have a time, reformat it */ + time_t secs=time(NULL); + /* using the good old yacc/bison yuck */ + snprintf(buf, sizeof(conn->data->state.buffer), + "%04d%02d%02d %02d:%02d:%02d GMT", + year, month, day, hour, minute, second); + /* now, convert this into a time() value: */ + data->info.filetime = curl_getdate(buf, &secs); + } + + /* If we asked for a time of the file and we actually got one as well, + we "emulate" a HTTP-style header in our output. */ + +#ifdef HAVE_STRFTIME + if(data->set.get_filetime && (data->info.filetime>=0) ) { + struct tm *tm; + time_t clock = (time_t)data->info.filetime; +#ifdef HAVE_GMTIME_R + struct tm buffer; + tm = (struct tm *)gmtime_r(&clock, &buffer); +#else + tm = gmtime(&clock); +#endif + /* format: "Tue, 15 Nov 1994 12:45:26" */ + strftime(buf, BUFSIZE-1, + "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n", tm); + result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0); + if(result) + return result; + } +#endif + } + break; + default: + infof(data, "unsupported MDTM reply format\n"); + break; + case 550: /* "No such file or directory" */ + failf(data, "Given file does not exist"); + result = CURLE_FTP_COULDNT_RETR_FILE; + break; + } + + if(data->set.timecondition) { + if((data->info.filetime > 0) && (data->set.timevalue > 0)) { + switch(data->set.timecondition) { + case CURL_TIMECOND_IFMODSINCE: + default: + if(data->info.filetime < data->set.timevalue) { + infof(data, "The requested document is not new enough\n"); + ftp->no_transfer = TRUE; /* mark this to not transfer data */ + state(conn, FTP_STOP); + return CURLE_OK; + } + break; + case CURL_TIMECOND_IFUNMODSINCE: + if(data->info.filetime > data->set.timevalue) { + infof(data, "The requested document is not old enough\n"); + ftp->no_transfer = TRUE; /* mark this to not transfer data */ + state(conn, FTP_STOP); + return CURLE_OK; + } + break; + } /* switch */ + } + else { + infof(data, "Skipping time comparison\n"); + } + } + + if(!result) + result = ftp_state_post_mdtm(conn); + + return result; +} + +static CURLcode ftp_state_type_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data=conn->data; + + if(ftpcode != 200) { + failf(data, "Couldn't set desired mode"); + return CURLE_FTP_COULDNT_SET_BINARY; /* FIX */ + } + if(instate == FTP_TYPE) + result = ftp_state_post_type(conn); + else if(instate == FTP_LIST_TYPE) + result = ftp_state_post_listtype(conn); + else if(instate == FTP_RETR_TYPE) + result = ftp_state_post_retrtype(conn); + else if(instate == FTP_STOR_TYPE) + result = ftp_state_post_stortype(conn); + + return result; +} + +static CURLcode ftp_state_post_retr_size(struct connectdata *conn, + curl_off_t filesize) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data=conn->data; + struct FTP *ftp = conn->proto.ftp; + + if (data->set.max_filesize && (filesize > data->set.max_filesize)) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + ftp->downloadsize = filesize; + + if(conn->resume_from) { + /* We always (attempt to) get the size of downloads, so it is done before + this even when not doing resumes. */ + if(filesize == -1) { + infof(data, "ftp server doesn't support SIZE\n"); + /* We couldn't get the size and therefore we can't know if there really + is a part of the file left to get, although the server will just + close the connection when we start the connection so it won't cause + us any harm, just not make us exit as nicely. */ + } + else { + /* We got a file size report, so we check that there actually is a + part of the file left to get, or else we go home. */ + if(conn->resume_from< 0) { + /* We're supposed to download the last abs(from) bytes */ + if(filesize < -conn->resume_from) { + failf(data, "Offset (%" FORMAT_OFF_T + ") was beyond file size (%" FORMAT_OFF_T ")", + conn->resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* convert to size to download */ + ftp->downloadsize = -conn->resume_from; + /* download from where? */ + conn->resume_from = filesize - ftp->downloadsize; + } + else { + if(filesize < conn->resume_from) { + failf(data, "Offset (%" FORMAT_OFF_T + ") was beyond file size (%" FORMAT_OFF_T ")", + conn->resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* Now store the number of bytes we are expected to download */ + ftp->downloadsize = filesize-conn->resume_from; + } + } + + if(ftp->downloadsize == 0) { + /* no data to transfer */ + result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + infof(data, "File already completely downloaded\n"); + + /* Set no_transfer so that we won't get any error in Curl_ftp_done() + * because we didn't transfer the any file */ + ftp->no_transfer = TRUE; + state(conn, FTP_STOP); + return CURLE_OK; + } + + /* Set resume file transfer offset */ + infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T + "\n", conn->resume_from); + + NBFTPSENDF(conn, "REST %" FORMAT_OFF_T, conn->resume_from); + + state(conn, FTP_RETR_REST); + + } + else { + /* no resume */ + NBFTPSENDF(conn, "RETR %s", ftp->file); + state(conn, FTP_RETR); + } + + return result; +} + +static CURLcode ftp_state_size_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data=conn->data; + curl_off_t filesize; + char *buf = data->state.buffer; + + /* get the size from the ascii string: */ + filesize = (ftpcode == 213)?curlx_strtoofft(buf+4, NULL, 0):-1; + + if(instate == FTP_SIZE) { + if(-1 != filesize) { + snprintf(buf, sizeof(data->state.buffer), + "Content-Length: %" FORMAT_OFF_T "\r\n", filesize); + result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0); + if(result) + return result; + } + result = ftp_state_post_size(conn); + } + else if(instate == FTP_RETR_SIZE) + result = ftp_state_post_retr_size(conn, filesize); + else if(instate == FTP_STOR_SIZE) { + conn->resume_from = filesize; + result = ftp_state_ul_setup(conn); + } + + return result; +} + +static CURLcode ftp_state_rest_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + + switch(instate) { + case FTP_REST: + default: + if (ftpcode == 350) { + result = Curl_client_write(conn->data, CLIENTWRITE_BOTH, + (char *)"Accept-ranges: bytes\r\n", 0); + if(result) + return result; + } + + result = ftp_state_post_rest(conn); + break; + + case FTP_RETR_REST: + if (ftpcode != 350) { + failf(conn->data, "Couldn't use REST"); + result = CURLE_FTP_COULDNT_USE_REST; + } + else { + NBFTPSENDF(conn, "RETR %s", ftp->file); + state(conn, FTP_RETR); + } + break; + } + + return result; +} + +static CURLcode ftp_state_stor_resp(struct connectdata *conn, + int ftpcode) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct FTP *ftp = conn->proto.ftp; + + if(ftpcode>=400) { + failf(data, "Failed FTP upload: %0d", ftpcode); + /* oops, we never close the sockets! */ + return CURLE_FTP_COULDNT_STOR_FILE; + } + + 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; + } + + 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_SSLConnect(conn, SECONDARYSOCKET); + 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); + + result = Curl_Transfer(conn, -1, -1, FALSE, NULL, /* no download */ + SECONDARYSOCKET, ftp->bytecountp); + state(conn, FTP_STOP); + + return result; +} + +/* for LIST and RETR responses */ +static CURLcode ftp_state_get_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct FTP *ftp = conn->proto.ftp; + char *buf = data->state.buffer; + + if((ftpcode == 150) || (ftpcode == 125)) { + + /* + A; + 150 Opening BINARY mode data connection for /etc/passwd (2241 + bytes). (ok, the file is being transfered) + + B: + 150 Opening ASCII mode data connection for /bin/ls + + C: + 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes). + + D: + 150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes). + + E: + 125 Data connection already open; Transfer starting. */ + + curl_off_t size=-1; /* default unknown size */ + + + /* + * It appears that there are FTP-servers that return size 0 for files when + * SIZE is used on the file while being in BINARY mode. To work around + * that (stupid) behavior, we attempt to parse the RETR response even if + * the SIZE returned size zero. + * + * Debugging help from Salvatore Sorrentino on February 26, 2003. + */ + + if((instate != FTP_LIST) && + !data->set.ftp_ascii && + (ftp->downloadsize < 1)) { + /* + * It seems directory listings either don't show the size or very + * often uses size 0 anyway. ASCII transfers may very well turn out + * that the transfered amount of data is not the same as this line + * tells, why using this number in those cases only confuses us. + * + * Example D above makes this parsing a little tricky */ + char *bytes; + bytes=strstr(buf, " bytes"); + if(bytes--) { + long in=bytes-buf; + /* this is a hint there is size information in there! ;-) */ + while(--in) { + /* scan for the left parenthesis and break there */ + if('(' == *bytes) + break; + /* skip only digits */ + if(!isdigit((int)*bytes)) { + bytes=NULL; + break; + } + /* one more estep backwards */ + bytes--; + } + /* if we have nothing but digits: */ + if(bytes++) { + /* get the number! */ + size = curlx_strtoofft(bytes, NULL, 0); + } + } + } + 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_SSLConnect(conn, SECONDARYSOCKET); + if(result) + return result; + } + + if(size > conn->maxdownload && conn->maxdownload > 0) + size = conn->size = conn->maxdownload; + + if(instate != FTP_LIST) + infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size); + + /* FTP download: */ + result=Curl_Transfer(conn, SECONDARYSOCKET, size, FALSE, + ftp->bytecountp, + -1, NULL); /* no upload here */ + if(result) + return result; + + state(conn, FTP_STOP); + } + else { + if((instate == FTP_LIST) && (ftpcode == 450)) { + /* simply no matching files in the dir listing */ + ftp->no_transfer = TRUE; /* don't download anything */ + state(conn, FTP_STOP); /* this phase is over */ + } + else { + failf(data, "%s", buf+4); + return CURLE_FTP_COULDNT_RETR_FILE; + } + } + + return result; +} + +/* after USER, PASS and ACCT */ +static CURLcode ftp_state_loggedin(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + infof(data, "We have successfully logged in\n"); + +#ifdef HAVE_KRB4 + /* We are logged in with Kerberos, now set the requested + * protection level + */ + if(conn->sec_complete) + /* BLOCKING */ + Curl_sec_set_protection_level(conn); + + /* We may need to issue a KAUTH here to have access to the files + * do it if user supplied a password + */ + if(conn->passwd && *conn->passwd) { + /* BLOCKING */ + result = Curl_krb_kauth(conn); + if(result) + return result; + } +#endif + if(conn->ssl[FIRSTSOCKET].use) { + /* PBSZ = PROTECTION BUFFER SIZE. + + The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: + + Specifically, the PROT command MUST be preceded by a PBSZ + command and a PBSZ command MUST be preceded by a successful + security data exchange (the TLS negotiation in this case) + + ... (and on page 8): + + Thus the PBSZ command must still be issued, but must have a + parameter of '0' to indicate that no buffering is taking place + and the data connection should not be encapsulated. + */ + NBFTPSENDF(conn, "PBSZ %d", 0); + state(conn, FTP_PBSZ); + } + else { + result = ftp_state_pwd(conn); + } + return result; +} + +/* for USER and PASS responses */ +static CURLcode ftp_state_user_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct FTP *ftp = conn->proto.ftp; + (void)instate; /* no use for this yet */ + + if((ftpcode == 331) && (ftp->state == FTP_USER)) { + /* 331 Password required for ... + (the server requires to send the user's password too) */ + NBFTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:""); + state(conn, FTP_PASS); + } + else if(ftpcode/100 == 2) { + /* 230 User ... logged in. + (the user logged in with or without password) */ + result = ftp_state_loggedin(conn); + } + else if(ftpcode == 332) { + if(data->set.ftp_account) { + NBFTPSENDF(conn, "ACCT %s", data->set.ftp_account); + state(conn, FTP_ACCT); + } + else { + failf(data, "ACCT requested by none available"); + result = CURLE_LOGIN_DENIED; + } + } + else { + /* All other response codes, like: + + 530 User ... access denied + (the server denies to log the specified user) */ + failf(data, "Access denied: %03d", ftpcode); + result = CURLE_LOGIN_DENIED; + } + return result; +} + +/* for ACCT response */ +static CURLcode ftp_state_acct_resp(struct connectdata *conn, + int ftpcode) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + if(ftpcode != 230) { + failf(data, "ACCT rejected by server: %03d", ftpcode); + result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ + } + else + result = ftp_state_loggedin(conn); + + return result; +} + + +static CURLcode ftp_statemach_act(struct connectdata *conn) +{ + CURLcode result; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + struct SessionHandle *data=conn->data; + int ftpcode; + struct FTP *ftp = conn->proto.ftp; + static const char * const ftpauth[] = { + "SSL", "TLS" + }; + size_t nread; + + if(ftp->sendleft) { + /* we have a piece of a command still left to send */ + ssize_t written; + result = Curl_write(conn, sock, ftp->sendthis + ftp->sendsize - + ftp->sendleft, ftp->sendleft, &written); + if(result) + return result; + + if(written != (ssize_t)ftp->sendleft) { + /* only a fraction was sent */ + ftp->sendleft -= written; + } + else { + free(ftp->sendthis); + ftp->sendthis=NULL; + ftp->sendleft = ftp->sendsize = 0; + ftp->response = Curl_tvnow(); + } + return CURLE_OK; + } + + /* we read a piece of response */ + result = ftp_readresp(sock, conn, &ftpcode, &nread); + if(result) + return result; + + if(ftpcode) { + /* we have now received a full FTP server response */ + switch(ftp->state) { + case FTP_WAIT220: + if(ftpcode != 220) { + failf(data, "This doesn't seem like a nice ftp-server response"); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + /* We have received a 220 response fine, now we proceed. */ +#ifdef HAVE_KRB4 + if(data->set.krb4) { + /* If not anonymous login, try a secure login. Note that this + procedure is still BLOCKING. */ + + Curl_sec_request_prot(conn, "private"); + /* We set private first as default, in case the line below fails to + set a valid level */ + Curl_sec_request_prot(conn, data->set.krb4_level); + + if(Curl_sec_login(conn) != 0) + infof(data, "Logging in with password in cleartext!\n"); + else + infof(data, "Authentication successful\n"); + } +#endif + + if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) { + /* We don't have a SSL/TLS connection yet, but FTPS is + requested. Try a FTPS connection now */ + + ftp->count3=0; + switch(data->set.ftpsslauth) { + case CURLFTPAUTH_DEFAULT: + case CURLFTPAUTH_SSL: + ftp->count2 = 1; /* add one to get next */ + ftp->count1 = 0; + break; + case CURLFTPAUTH_TLS: + ftp->count2 = -1; /* subtract one to get next */ + ftp->count1 = 1; + break; + default: + failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d\n", + data->set.ftpsslauth); + return CURLE_FAILED_INIT; /* we don't know what to do */ + } + NBFTPSENDF(conn, "AUTH %s", ftpauth[ftp->count1]); + state(conn, FTP_AUTH); + } + else { + ftp_state_user(conn); + if(result) + return result; + } + + break; + + case FTP_AUTH: + /* we have gotten the response to a previous AUTH command */ + + /* RFC2228 (page 5) says: + * + * If the server is willing to accept the named security mechanism, + * and does not require any security data, it must respond with + * reply code 234/334. + */ + + if((ftpcode == 234) || (ftpcode == 334)) { + /* Curl_SSLConnect is BLOCKING */ + result = Curl_SSLConnect(conn, FIRSTSOCKET); + if(result) + return result; + conn->protocol |= PROT_FTPS; + conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */ + } + else if(ftp->count3 < 1) { + ftp->count3++; + ftp->count1 += ftp->count2; /* get next attempt */ + NBFTPSENDF(conn, "AUTH %s", ftpauth[ftp->count1]); + /* remain in this same state */ + } + else { + result = ftp_state_user(conn); + if(result) + return result; + } + break; + + case FTP_USER: + case FTP_PASS: + result = ftp_state_user_resp(conn, ftpcode, ftp->state); + break; + + case FTP_ACCT: + result = ftp_state_acct_resp(conn, ftpcode); + break; + + case FTP_PBSZ: + /* FIX: check response code */ + + /* For TLS, the data connection can have one of two security levels. + + 1) Clear (requested by 'PROT C') + + 2)Private (requested by 'PROT P') + */ + if(!conn->ssl[SECONDARYSOCKET].use) { + NBFTPSENDF(conn, "PROT %c", 'P'); + state(conn, FTP_PROT); + } + else { + result = ftp_state_pwd(conn); + if(result) + return result; + } + + break; + + case FTP_PROT: + if(ftpcode/100 == 2) + /* We have enabled SSL for the data connection! */ + conn->ssl[SECONDARYSOCKET].use = TRUE; + /* FTP servers typically responds with 500 if they decide to reject + our 'P' request */ + else if(data->set.ftp_ssl> CURLFTPSSL_CONTROL) + /* we failed and bails out */ + return CURLE_FTP_SSL_FAILED; + + result = ftp_state_pwd(conn); + if(result) + return result; + break; + + case FTP_PWD: + if(ftpcode == 257) { + char *dir = (char *)malloc(nread+1); + char *store=dir; + char *ptr=&data->state.buffer[4]; /* start on the first letter */ + + if(!dir) + return CURLE_OUT_OF_MEMORY; + + /* Reply format is like + 257"" and the RFC959 + says + + The directory name can contain any character; embedded + double-quotes should be escaped by double-quotes (the + "quote-doubling" convention). + */ + if('\"' == *ptr) { + /* it started good */ + ptr++; + while(ptr && *ptr) { + if('\"' == *ptr) { + if('\"' == ptr[1]) { + /* "quote-doubling" */ + *store = ptr[1]; + ptr++; + } + else { + /* end of path */ + *store = '\0'; /* zero terminate */ + break; /* get out of this loop */ + } + } + else + *store = *ptr; + store++; + ptr++; + } + ftp->entrypath =dir; /* remember this */ + infof(data, "Entry path is '%s'\n", ftp->entrypath); + } + else { + /* couldn't get the path */ + free(dir); + infof(data, "Failed to figure out path\n"); + } + } + state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ + infof(data, "protocol connect phase DONE\n"); + break; + + case FTP_QUOTE: + case FTP_POSTQUOTE: + case FTP_RETR_PREQUOTE: + case FTP_STOR_PREQUOTE: + if(ftpcode >= 400) { + failf(conn->data, "QUOT command failed with %03d", ftpcode); + return CURLE_FTP_QUOTE_ERROR; + } + result = ftp_state_quote(conn, FALSE, ftp->state); + if(result) + return result; + + break; + + case FTP_CWD: + if(ftpcode/100 != 2) { + /* failure to CWD there */ + if(conn->data->set.ftp_create_missing_dirs && + ftp->count1 && !ftp->count2) { + /* try making it */ + ftp->count2++; /* counter to prevent CWD-MKD loops */ + NBFTPSENDF(conn, "MKD %s", ftp->dirs[ftp->count1 - 1]); + state(conn, FTP_MKD); + } + else + /* return failure */ + return CURLE_FTP_ACCESS_DENIED; + } + else { + /* success */ + ftp->count2=0; + if(++ftp->count1 <= ftp->dirdepth) { + /* send next CWD */ + NBFTPSENDF(conn, "CWD %s", ftp->dirs[ftp->count1 - 1]); + } + else { + result = ftp_state_post_cwd(conn); + if(result) + return result; + } + } + break; + + case FTP_MKD: + if(ftpcode/100 != 2) { + /* failure to MKD the dir */ + failf(data, "Failed to MKD dir: %03d", ftpcode); + return CURLE_FTP_ACCESS_DENIED; + } + state(conn, FTP_CWD); + /* send CWD */ + NBFTPSENDF(conn, "CWD %s", ftp->dirs[ftp->count1 - 1]); + break; + + case FTP_MDTM: + result = ftp_state_mdtm_resp(conn, ftpcode); + break; + + case FTP_TYPE: + case FTP_LIST_TYPE: + case FTP_RETR_TYPE: + case FTP_STOR_TYPE: + result = ftp_state_type_resp(conn, ftpcode, ftp->state); + break; + + case FTP_SIZE: + case FTP_RETR_SIZE: + case FTP_STOR_SIZE: + result = ftp_state_size_resp(conn, ftpcode, ftp->state); + break; + + case FTP_REST: + case FTP_RETR_REST: + result = ftp_state_rest_resp(conn, ftpcode, ftp->state); + break; + + case FTP_PASV: + result = ftp_state_pasv_resp(conn, ftpcode); + break; + + case FTP_PORT: + result = ftp_state_port_resp(conn, ftpcode); + break; + + case FTP_LIST: + case FTP_RETR: + result = ftp_state_get_resp(conn, ftpcode, ftp->state); + break; + + case FTP_STOR: + result = ftp_state_stor_resp(conn, ftpcode); + break; + + case FTP_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + state(conn, FTP_STOP); + break; + } + } /* if(ftpcode) */ + + return result; +} + +/* Returns timeout in ms. 0 or negative number means the timeout has already + triggered */ +static long ftp_state_timeout(struct connectdata *conn) +{ + struct SessionHandle *data=conn->data; + struct FTP *ftp = conn->proto.ftp; + long timeout_ms=360000; /* in milliseconds */ + + if(data->set.ftp_response_timeout ) + /* if CURLOPT_FTP_RESPONSE_TIMEOUT is set, use that to determine remaining + time. Also, use ftp->response because FTP_RESPONSE_TIMEOUT is supposed + to govern the response for any given ftp response, not for the time + from connect to the given ftp response. */ + timeout_ms = data->set.ftp_response_timeout*1000 - /* timeout time */ + Curl_tvdiff(Curl_tvnow(), ftp->response); /* spent time */ + else if(data->set.timeout) + /* if timeout is requested, find out how much remaining time we have */ + timeout_ms = data->set.timeout*1000 - /* timeout time */ + Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ + else + /* Without a requested timeout, we only wait 'response_time' seconds for + the full response to arrive before we bail out */ + timeout_ms = ftp->response_time*1000 - + Curl_tvdiff(Curl_tvnow(), ftp->response); /* spent time */ + + return timeout_ms; +} + + +/* called repeatedly until done from multi.c */ +CURLcode Curl_ftp_multi_statemach(struct connectdata *conn, + bool *done) +{ + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc; + struct SessionHandle *data=conn->data; + struct FTP *ftp = conn->proto.ftp; + CURLcode result = CURLE_OK; + long timeout_ms = ftp_state_timeout(conn); + + *done = FALSE; /* default to not done yet */ + + if(timeout_ms <= 0) { + failf(data, "FTP response timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + rc = Curl_select(ftp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + ftp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + 0); + + if(rc == -1) { + failf(data, "select error"); + return CURLE_OUT_OF_MEMORY; + } + else if(rc != 0) { + result = ftp_statemach_act(conn); + *done = (ftp->state == FTP_STOP); + } + /* if rc == 0, then select() timed out */ + + return result; +} + +static CURLcode ftp_easy_statemach(struct connectdata *conn) +{ + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc; + struct SessionHandle *data=conn->data; + struct FTP *ftp = conn->proto.ftp; + CURLcode result = CURLE_OK; + + while(ftp->state != FTP_STOP) { + long timeout_ms = ftp_state_timeout(conn); + + if(timeout_ms <=0 ) { + failf(data, "FTP response timeout"); + return CURLE_OPERATION_TIMEDOUT; /* already too little time */ + } + + rc = Curl_select(ftp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + ftp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + timeout_ms); + + if(rc == -1) { + failf(data, "select error"); + return CURLE_OUT_OF_MEMORY; + } + else if(rc == 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + else { + result = ftp_statemach_act(conn); + if(result) + break; + } + } + + return result; +} + /* * Curl_ftp_connect() should do everything that is to be considered a part of * the connection phase. + * + * The variable 'done' points to will be TRUE if the protocol-layer connect + * phase is done when this function returns, or FALSE is not. When called as + * a part of the easy interface, it will always be TRUE. */ -CURLcode Curl_ftp_connect(struct connectdata *conn) +CURLcode Curl_ftp_connect(struct connectdata *conn, + bool *done) /* see description above */ { - /* this is FTP and no proxy */ - ssize_t nread; - struct SessionHandle *data=conn->data; - char *buf = data->state.buffer; /* this is our buffer */ struct FTP *ftp; CURLcode result; - int ftpcode, trynum; - static const char * const ftpauth[] = { - "SSL", "TLS", NULL - }; + + *done = FALSE; /* default to not done yet */ ftp = (struct FTP *)calloc(sizeof(struct FTP), 1); if(!ftp) @@ -482,13 +2707,14 @@ CURLcode Curl_ftp_connect(struct connectdata *conn) /* no need to duplicate them, this connectdata struct won't change */ ftp->user = conn->user; ftp->passwd = conn->passwd; - if (isBadFtpString(ftp->user) || isBadFtpString(ftp->passwd)) { + if (isBadFtpString(ftp->user) || isBadFtpString(ftp->passwd)) return CURLE_URL_MALFORMAT; - } + ftp->response_time = 3600; /* set default response time-out */ #ifndef CURL_DISABLE_HTTP if (conn->bits.tunnel_proxy) { + /* BLOCKING */ /* We want "seamless" FTP operations through HTTP proxy tunnel */ result = Curl_ConnectHTTPProxyTunnel(conn, FIRSTSOCKET, conn->host.name, conn->remote_port); @@ -498,6 +2724,7 @@ CURLcode Curl_ftp_connect(struct connectdata *conn) #endif /* CURL_DISABLE_HTTP */ if(conn->protocol & PROT_FTPS) { + /* BLOCKING */ /* FTPS is simply ftp with SSL for the control channel */ /* now, perform the SSL initialization for this socket */ result = Curl_SSLConnect(conn, FIRSTSOCKET); @@ -505,273 +2732,20 @@ CURLcode Curl_ftp_connect(struct connectdata *conn) return result; } - /* The first thing we do is wait for the "220*" line: */ - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; + /* When we connect, we start in the state where we await the 220 + response */ + state(conn, FTP_WAIT220); + ftp->response = Curl_tvnow(); /* start response time-out now! */ - if(ftpcode != 220) { - failf(data, "This doesn't seem like a nice ftp-server response"); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - -#ifdef HAVE_KRB4 - /* if not anonymous login, try a secure login */ - if(data->set.krb4) { - - /* request data protection level (default is 'clear') */ - Curl_sec_request_prot(conn, "private"); - - /* We set private first as default, in case the line below fails to - set a valid level */ - Curl_sec_request_prot(conn, data->set.krb4_level); - - if(Curl_sec_login(conn) != 0) - infof(data, "Logging in with password in cleartext!\n"); - else - infof(data, "Authentication successful\n"); - } -#endif - - if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* we don't have a SSL/TLS connection, try a FTPS connection now */ - int start; - int trynext; - int count=0; - - switch(data->set.ftpsslauth) { - case CURLFTPAUTH_DEFAULT: - case CURLFTPAUTH_SSL: - start = 0; - trynext = 1; - break; - case CURLFTPAUTH_TLS: - start = 1; - trynext = 0; - break; - default: - failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d\n", - data->set.ftpsslauth); - return CURLE_FAILED_INIT; /* we don't know what to do */ - } - - for (trynum = start; ftpauth[count]; trynum=trynext, count++) { - - FTPSENDF(conn, "AUTH %s", ftpauth[trynum]); - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - - if(result) - return result; - - /* RFC2228 (page 5) says: - * - * If the server is willing to accept the named security mechanism, and - * does not require any security data, it must respond with reply code - * 234/334. - */ - - if((ftpcode == 234) || (ftpcode == 334)) { - result = Curl_SSLConnect(conn, FIRSTSOCKET); - if(result) - return result; - conn->protocol |= PROT_FTPS; - conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */ - break; - } - } - } - - /* send USER */ - FTPSENDF(conn, "USER %s", ftp->user?ftp->user:""); - - /* wait for feedback */ - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(ftpcode == 530) { - /* 530 User ... access denied - (the server denies to log the specified user) */ - failf(data, "Access denied: %03d", ftpcode); - return CURLE_FTP_ACCESS_DENIED; - } - else if(ftpcode == 331) { - /* 331 Password required for ... - (the server requires to send the user's password too) */ - FTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:""); - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(ftpcode == 530) { - /* 530 Login incorrect. - (the username and/or the password are incorrect) - or - 530 Sorry, the maximum number of allowed users are already connected - */ - failf(data, "not logged in: %03d", ftpcode); - return CURLE_FTP_USER_PASSWORD_INCORRECT; - } - else if(ftpcode/100 == 2) { - /* 230 User ... logged in. - (user successfully logged in) - - Apparently, proftpd with SSL returns 232 here at times. */ - - infof(data, "We have successfully logged in\n"); - } - else if(ftpcode == 332) { - /* 332 Please provide account info */ - if(data->set.ftp_account) { - FTPSENDF(conn, "ACCT %s", data->set.ftp_account); - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(!result && (ftpcode != 230)) { - failf(data, "ACCT rejected by server: %03d", ftpcode); - result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ - } - } - else { - failf(data, "ACCT requested by none available"); - result = CURLE_FTP_WEIRD_PASS_REPLY; - } - if(result) - return result; - } - else { - failf(data, "Odd return code after PASS"); - return CURLE_FTP_WEIRD_PASS_REPLY; - } - } - else if(buf[0] == '2') { - /* 230 User ... logged in. - (the user logged in without password) */ - infof(data, "We have successfully logged in\n"); - if (conn->ssl[FIRSTSOCKET].use) { -#ifdef HAVE_KRB4 - /* We are logged in with Kerberos, now set the requested protection - * level - */ - if(conn->sec_complete) - Curl_sec_set_protection_level(conn); - - /* We may need to issue a KAUTH here to have access to the files - * do it if user supplied a password - */ - if(conn->passwd && *conn->passwd) { - result = Curl_krb_kauth(conn); - if(result) - return result; - } -#endif - } - } + if(conn->data->state.used_interface == Curl_if_multi) + result = Curl_ftp_multi_statemach(conn, done); else { - failf(data, "Odd return code after USER"); - return CURLE_FTP_WEIRD_USER_REPLY; + result = ftp_easy_statemach(conn); + if(!result) + *done = TRUE; } - if(conn->ssl[FIRSTSOCKET].use) { - /* PBSZ = PROTECTION BUFFER SIZE. - - The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: - - Specifically, the PROT command MUST be preceded by a PBSZ command - and a PBSZ command MUST be preceded by a successful security data - exchange (the TLS negotiation in this case) - - ... (and on page 8): - - Thus the PBSZ command must still be issued, but must have a parameter - of '0' to indicate that no buffering is taking place and the data - connection should not be encapsulated. - */ - FTPSENDF(conn, "PBSZ %d", 0); - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - /* For TLS, the data connection can have one of two security levels. - - 1)Clear (requested by 'PROT C') - - 2)Private (requested by 'PROT P') - */ - if(!conn->ssl[SECONDARYSOCKET].use) { - FTPSENDF(conn, "PROT %c", 'P'); - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(ftpcode/100 == 2) - /* We have enabled SSL for the data connection! */ - conn->ssl[SECONDARYSOCKET].use = TRUE; - /* FTP servers typically responds with 500 if they decide to reject - our 'P' request */ - else if(data->set.ftp_ssl> CURLFTPSSL_CONTROL) - /* we failed and bails out */ - return CURLE_FTP_SSL_FAILED; - } - } - - /* send PWD to discover our entry point */ - FTPSENDF(conn, "PWD", NULL); - - /* wait for feedback */ - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(ftpcode == 257) { - char *dir = (char *)malloc(nread+1); - char *store=dir; - char *ptr=&buf[4]; /* start on the first letter */ - - if(!dir) - return CURLE_OUT_OF_MEMORY; - - /* Reply format is like - 257"" and the RFC959 says - - The directory name can contain any character; embedded double-quotes - should be escaped by double-quotes (the "quote-doubling" convention). - */ - if('\"' == *ptr) { - /* it started good */ - ptr++; - while(ptr && *ptr) { - if('\"' == *ptr) { - if('\"' == ptr[1]) { - /* "quote-doubling" */ - *store = ptr[1]; - ptr++; - } - else { - /* end of path */ - *store = '\0'; /* zero terminate */ - break; /* get out of this loop */ - } - } - else - *store = *ptr; - store++; - ptr++; - } - ftp->entrypath =dir; /* remember this */ - infof(data, "Entry path is '%s'\n", ftp->entrypath); - } - else { - /* couldn't get the path */ - free(dir); - infof(data, "Failed to figure out path\n"); - } - - } - else { - /* We couldn't read the PWD response! */ - } - - return CURLE_OK; + return result; } /*********************************************************************** @@ -968,58 +2942,6 @@ CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote) return CURLE_OK; } -/*********************************************************************** - * - * ftp_getfiletime() - * - * Get the timestamp of the given file. - */ -static -CURLcode ftp_getfiletime(struct connectdata *conn, char *file) -{ - CURLcode result=CURLE_OK; - int ftpcode; /* for ftp status */ - ssize_t nread; - char *buf = conn->data->state.buffer; - - /* we have requested to get the modified-time of the file, this is yet - again a grey area as the MDTM is not kosher RFC959 */ - FTPSENDF(conn, "MDTM %s", file); - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - switch(ftpcode) { - case 213: - { - /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the - last .sss part is optional and means fractions of a second */ - int year, month, day, hour, minute, second; - if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d", - &year, &month, &day, &hour, &minute, &second)) { - /* we have a time, reformat it */ - time_t secs=time(NULL); - /* using the good old yacc/bison yuck */ - snprintf(buf, sizeof(conn->data->state.buffer), - "%04d%02d%02d %02d:%02d:%02d GMT", - year, month, day, hour, minute, second); - /* now, convert this into a time() value: */ - conn->data->info.filetime = curl_getdate(buf, &secs); - } - } - break; - default: - infof(conn->data, "unsupported MDTM reply format\n"); - break; - case 550: /* "No such file or directory" */ - failf(conn->data, "Given file does not exist"); - result = CURLE_FTP_COULDNT_RETR_FILE; - break; - } - return result; -} - /*********************************************************************** * * ftp_transfertype() @@ -1050,38 +2972,6 @@ static CURLcode ftp_transfertype(struct connectdata *conn, return CURLE_OK; } -/*********************************************************************** - * - * ftp_getsize() - * - * Returns the file size (in bytes) of the given remote file. - */ - -static -CURLcode ftp_getsize(struct connectdata *conn, char *file, - curl_off_t *size) -{ - struct SessionHandle *data = conn->data; - int ftpcode; - ssize_t nread; - char *buf=data->state.buffer; - CURLcode result; - - FTPSENDF(conn, "SIZE %s", file); - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(ftpcode == 213) { - /* get the size from the ascii string: */ - *size = curlx_strtoofft(buf+4, NULL, 0); - } - else - return CURLE_FTP_COULDNT_GET_SIZE; - - return CURLE_OK; -} - /*************************************************************************** * * ftp_pasv_verbose() @@ -1102,1071 +2992,117 @@ ftp_pasv_verbose(struct connectdata *conn, infof(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port); } -/*********************************************************************** - * - * ftp_use_port() - * - * Send the proper PORT command. PORT is the ftp client's way of telling the - * server that *WE* open a port that we listen on an awaits the server to - * connect to. This is the opposite of PASV. +/* + Check if this is a range download, and if so, set the internal variables + properly. */ -static -CURLcode ftp_use_port(struct connectdata *conn) -{ - struct SessionHandle *data=conn->data; - curl_socket_t portsock= CURL_SOCKET_BAD; - ssize_t nread; - int ftpcode; /* receive FTP response codes in this */ - CURLcode result; - -#ifdef ENABLE_IPV6 - /****************************************************************** - * - * Here's a piece of IPv6-specific code coming up - * - */ - - struct addrinfo *res, *ai; - struct sockaddr_storage ss; - socklen_t sslen; - char hbuf[NI_MAXHOST]=""; - struct sockaddr *sa=(struct sockaddr *)&ss; - unsigned char *ap; - unsigned char *pp; - char portmsgbuf[1024], tmp[1024]; - enum ftpcommand { EPRT, LPRT, PORT, DONE } fcmd; - const char *mode[] = { "EPRT", "LPRT", "PORT", NULL }; - int rc; - int error; - char *host=NULL; - struct Curl_dns_entry *h=NULL; - - if(data->set.ftpport && (strlen(data->set.ftpport) > 1)) { - /* attempt to get the address of the given interface name */ - if(!Curl_if2ip(data->set.ftpport, hbuf, sizeof(hbuf))) - /* not an interface, use the given string as host name instead */ - host = data->set.ftpport; - else - host = hbuf; /* use the hbuf for host name */ - } /* data->set.ftpport */ - - if(!host) { - /* not an interface and not a host name, get default by extracting - the IP from the control connection */ - - sslen = sizeof(ss); - rc = getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen); - if(rc < 0) { - failf(data, "getsockname() returned %d\n", rc); - return CURLE_FTP_PORT_FAILED; - } - - rc = getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL, - 0, NIFLAGS); - if(rc) { - failf(data, "getnameinfo() returned %d\n", rc); - return CURLE_FTP_PORT_FAILED; - } - host = hbuf; /* use this host name */ - } - - rc = Curl_resolv(conn, host, 0, &h); - if(rc == CURLRESOLV_PENDING) - rc = Curl_wait_for_resolv(conn, &h); - if(h) { - res = h->addr; - /* when we return from this function, we can forget about this entry - to we can unlock it now already */ - Curl_resolv_unlock(data, h); - } /* (h) */ - else - res = NULL; /* failure! */ - - portsock = CURL_SOCKET_BAD; - error = 0; - for (ai = res; ai; ai = ai->ai_next) { - /* - * Workaround for AIX5 getaddrinfo() problem (it doesn't set ai_socktype): - */ - if (ai->ai_socktype == 0) - ai->ai_socktype = SOCK_STREAM; - - portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (portsock == CURL_SOCKET_BAD) { - error = Curl_ourerrno(); - continue; - } - if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) { - error = Curl_ourerrno(); - sclose(portsock); - portsock = CURL_SOCKET_BAD; - continue; - } - - if (listen(portsock, 1) < 0) { - error = Curl_ourerrno(); - sclose(portsock); - portsock = CURL_SOCKET_BAD; - continue; - } - - break; - } - - if (portsock == CURL_SOCKET_BAD) { - failf(data, "%s", Curl_strerror(conn,error)); - return CURLE_FTP_PORT_FAILED; - } - - sslen = sizeof(ss); - if (getsockname(portsock, sa, &sslen) < 0) { - failf(data, "%s", Curl_strerror(conn,Curl_ourerrno())); - return CURLE_FTP_PORT_FAILED; - } - -#ifdef PF_INET6 - if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) - /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the - request and enable EPRT again! */ - conn->bits.ftp_use_eprt = TRUE; -#endif - - for (fcmd = EPRT; fcmd != DONE; fcmd++) { - int lprtaf, eprtaf; - int alen=0, plen=0; - - if(!conn->bits.ftp_use_eprt && (EPRT == fcmd)) - /* if disabled, goto next */ - continue; - - if(!conn->bits.ftp_use_lprt && (LPRT == fcmd)) - /* if disabled, goto next */ - continue; - - switch (sa->sa_family) { - case AF_INET: - ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr; - alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr); - pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port; - plen = sizeof(((struct sockaddr_in *)&ss)->sin_port); - lprtaf = 4; - eprtaf = 1; - break; - case AF_INET6: - ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr; - alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr); - pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port; - plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port); - lprtaf = 6; - eprtaf = 2; - break; - default: - ap = pp = NULL; - lprtaf = eprtaf = -1; - break; - } - - if (EPRT == fcmd) { - if (eprtaf < 0) - continue; - if (getnameinfo((struct sockaddr *)&ss, sslen, - portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp), - NIFLAGS)) - continue; - - /* do not transmit IPv6 scope identifier to the wire */ - if (sa->sa_family == AF_INET6) { - char *q = strchr(portmsgbuf, '%'); - if (q) - *q = '\0'; - } - - result = Curl_ftpsendf(conn, "%s |%d|%s|%s|", mode[fcmd], eprtaf, - portmsgbuf, tmp); - if(result) - return result; - } - else if ((LPRT == fcmd) || (PORT == fcmd)) { - int i; - - if ((LPRT == fcmd) && lprtaf < 0) - continue; - if ((PORT == fcmd) && sa->sa_family != AF_INET) - continue; - - portmsgbuf[0] = '\0'; - if (LPRT == fcmd) { - snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen); - if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= - sizeof(portmsgbuf)) { - continue; - } - } - - for (i = 0; i < alen; i++) { - if (portmsgbuf[0]) - snprintf(tmp, sizeof(tmp), ",%u", ap[i]); - else - snprintf(tmp, sizeof(tmp), "%u", ap[i]); - - if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= - sizeof(portmsgbuf)) { - continue; - } - } - - if (LPRT == fcmd) { - snprintf(tmp, sizeof(tmp), ",%d", plen); - - if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf)) - continue; - } - - for (i = 0; i < plen; i++) { - snprintf(tmp, sizeof(tmp), ",%u", pp[i]); - - if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= - sizeof(portmsgbuf)) { - continue; - } - } - - result = Curl_ftpsendf(conn, "%s %s", mode[fcmd], portmsgbuf); - if(result) - return result; - } - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if (ftpcode != 200) { - if (EPRT == fcmd) { - infof(data, "disabling EPRT usage\n"); - conn->bits.ftp_use_eprt = FALSE; - } - else if (LPRT == fcmd) { - infof(data, "disabling LPRT usage\n"); - conn->bits.ftp_use_lprt = FALSE; - } - continue; - } - else - break; - } - - if (fcmd == DONE) { - sclose(portsock); - failf(data, "PORT command attempts failed"); - return CURLE_FTP_PORT_FAILED; - } - /* we set the secondary socket variable to this for now, it - is only so that the cleanup function will close it in case - we fail before the true secondary stuff is made */ - conn->sock[SECONDARYSOCKET] = portsock; - -#else - /****************************************************************** - * - * Here's a piece of IPv4-specific code coming up - * - */ - struct sockaddr_in sa; - unsigned short porttouse; - char myhost[256] = ""; - bool sa_filled_in = FALSE; - Curl_addrinfo *addr = NULL; - unsigned short ip[4]; - - if(data->set.ftpport) { - in_addr_t in; - - /* First check if the given name is an IP address */ - in=inet_addr(data->set.ftpport); - - if(in != CURL_INADDR_NONE) - /* this is an IPv4 address */ - addr = Curl_ip2addr(in, data->set.ftpport, 0); - else { - if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) { - /* The interface to IP conversion provided a dotted address */ - in=inet_addr(myhost); - addr = Curl_ip2addr(in, myhost, 0); - } - else if(strlen(data->set.ftpport)> 1) { - /* might be a host name! */ - struct Curl_dns_entry *h=NULL; - int rc = Curl_resolv(conn, myhost, 0, &h); - if(rc == CURLRESOLV_PENDING) - rc = Curl_wait_for_resolv(conn, &h); - if(h) { - addr = h->addr; - /* when we return from this function, we can forget about this entry - to we can unlock it now already */ - Curl_resolv_unlock(data, h); - } /* (h) */ - } /* strlen */ - } /* CURL_INADDR_NONE */ - } /* data->set.ftpport */ - - if(!addr) { - /* pick a suitable default here */ - - socklen_t sslen; - - sslen = sizeof(sa); - if (getsockname(conn->sock[FIRSTSOCKET], - (struct sockaddr *)&sa, &sslen) < 0) { - failf(data, "getsockname() failed"); - return CURLE_FTP_PORT_FAILED; - } - - sa_filled_in = TRUE; /* the sa struct is filled in */ - } - - if (addr || sa_filled_in) { - portsock = socket(AF_INET, SOCK_STREAM, 0); - if(CURL_SOCKET_BAD != portsock) { - socklen_t size; - - /* we set the secondary socket variable to this for now, it - is only so that the cleanup function will close it in case - we fail before the true secondary stuff is made */ - conn->sock[SECONDARYSOCKET] = portsock; - - if(!sa_filled_in) { - memcpy(&sa, addr->ai_addr, sizeof(sa)); - sa.sin_addr.s_addr = INADDR_ANY; - } - - sa.sin_port = 0; - size = sizeof(sa); - - if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) { - /* we succeeded to bind */ - struct sockaddr_in add; - socklen_t socksize = sizeof(add); - - if(getsockname(portsock, (struct sockaddr *) &add, - &socksize)<0) { - failf(data, "getsockname() failed"); - return CURLE_FTP_PORT_FAILED; - } - porttouse = ntohs(add.sin_port); - - if ( listen(portsock, 1) < 0 ) { - failf(data, "listen(2) failed on socket"); - return CURLE_FTP_PORT_FAILED; - } - } - else { - failf(data, "bind(2) failed on socket"); - return CURLE_FTP_PORT_FAILED; - } - } - else { - failf(data, "socket(2) failed (%s)"); - return CURLE_FTP_PORT_FAILED; - } - } - else { - failf(data, "could't find IP address to use"); - return CURLE_FTP_PORT_FAILED; - } - - if(sa_filled_in) - Curl_inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr, - myhost, sizeof(myhost)); - else - Curl_printable_address(addr, myhost, sizeof(myhost)); - - if(4 == sscanf(myhost, "%hu.%hu.%hu.%hu", - &ip[0], &ip[1], &ip[2], &ip[3])) { - - infof(data, "Telling server to connect to %d.%d.%d.%d:%d\n", - ip[0], ip[1], ip[2], ip[3], porttouse); - - result=Curl_ftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d", - ip[0], ip[1], ip[2], ip[3], - porttouse >> 8, - porttouse & 255); - if(result) - return result; - - } - else - return CURLE_FTP_PORT_FAILED; - - Curl_freeaddrinfo(addr); - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(ftpcode != 200) { - failf(data, "Server does not grok PORT, try without it!"); - return CURLE_FTP_PORT_FAILED; - } -#endif /* end of ipv4-specific code */ - - return CURLE_OK; -} - -/*********************************************************************** - * - * ftp_use_pasv() - * - * Send the PASV command. PASV is the ftp client's way of asking the server to - * open a second port that we can connect to (for the data transfer). This is - * the opposite of PORT. - */ - -static -CURLcode ftp_use_pasv(struct connectdata *conn, - bool *connected) +static CURLcode ftp_range(struct connectdata *conn) { + curl_off_t from, to; + curl_off_t totalsize=-1; + char *ptr; + char *ptr2; struct SessionHandle *data = conn->data; - ssize_t nread; - char *buf = data->state.buffer; /* this is our buffer */ - int ftpcode; /* receive FTP response codes in this */ - CURLcode result; - struct Curl_dns_entry *addr=NULL; - Curl_addrinfo *conninfo; - int rc; + struct FTP *ftp = conn->proto.ftp; - /* - Here's the excecutive summary on what to do: - - PASV is RFC959, expect: - 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) - - LPSV is RFC1639, expect: - 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) - - EPSV is RFC2428, expect: - 229 Entering Extended Passive Mode (|||port|) - - */ - - const char *mode[] = { "EPSV", "PASV", NULL }; - int results[] = { 229, 227, 0 }; - int modeoff; - unsigned short connectport; /* the local port connect() should use! */ - unsigned short newport=0; /* remote port, not necessary the local one */ - - /* newhost must be able to hold a full IP-style address in ASCII, which - in the IPv6 case means 5*8-1 = 39 letters */ -#define NEWHOST_BUFSIZE 48 - char newhost[NEWHOST_BUFSIZE]; - -#ifdef PF_INET6 - if(!conn->bits.ftp_use_epsv && conn->bits.ipv6) - /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the - request and enable EPSV again! */ - conn->bits.ftp_use_epsv = TRUE; -#endif - - for (modeoff = (conn->bits.ftp_use_epsv?0:1); - mode[modeoff]; modeoff++) { - result = Curl_ftpsendf(conn, "%s", mode[modeoff]); - if(result) - return result; - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - if (ftpcode == results[modeoff]) - break; - - if(modeoff == 0) { - /* EPSV is not supported, disable it for next transfer */ - conn->bits.ftp_use_epsv = FALSE; - infof(data, "disabling EPSV usage\n"); - } - } - - if (!mode[modeoff]) { - failf(data, "Odd return code after PASV"); - return CURLE_FTP_WEIRD_PASV_REPLY; - } - else if (227 == results[modeoff]) { - int ip[4]; - int port[2]; - char *str=buf; - - /* - * New 227-parser June 3rd 1999. - * It now scans for a sequence of six comma-separated numbers and - * will take them as IP+port indicators. - * - * Found reply-strings include: - * "227 Entering Passive Mode (127,0,0,1,4,51)" - * "227 Data transfer will passively listen to 127,0,0,1,4,51" - * "227 Entering passive mode. 127,0,0,1,4,51" - */ - - while(*str) { - if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d", - &ip[0], &ip[1], &ip[2], &ip[3], - &port[0], &port[1])) - break; - str++; - } - - if(!*str) { - failf(data, "Couldn't interpret the 227-reply"); - return CURLE_FTP_WEIRD_227_FORMAT; - } - - snprintf(newhost, sizeof(newhost), - "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - newport = (port[0]<<8) + port[1]; - } - else if (229 == results[modeoff]) { - char *ptr = strchr(buf, '('); - if(ptr) { - unsigned int num; - char separator[4]; + if(conn->bits.use_range && conn->range) { + from=curlx_strtoofft(conn->range, &ptr, 0); + while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-'))) ptr++; - if(5 == sscanf(ptr, "%c%c%c%u%c", - &separator[0], - &separator[1], - &separator[2], - &num, - &separator[3])) { - char sep1 = separator[0]; - int i; - - /* The four separators should be identical, or else this is an oddly - formatted reply and we bail out immediately. */ - for(i=1; i<4; i++) { - if(separator[i] != sep1) { - ptr=NULL; /* set to NULL to signal error */ - break; - } - } - if(ptr) { - newport = num; - - /* We must use the same IP we are already connected to */ - snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str); - } - } - else - ptr=NULL; + to=curlx_strtoofft(ptr, &ptr2, 0); + if(ptr == ptr2) { + /* we didn't get any digit */ + to=-1; } - if(!ptr) { - failf(data, "Weirdly formatted EPSV reply"); - return CURLE_FTP_WEIRD_PASV_REPLY; + if((-1 == to) && (from>=0)) { + /* X - */ + conn->resume_from = from; + infof(data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", from); } - } - else - return CURLE_FTP_CANT_RECONNECT; - - if(data->change.proxy && *data->change.proxy) { - /* - * This is a tunnel through a http proxy and we need to connect to the - * proxy again here. - * - * We don't want to rely on a former host lookup that might've expired - * now, instead we remake the lookup here and now! - */ - rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr); - if(rc == CURLRESOLV_PENDING) - rc = Curl_wait_for_resolv(conn, &addr); - - connectport = - (unsigned short)conn->port; /* we connect to the proxy's port */ - - } - else { - /* normal, direct, ftp connection */ - rc = Curl_resolv(conn, newhost, newport, &addr); - if(rc == CURLRESOLV_PENDING) - rc = Curl_wait_for_resolv(conn, &addr); - - if(!addr) { - failf(data, "Can't resolve new host %s:%d", newhost, newport); - return CURLE_FTP_CANT_GET_HOST; + else if(from < 0) { + /* -Y */ + totalsize = -from; + conn->maxdownload = -from; + conn->resume_from = from; + infof(data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", totalsize); } - connectport = newport; /* we connect to the remote port */ + else { + /* X-Y */ + totalsize = to-from; + conn->maxdownload = totalsize+1; /* include the last mentioned byte */ + conn->resume_from = from; + infof(data, "FTP RANGE from %" FORMAT_OFF_T + " getting %" FORMAT_OFF_T " bytes\n", from, conn->maxdownload); + } + infof(data, "range-download from %" FORMAT_OFF_T + " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n", + from, to, conn->maxdownload); + ftp->dont_check = TRUE; /* dont check for successful transfer */ } - - result = Curl_connecthost(conn, - addr, - &conn->sock[SECONDARYSOCKET], - &conninfo, - connected); - - Curl_resolv_unlock(data, addr); /* we're done using this address */ - - if(result) - return result; - - /* - * When this is used from the multi interface, this might've returned with - * the 'connected' set to FALSE and thus we are now awaiting a non-blocking - * connect to connect and we should not be "hanging" here waiting. - */ - - if(data->set.verbose) - /* this just dumps information about this second connection */ - ftp_pasv_verbose(conn, conninfo, newhost, connectport); - -#ifndef CURL_DISABLE_HTTP - if(conn->bits.tunnel_proxy) { - /* We want "seamless" FTP operations through HTTP proxy tunnel */ - result = Curl_ConnectHTTPProxyTunnel(conn, SECONDARYSOCKET, - newhost, newport); - if(CURLE_OK != result) - return result; - } -#endif /* CURL_DISABLE_HTTP */ - return CURLE_OK; } + /* * Curl_ftp_nextconnect() * - * This function shall be called when the second FTP connection has been - * established and is confirmed connected. + * This function shall be called when the second FTP (data) connection is + * connected. */ CURLcode Curl_ftp_nextconnect(struct connectdata *conn) { struct SessionHandle *data=conn->data; - char *buf = data->state.buffer; /* this is our buffer */ - CURLcode result; - ssize_t nread; - int ftpcode; /* for ftp status */ + CURLcode result = CURLE_OK; - /* the ftp struct is already inited in Curl_ftp_connect() */ + /* the ftp struct is inited in Curl_ftp_connect() */ struct FTP *ftp = conn->proto.ftp; - curl_off_t *bytecountp = ftp->bytecountp; - if(data->set.upload) { + infof(data, "DO-MORE phase starts\n"); - /* Set type to binary (unless specified ASCII) */ - result = ftp_transfertype(conn, data->set.ftp_ascii); - if(result) - return result; + if(!ftp->no_transfer && !conn->bits.no_body) { + /* a transfer is about to take place */ - /* Send any PREQUOTE strings after transfer type is set? (Wesley Laxton)*/ - if(data->set.prequote) { - if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK) - return result; - } - - if(conn->resume_from) { - /* we're about to continue the uploading of a file */ - /* 1. get already existing file's size. We use the SIZE - command for this which may not exist in the server! - The SIZE command is not in RFC959. */ - - /* 2. This used to set REST. But since we can do append, we - don't another ftp command. We just skip the source file - offset and then we APPEND the rest on the file instead */ - - /* 3. pass file-size number of bytes in the source file */ - /* 4. lower the infilesize counter */ - /* => transfer as usual */ - - if(conn->resume_from < 0 ) { - /* we could've got a specified offset from the command line, - but now we know we didn't */ - curl_off_t gottensize; - - if(CURLE_OK != ftp_getsize(conn, ftp->file, &gottensize)) { - failf(data, "Couldn't get remote file size"); - return CURLE_FTP_COULDNT_GET_SIZE; - } - conn->resume_from = gottensize; - } - - if(conn->resume_from) { - /* do we still game? */ - curl_off_t passed=0; - /* enable append instead */ - data->set.ftp_append = 1; - - /* Now, let's read off the proper amount of bytes from the - input. If we knew it was a proper file we could've just - fseek()ed but we only have a stream here */ - do { - curl_off_t readthisamountnow = (conn->resume_from - passed); - curl_off_t actuallyread; - - if(readthisamountnow > BUFSIZE) - readthisamountnow = BUFSIZE; - - actuallyread = (curl_off_t) - conn->fread(data->state.buffer, 1, (size_t)readthisamountnow, - conn->fread_in); - - passed += actuallyread; - if(actuallyread != readthisamountnow) { - failf(data, "Could only read %" FORMAT_OFF_T - " bytes from the input", passed); - return CURLE_FTP_COULDNT_USE_REST; - } - } - while(passed != conn->resume_from); - - /* now, decrease the size of the read */ - if(data->set.infilesize>0) { - data->set.infilesize -= conn->resume_from; - - if(data->set.infilesize <= 0) { - infof(data, "File already completely uploaded\n"); - - /* no data to transfer */ - result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - - /* Set no_transfer so that we won't get any error in - * Curl_ftp_done() because we didn't transfer anything! */ - ftp->no_transfer = TRUE; - - return CURLE_OK; - } - } - /* we've passed, proceed as normal */ - } - } - - /* Send everything on data->state.in to the socket */ - if(data->set.ftp_append) { - /* we append onto the file instead of rewriting it */ - FTPSENDF(conn, "APPE %s", ftp->file); + if(data->set.upload) { + NBFTPSENDF(conn, "TYPE %c", data->set.ftp_ascii?'A':'I'); + state(conn, FTP_STOR_TYPE); } else { - FTPSENDF(conn, "STOR %s", ftp->file); - } + /* download */ + ftp->downloadsize = -1; /* unknown as of yet */ - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(ftpcode>=400) { - failf(data, "Failed FTP upload: %03d", ftpcode); - /* oops, we never close the sockets! */ - return CURLE_FTP_COULDNT_STOR_FILE; - } - - if(data->set.ftp_use_port) { - /* PORT means we are now awaiting the server to connect to us. */ - 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_SSLConnect(conn, SECONDARYSOCKET); + result = ftp_range(conn); if(result) - return result; - } - - *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); - - result = Curl_Transfer(conn, -1, -1, FALSE, NULL, /* no download */ - SECONDARYSOCKET, bytecountp); - if(result) - return result; - - } - else if(!conn->bits.no_body) { - /* Retrieve file or directory */ - bool dirlist=FALSE; - curl_off_t downloadsize=-1; - - if(conn->bits.use_range && conn->range) { - curl_off_t from, to; - curl_off_t totalsize=-1; - char *ptr; - char *ptr2; - - from=curlx_strtoofft(conn->range, &ptr, 0); - while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-'))) - ptr++; - to=curlx_strtoofft(ptr, &ptr2, 0); - if(ptr == ptr2) { - /* we didn't get any digit */ - to=-1; - } - if((-1 == to) && (from>=0)) { - /* X - */ - conn->resume_from = from; - infof(data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", from); - } - else if(from < 0) { - /* -Y */ - totalsize = -from; - conn->maxdownload = -from; - conn->resume_from = from; - infof(data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", totalsize); + ; + else if((data->set.ftp_list_only) || !ftp->file) { + /* The specified path ends with a slash, and therefore we think this + is a directory that is requested, use LIST. But before that we + need to set ASCII transfer mode. */ + NBFTPSENDF(conn, "TYPE A", NULL); + state(conn, FTP_LIST_TYPE); } else { - /* X-Y */ - totalsize = to-from; - conn->maxdownload = totalsize+1; /* include the last mentioned byte */ - conn->resume_from = from; - infof(data, "FTP RANGE from %" FORMAT_OFF_T - " getting %" FORMAT_OFF_T " bytes\n", from, conn->maxdownload); - } - infof(data, "range-download from %" FORMAT_OFF_T - " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n", - from, to, conn->maxdownload); - ftp->dont_check = TRUE; /* dont check for successful transfer */ - } - - if((data->set.ftp_list_only) || !ftp->file) { - /* The specified path ends with a slash, and therefore we think this - is a directory that is requested, use LIST. But before that we - need to set ASCII transfer mode. */ - dirlist = TRUE; - - /* Set type to ASCII */ - result = ftp_transfertype(conn, TRUE /* ASCII enforced */); - if(result) - return result; - - /* if this output is to be machine-parsed, the NLST command will be - better used since the LIST command output is not specified or - standard in any way */ - - FTPSENDF(conn, "%s", - data->set.customrequest?data->set.customrequest: - (data->set.ftp_list_only?"NLST":"LIST")); - } - else { - curl_off_t foundsize; - - /* Set type to binary (unless specified ASCII) */ - result = ftp_transfertype(conn, data->set.ftp_ascii); - if(result) - return result; - - /* Send any PREQUOTE strings after transfer type is set? */ - if(data->set.prequote) { - if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK) - return result; - } - - /* Attempt to get the size, it'll be useful in some cases: for resumed - downloads and when talking to servers that don't give away the size - in the RETR response line. */ - result = ftp_getsize(conn, ftp->file, &foundsize); - if(CURLE_OK == result) { - if (data->set.max_filesize && foundsize > data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - downloadsize = foundsize; - } - - if(conn->resume_from) { - - /* Daniel: (August 4, 1999) - * - * We start with trying to use the SIZE command to figure out the size - * of the file we're gonna get. If we can get the size, this is by far - * the best way to know if we're trying to resume beyond the EOF. - * - * Daniel, November 28, 2001. We *always* get the size on downloads - * now, so it is done before this even when not doing resumes. I saved - * the comment above for nostalgical reasons! ;-) - */ - if(CURLE_OK != result) { - infof(data, "ftp server doesn't support SIZE\n"); - /* We couldn't get the size and therefore we can't know if there - really is a part of the file left to get, although the server - will just close the connection when we start the connection so it - won't cause us any harm, just not make us exit as nicely. */ - } - else { - /* We got a file size report, so we check that there actually is a - part of the file left to get, or else we go home. */ - if(conn->resume_from< 0) { - /* We're supposed to download the last abs(from) bytes */ - if(foundsize < -conn->resume_from) { - failf(data, "Offset (%" FORMAT_OFF_T - ") was beyond file size (%" FORMAT_OFF_T ")", - conn->resume_from, foundsize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* convert to size to download */ - downloadsize = -conn->resume_from; - /* download from where? */ - conn->resume_from = foundsize - downloadsize; - } - else { - if(foundsize < conn->resume_from) { - failf(data, "Offset (%" FORMAT_OFF_T - ") was beyond file size (%" FORMAT_OFF_T ")", - conn->resume_from, foundsize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* Now store the number of bytes we are expected to download */ - downloadsize = foundsize-conn->resume_from; - } - } - - if (downloadsize == 0) { - /* no data to transfer */ - result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - infof(data, "File already completely downloaded\n"); - - /* Set no_transfer so that we won't get any error in Curl_ftp_done() - * because we didn't transfer the any file */ - ftp->no_transfer = TRUE; - return CURLE_OK; - } - - /* Set resume file transfer offset */ - infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T - "\n", - conn->resume_from); - - FTPSENDF(conn, "REST %" FORMAT_OFF_T, conn->resume_from); - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(ftpcode != 350) { - failf(data, "Couldn't use REST: %03d", ftpcode); - return CURLE_FTP_COULDNT_USE_REST; - } - } - - FTPSENDF(conn, "RETR %s", ftp->file); - } - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if((ftpcode == 150) || (ftpcode == 125)) { - - /* - A; - 150 Opening BINARY mode data connection for /etc/passwd (2241 - bytes). (ok, the file is being transfered) - - B: - 150 Opening ASCII mode data connection for /bin/ls - - C: - 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes). - - D: - 150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes). - - E: - 125 Data connection already open; Transfer starting. */ - - curl_off_t size=-1; /* default unknown size */ - - - /* - * It appears that there are FTP-servers that return size 0 for files - * when SIZE is used on the file while being in BINARY mode. To work - * around that (stupid) behavior, we attempt to parse the RETR response - * even if the SIZE returned size zero. - * - * Debugging help from Salvatore Sorrentino on February 26, 2003. - */ - - if(!dirlist && - !data->set.ftp_ascii && - (downloadsize < 1)) { - /* - * It seems directory listings either don't show the size or very - * often uses size 0 anyway. ASCII transfers may very well turn out - * that the transfered amount of data is not the same as this line - * tells, why using this number in those cases only confuses us. - * - * Example D above makes this parsing a little tricky */ - char *bytes; - bytes=strstr(buf, " bytes"); - if(bytes--) { - long in=bytes-buf; - /* this is a hint there is size information in there! ;-) */ - while(--in) { - /* scan for the parenthesis and break there */ - if('(' == *bytes) - break; - /* if only skip digits, or else we're in deep trouble */ - if(!isdigit((int)*bytes)) { - bytes=NULL; - break; - } - /* one more estep backwards */ - bytes--; - } - /* only if we have nothing but digits: */ - if(bytes++) { - /* get the number! */ - size = curlx_strtoofft(bytes, NULL, 0); - } - - } - } - else if(downloadsize > -1) - size = downloadsize; - - if(data->set.ftp_use_port) { - 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_SSLConnect(conn, SECONDARYSOCKET); - if(result) - return result; - } - - if(size > conn->maxdownload && conn->maxdownload > 0) - size = conn->size = conn->maxdownload; - - infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size); - - /* FTP download: */ - result=Curl_Transfer(conn, SECONDARYSOCKET, size, FALSE, - bytecountp, - -1, NULL); /* no upload here */ - if(result) - return result; - } - else { - if(dirlist && (ftpcode == 450)) { - /* simply no matching files */ - ftp->no_transfer = TRUE; /* don't think we should download anything */ - } - else { - failf(data, "RETR failed: %03d", ftpcode); - return CURLE_FTP_COULDNT_RETR_FILE; + NBFTPSENDF(conn, "TYPE %c", data->set.ftp_ascii?'A':'I'); + state(conn, FTP_RETR_TYPE); } } - + result = ftp_easy_statemach(conn); } + + if(ftp->no_transfer) + /* no data to transfer. FIX: it feels like a kludge to have this here + too! */ + result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + /* end of transfer */ + infof(data, "DO-MORE phase ends\n"); - return CURLE_OK; + return result; } + + /*********************************************************************** * * ftp_perform() @@ -2177,151 +3113,33 @@ CURLcode Curl_ftp_nextconnect(struct connectdata *conn) static CURLcode ftp_perform(struct connectdata *conn, - bool *connected) /* for the TCP connect status after - PASV / PORT */ + bool *connected, /* connect status after PASV / PORT */ + bool *dophase_done) { /* this is FTP and no proxy */ CURLcode result=CURLE_OK; struct SessionHandle *data=conn->data; - char *buf = data->state.buffer; /* this is our buffer */ - /* the ftp struct is already inited in Curl_ftp_connect() */ - struct FTP *ftp = conn->proto.ftp; + infof(data, "DO phase starts\n"); - /* Send any QUOTE strings? */ - if(data->set.quote) { - if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK) - return result; - } + *dophase_done = FALSE; /* not done yet */ - result = ftp_cwd_and_create_path(conn); - if (result) + /* start the first command in the DO phase */ + result = ftp_state_quote(conn, TRUE, FTP_QUOTE); + if(result) return result; - /* Requested time of file or time-depended transfer? */ - if((data->set.get_filetime || data->set.timecondition) && - ftp->file) { - result = ftp_getfiletime(conn, ftp->file); - switch( result ) - { - case CURLE_FTP_COULDNT_RETR_FILE: - case CURLE_OK: - if(data->set.timecondition) { - if((data->info.filetime > 0) && (data->set.timevalue > 0)) { - switch(data->set.timecondition) { - case CURL_TIMECOND_IFMODSINCE: - default: - if(data->info.filetime < data->set.timevalue) { - infof(data, "The requested document is not new enough\n"); - ftp->no_transfer = TRUE; /* mark this to not transfer data */ - return CURLE_OK; - } - break; - case CURL_TIMECOND_IFUNMODSINCE: - if(data->info.filetime > data->set.timevalue) { - infof(data, "The requested document is not old enough\n"); - ftp->no_transfer = TRUE; /* mark this to not transfer data */ - return CURLE_OK; - } - break; - } /* switch */ - } - else { - infof(data, "Skipping time comparison\n"); - } - } - break; - default: - return result; - } /* switch */ - } - - /* If we have selected NOBODY and HEADER, it means that we only want file - information. Which in FTP can't be much more than the file size and - date. */ - if(conn->bits.no_body && data->set.include_header && ftp->file) { - /* The SIZE command is _not_ RFC 959 specified, and therefor many servers - may not support it! It is however the only way we have to get a file's - size! */ - curl_off_t filesize; - ssize_t nread; - int ftpcode; - - ftp->no_transfer = TRUE; /* this means no actual transfer is made */ - - /* Some servers return different sizes for different modes, and thus we - must set the proper type before we check the size */ - result = ftp_transfertype(conn, data->set.ftp_ascii); - if(result) - return result; - - /* failing to get size is not a serious error */ - result = ftp_getsize(conn, ftp->file, &filesize); - - if(CURLE_OK == result) { - snprintf(buf, sizeof(data->state.buffer), - "Content-Length: %" FORMAT_OFF_T "\r\n", filesize); - result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0); - if(result) - return result; - } - - /* Determine if server can respond to REST command and therefore - whether it can do a range */ - FTPSENDF(conn, "REST 0", NULL); - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - - if ((CURLE_OK == result) && (ftpcode == 350)) { - result = Curl_client_write(data, CLIENTWRITE_BOTH, - (char *)"Accept-ranges: bytes\r\n", 0); - if(result) - return result; - } - - /* If we asked for a time of the file and we actually got one as - well, we "emulate" a HTTP-style header in our output. */ - -#ifdef HAVE_STRFTIME - if(data->set.get_filetime && (data->info.filetime>=0) ) { - struct tm *tm; - time_t clock = (time_t)data->info.filetime; -#ifdef HAVE_GMTIME_R - struct tm buffer; - tm = (struct tm *)gmtime_r(&clock, &buffer); -#else - tm = gmtime(&clock); -#endif - /* format: "Tue, 15 Nov 1994 12:45:26" */ - strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n", - tm); - result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0); - if(result) - return result; - } -#endif - - return CURLE_OK; - } - - if(conn->bits.no_body) - /* doesn't really transfer any data */ - ftp->no_transfer = TRUE; - /* Get us a second connection up and connected */ - else if(data->set.ftp_use_port) { - /* We have chosen to use the PORT command */ - result = ftp_use_port(conn); - if(CURLE_OK == result) { - /* we have the data connection ready */ - infof(data, "Ordered connect of the data stream with PORT!\n"); - *connected = TRUE; /* mark us "still connected" */ - } - } + /* run the state-machine */ + if(conn->data->state.used_interface == Curl_if_multi) + result = Curl_ftp_multi_statemach(conn, dophase_done); else { - /* We have chosen (this is default) to use the PASV command */ - result = ftp_use_pasv(conn, connected); - if(CURLE_OK == result && *connected) - infof(data, "Connected the data stream with PASV!\n"); + result = ftp_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ } + *connected = conn->bits.tcpconnect; + + if(*dophase_done) + infof(data, "DO phase is comlete\n"); return result; } @@ -2335,31 +3153,89 @@ CURLcode ftp_perform(struct connectdata *conn, * * The input argument is already checked for validity. */ -CURLcode Curl_ftp(struct connectdata *conn) +CURLcode Curl_ftp(struct connectdata *conn, bool *done) { CURLcode retcode = CURLE_OK; + *done = FALSE; /* default to false */ + retcode = ftp_parse_url_path(conn); if (retcode) return retcode; - if (conn->sec_conn) /* 3rd party transfer */ + if (conn->sec_conn) { + /* 3rd party transfer */ + *done = TRUE; /* BLOCKING */ retcode = ftp_3rdparty(conn); + } else - retcode = ftp_regular_transfer(conn); + retcode = ftp_regular_transfer(conn, done); return retcode; } /*********************************************************************** * - * Curl_ftpsendf() + * Curl_(nb)ftpsendf() * * Sends the formated string as a ftp command to a ftp server * * NOTE: we build the command in a fixed-length buffer, which sets length * restrictions on the command! + * + * The "nb" version is made to Never Block. */ +CURLcode Curl_nbftpsendf(struct connectdata *conn, + const char *fmt, ...) +{ + ssize_t bytes_written; + char s[256]; + size_t write_len; + char *sptr=s; + CURLcode res = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + struct SessionHandle *data = conn->data; + + va_list ap; + va_start(ap, fmt); + vsnprintf(s, 250, fmt, ap); + va_end(ap); + + strcat(s, "\r\n"); /* append a trailing CRLF */ + + bytes_written=0; + write_len = strlen(s); + + res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len, + &bytes_written); + + if(CURLE_OK != res) + return res; + + if(conn->data->set.verbose) + Curl_debug(conn->data, CURLINFO_HEADER_OUT, sptr, bytes_written, + conn); + + if(bytes_written != (ssize_t)write_len) { + /* the whole chunk was not sent, store the rest of the data */ + write_len -= bytes_written; + sptr += bytes_written; + ftp->sendthis = malloc(write_len); + if(ftp->sendthis) { + memcpy(ftp->sendthis, sptr, write_len); + ftp->sendsize=ftp->sendleft=write_len; + } + else { + failf(data, "out of memory"); + res = CURLE_OUT_OF_MEMORY; + } + } + else + ftp->response = Curl_tvnow(); + + return res; +} + CURLcode Curl_ftpsendf(struct connectdata *conn, const char *fmt, ...) { @@ -2412,17 +3288,16 @@ CURLcode Curl_ftpsendf(struct connectdata *conn, */ static CURLcode ftp_quit(struct connectdata *conn) { - ssize_t nread; - int ftpcode; - CURLcode ret = CURLE_OK; + CURLcode result = CURLE_OK; if(conn->proto.ftp->ctl_valid) { - ret = Curl_ftpsendf(conn, "%s", "QUIT"); - if(CURLE_OK == ret) - ret = Curl_GetFTPResponse(&nread, conn, &ftpcode); + NBFTPSENDF(conn, "QUIT", NULL); + state(conn, FTP_QUIT); + + result = ftp_easy_statemach(conn); } - return ret; + return result; } /*********************************************************************** @@ -2430,7 +3305,7 @@ static CURLcode ftp_quit(struct connectdata *conn) * Curl_ftp_disconnect() * * Disconnect from an FTP server. Cleanup protocol-specific per-connection - * resources + * resources. BLOCKING. */ CURLcode Curl_ftp_disconnect(struct connectdata *conn) { @@ -2881,21 +3756,66 @@ CURLcode ftp_cwd_and_create_path(struct connectdata *conn) return result; } +/* call this when the DO phase has completed */ +static CURLcode ftp_dophase_done(struct connectdata *conn, + bool connected) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->proto.ftp; + + if(connected) + result = Curl_ftp_nextconnect(conn); + + if(result && (conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD)) { + /* Failure detected, close the second socket if it was created already */ + sclose(conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; + } + + if(ftp->no_transfer) + /* no data to transfer */ + result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + else if(!connected) + /* since we didn't connect now, we want do_more to get called */ + conn->bits.do_more = TRUE; + + ftp->ctl_valid = TRUE; /* seems good */ + + return result; +} + +/* called from multi.c while DOing */ +CURLcode Curl_ftp_doing(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result; + result = Curl_ftp_multi_statemach(conn, dophase_done); + + if(*dophase_done) { + result = ftp_dophase_done(conn, FALSE /* not connected */); + + infof(conn->data, "DO phase is comlete\n"); + } + return result; +} /*********************************************************************** * * ftp_regular_transfer() * * The input argument is already checked for validity. - * Performs a regular transfer between local and remote hosts. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. * * ftp->ctl_valid starts out as FALSE, and gets set to TRUE if we reach the * Curl_ftp_done() function without finding any major problem. */ static -CURLcode ftp_regular_transfer(struct connectdata *conn) +CURLcode ftp_regular_transfer(struct connectdata *conn, + bool *dophase_done) { - CURLcode retcode=CURLE_OK; + CURLcode result=CURLE_OK; bool connected=0; struct SessionHandle *data = conn->data; struct FTP *ftp; @@ -2909,31 +3829,26 @@ CURLcode ftp_regular_transfer(struct connectdata *conn) Curl_pgrsSetUploadSize(data, 0); Curl_pgrsSetDownloadSize(data, 0); - retcode = ftp_perform(conn, &connected); + ftp->ctl_valid = TRUE; /* starts good */ - if(CURLE_OK == retcode) { - if(connected) - retcode = Curl_ftp_nextconnect(conn); + result = ftp_perform(conn, + &connected, /* have we connected after PASV/PORT */ + dophase_done); /* all commands in the DO-phase done? */ - if(retcode && (conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD)) { - /* Failure detected, close the second socket if it was created already */ - sclose(conn->sock[SECONDARYSOCKET]); - conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; - } + if(CURLE_OK == result) { - if(ftp->no_transfer) - /* no data to transfer */ - retcode=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - else if(!connected) - /* since we didn't connect now, we want do_more to get called */ - conn->bits.do_more = TRUE; + if(!*dophase_done) + /* the DO phase has not completed yet */ + return CURLE_OK; + + result = ftp_dophase_done(conn, connected); + if(result) + return result; } else freedirs(ftp); - ftp->ctl_valid = TRUE; /* seems good */ - - return retcode; + return result; } @@ -2947,16 +3862,16 @@ CURLcode ftp_regular_transfer(struct connectdata *conn) */ static CURLcode ftp_3rdparty(struct connectdata *conn) { - CURLcode retcode = CURLE_OK; + CURLcode result = CURLE_OK; conn->proto.ftp->ctl_valid = conn->sec_conn->proto.ftp->ctl_valid = TRUE; conn->size = conn->sec_conn->size = -1; - retcode = ftp_3rdparty_pretransfer(conn); - if (!retcode) - retcode = ftp_3rdparty_transfer(conn); + result = ftp_3rdparty_pretransfer(conn); + if (!result) + result = ftp_3rdparty_transfer(conn); - return retcode; + return result; } #endif /* CURL_DISABLE_FTP */ diff --git a/lib/ftp.h b/lib/ftp.h index dc7cf79c0..3c26cc38b 100644 --- a/lib/ftp.h +++ b/lib/ftp.h @@ -1,18 +1,18 @@ #ifndef __FTP_H #define __FTP_H /*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. - * + * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. @@ -24,14 +24,21 @@ ***************************************************************************/ #ifndef CURL_DISABLE_FTP -CURLcode Curl_ftp(struct connectdata *conn); +CURLcode Curl_ftp(struct connectdata *conn, bool *done); CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode); -CURLcode Curl_ftp_connect(struct connectdata *conn); +CURLcode Curl_ftp_connect(struct connectdata *conn, bool *done); CURLcode Curl_ftp_disconnect(struct connectdata *conn); CURLcode Curl_ftpsendf(struct connectdata *, const char *fmt, ...); +CURLcode Curl_nbftpsendf(struct connectdata *, const char *fmt, ...); CURLcode Curl_GetFTPResponse(ssize_t *nread, struct connectdata *conn, int *ftpcode); CURLcode Curl_ftp_nextconnect(struct connectdata *conn); -#endif - -#endif +CURLcode Curl_ftp_multi_statemach(struct connectdata *conn, bool *done); +CURLcode Curl_ftp_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp); +CURLcode Curl_ftp_doing(struct connectdata *conn, + bool *dophase_done); +#endif /* CURL_DISABLE_FTP */ +#endif /* __FTP_H */ diff --git a/lib/hostares.c b/lib/hostares.c index c17e991e2..13c0dc237 100644 --- a/lib/hostares.c +++ b/lib/hostares.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -99,7 +99,7 @@ #ifdef CURLRES_ARES /* - * Curl_fdset() is called when someone from the outside world (using + * Curl_resolv_fdset() is called when someone from the outside world (using * curl_multi_fdset()) wants to get our fd_set setup and we're talking with * ares. The caller must make sure that this function is only called when we * have a working ares channel. @@ -107,10 +107,10 @@ * Returns: CURLE_OK always! */ -CURLcode Curl_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +CURLcode Curl_resolv_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) { int max = ares_fds(conn->data->state.areschannel, diff --git a/lib/hostasyn.c b/lib/hostasyn.c index ec7a34a43..e31219030 100644 --- a/lib/hostasyn.c +++ b/lib/hostasyn.c @@ -67,11 +67,6 @@ #include #endif -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - #include "urldata.h" #include "sendf.h" #include "hostip.h" diff --git a/lib/hostip.c b/lib/hostip.c index 460b8f626..233d81cf3 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -67,11 +67,6 @@ #include #endif -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - #include "urldata.h" #include "sendf.h" #include "hostip.h" diff --git a/lib/hostip.h b/lib/hostip.h index 4fd03c1af..0b06bae6a 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -159,13 +159,13 @@ CURLcode Curl_is_resolved(struct connectdata *conn, CURLcode Curl_wait_for_resolv(struct connectdata *conn, struct Curl_dns_entry **dnsentry); -/* Curl_fdset() is a generic function that exists in multiple versions +/* Curl_resolv_fdset() is a generic function that exists in multiple versions depending on what name resolve technology we've built to use. The function is called from the curl_multi_fdset() function */ -CURLcode Curl_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp); +CURLcode Curl_resolv_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp); /* unlock a previously resolved dns entry */ void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns); diff --git a/lib/hostip6.c b/lib/hostip6.c index e1a08f78a..4da05078f 100644 --- a/lib/hostip6.c +++ b/lib/hostip6.c @@ -67,11 +67,6 @@ #include #endif -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - #include "urldata.h" #include "sendf.h" #include "hostip.h" diff --git a/lib/hostsyn.c b/lib/hostsyn.c index d2343b962..38030b7f0 100644 --- a/lib/hostsyn.c +++ b/lib/hostsyn.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -67,11 +67,6 @@ #include #endif -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - #include "urldata.h" #include "sendf.h" #include "hostip.h" @@ -133,10 +128,10 @@ CURLcode Curl_is_resolved(struct connectdata *conn, * It is present here to keep #ifdefs out from multi.c */ -CURLcode Curl_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +CURLcode Curl_resolv_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) { (void)conn; (void)read_fd_set; diff --git a/lib/hostthre.c b/lib/hostthre.c index 47c9ea1c8..ed653fed6 100644 --- a/lib/hostthre.c +++ b/lib/hostthre.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -155,7 +155,7 @@ struct thread_data { HANDLE thread_hnd; unsigned thread_id; DWORD thread_status; - curl_socket_t dummy_sock; /* dummy for Curl_fdset() */ + curl_socket_t dummy_sock; /* dummy for Curl_resolv_fdset() */ FILE *stderr_file; HANDLE mutex_waiting; /* marks that we are still waiting for a resolve */ HANDLE event_resolved; /* marks that the thread obtained the information */ @@ -404,9 +404,9 @@ static bool init_resolve_thread (struct connectdata *conn, destroy_thread_data(&conn->async); return FALSE; } - /* This socket is only to keep Curl_fdset() and select() happy; should never - * become signalled for read/write since it's unbound but Windows needs - * atleast 1 socket in select(). + /* This socket is only to keep Curl_resolv_fdset() and select() happy; + * should never become signalled for read/write since it's unbound but + * Windows needs atleast 1 socket in select(). */ td->dummy_sock = socket(AF_INET, SOCK_DGRAM, 0); return TRUE; @@ -541,10 +541,10 @@ CURLcode Curl_is_resolved(struct connectdata *conn, return CURLE_OK; } -CURLcode Curl_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +CURLcode Curl_resolv_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) { const struct thread_data *td = (const struct thread_data *) conn->async.os_specific; diff --git a/lib/http.c b/lib/http.c index 3a7af8f6e..051643126 100644 --- a/lib/http.c +++ b/lib/http.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -1222,7 +1222,7 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, * Curl_http_connect() performs HTTP stuff to do at connect-time, called from * the generic Curl_connect(). */ -CURLcode Curl_http_connect(struct connectdata *conn) +CURLcode Curl_http_connect(struct connectdata *conn, bool *done) { struct SessionHandle *data; CURLcode result; @@ -1261,6 +1261,8 @@ CURLcode Curl_http_connect(struct connectdata *conn) data->state.first_host = strdup(conn->host.name); } + *done = TRUE; + return CURLE_OK; } @@ -1328,7 +1330,7 @@ CURLcode Curl_http_done(struct connectdata *conn, * request is to be performed. This creates and sends a properly constructed * HTTP request. */ -CURLcode Curl_http(struct connectdata *conn) +CURLcode Curl_http(struct connectdata *conn, bool *done) { struct SessionHandle *data=conn->data; char *buf = data->state.buffer; /* this is a short cut to the buffer */ @@ -1342,6 +1344,11 @@ CURLcode Curl_http(struct connectdata *conn) Curl_HttpReq httpreq = data->set.httpreq; char *addcookies = NULL; + /* Always consider the DO phase done after this function call, even if there + may be parts of the request that is not yet sent, since we can deal with + the rest of the request in the PERFORM phase. */ + *done = TRUE; + if(!conn->proto.http) { /* Only allocate this struct if we don't already have it! */ diff --git a/lib/http.h b/lib/http.h index 57f1d115e..23afc9115 100644 --- a/lib/http.h +++ b/lib/http.h @@ -8,7 +8,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -34,9 +34,9 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, char *hostname, int remote_port); /* protocol-specific functions set up to be called by the main engine */ -CURLcode Curl_http(struct connectdata *conn); +CURLcode Curl_http(struct connectdata *conn, bool *done); CURLcode Curl_http_done(struct connectdata *, CURLcode); -CURLcode Curl_http_connect(struct connectdata *conn); +CURLcode Curl_http_connect(struct connectdata *conn, bool *done); /* The following functions are defined in http_chunks.c */ void Curl_httpchunk_init(struct connectdata *conn); diff --git a/lib/ldap.c b/lib/ldap.c index 104268892..3afa81352 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -224,7 +224,7 @@ static void (*ldap_free_urldesc)(LDAPURLDesc *) = _ldap_free_urldesc; #endif -CURLcode Curl_ldap(struct connectdata *conn) +CURLcode Curl_ldap(struct connectdata *conn, bool *done) { CURLcode status = CURLE_OK; int rc = 0; @@ -256,6 +256,7 @@ CURLcode Curl_ldap(struct connectdata *conn) int num = 0; struct SessionHandle *data=conn->data; + *done = TRUE; /* unconditionally */ infof(data, "LDAP local: %s\n", data->change.url); if (!DynaOpen(&mod_name)) { diff --git a/lib/ldap.h b/lib/ldap.h index b95cf74ad..b2d4f3973 100644 --- a/lib/ldap.h +++ b/lib/ldap.h @@ -2,18 +2,18 @@ #define __LDAP_H /*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. - * + * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. @@ -24,6 +24,6 @@ * $Id$ ***************************************************************************/ #ifndef CURL_DISABLE_LDAP -CURLcode Curl_ldap(struct connectdata *conn); +CURLcode Curl_ldap(struct connectdata *conn, bool *done); #endif #endif /* __LDAP_H */ diff --git a/lib/multi.c b/lib/multi.c index 159e51e53..643f6362f 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -45,6 +45,7 @@ #include "memory.h" #include "easyif.h" #include "multiif.h" +#include "sendf.h" /* The last #include file should be: */ #include "memdebug.h" @@ -56,11 +57,14 @@ struct Curl_message { }; typedef enum { - CURLM_STATE_INIT, + CURLM_STATE_INIT, /* start in this state */ CURLM_STATE_CONNECT, /* resolve/connect has been sent off */ - CURLM_STATE_WAITRESOLVE, /* we're awaiting the resolve to finalize */ - CURLM_STATE_WAITCONNECT, /* we're awaiting the connect to finalize */ - CURLM_STATE_DO, /* send off the request (part 1) */ + CURLM_STATE_WAITRESOLVE, /* awaiting the resolve to finalize */ + CURLM_STATE_WAITCONNECT, /* awaiting the connect to finalize */ + CURLM_STATE_PROTOCONNECT, /* completing the protocol-specific connect + phase */ + CURLM_STATE_DO, /* start send off the request (part 1) */ + CURLM_STATE_DOING, /* sending off the request (part 1) */ CURLM_STATE_DO_MORE, /* send off the request (part 2) */ CURLM_STATE_PERFORM, /* transfer data */ CURLM_STATE_DONE, /* post data transfer operation */ @@ -111,6 +115,33 @@ struct Curl_multi { struct curl_hash *hostcache; }; +/* always use this function to change state, to make debugging easier */ +static void multistate(struct Curl_one_easy *easy, CURLMstate state) +{ +#ifdef CURLDEBUG + const char *statename[]={ + "INIT", + "CONNECT", + "WAITRESOLVE", + "WAITCONNECT", + "PROTOCONNECT", + "DO", + "DOING", + "DO_MORE", + "PERFORM", + "DONE", + "COMPLETED", + }; + CURLMstate oldstate = easy->state; +#endif + easy->state = state; + +#ifdef CURLDEBUG + infof(easy->easy_handle, + "STATE: %s => %s handle %p: \n", + statename[oldstate], statename[easy->state], (char *)easy); +#endif +} CURLM *curl_multi_init(void) { @@ -158,7 +189,7 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle, /* set the easy handle */ easy->easy_handle = easy_handle; - easy->state = CURLM_STATE_INIT; + multistate(easy, CURLM_STATE_INIT); /* for multi interface connections, we share DNS cache automaticly */ easy->easy_handle->hostcache = multi->hostcache; @@ -258,7 +289,22 @@ CURLMcode curl_multi_fdset(CURLM *multi_handle, break; case CURLM_STATE_WAITRESOLVE: /* waiting for a resolve to complete */ - Curl_fdset(easy->easy_conn, read_fd_set, write_fd_set, &this_max_fd); + Curl_resolv_fdset(easy->easy_conn, read_fd_set, write_fd_set, + &this_max_fd); + if(this_max_fd > *max_fd) + *max_fd = this_max_fd; + break; + + case CURLM_STATE_PROTOCONNECT: + Curl_protocol_fdset(easy->easy_conn, read_fd_set, write_fd_set, + &this_max_fd); + if(this_max_fd > *max_fd) + *max_fd = this_max_fd; + break; + + case CURLM_STATE_DOING: + Curl_doing_fdset(easy->easy_conn, read_fd_set, write_fd_set, + &this_max_fd); if(this_max_fd > *max_fd) *max_fd = this_max_fd; break; @@ -318,6 +364,8 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) struct Curl_message *msg = NULL; bool connected; bool async; + bool protocol_connect; + bool dophase_done; *running_handles = 0; /* bump this once for every living handle */ @@ -326,10 +374,6 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) easy=multi->easy.next; while(easy) { -#if 0 - fprintf(stderr, "HANDLE %p: State: %x\n", - (char *)easy, easy->state); -#endif do { if (CURLM_STATE_WAITCONNECT <= easy->state && easy->state <= CURLM_STATE_DO && @@ -344,13 +388,13 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) easy->easy_handle->change.url_changed = FALSE; easy->result = Curl_follow(easy->easy_handle, gotourl, FALSE); if(CURLE_OK == easy->result) - easy->state = CURLM_STATE_CONNECT; + multistate(easy, CURLM_STATE_CONNECT); else free(gotourl); } else { easy->result = CURLE_OUT_OF_MEMORY; - easy->state = CURLM_STATE_COMPLETED; + multistate(easy, CURLM_STATE_COMPLETED); break; } } @@ -365,7 +409,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) if(CURLE_OK == easy->result) { /* after init, go CONNECT */ - easy->state = CURLM_STATE_CONNECT; + multistate(easy, CURLM_STATE_CONNECT); result = CURLM_CALL_MULTI_PERFORM; easy->easy_handle->state.used_interface = Curl_if_multi; @@ -376,16 +420,22 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) /* Connect. We get a connection identifier filled in. */ Curl_pgrsTime(easy->easy_handle, TIMER_STARTSINGLE); easy->result = Curl_connect(easy->easy_handle, &easy->easy_conn, - &async); + &async, &protocol_connect); if(CURLE_OK == easy->result) { if(async) /* We're now waiting for an asynchronous name lookup */ - easy->state = CURLM_STATE_WAITRESOLVE; + multistate(easy, CURLM_STATE_WAITRESOLVE); else { - /* after the connect has been sent off, go WAITCONNECT */ - easy->state = CURLM_STATE_WAITCONNECT; + /* after the connect has been sent off, go WAITCONNECT unless the + protocol connect is already done and we can go directly to + DO! */ result = CURLM_CALL_MULTI_PERFORM; + + if(protocol_connect) + multistate(easy, CURLM_STATE_DO); + else + multistate(easy, CURLM_STATE_WAITCONNECT); } } break; @@ -401,14 +451,17 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) if(dns) { /* Perform the next step in the connection phase, and then move on to the WAITCONNECT state */ - easy->result = Curl_async_resolved(easy->easy_conn); + easy->result = Curl_async_resolved(easy->easy_conn, + &protocol_connect); if(CURLE_OK != easy->result) /* if Curl_async_resolved() returns failure, the connection struct is already freed and gone */ easy->easy_conn = NULL; /* no more connection */ - - easy->state = CURLM_STATE_WAITCONNECT; + else { + /* FIX: what if protocol_connect is TRUE here?! */ + multistate(easy, CURLM_STATE_WAITCONNECT); + } } if(CURLE_OK != easy->result) { @@ -425,7 +478,8 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) easy->result = Curl_is_connected(easy->easy_conn, FIRSTSOCKET, &connected); if(connected) - easy->result = Curl_protocol_connect(easy->easy_conn); + easy->result = Curl_protocol_connect(easy->easy_conn, + &protocol_connect); if(CURLE_OK != easy->result) { /* failure detected */ @@ -435,29 +489,64 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) } if(connected) { + if(!protocol_connect) { + /* We have a TCP connection, but 'protocol_connect' may be false + and then we continue to 'STATE_PROTOCONNECT'. If protocol + connect is TRUE, we move on to STATE_DO. */ + multistate(easy, CURLM_STATE_PROTOCONNECT); + fprintf(stderr, "WAITCONNECT => PROTOCONNECT\n"); + } + else { + /* after the connect has completed, go DO */ + multistate(easy, CURLM_STATE_DO); + result = CURLM_CALL_MULTI_PERFORM; + } + } + break; + + case CURLM_STATE_PROTOCONNECT: + /* protocol-specific connect phase */ + easy->result = Curl_protocol_connecting(easy->easy_conn, + &protocol_connect); + if(protocol_connect) { /* after the connect has completed, go DO */ - easy->state = CURLM_STATE_DO; + multistate(easy, CURLM_STATE_DO); result = CURLM_CALL_MULTI_PERFORM; } + else if(easy->result) { + /* failure detected */ + Curl_posttransfer(easy->easy_handle); + Curl_done(&easy->easy_conn, easy->result); + Curl_disconnect(easy->easy_conn); /* close the connection */ + easy->easy_conn = NULL; /* no more connection */ + } break; case CURLM_STATE_DO: - /* Do the fetch or put request */ - easy->result = Curl_do(&easy->easy_conn); + /* Perform the protocol's DO action */ + easy->result = Curl_do(&easy->easy_conn, &dophase_done); + if(CURLE_OK == easy->result) { - /* after do, go PERFORM... or DO_MORE */ - if(easy->easy_conn->bits.do_more) { + if(!dophase_done) { + /* DO was not completed in one function call, we must continue + DOING... */ + multistate(easy, CURLM_STATE_DOING); + result = CURLM_OK; + } + + /* after DO, go PERFORM... or DO_MORE */ + else if(easy->easy_conn->bits.do_more) { /* we're supposed to do more, but we need to sit down, relax and wait a little while first */ - easy->state = CURLM_STATE_DO_MORE; + multistate(easy, CURLM_STATE_DO_MORE); result = CURLM_OK; } else { /* we're done with the DO, now PERFORM */ easy->result = Curl_readwrite_init(easy->easy_conn); if(CURLE_OK == easy->result) { - easy->state = CURLM_STATE_PERFORM; + multistate(easy, CURLM_STATE_PERFORM); result = CURLM_CALL_MULTI_PERFORM; } } @@ -471,10 +560,39 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) } break; + case CURLM_STATE_DOING: + /* we continue DOING until the DO phase is complete */ + easy->result = Curl_protocol_doing(easy->easy_conn, &dophase_done); + if(CURLE_OK == easy->result) { + if(dophase_done) { + /* after DO, go PERFORM... or DO_MORE */ + if(easy->easy_conn->bits.do_more) { + /* we're supposed to do more, but we need to sit down, relax + and wait a little while first */ + multistate(easy, CURLM_STATE_DO_MORE); + result = CURLM_OK; + } + else { + /* we're done with the DO, now PERFORM */ + easy->result = Curl_readwrite_init(easy->easy_conn); + if(CURLE_OK == easy->result) { + multistate(easy, CURLM_STATE_PERFORM); + result = CURLM_CALL_MULTI_PERFORM; + } + } + } /* dophase_done */ + } + else { + /* failure detected */ + Curl_posttransfer(easy->easy_handle); + Curl_done(&easy->easy_conn, easy->result); + Curl_disconnect(easy->easy_conn); /* close the connection */ + easy->easy_conn = NULL; /* no more connection */ + } + break; + case CURLM_STATE_DO_MORE: - /* - * First, check if we really are ready to do more. - */ + /* Ready to do more? */ easy->result = Curl_is_connected(easy->easy_conn, SECONDARYSOCKET, &connected); if(connected) { @@ -487,7 +605,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) easy->result = Curl_readwrite_init(easy->easy_conn); if(CURLE_OK == easy->result) { - easy->state = CURLM_STATE_PERFORM; + multistate(easy, CURLM_STATE_PERFORM); result = CURLM_CALL_MULTI_PERFORM; } } @@ -532,7 +650,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) if(easy->result == CURLE_OK) easy->result = Curl_follow(easy->easy_handle, newurl, retry); if(CURLE_OK == easy->result) { - easy->state = CURLM_STATE_CONNECT; + multistate(easy, CURLM_STATE_CONNECT); result = CURLM_CALL_MULTI_PERFORM; } else @@ -542,7 +660,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) } else { /* after the transfer is done, go DONE */ - easy->state = CURLM_STATE_DONE; + multistate(easy, CURLM_STATE_DONE); result = CURLM_CALL_MULTI_PERFORM; } } @@ -553,7 +671,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) /* after we have DONE what we're supposed to do, go COMPLETED, and it doesn't matter what the Curl_done() returned! */ - easy->state = CURLM_STATE_COMPLETED; + multistate(easy, CURLM_STATE_COMPLETED); break; case CURLM_STATE_COMPLETED: @@ -571,7 +689,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) /* * If an error was returned, and we aren't in completed state now, * then we go to completed and consider this transfer aborted. */ - easy->state = CURLM_STATE_COMPLETED; + multistate(easy, CURLM_STATE_COMPLETED); } else /* this one still lives! */ @@ -600,7 +718,6 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) multi->num_msgs++; /* increase message counter */ } - easy = easy->next; /* operate on next handle */ } diff --git a/lib/multiif.h b/lib/multiif.h index 5c3a9c53f..dc6632c6a 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -27,5 +27,4 @@ * Prototypes for library-wide functions provided by multi.c */ void Curl_multi_rmeasy(void *multi, CURL *data); - #endif /* __MULTIIF_H */ diff --git a/lib/sendf.h b/lib/sendf.h index 77ff52e3a..ae96af3e0 100644 --- a/lib/sendf.h +++ b/lib/sendf.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/lib/telnet.c b/lib/telnet.c index 9fbf85ff3..d528615a5 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -1065,7 +1065,7 @@ CURLcode Curl_telnet_done(struct connectdata *conn, CURLcode status) return CURLE_OK; } -CURLcode Curl_telnet(struct connectdata *conn) +CURLcode Curl_telnet(struct connectdata *conn, bool *done) { CURLcode code; struct SessionHandle *data = conn->data; @@ -1093,6 +1093,8 @@ CURLcode Curl_telnet(struct connectdata *conn) char *buf = data->state.buffer; struct TELNET *tn; + *done = TRUE; /* uncontionally */ + code = init_telnet(conn); if(code) return code; diff --git a/lib/telnet.h b/lib/telnet.h index 86dd99b6f..bb30f083f 100644 --- a/lib/telnet.h +++ b/lib/telnet.h @@ -2,18 +2,18 @@ #define __TELNET_H /*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. - * + * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. @@ -24,7 +24,7 @@ * $Id$ ***************************************************************************/ #ifndef CURL_DISABLE_TELNET -CURLcode Curl_telnet(struct connectdata *conn); +CURLcode Curl_telnet(struct connectdata *conn, bool *done); CURLcode Curl_telnet_done(struct connectdata *conn, CURLcode); #endif #endif diff --git a/lib/transfer.c b/lib/transfer.c index ea7c5dbe3..9b37988de 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -2027,9 +2027,11 @@ Curl_connect_host(struct SessionHandle *data, do { bool async; + bool protocol_done=TRUE; /* will be TRUE always since this is only used + within the easy interface */ Curl_pgrsTime(data, TIMER_STARTSINGLE); data->change.url_changed = FALSE; - res = Curl_connect(data, conn, &async); + res = Curl_connect(data, conn, &async, &protocol_done); if((CURLE_OK == res) && async) { /* Now, if async is TRUE here, we need to wait for the name @@ -2037,8 +2039,9 @@ Curl_connect_host(struct SessionHandle *data, res = Curl_wait_for_resolv(*conn, NULL); if(CURLE_OK == res) /* Resolved, continue with the connection */ - res = Curl_async_resolved(*conn); + res = Curl_async_resolved(*conn, &protocol_done); else + /* if we can't resolve, we kill this "connection" now */ (void)Curl_disconnect(*conn); } if(res) @@ -2126,8 +2129,8 @@ CURLcode Curl_perform(struct SessionHandle *data) } if(res == CURLE_OK) { - - res = Curl_do(&conn); + bool do_done; + res = Curl_do(&conn, &do_done); /* for non 3rd party transfer only */ if(res == CURLE_OK && !data->set.source_url) { diff --git a/lib/url.c b/lib/url.c index d25f43ebc..7de06ff3f 100644 --- a/lib/url.c +++ b/lib/url.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -1989,41 +1989,117 @@ static void verboseconnect(struct connectdata *conn) conn->ip_addr_str, conn->port); } +CURLcode Curl_protocol_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) +{ + CURLcode res = CURLE_OK; + if(conn->curl_proto_fdset) + res = conn->curl_proto_fdset(conn, read_fd_set, write_fd_set, max_fdp); + return res; +} + +CURLcode Curl_doing_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) +{ + CURLcode res = CURLE_OK; + if(conn && conn->curl_doing_fdset) + res = conn->curl_doing_fdset(conn, read_fd_set, write_fd_set, max_fdp); + return res; +} + +/* + * We are doing protocol-specific connecting and this is being called over and + * over from the multi interface until the connection phase is done on + * protocol layer. + */ + +CURLcode Curl_protocol_connecting(struct connectdata *conn, bool *done) +{ + CURLcode result=CURLE_OK; + + if(conn && conn->curl_connecting) { + *done = FALSE; + result = conn->curl_connecting(conn, done); + } + else + *done = TRUE; + + return result; +} + +/* + * We are DOING this is being called over and over from the multi interface + * until the DOING phase is done on protocol layer. + */ + +CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done) +{ + CURLcode result=CURLE_OK; + + if(conn && conn->curl_doing) { + *done = FALSE; + result = conn->curl_doing(conn, done); + } + else + *done = TRUE; + + return result; +} + /* * We have discovered that the TCP connection has been successful, we can now * proceed with some action. * - * If we're using the multi interface, this host address pointer is most - * likely NULL at this point as we can't keep the resolved info around. This - * may call for some reworking, like a reference counter in the struct or - * something. */ -CURLcode Curl_protocol_connect(struct connectdata *conn) +CURLcode Curl_protocol_connect(struct connectdata *conn, bool *protocol_done) { struct SessionHandle *data = conn->data; CURLcode result=CURLE_OK; - if(conn->bits.tcpconnect) + *protocol_done = FALSE; + + if(conn->bits.tcpconnect && conn->bits.protoconnstart) { /* We already are connected, get back. This may happen when the connect worked fine in the first call, like when we connect to a local server - or proxy. */ + or proxy. Note that we don't know if the protocol is actually done. + + Unless this protocol doesn't have any protocol-connect callback, as + then we know we're done. */ + if(!conn->curl_connecting) + *protocol_done = TRUE; + return CURLE_OK; + } - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ + if(!conn->bits.tcpconnect) { - if(data->set.verbose) - verboseconnect(conn); + Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - if(conn->curl_connect) { - /* is there a protocol-specific connect() procedure? */ + if(data->set.verbose) + verboseconnect(conn); + } - /* set start time here for timeout purposes in the - * connect procedure, it is later set again for the - * progress meter purpose */ - conn->now = Curl_tvnow(); + if(!conn->bits.protoconnstart) { + if(conn->curl_connect) { + /* is there a protocol-specific connect() procedure? */ - /* Call the protocol-specific connect function */ - result = conn->curl_connect(conn); + /* Set start time here for timeout purposes in the connect procedure, it + is later set again for the progress meter purpose */ + conn->now = Curl_tvnow(); + + /* Call the protocol-specific connect function */ + result = conn->curl_connect(conn, protocol_done); + } + else + *protocol_done = TRUE; + + /* it has started, possibly even completed but that knowledge isn't stored + in this bit! */ + conn->bits.protoconnstart = TRUE; } return result; /* pass back status */ @@ -2733,6 +2809,10 @@ static CURLcode CreateConnection(struct SessionHandle *data, conn->curl_do_more = Curl_ftp_nextconnect; conn->curl_done = Curl_ftp_done; conn->curl_connect = Curl_ftp_connect; + conn->curl_connecting = Curl_ftp_multi_statemach; + conn->curl_doing = Curl_ftp_doing; + conn->curl_proto_fdset = Curl_ftp_fdset; + conn->curl_doing_fdset = Curl_ftp_fdset; conn->curl_disconnect = Curl_ftp_disconnect; } @@ -3385,17 +3465,21 @@ static CURLcode CreateConnection(struct SessionHandle *data, */ static CURLcode SetupConnection(struct connectdata *conn, - struct Curl_dns_entry *hostaddr) + struct Curl_dns_entry *hostaddr, + bool *protocol_done) { struct SessionHandle *data = conn->data; CURLcode result=CURLE_OK; Curl_pgrsTime(data, TIMER_NAMELOOKUP); - if(conn->protocol & PROT_FILE) + if(conn->protocol & PROT_FILE) { /* There's nothing in this function to setup if we're only doing a file:// transfer */ + *protocol_done = TRUE; return result; + } + *protocol_done = FALSE; /* default to not done */ /************************************************************* * Send user-agent to HTTP proxies even if the target protocol @@ -3416,13 +3500,13 @@ static CURLcode SetupConnection(struct connectdata *conn, conn->headerbytecount = 0; if(CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET]) { - bool connected; + bool connected = FALSE; /* Connect only if not already connected! */ result = ConnectPlease(conn, hostaddr, &connected); if(connected) { - result = Curl_protocol_connect(conn); + result = Curl_protocol_connect(conn, protocol_done); if(CURLE_OK == result) conn->bits.tcpconnect = TRUE; } @@ -3436,6 +3520,7 @@ static CURLcode SetupConnection(struct connectdata *conn, else { Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */ conn->bits.tcpconnect = TRUE; + *protocol_done = TRUE; if(data->set.verbose) verboseconnect(conn); } @@ -3460,7 +3545,8 @@ static CURLcode SetupConnection(struct connectdata *conn, CURLcode Curl_connect(struct SessionHandle *data, struct connectdata **in_connect, - bool *asyncp) + bool *asyncp, + bool *protocol_done) { CURLcode code; struct Curl_dns_entry *dns; @@ -3476,7 +3562,7 @@ CURLcode Curl_connect(struct SessionHandle *data, /* If an address is available it means that we already have the name resolved, OR it isn't async. If so => continue connecting from here */ - code = SetupConnection(*in_connect, dns); + code = SetupConnection(*in_connect, dns, protocol_done); /* else response will be received and treated async wise */ } @@ -3494,12 +3580,16 @@ CURLcode Curl_connect(struct SessionHandle *data, } /* Call this function after Curl_connect() has returned async=TRUE and - then a successful name resolve has been received */ -CURLcode Curl_async_resolved(struct connectdata *conn) + then a successful name resolve has been received. + + Note: this function disconnects and frees the conn data in case of + resolve failure */ +CURLcode Curl_async_resolved(struct connectdata *conn, + bool *protocol_done) { #if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) || \ defined(USE_THREADING_GETADDRINFO) - CURLcode code = SetupConnection(conn, conn->async.dns); + CURLcode code = SetupConnection(conn, conn->async.dns, protocol_done); if(code) /* We're not allowed to return failure with memory left allocated @@ -3573,7 +3663,7 @@ CURLcode Curl_done(struct connectdata **connp, return result; } -CURLcode Curl_do(struct connectdata **connp) +CURLcode Curl_do(struct connectdata **connp, bool *done) { CURLcode result=CURLE_OK; struct connectdata *conn = *connp; @@ -3583,7 +3673,7 @@ CURLcode Curl_do(struct connectdata **connp) if(conn->curl_do) { /* generic protocol-specific function pointer set in curl_connect() */ - result = conn->curl_do(conn); + result = conn->curl_do(conn, done); /* This was formerly done in transfer.c, but we better do it here */ @@ -3603,8 +3693,10 @@ CURLcode Curl_do(struct connectdata **connp) if(CURLE_OK == result) { bool async; + bool protocol_done = TRUE; + /* Now, redo the connect and get a new connection */ - result = Curl_connect(data, connp, &async); + result = Curl_connect(data, connp, &async, &protocol_done); if(CURLE_OK == result) { /* We have connected or sent away a name resolve query fine */ @@ -3617,13 +3709,13 @@ CURLcode Curl_do(struct connectdata **connp) return result; /* Resolved, continue with the connection */ - result = Curl_async_resolved(conn); + result = Curl_async_resolved(conn, &protocol_done); if(result) return result; } /* ... finally back to actually retry the DO phase */ - result = conn->curl_do(conn); + result = conn->curl_do(conn, done); } } } diff --git a/lib/url.h b/lib/url.h index 8fd4795e9..5a1dff266 100644 --- a/lib/url.h +++ b/lib/url.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -31,17 +31,28 @@ CURLcode Curl_open(struct SessionHandle **curl); CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, ...); CURLcode Curl_close(struct SessionHandle *data); /* opposite of curl_open() */ CURLcode Curl_connect(struct SessionHandle *, struct connectdata **, - bool *async); -CURLcode Curl_async_resolved(struct connectdata *conn); -CURLcode Curl_do(struct connectdata **); + bool *async, bool *protocol_connect); +CURLcode Curl_async_resolved(struct connectdata *conn, + bool *protocol_connect); +CURLcode Curl_do(struct connectdata **, bool *done); CURLcode Curl_do_more(struct connectdata *); CURLcode Curl_done(struct connectdata **, CURLcode); CURLcode Curl_disconnect(struct connectdata *); -CURLcode Curl_protocol_connect(struct connectdata *conn); +CURLcode Curl_protocol_connect(struct connectdata *conn, bool *done); +CURLcode Curl_protocol_connecting(struct connectdata *conn, bool *done); +CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done); bool Curl_ssl_config_matches(struct ssl_config_data* data, struct ssl_config_data* needle); bool Curl_clone_ssl_config(struct ssl_config_data* source, struct ssl_config_data* dest); void Curl_free_ssl_config(struct ssl_config_data* sslc); void Curl_safefree(void *ptr); +CURLcode Curl_protocol_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp); +CURLcode Curl_doing_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp); #endif diff --git a/lib/urldata.h b/lib/urldata.h index 2068c9a70..e270334c0 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -243,6 +243,42 @@ struct HTTP { /**************************************************************************** * FTP unique setup ***************************************************************************/ +typedef enum { + FTP_STOP, /* do nothing state, stops the state machine */ + FTP_WAIT220, /* waiting for the inintial 220 response immediately after + a connect */ + FTP_AUTH, + FTP_USER, + FTP_PASS, + FTP_ACCT, + FTP_PBSZ, + FTP_PROT, + FTP_PWD, + FTP_QUOTE, /* waiting for a response to a command sent in a quote list */ + FTP_RETR_PREQUOTE, + FTP_STOR_PREQUOTE, + FTP_POSTQUOTE, + FTP_CWD, /* change dir */ + FTP_MKD, /* if the dir didn't exist */ + FTP_MDTM, /* to figure out the datestamp */ + FTP_TYPE, /* to set type when doing a head-like request */ + FTP_LIST_TYPE, /* set type when about to do a dir list */ + FTP_RETR_TYPE, /* set type when about to RETR a file */ + FTP_STOR_TYPE, /* set type when about to STOR a file */ + FTP_SIZE, /* get the remote file's size for head-like request */ + FTP_RETR_SIZE, /* get the remote file's size for RETR */ + FTP_STOR_SIZE, /* get the size for (resumed) STOR */ + FTP_REST, /* when used to check if the server supports it in head-like */ + FTP_RETR_REST, /* when asking for "resume" in for RETR */ + FTP_PORT, /* generic state for PORT, LPRT and EPRT, check count1 */ + FTP_PASV, /* generic state for PASV and EPSV, check count1 */ + FTP_LIST, /* generic state for LIST, NLST or a custom list command */ + FTP_RETR, + FTP_STOR, /* generic state for STOR and APPE */ + FTP_QUIT, + FTP_LAST /* never used */ +} ftpstate; + struct FTP { curl_off_t *bytecountp; char *user; /* user name string */ @@ -271,6 +307,18 @@ struct FTP { bool cwddone; /* if it has been determined that the proper CWD combo already has been done */ char *prevpath; /* conn->path from the previous transfer */ + size_t nread_resp; /* number of bytes currently read of a server response */ + int count1; /* general purpose counter for the state machine */ + int count2; /* general purpose counter for the state machine */ + int count3; /* general purpose counter for the state machine */ + char *sendthis; /* allocated pointer to a buffer that is to be sent to the + ftp server */ + size_t sendleft; /* number of bytes left to send from the sendthis buffer */ + size_t sendsize; /* total size of the sendthis buffer */ + struct timeval response; /* set to Curl_tvnow() when a command has been sent + off, used to time-out response reading */ + ftpstate state; /* always use ftp.c:state() to change state! */ + curl_off_t downloadsize; }; /**************************************************************************** @@ -309,9 +357,12 @@ struct ConnectBits { bool forbidchunk; /* used only to explicitly forbid chunk-upload for specific upload buffers. See readmoredata() in http.c for details. */ - bool tcpconnect; /* the tcp stream (or simimlar) is connected, this - is set the first time on the first connect function - call */ + + bool tcpconnect; /* the TCP layer (or simimlar) is connected, this is set + the first time on the first connect function call */ + bool protoconnstart;/* the protocol layer has STARTED its operation after + the TCP layer connect */ + bool retry; /* this connection is about to get closed and then re-attempted at another connection. */ bool no_body; /* CURLOPT_NO_BODY (or similar) was set */ @@ -510,7 +561,7 @@ struct connectdata { /* These two functions MUST be set by the curl_connect() function to be be protocol dependent */ - CURLcode (*curl_do)(struct connectdata *); + CURLcode (*curl_do)(struct connectdata *, bool *done); CURLcode (*curl_done)(struct connectdata *, CURLcode); /* If the curl_do() function is better made in two halves, this @@ -521,8 +572,29 @@ struct connectdata { /* This function *MAY* be set to a protocol-dependent function that is run * after the connect() and everything is done, as a step in the connection. + * The 'done' pointer points to a bool that should be set to TRUE if the + * function completes before return. If it doesn't complete, the caller + * should call the curl_connecting() function until it is. */ - CURLcode (*curl_connect)(struct connectdata *); + CURLcode (*curl_connect)(struct connectdata *, bool *done); + + /* See above. Currently only used for FTP. */ + CURLcode (*curl_connecting)(struct connectdata *, bool *done); + CURLcode (*curl_doing)(struct connectdata *, bool *done); + + /* Called from the multi interface during the PROTOCONNECT phase, and it + should then return a proper fd set */ + CURLcode (*curl_proto_fdset)(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp); + + /* Called from the multi interface during the DOING phase, and it should + then return a proper fd set */ + CURLcode (*curl_doing_fdset)(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp); /* This function *MAY* be set to a protocol-dependent function that is run * by the curl_disconnect(), as a step in the disconnection. diff --git a/src/main.c b/src/main.c index 1558d18f2..c1317ad27 100644 --- a/src/main.c +++ b/src/main.c @@ -21,7 +21,6 @@ * $Id$ ***************************************************************************/ -/* This is now designed to have its own local setup.h */ #include "setup.h" #include @@ -3726,7 +3725,8 @@ operate(struct Configurable *config, int argc, char *argv[]) } } } /* if CURLE_OK */ - else if(CURLE_FTP_USER_PASSWORD_INCORRECT == res) { + else if((CURLE_FTP_USER_PASSWORD_INCORRECT == res) || + (CURLE_LOGIN_DENIED == res)) { curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); if(response/100 == 5) diff --git a/tests/data/test113 b/tests/data/test113 index 19dcc5582..c80fcca0e 100644 --- a/tests/data/test113 +++ b/tests/data/test113 @@ -21,7 +21,7 @@ REPLY USER 314 bluah you fewl! # Verify data after the test has been "shot" -12 +67 USER anonymous diff --git a/tests/data/test114 b/tests/data/test114 index 05555abf9..2f24e55f0 100644 --- a/tests/data/test114 +++ b/tests/data/test114 @@ -21,7 +21,7 @@ REPLY PASS 314 bluah you f00l! # Verify data after the test has been "shot" -11 +67 USER anonymous diff --git a/tests/data/test190 b/tests/data/test190 index d5c48fb6c..78b179e82 100644 --- a/tests/data/test190 +++ b/tests/data/test190 @@ -15,7 +15,7 @@ ftp FTP download with strict timeout and slow CWD -ftp://%HOSTIP:%FTPPORT/path/to/file/126 -m 3 +ftp://%HOSTIP:%FTPPORT/path/to/file/190 -m 3 DELAY CWD 15 @@ -24,6 +24,7 @@ DELAY CWD 15 # Verify data after the test has been "shot" +# 28 is CURLE_OPERATION_TIMEOUTED 28 diff --git a/tests/data/test195 b/tests/data/test195 index 39b0744ed..cadfb3d6f 100644 --- a/tests/data/test195 +++ b/tests/data/test195 @@ -21,7 +21,7 @@ REPLY PASS 530 temporarily not available # Verify data after the test has been "shot" -10 +67 USER anonymous diff --git a/tests/data/test196 b/tests/data/test196 index ec43918b0..b47cf5d6b 100644 --- a/tests/data/test196 +++ b/tests/data/test196 @@ -20,8 +20,9 @@ REPLY PASS 530 temporarily not available # Verify data after the test has been "shot" +# 67 is CURLE_LOGIN_DENIED -10 +67 USER anonymous diff --git a/tests/libtest/lib511.c b/tests/libtest/lib511.c index 4afb1dc9f..c0861a053 100644 --- a/tests/libtest/lib511.c +++ b/tests/libtest/lib511.c @@ -9,7 +9,6 @@ int test(char *URL) curl_easy_setopt(curl, CURLOPT_NOBODY, 1); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); res = curl_easy_perform(curl); - curl_easy_cleanup(curl); + curl_easy_cleanup(curl); return (int)res; } -