diff options
Diffstat (limited to 'Utilities/cmcurl/ftp.c')
-rw-r--r-- | Utilities/cmcurl/ftp.c | 3865 |
1 files changed, 3865 insertions, 0 deletions
diff --git a/Utilities/cmcurl/ftp.c b/Utilities/cmcurl/ftp.c new file mode 100644 index 000000000..883988938 --- /dev/null +++ b/Utilities/cmcurl/ftp.c @@ -0,0 +1,3865 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, 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. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + +#include "setup.h" + +#ifndef CURL_DISABLE_FTP +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <ctype.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef WIN32 + +#else /* probably some kind of unix */ +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <sys/types.h> +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_UTSNAME_H +#include <sys/utsname.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef VMS +#include <in.h> +#include <inet.h> +#endif +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "sendf.h" +#include "easyif.h" /* for Curl_convert_... prototypes */ + +#include "if2ip.h" +#include "hostip.h" +#include "progress.h" +#include "transfer.h" +#include "escape.h" +#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "ftp.h" + +#ifdef HAVE_KRB4 +#include "krb4.h" +#endif + +#include "strtoofft.h" +#include "strequal.h" +#include "sslgen.h" +#include "connect.h" +#include "strerror.h" +#include "memory.h" +#include "inet_ntop.h" +#include "select.h" +#include "parsedate.h" /* for the week day and month names */ +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "multiif.h" + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +#define _MPRINTF_REPLACE /* use our functions only */ +#include <curl/mprintf.h> + +/* The last #include file should be: */ +#ifdef CURLDEBUG +#include "memdebug.h" +#endif + +#ifdef HAVE_NI_WITHSCOPEID +#define NIFLAGS NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID +#else +#define NIFLAGS NI_NUMERICHOST | NI_NUMERICSERV +#endif + +/* Local API functions */ +static CURLcode ftp_sendquote(struct connectdata *conn, + struct curl_slist *quote); +static CURLcode ftp_quit(struct connectdata *conn); +static CURLcode ftp_parse_url_path(struct connectdata *conn); +static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done); +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); +static CURLcode ftp_nb_type(struct connectdata *conn, + bool ascii, ftpstate state); +static int ftp_need_type(struct connectdata *conn, + bool ascii); + +/* easy-to-use macro: */ +#define FTPSENDF(x,y,z) if ((result = Curl_ftpsendf(x,y,z)) != CURLE_OK) \ + return result +#define NBFTPSENDF(x,y,z) if ((result = Curl_nbftpsendf(x,y,z)) != CURLE_OK) \ + return result + +static void freedirs(struct connectdata *conn) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct FTP *ftp = conn->data->reqdata.proto.ftp; + + int i; + if(ftpc->dirs) { + for (i=0; i < ftpc->dirdepth; i++){ + if(ftpc->dirs[i]) { + free(ftpc->dirs[i]); + ftpc->dirs[i]=NULL; + } + } + free(ftpc->dirs); + ftpc->dirs = NULL; + } + if(ftp->file) { + free(ftp->file); + ftp->file = NULL; + } +} + +/* Returns non-zero if the given string contains CR (\r) or LF (\n), + which are not allowed within RFC 959 <string>. + Note: The input string is in the client's encoding which might + not be ASCII, so escape sequences \r & \n must be used instead + of hex values 0x0d & 0x0a. +*/ +static bool isBadFtpString(const char *string) +{ + return (bool)((NULL != strchr(string, '\r')) || (NULL != strchr(string, '\n'))); +} + +/*********************************************************************** + * + * AllowServerConnect() + * + * When we've issue the PORT command, we have told the server to connect + * to us. This function will sit and wait here until the server has + * connected. + * + */ +static CURLcode AllowServerConnect(struct connectdata *conn) +{ + int timeout_ms; + struct SessionHandle *data = conn->data; + curl_socket_t sock = conn->sock[SECONDARYSOCKET]; + struct timeval now = Curl_tvnow(); + long timespent = Curl_tvdiff(Curl_tvnow(), now)/1000; + long timeout = data->set.connecttimeout?data->set.connecttimeout: + (data->set.timeout?data->set.timeout: 0); + + if(timeout) { + timeout -= timespent; + if(timeout<=0) { + failf(data, "Timed out before server could connect to us"); + return CURLE_OPERATION_TIMEDOUT; + } + } + + /* We allow the server 60 seconds to connect to us, or a custom timeout. + Note the typecast here. */ + timeout_ms = (timeout?(int)timeout:60) * 1000; + + switch (Curl_select(sock, CURL_SOCKET_BAD, timeout_ms)) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_PORT_FAILED; + case 0: /* timeout */ + /* let's die here */ + failf(data, "Timeout while waiting for server connect"); + return CURLE_FTP_PORT_FAILED; + default: + /* we have received data here */ + { + curl_socket_t s = CURL_SOCKET_BAD; +#ifdef ENABLE_IPV6 + struct Curl_sockaddr_storage add; +#else + struct sockaddr_in add; +#endif + socklen_t size = (socklen_t) sizeof(add); + + if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { + size = sizeof(add); + + s=accept(sock, (struct sockaddr *) &add, &size); + } + sclose(sock); /* close the first socket */ + + if (CURL_SOCKET_BAD == s) { + /* DIE! */ + failf(data, "Error accept()ing server connect"); + return CURLE_FTP_PORT_FAILED; + } + infof(data, "Connection accepted from server\n"); + + conn->sock[SECONDARYSOCKET] = s; + Curl_nonblock(s, TRUE); /* enable non-blocking */ + } + break; + } + + return CURLE_OK; +} + +/* initialize stuff to prepare for reading a fresh new response */ +static void ftp_respinit(struct connectdata *conn) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + ftpc->nread_resp = 0; + ftpc->linestart_resp = conn->data->state.buffer; +} + +/* macro to check for the last line in an FTP server response */ +#define lastline(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ + ISDIGIT(line[2]) && (' ' == line[3])) + +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 *buf = data->state.buffer; + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + int code = 0; + + if (ftpcode) + *ftpcode = 0; /* 0 for errors or not done */ + + ptr=buf + ftpc->nread_resp; + + perline= (int)(ptr-ftpc->linestart_resp); /* number of bytes in the current + line, so far */ + keepon=TRUE; + + while((ftpc->nread_resp<BUFSIZE) && (keepon && !result)) { + + if(ftpc->cache) { + /* 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, ftpc->cache, (int)ftpc->cache_size); + gotbytes = (int)ftpc->cache_size; + free(ftpc->cache); /* free the cache */ + ftpc->cache = NULL; /* clear the pointer */ + ftpc->cache_size = 0; /* zero the size just in case */ + } + else { + int res = Curl_read(conn, sockfd, ptr, BUFSIZE-ftpc->nread_resp, + &gotbytes); + if(res < 0) + /* EWOULDBLOCK */ + return CURLE_OK; /* return */ + +#ifdef CURL_DOES_CONVERSIONS + if((res == CURLE_OK) && (gotbytes > 0)) { + /* convert from the network encoding */ + result = res = Curl_convert_from_network(data, ptr, gotbytes); + /* Curl_convert_from_network calls failf if unsuccessful */ + } +#endif /* CURL_DOES_CONVERSIONS */ + + 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; + + ftpc->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, + ftpc->linestart_resp, (size_t)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(conn, CLIENTWRITE_HEADER, + ftpc->linestart_resp, perline); + if(result) + return result; + + if(perline>3 && lastline(ftpc->linestart_resp)) { + /* 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=ftpc->linestart_resp, n=0; meow<ptr; meow++, n++) + buf[n] = *meow; + *meow=0; /* zero terminate */ + keepon=FALSE; + ftpc->linestart_resp = ptr+1; /* advance pointer */ + i++; /* skip this before getting out */ + + *size = ftpc->nread_resp; /* size of the response */ + ftpc->nread_resp = 0; /* restart */ + break; + } + perline=0; /* line starts over here */ + ftpc->linestart_resp = 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! */ + ftpc->cache_size = gotbytes - i; + ftpc->cache = (char *)malloc((int)ftpc->cache_size); + if(ftpc->cache) + memcpy(ftpc->cache, ftpc->linestart_resp, (int)ftpc->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 --- */ + +/* + * Curl_GetFTPResponse() is supposed to be invoked after each command sent to + * a remote FTP server. This function will wait and read all lines of the + * response and extract the relevant return code for the invoking function. + */ + +CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ + struct connectdata *conn, + int *ftpcode) /* return the ftp-code */ +{ + /* + * 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. */ + + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + int perline; /* count bytes per line */ + bool keepon=TRUE; + ssize_t gotbytes; + char *ptr; + long timeout; /* timeout in seconds */ + int interval_ms; + struct SessionHandle *data = conn->data; + char *line_start; + int code=0; /* default ftp "error code" to return */ + char *buf = data->state.buffer; + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct timeval now = Curl_tvnow(); + + if (ftpcode) + *ftpcode = 0; /* 0 for errors */ + + ptr=buf; + line_start = buf; + + *nreadp=0; + perline=0; + keepon=TRUE; + + while((*nreadp<BUFSIZE) && (keepon && !result)) { + /* check and reset timeout value every lap */ + if(data->set.ftp_response_timeout ) + /* if CURLOPT_FTP_RESPONSE_TIMEOUT is set, use that to determine + remaining time. Also, use "now" as opposed to "conn->now" + because ftp_response_timeout is only supposed to govern + the response for any given ftp response, not for the time + from connect to the given ftp response. */ + timeout = data->set.ftp_response_timeout - /* timeout time */ + Curl_tvdiff(Curl_tvnow(), now)/1000; /* spent time */ + else if(data->set.timeout) + /* if timeout is requested, find out how much remaining time we have */ + timeout = data->set.timeout - /* timeout time */ + Curl_tvdiff(Curl_tvnow(), conn->now)/1000; /* spent time */ + else + /* Even without a requested timeout, we only wait response_time + seconds for the full response to arrive before we bail out */ + timeout = ftpc->response_time - + Curl_tvdiff(Curl_tvnow(), now)/1000; /* spent time */ + + if(timeout <=0 ) { + failf(data, "FTP response timeout"); + return CURLE_OPERATION_TIMEDOUT; /* already too little time */ + } + + if(!ftpc->cache) { + interval_ms = 1 * 1000; /* use 1 second timeout intervals */ + + switch (Curl_select(sockfd, CURL_SOCKET_BAD, interval_ms)) { + case -1: /* select() error, stop reading */ + result = CURLE_RECV_ERROR; + failf(data, "FTP response aborted due to select() error: %d", + Curl_sockerrno()); + break; + case 0: /* timeout */ + if(Curl_pgrsUpdate(conn)) + return CURLE_ABORTED_BY_CALLBACK; + continue; /* just continue in our loop for the timeout duration */ + + default: + break; + } + } + if(CURLE_OK == result) { + /* + * This code previously didn't use the kerberos sec_read() code + * to read, but when we use Curl_read() it may do so. Do confirm + * that this is still ok and then remove this comment! + */ + if(ftpc->cache) { + /* we had data in the "cache", copy that instead of doing an actual + * read + * + * Dave Meyer, December 2003: + * 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, ftpc->cache, (int)ftpc->cache_size); + gotbytes = (int)ftpc->cache_size; + free(ftpc->cache); /* free the cache */ + ftpc->cache = NULL; /* clear the pointer */ + ftpc->cache_size = 0; /* zero the size just in case */ + } + else { + int res = Curl_read(conn, sockfd, ptr, BUFSIZE-*nreadp, &gotbytes); + if(res < 0) + /* EWOULDBLOCK */ + continue; /* go looping again */ + +#ifdef CURL_DOES_CONVERSIONS + if((res == CURLE_OK) && (gotbytes > 0)) { + /* convert from the network encoding */ + result = res = Curl_convert_from_network(data, ptr, gotbytes); + /* Curl_convert_from_network calls failf if unsuccessful */ + } +#endif /* CURL_DOES_CONVERSIONS */ + + 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; + + *nreadp += 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, (size_t)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(conn, CLIENTWRITE_HEADER, + line_start, perline); + if(result) + return result; + + 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; meow<ptr; meow++, n++) + buf[n] = *meow; + *meow=0; /* zero terminate */ + keepon=FALSE; + line_start = ptr+1; /* advance pointer */ + i++; /* skip this before getting out */ + 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! Cleverly figured out by Eric Lavigne in December + 2001. */ + ftpc->cache_size = gotbytes - i; + ftpc->cache = (char *)malloc((int)ftpc->cache_size); + if(ftpc->cache) + memcpy(ftpc->cache, line_start, (int)ftpc->cache_size); + else + return CURLE_OUT_OF_MEMORY; /**BANG**/ + } + } /* there was data */ + } /* if(no error) */ + } /* 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 + + if(ftpcode) + *ftpcode=code; /* return the initial number like this */ + + /* store the latest code for later retrieval */ + conn->data->info.httpcode=code; + + 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", + "CCC", + "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_conn *ftpc = &conn->proto.ftpc; +#ifdef CURLDEBUG + if(ftpc->state != state) + infof(conn->data, "FTP %p state change from %s to %s\n", + ftpc, names[ftpc->state], names[state]); +#endif + ftpc->state = state; +} + +static CURLcode ftp_state_user(struct connectdata *conn) +{ + CURLcode result; + struct FTP *ftp = conn->data->reqdata.proto.ftp; + /* send USER */ + NBFTPSENDF(conn, "USER %s", ftp->user?ftp->user:""); + + state(conn, FTP_USER); + conn->data->state.ftp_trying_alternative = FALSE; + + 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 */ +int Curl_ftp_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(!numsocks) + return GETSOCK_BLANK; + + socks[0] = conn->sock[FIRSTSOCKET]; + + if(ftpc->sendleft) { + /* write mode */ + return GETSOCK_WRITESOCK(0); + } + + /* read mode */ + return GETSOCK_READSOCK(0); +} + +/* 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_conn *ftpc = &conn->proto.ftpc; + + if(ftpc->cwddone) + /* already done and fine */ + result = ftp_state_post_cwd(conn); + else { + ftpc->count2 = 0; + if (conn->bits.reuse && ftpc->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: */ + ftpc->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", ftpc->entrypath); + state(conn, FTP_CWD); + } + else { + if(ftpc->dirdepth) { + ftpc->count1 = 1; + /* issue the first CWD, the rest is sent when the CWD responses are + received... */ + NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 -1]); + state(conn, FTP_CWD); + } + else { + /* No CWD necessary */ + result = ftp_state_post_cwd(conn); + } + } + } + return result; +} + +typedef enum { + EPRT, + PORT, + DONE +} ftpport; + +static CURLcode ftp_state_use_port(struct connectdata *conn, + ftpport fcmd) /* start with this */ + +{ + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct SessionHandle *data=conn->data; + curl_socket_t portsock= CURL_SOCKET_BAD; + char myhost[256] = ""; + +#ifdef ENABLE_IPV6 + /****************************************************************** + * IPv6-specific section + */ + struct Curl_sockaddr_storage ss; + struct addrinfo *res, *ai; + socklen_t sslen; + char hbuf[NI_MAXHOST]; + struct sockaddr *sa=(struct sockaddr *)&ss; + char tmp[1024]; + const char *mode[] = { "EPRT", "PORT", NULL }; + int rc; + int error; + char *host=NULL; + struct Curl_dns_entry *h=NULL; + unsigned short port = 0; + + /* Step 1, figure out what address that is requested */ + + 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); + if (getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, Curl_sockerrno()) ); + return CURLE_FTP_PORT_FAILED; + } + + if (sslen > (socklen_t)sizeof(ss)) + sslen = sizeof(ss); + 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! */ + + + /* step 2, create a socket for the requested address */ + + 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 = conn->socktype; + + portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (portsock == CURL_SOCKET_BAD) { + error = Curl_sockerrno(); + continue; + } + break; + } + if(!ai) { + failf(data, "socket failure: %s", Curl_strerror(conn, error)); + return CURLE_FTP_PORT_FAILED; + } + + /* step 3, bind to a suitable local address */ + + /* Try binding the given address. */ + if (bind(portsock, ai->ai_addr, ai->ai_addrlen)) { + + /* It failed. Bind the address used for the control connection instead */ + sslen = sizeof(ss); + if (getsockname(conn->sock[FIRSTSOCKET], + (struct sockaddr *)sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, Curl_sockerrno()) ); + sclose(portsock); + return CURLE_FTP_PORT_FAILED; + } + + /* set port number to zero to make bind() pick "any" */ + if(((struct sockaddr *)sa)->sa_family == AF_INET) + ((struct sockaddr_in *)sa)->sin_port=0; + else + ((struct sockaddr_in6 *)sa)->sin6_port =0; + + if (sslen > (socklen_t)sizeof(ss)) + sslen = sizeof(ss); + + if(bind(portsock, (struct sockaddr *)sa, sslen)) { + failf(data, "bind failed: %s", Curl_strerror(conn, Curl_sockerrno())); + sclose(portsock); + return CURLE_FTP_PORT_FAILED; + } + } + + /* get the name again after the bind() so that we can extract the + port number it uses now */ + sslen = sizeof(ss); + if(getsockname(portsock, (struct sockaddr *)sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, Curl_sockerrno()) ); + sclose(portsock); + return CURLE_FTP_PORT_FAILED; + } + + /* step 4, listen on the socket */ + + if (listen(portsock, 1)) { + failf(data, "socket failure: %s", Curl_strerror(conn, Curl_sockerrno())); + sclose(portsock); + return CURLE_FTP_PORT_FAILED; + } + + /* step 5, send the proper FTP command */ + + /* get a plain printable version of the numerical address to work with + below */ + Curl_printable_address(ai, myhost, sizeof(myhost)); + +#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++) { + + if(!conn->bits.ftp_use_eprt && (EPRT == fcmd)) + /* if disabled, goto next */ + continue; + + switch (sa->sa_family) { + case AF_INET: + port = ntohs(((struct sockaddr_in *)sa)->sin_port); + break; + case AF_INET6: + port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port); + break; + default: + break; + } + + if (EPRT == fcmd) { + /* + * Two fine examples from RFC2428; + * + * EPRT |1|132.235.1.2|6275| + * + * EPRT |2|1080::8:800:200C:417A|5282| + */ + + result = Curl_nbftpsendf(conn, "%s |%d|%s|%d|", mode[fcmd], + ai->ai_family == AF_INET?1:2, + myhost, port); + if(result) + return result; + break; + } + else if (PORT == fcmd) { + char *source = myhost; + char *dest = tmp; + + if ((PORT == fcmd) && ai->ai_family != AF_INET) + continue; + + /* translate x.x.x.x to x,x,x,x */ + while(source && *source) { + if(*source == '.') + *dest=','; + else + *dest = *source; + dest++; + source++; + } + *dest = 0; + snprintf(dest, 20, ",%d,%d", port>>8, port&0xff); + + result = Curl_nbftpsendf(conn, "%s %s", mode[fcmd], tmp); + if(result) + return result; + break; + } + } + + /* store which command was sent */ + ftpc->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(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) + sclose(conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = portsock; + +#else + /****************************************************************** + * IPv4-specific section + */ + struct sockaddr_in sa; + unsigned short porttouse; + bool sa_filled_in = FALSE; + Curl_addrinfo *addr = NULL; + unsigned short ip[4]; + bool freeaddr = TRUE; + socklen_t sslen = sizeof(sa); + + (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, data->set.ftpport, 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); + + freeaddr = FALSE; /* make sure we don't free 'addr' in this function + since it points to a DNS cache entry! */ + } /* (h) */ + else { + infof(data, "Failed to resolve host name %s\n", data->set.ftpport); + } + } /* strlen */ + } /* CURL_INADDR_NONE */ + } /* data->set.ftpport */ + + if(!addr) { + /* pick a suitable default here */ + + if (getsockname(conn->sock[FIRSTSOCKET], + (struct sockaddr *)&sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, Curl_sockerrno()) ); + return CURLE_FTP_PORT_FAILED; + } + if (sslen > (socklen_t)sizeof(sa)) + sslen = sizeof(sa); + + 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) { + + /* 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(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) + sclose(conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = portsock; + + if(!sa_filled_in) { + memcpy(&sa, addr->ai_addr, sslen); + sa.sin_addr.s_addr = INADDR_ANY; + } + + sa.sin_port = 0; + sslen = sizeof(sa); + + if(bind(portsock, (struct sockaddr *)&sa, sslen) == 0) { + /* we succeeded to bind */ + struct sockaddr_in add; + socklen_t socksize = sizeof(add); + + if(getsockname(portsock, (struct sockaddr *) &add, + &socksize)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, Curl_sockerrno()) ); + 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, "couldn'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; + + if(freeaddr) + Curl_freeaddrinfo(addr); + + ftpc->count1 = PORT; + +#endif /* end of ipv4-specific code */ + + /* this tcpconnect assignment below is a hackish work-around to make the + multi interface with active FTP work - as it will not wait for a + (passive) connect in Curl_is_connected(). + + The *proper* fix is to make sure that the active connection from the + server is done in a non-blocking way. Currently, it is still BLOCKING. + */ + conn->bits.tcpconnect = TRUE; + + state(conn, FTP_PORT); + return result; +} + +static CURLcode ftp_state_use_pasv(struct connectdata *conn) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + 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; + + ftpc->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->data->reqdata.proto.ftp; + struct SessionHandle *data = conn->data; + + if(ftp->no_transfer || conn->bits.no_body) { + /* doesn't transfer any data */ + ftp->no_transfer = TRUE; + + /* still possibly do PRE QUOTE jobs */ + state(conn, FTP_RETR_PREQUOTE); + result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE); + } + 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->data->reqdata.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->data->reqdata.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->data->reqdata.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 && + ftp_need_type(conn, data->set.prefer_ascii)) { + /* 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 */ + result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_TYPE); + if (result) + return result; + } + 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->data->reqdata.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, + bool sizechecked) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->data->reqdata.proto.ftp; + struct SessionHandle *data = conn->data; + curl_off_t passed=0; + + if((data->reqdata.resume_from && !sizechecked) || + ((data->reqdata.resume_from > 0) && sizechecked)) { + /* 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(data->reqdata.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 = (data->reqdata.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 != data->reqdata.resume_from); + + /* now, decrease the size of the read */ + if(data->set.infilesize>0) { + data->set.infilesize -= data->reqdata.resume_from; + + if(data->set.infilesize <= 0) { + infof(data, "File already completely uploaded\n"); + + /* no data to transfer */ + result=Curl_setup_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 SessionHandle *data = conn->data; + struct FTP *ftp = data->reqdata.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + 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) + ftpc->count1 = 0; + else + ftpc->count1++; + + if(item) { + int i = 0; + + /* Skip count1 items in the linked list */ + while((i< ftpc->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: + if (ftp->no_transfer) + state(conn, FTP_STOP); + else { + NBFTPSENDF(conn, "SIZE %s", ftp->file); + state(conn, FTP_RETR_SIZE); + } + break; + case FTP_STOR_PREQUOTE: + result = ftp_state_ul_setup(conn, FALSE); + break; + case FTP_POSTQUOTE: + break; + } + } + + return result; +} + +static CURLcode ftp_state_pasv_resp(struct connectdata *conn, + int ftpcode) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + 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((ftpc->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])) { + const 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; + + if (conn->bits.tunnel_proxy) + /* proxy tunnel -> use other host info because ip_addr_str is the + proxy address not the ftp host */ + snprintf(newhost, sizeof(newhost), "%s", conn->host.name); + else + /* use the same IP we are already connected to */ + snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str); + } + } + else + ptr=NULL; + } + if(!ptr) { + failf(data, "Weirdly formatted EPSV reply"); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + } + else if((ftpc->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; + } + + /* we got OK from server */ + if(data->set.ftp_skip_ip) { + /* told to ignore the remotely given IP but instead use the one we used + for the control connection */ + infof(data, "Skips %d.%d.%d.%d for data connection, uses %s instead\n", + ip[0], ip[1], ip[2], ip[3], + conn->ip_addr_str); + if (conn->bits.tunnel_proxy) + /* proxy tunnel -> use other host info because ip_addr_str is the + proxy address not the ftp host */ + snprintf(newhost, sizeof(newhost), "%s", conn->host.name); + else + snprintf(newhost, sizeof(newhost), "%s", conn->ip_addr_str); + } + else + snprintf(newhost, sizeof(newhost), + "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + newport = (port[0]<<8) + port[1]; + } + else if(ftpc->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); + ftpc->count1++; + /* remain in the FTP_PASV state */ + return result; + } + else { + failf(data, "Bad PASV/EPSV response: %03d", ftpcode); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + + if(data->set.proxy && *data->set.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 && ftpc->count1 == 0 && ftpcode == 229) { + infof(data, "got positive EPSV response, but can't connect. " + "Disabling EPSV\n"); + /* disable it for next transfer */ + conn->bits.ftp_use_epsv = FALSE; + data->state.errorbuf = FALSE; /* allow error message to get rewritten */ + NBFTPSENDF(conn, "PASV", NULL); + ftpc->count1++; + /* remain in the FTP_PASV state */ + return result; + } + + 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 && conn->bits.httpproxy) { + /* FIX: this MUST wait for a proper connect first if 'connected' is + * FALSE */ + + /* BLOCKING */ + /* We want "seamless" FTP operations through HTTP proxy tunnel */ + + /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member + * conn->proto.http; we want FTP through HTTP and we have to change the + * member temporarily for connecting to the HTTP proxy. After + * Curl_proxyCONNECT we have to set back the member to the original struct + * FTP pointer + */ + struct HTTP http_proxy; + struct FTP *ftp_save = data->reqdata.proto.ftp; + memset(&http_proxy, 0, sizeof(http_proxy)); + data->reqdata.proto.http = &http_proxy; + + result = Curl_proxyCONNECT(conn, SECONDARYSOCKET, newhost, newport); + + data->reqdata.proto.ftp = ftp_save; + + 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 SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + ftpport fcmd = (ftpport)ftpc->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; + } + 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 SessionHandle *data=conn->data; + struct FTP *ftp = data->reqdata.proto.ftp; + + 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 = (long)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. */ + + if(conn->bits.no_body && + data->set.include_header && + ftp->file && + 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" */ + snprintf(buf, BUFSIZE-1, + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); + if(result) + return result; + } /* end of a ridiculous amount of conditionals */ + } + 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/100 != 2) { + /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a + successful 'TYPE I'. While that is not as RFC959 says, it is still a + positive response code and we allow that. */ + failf(data, "Couldn't set desired mode"); + return CURLE_FTP_COULDNT_SET_BINARY; /* FIX */ + } + if(ftpcode != 200) + infof(data, "Got a %03d response code instead of the assumed 200\n", + ftpcode); + + 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 = data->reqdata.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(data->reqdata.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(data->reqdata.resume_from< 0) { + /* We're supposed to download the last abs(from) bytes */ + if(filesize < -data->reqdata.resume_from) { + failf(data, "Offset (%" FORMAT_OFF_T + ") was beyond file size (%" FORMAT_OFF_T ")", + data->reqdata.resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* convert to size to download */ + ftp->downloadsize = -data->reqdata.resume_from; + /* download from where? */ + data->reqdata.resume_from = filesize - ftp->downloadsize; + } + else { + if(filesize < data->reqdata.resume_from) { + failf(data, "Offset (%" FORMAT_OFF_T + ") was beyond file size (%" FORMAT_OFF_T ")", + data->reqdata.resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* Now store the number of bytes we are expected to download */ + ftp->downloadsize = filesize-data->reqdata.resume_from; + } + } + + if(ftp->downloadsize == 0) { + /* no data to transfer */ + result = Curl_setup_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", data->reqdata.resume_from); + + NBFTPSENDF(conn, "REST %" FORMAT_OFF_T, data->reqdata.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(conn, 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) { + data->reqdata.resume_from = filesize; + result = ftp_state_ul_setup(conn, TRUE); + } + + return result; +} + +static CURLcode ftp_state_rest_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->data->reqdata.proto.ftp; + + switch(instate) { + case FTP_REST: + default: + if (ftpcode == 350) { + result = Curl_client_write(conn, 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 = data->reqdata.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_ssl_connect(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_setup_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 = data->reqdata.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.prefer_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=(long)(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(*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_ssl_connect(conn, SECONDARYSOCKET); + if(result) + return result; + } + + if(size > data->reqdata.maxdownload && data->reqdata.maxdownload > 0) + size = data->reqdata.size = data->reqdata.maxdownload; + + infof(data, "Maxdownload = %" FORMAT_OFF_T "\n", data->reqdata.maxdownload); + + if(instate != FTP_LIST) + infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size); + + /* FTP download: */ + result=Curl_setup_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, "RETR response: %03d", ftpcode); + 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; + +#ifdef HAVE_KRB4 + if(conn->data->set.krb4) { + /* We are logged in, asked to use Kerberos. 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 = data->reqdata.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + (void)instate; /* no use for this yet */ + + if((ftpcode == 331) && (ftpc->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 but none available"); + result = CURLE_LOGIN_DENIED; + } + } + else { + /* All other response codes, like: + + 530 User ... access denied + (the server denies to log the specified user) */ + + if (conn->data->set.ftp_alternative_to_user && + !conn->data->state.ftp_trying_alternative) { + /* Ok, USER failed. Let's try the supplied command. */ + NBFTPSENDF(conn, "%s", conn->data->set.ftp_alternative_to_user); + conn->data->state.ftp_trying_alternative = TRUE; + state(conn, FTP_USER); + result = CURLE_OK; + } + else { + 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_conn *ftpc = &conn->proto.ftpc; + static const char * const ftpauth[] = { + "SSL", "TLS" + }; + size_t nread = 0; + + if(ftpc->sendleft) { + /* we have a piece of a command still left to send */ + ssize_t written; + result = Curl_write(conn, sock, ftpc->sendthis + ftpc->sendsize - + ftpc->sendleft, ftpc->sendleft, &written); + if(result) + return result; + + if(written != (ssize_t)ftpc->sendleft) { + /* only a fraction was sent */ + ftpc->sendleft -= written; + } + else { + free(ftpc->sendthis); + ftpc->sendthis=NULL; + ftpc->sendleft = ftpc->sendsize = 0; + ftpc->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(ftpc->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 */ + + ftpc->count3=0; + switch(data->set.ftpsslauth) { + case CURLFTPAUTH_DEFAULT: + case CURLFTPAUTH_SSL: + ftpc->count2 = 1; /* add one to get next */ + ftpc->count1 = 0; + break; + case CURLFTPAUTH_TLS: + ftpc->count2 = -1; /* subtract one to get next */ + ftpc->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[ftpc->count1]); + state(conn, FTP_AUTH); + } + else { + result = 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_ssl_connect is BLOCKING */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(CURLE_OK == result) { + conn->protocol |= PROT_FTPS; + conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */ + result = ftp_state_user(conn); + } + } + else if(ftpc->count3 < 1) { + ftpc->count3++; + ftpc->count1 += ftpc->count2; /* get next attempt */ + result = Curl_nbftpsendf(conn, "AUTH %s", ftpauth[ftpc->count1]); + /* remain in this same state */ + } + else { + if(data->set.ftp_ssl > CURLFTPSSL_TRY) + /* we failed and CURLFTPSSL_CONTROL or CURLFTPSSL_ALL is set */ + result = CURLE_FTP_SSL_FAILED; + else + /* ignore the failure and continue */ + result = ftp_state_user(conn); + } + + if(result) + return result; + break; + + case FTP_USER: + case FTP_PASS: + result = ftp_state_user_resp(conn, ftpcode, ftpc->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", + data->set.ftp_ssl == CURLFTPSSL_CONTROL ? '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 = + (bool)(data->set.ftp_ssl != CURLFTPSSL_CONTROL); + /* 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; + + if(data->set.ftp_use_ccc) { + /* CCC - Clear Command Channel + */ + NBFTPSENDF(conn, "CCC", NULL); + state(conn, FTP_CCC); + } + else { + result = ftp_state_pwd(conn); + if(result) + return result; + } + break; + + case FTP_CCC: + if (ftpcode < 500) { + /* First shut down the SSL layer (note: this call will block) */ + result = Curl_ssl_shutdown(conn, FIRSTSOCKET); + + if(result) { + failf(conn->data, "Failed to clear the command channel (CCC)"); + return result; + } + } + + /* Then continue as normal */ + 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<space>"<directory-name>"<space><commentary> 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++; + } + ftpc->entrypath =dir; /* remember this */ + infof(data, "Entry path is '%s'\n", ftpc->entrypath); + /* also save it where getinfo can access it: */ + data->state.most_recent_ftp_entrypath = ftpc->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! */ + DEBUGF(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, ftpc->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 && + ftpc->count1 && !ftpc->count2) { + /* try making it */ + ftpc->count2++; /* counter to prevent CWD-MKD loops */ + NBFTPSENDF(conn, "MKD %s", ftpc->dirs[ftpc->count1 - 1]); + state(conn, FTP_MKD); + } + else { + /* return failure */ + failf(data, "Server denied you to change to the given directory"); + ftpc->cwdfail = TRUE; /* don't remember this path as we failed + to enter it */ + return CURLE_FTP_ACCESS_DENIED; + } + } + else { + /* success */ + ftpc->count2=0; + if(++ftpc->count1 <= ftpc->dirdepth) { + /* send next CWD */ + NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->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", ftpc->dirs[ftpc->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, ftpc->state); + break; + + case FTP_SIZE: + case FTP_RETR_SIZE: + case FTP_STOR_SIZE: + result = ftp_state_size_resp(conn, ftpcode, ftpc->state); + break; + + case FTP_REST: + case FTP_RETR_REST: + result = ftp_state_rest_resp(conn, ftpcode, ftpc->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, ftpc->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_conn *ftpc = &conn->proto.ftpc; + 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(), ftpc->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 = ftpc->response_time*1000 - + Curl_tvdiff(Curl_tvnow(), ftpc->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_conn *ftpc = &conn->proto.ftpc; + 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(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + ftpc->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 = (bool)(ftpc->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_conn *ftpc = &conn->proto.ftpc; + CURLcode result = CURLE_OK; + + while(ftpc->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(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + (int)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; +} + +/* + * Allocate and initialize the struct FTP for the current SessionHandle. If + * need be. + */ +static CURLcode ftp_init(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct FTP *ftp; + if(data->reqdata.proto.ftp) + return CURLE_OK; + + ftp = (struct FTP *)calloc(sizeof(struct FTP), 1); + if(!ftp) + return CURLE_OUT_OF_MEMORY; + + data->reqdata.proto.ftp = ftp; + + /* get some initial data into the ftp struct */ + ftp->bytecountp = &data->reqdata.keep.bytecount; + + /* 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)) + return CURLE_URL_MALFORMAT; + + return CURLE_OK; +} + +/* + * 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, + bool *done) /* see description above */ +{ + CURLcode result; +#ifndef CURL_DISABLE_HTTP + /* for FTP over HTTP proxy */ + struct HTTP http_proxy; + struct FTP *ftp_save; +#endif /* CURL_DISABLE_HTTP */ + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct SessionHandle *data=conn->data; + + *done = FALSE; /* default to not done yet */ + + if (data->reqdata.proto.ftp) { + Curl_ftp_disconnect(conn); + free(data->reqdata.proto.ftp); + data->reqdata.proto.ftp = NULL; + } + + result = ftp_init(conn); + if(result) + return result; + + /* We always support persistant connections on ftp */ + conn->bits.close = FALSE; + + ftpc->response_time = 3600; /* set default response time-out */ + +#ifndef CURL_DISABLE_HTTP + if (conn->bits.tunnel_proxy && conn->bits.httpproxy) { + /* BLOCKING */ + /* We want "seamless" FTP operations through HTTP proxy tunnel */ + + /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member + * conn->proto.http; we want FTP through HTTP and we have to change the + * member temporarily for connecting to the HTTP proxy. After + * Curl_proxyCONNECT we have to set back the member to the original struct + * FTP pointer + */ + ftp_save = data->reqdata.proto.ftp; + memset(&http_proxy, 0, sizeof(http_proxy)); + data->reqdata.proto.http = &http_proxy; + + result = Curl_proxyCONNECT(conn, FIRSTSOCKET, + conn->host.name, conn->remote_port); + + data->reqdata.proto.ftp = ftp_save; + + if(CURLE_OK != result) + return result; + } +#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_ssl_connect(conn, FIRSTSOCKET); + if(result) + return result; + } + + /* When we connect, we start in the state where we await the 220 + response */ + ftp_respinit(conn); /* init the response reader stuff */ + state(conn, FTP_WAIT220); + ftpc->response = Curl_tvnow(); /* start response time-out now! */ + + if(data->state.used_interface == Curl_if_multi) + result = Curl_ftp_multi_statemach(conn, done); + else { + result = ftp_easy_statemach(conn); + if(!result) + *done = TRUE; + } + + return result; +} + +/*********************************************************************** + * + * Curl_ftp_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status, bool premature) +{ + struct SessionHandle *data = conn->data; + struct FTP *ftp = data->reqdata.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + ssize_t nread; + int ftpcode; + CURLcode result=CURLE_OK; + bool was_ctl_valid = ftpc->ctl_valid; + size_t flen; + size_t dlen; + char *path; + char *path_to_use = data->reqdata.path; + struct Curl_transfer_keeper *k = &data->reqdata.keep; + + if(!ftp) + /* When the easy handle is removed from the multi while libcurl is still + * trying to resolve the host name, it seems that the ftp struct is not + * yet initialized, but the removal action calls Curl_done() which calls + * this function. So we simply return success if no ftp pointer is set. + */ + return CURLE_OK; + + switch(status) { + case CURLE_BAD_DOWNLOAD_RESUME: + case CURLE_FTP_WEIRD_PASV_REPLY: + case CURLE_FTP_PORT_FAILED: + case CURLE_FTP_COULDNT_SET_BINARY: + case CURLE_FTP_COULDNT_RETR_FILE: + case CURLE_FTP_COULDNT_STOR_FILE: + case CURLE_FTP_ACCESS_DENIED: + /* the connection stays alive fine even though this happened */ + /* fall-through */ + case CURLE_OK: /* doesn't affect the control connection's status */ + if (!premature) { + ftpc->ctl_valid = was_ctl_valid; + break; + } + /* until we cope better with prematurely ended requests, let them + * fallback as if in complete failure */ + default: /* by default, an error means the control connection is + wedged and should not be used anymore */ + ftpc->ctl_valid = FALSE; + ftpc->cwdfail = TRUE; /* set this TRUE to prevent us to remember the + current path, as this connection is going */ + conn->bits.close = TRUE; /* marked for closure */ + break; + } + + /* now store a copy of the directory we are in */ + if(ftpc->prevpath) + free(ftpc->prevpath); + + /* get the "raw" path */ + path = curl_easy_unescape(data, path_to_use, 0, NULL); + if(!path) + return CURLE_OUT_OF_MEMORY; + + flen = ftp->file?strlen(ftp->file):0; /* file is "raw" already */ + dlen = strlen(path)-flen; + if(dlen && !ftpc->cwdfail) { + ftpc->prevpath = path; + if(flen) + /* if 'path' is not the whole string */ + ftpc->prevpath[dlen]=0; /* terminate */ + infof(data, "Remembering we are in dir %s\n", ftpc->prevpath); + } + else { + ftpc->prevpath = NULL; /* no path */ + free(path); + } + /* free the dir tree and file parts */ + freedirs(conn); + +#ifdef HAVE_KRB4 + Curl_sec_fflush_fd(conn, conn->sock[SECONDARYSOCKET]); +#endif + + /* shut down the socket to inform the server we're done */ + +#ifdef _WIN32_WCE + shutdown(conn->sock[SECONDARYSOCKET],2); /* SD_BOTH */ +#endif + + sclose(conn->sock[SECONDARYSOCKET]); + + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; + + if(!ftp->no_transfer && !status && !premature) { + /* + * Let's see what the server says about the transfer we just performed, + * but lower the timeout as sometimes this connection has died while the + * data has been transfered. This happens when doing through NATs etc that + * abandon old silent connections. + */ + long old_time = ftpc->response_time; + + ftpc->response_time = 60; /* give it only a minute for now */ + + result = Curl_GetFTPResponse(&nread, conn, &ftpcode); + + ftpc->response_time = old_time; /* set this back to previous value */ + + if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) { + failf(data, "control connection looks dead"); + ftpc->ctl_valid = FALSE; /* mark control connection as bad */ + return result; + } + + if(result) + return result; + + if(!ftpc->dont_check) { + /* 226 Transfer complete, 250 Requested file action okay, completed. */ + if((ftpcode != 226) && (ftpcode != 250)) { + failf(data, "server did not report OK, got %d", ftpcode); + result = CURLE_PARTIAL_FILE; + } + } + } + + if(result || premature) + /* the response code from the transfer showed an error already so no + use checking further */ + ; + else if(data->set.upload) { + if((-1 != data->set.infilesize) && + (data->set.infilesize != *ftp->bytecountp) && + !data->set.crlf && + !ftp->no_transfer) { + failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T + " out of %" FORMAT_OFF_T " bytes)", + *ftp->bytecountp, data->set.infilesize); + result = CURLE_PARTIAL_FILE; + } + } + else { + if((-1 != k->size) && (k->size != *ftp->bytecountp) && +#ifdef CURL_DO_LINEEND_CONV + /* Most FTP servers don't adjust their file SIZE response for CRLFs, so + * we'll check to see if the discrepancy can be explained by the number + * of CRLFs we've changed to LFs. + */ + ((k->size + data->state.crlf_conversions) != *ftp->bytecountp) && +#endif /* CURL_DO_LINEEND_CONV */ + (k->maxdownload != *ftp->bytecountp)) { + failf(data, "Received only partial file: %" FORMAT_OFF_T " bytes", + *ftp->bytecountp); + result = CURLE_PARTIAL_FILE; + } + else if(!ftpc->dont_check && + !*ftp->bytecountp && + (k->size>0)) { + failf(data, "No data was received!"); + result = CURLE_FTP_COULDNT_RETR_FILE; + } + } + + /* clear these for next connection */ + ftp->no_transfer = FALSE; + ftpc->dont_check = FALSE; + + /* Send any post-transfer QUOTE strings? */ + if(!status && !result && !premature && data->set.postquote) + result = ftp_sendquote(conn, data->set.postquote); + + return result; +} + +/*********************************************************************** + * + * ftp_sendquote() + * + * Where a 'quote' means a list of custom commands to send to the server. + * The quote list is passed as an argument. + */ + +static +CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote) +{ + struct curl_slist *item; + ssize_t nread; + int ftpcode; + CURLcode result; + + item = quote; + while (item) { + if (item->data) { + FTPSENDF(conn, "%s", item->data); + + result = Curl_GetFTPResponse(&nread, conn, &ftpcode); + if (result) + return result; + + if (ftpcode >= 400) { + failf(conn->data, "QUOT string not accepted: %s", item->data); + return CURLE_FTP_QUOTE_ERROR; + } + } + + item = item->next; + } + + return CURLE_OK; +} + +/*********************************************************************** + * + * ftp_need_type() + * + * Returns TRUE if we in the current situation should send TYPE + */ +static int ftp_need_type(struct connectdata *conn, + bool ascii_wanted) +{ + return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I'); +} + +/*********************************************************************** + * + * ftp_nb_type() + * + * Set TYPE. We only deal with ASCII or BINARY so this function + * sets one of them. + * If the transfer type is not sent, simulate on OK response in newstate + */ +static CURLcode ftp_nb_type(struct connectdata *conn, + bool ascii, ftpstate newstate) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result; + int want = ascii?'A':'I'; + + if (ftpc->transfertype == want) { + state(conn, newstate); + return ftp_state_type_resp(conn, 200, newstate); + } + + NBFTPSENDF(conn, "TYPE %c", want); + state(conn, newstate); + + /* keep track of our current transfer type */ + ftpc->transfertype = want; + return CURLE_OK; +} + +/*************************************************************************** + * + * ftp_pasv_verbose() + * + * This function only outputs some informationals about this second connection + * when we've issued a PASV command before and thus we have connected to a + * possibly new IP address. + * + */ +static void +ftp_pasv_verbose(struct connectdata *conn, + Curl_addrinfo *ai, + char *newhost, /* ascii version */ + int port) +{ + char buf[256]; + Curl_printable_address(ai, buf, sizeof(buf)); + infof(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port); +} + +/* + Check if this is a range download, and if so, set the internal variables + properly. + */ + +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; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(data->reqdata.use_range && data->reqdata.range) { + from=curlx_strtoofft(data->reqdata.range, &ptr, 0); + while(ptr && *ptr && (ISSPACE(*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 - */ + data->reqdata.resume_from = from; + DEBUGF(infof(conn->data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", + from)); + } + else if(from < 0) { + /* -Y */ + totalsize = -from; + data->reqdata.maxdownload = -from; + data->reqdata.resume_from = from; + DEBUGF(infof(conn->data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", + totalsize)); + } + else { + /* X-Y */ + totalsize = to-from; + data->reqdata.maxdownload = totalsize+1; /* include last byte */ + data->reqdata.resume_from = from; + DEBUGF(infof(conn->data, "FTP RANGE from %" FORMAT_OFF_T + " getting %" FORMAT_OFF_T " bytes\n", + from, data->reqdata.maxdownload)); + } + DEBUGF(infof(conn->data, "range-download from %" FORMAT_OFF_T + " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n", + from, to, data->reqdata.maxdownload)); + ftpc->dont_check = TRUE; /* dont check for successful transfer */ + } + return CURLE_OK; +} + + +/* + * Curl_ftp_nextconnect() + * + * 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; + CURLcode result = CURLE_OK; + + /* the ftp struct is inited in Curl_ftp_connect() */ + struct FTP *ftp = data->reqdata.proto.ftp; + + DEBUGF(infof(data, "DO-MORE phase starts\n")); + + if(!ftp->no_transfer && !conn->bits.no_body) { + /* a transfer is about to take place */ + + if(data->set.upload) { + result = ftp_nb_type(conn, data->set.prefer_ascii, + FTP_STOR_TYPE); + if (result) + return result; + } + else { + /* download */ + ftp->downloadsize = -1; /* unknown as of yet */ + + result = ftp_range(conn); + if(result) + ; + 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. */ + result = ftp_nb_type(conn, 1, FTP_LIST_TYPE); + if (result) + return result; + } + else { + result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_RETR_TYPE); + if (result) + return result; + } + } + 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_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + /* end of transfer */ + DEBUGF(infof(data, "DO-MORE phase ends with %d\n", result)); + + return result; +} + + + +/*********************************************************************** + * + * ftp_perform() + * + * This is the actual DO function for FTP. Get a file/directory according to + * the options previously setup. + */ + +static +CURLcode ftp_perform(struct connectdata *conn, + bool *connected, /* connect status after PASV / PORT */ + bool *dophase_done) +{ + /* this is FTP and no proxy */ + CURLcode result=CURLE_OK; + + DEBUGF(infof(conn->data, "DO phase starts\n")); + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + result = ftp_state_quote(conn, TRUE, FTP_QUOTE); + if(result) + return result; + + /* run the state-machine */ + if(conn->data->state.used_interface == Curl_if_multi) + result = Curl_ftp_multi_statemach(conn, dophase_done); + else { + result = ftp_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect; + + if(*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + + return result; +} + +/*********************************************************************** + * + * Curl_ftp() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (ftp_perform). + * + * The input argument is already checked for validity. + */ +CURLcode Curl_ftp(struct connectdata *conn, bool *done) +{ + CURLcode retcode = CURLE_OK; + + *done = FALSE; /* default to false */ + + /* + Since connections can be re-used between SessionHandles, this might be a + connection already existing but on a fresh SessionHandle struct so we must + make sure we have a good 'struct FTP' to play with. For new connections, + the struct FTP is allocated and setup in the Curl_ftp_connect() function. + */ + retcode = ftp_init(conn); + if(retcode) + return retcode; + + retcode = ftp_parse_url_path(conn); + if (retcode) + return retcode; + + retcode = ftp_regular_transfer(conn, done); + + return retcode; +} + +/*********************************************************************** + * + * 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 SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + 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); + + ftp_respinit(conn); + +#ifdef CURL_DOES_CONVERSIONS + res = Curl_convert_to_network(data, s, write_len); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(res != CURLE_OK) { + return res; + } +#endif /* CURL_DOES_CONVERSIONS */ + + 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, (size_t)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; + ftpc->sendthis = malloc(write_len); + if(ftpc->sendthis) { + memcpy(ftpc->sendthis, sptr, write_len); + ftpc->sendsize = ftpc->sendleft = write_len; + } + else { + failf(data, "out of memory"); + res = CURLE_OUT_OF_MEMORY; + } + } + else + ftpc->response = Curl_tvnow(); + + return res; +} + +CURLcode Curl_ftpsendf(struct connectdata *conn, + const char *fmt, ...) +{ + ssize_t bytes_written; + char s[256]; + size_t write_len; + char *sptr=s; + CURLcode res = CURLE_OK; + + 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); + +#ifdef CURL_DOES_CONVERSIONS + res = Curl_convert_to_network(conn->data, s, write_len); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(res != CURLE_OK) { + return(res); + } +#endif /* CURL_DOES_CONVERSIONS */ + + while(1) { + res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len, + &bytes_written); + + if(CURLE_OK != res) + break; + + if(conn->data->set.verbose) + Curl_debug(conn->data, CURLINFO_HEADER_OUT, + sptr, (size_t)bytes_written, conn); + + if(bytes_written != (ssize_t)write_len) { + write_len -= bytes_written; + sptr += bytes_written; + } + else + break; + } + + return res; +} + +/*********************************************************************** + * + * ftp_quit() + * + * This should be called before calling sclose() on an ftp control connection + * (not data connections). We should then wait for the response from the + * server before returning. The calling code should then try to close the + * connection. + * + */ +static CURLcode ftp_quit(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + if(conn->proto.ftpc.ctl_valid) { + NBFTPSENDF(conn, "QUIT", NULL); + state(conn, FTP_QUIT); + + result = ftp_easy_statemach(conn); + } + + return result; +} + +/*********************************************************************** + * + * Curl_ftp_disconnect() + * + * Disconnect from an FTP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +CURLcode Curl_ftp_disconnect(struct connectdata *conn) +{ + struct ftp_conn *ftpc= &conn->proto.ftpc; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. + + ftp_quit() will check the state of ftp->ctl_valid. If it's ok it + will try to send the QUIT command, otherwise it will just return. + */ + + /* The FTP session may or may not have been allocated/setup at this point! */ + if(conn->data->reqdata.proto.ftp) { + (void)ftp_quit(conn); /* ignore errors on the QUIT */ + + if(ftpc->entrypath) { + struct SessionHandle *data = conn->data; + data->state.most_recent_ftp_entrypath = NULL; + free(ftpc->entrypath); + ftpc->entrypath = NULL; + } + if(ftpc->cache) { + free(ftpc->cache); + ftpc->cache = NULL; + } + freedirs(conn); + if(ftpc->prevpath) { + free(ftpc->prevpath); + ftpc->prevpath = NULL; + } + } + return CURLE_OK; +} + +/*********************************************************************** + * + * ftp_parse_url_path() + * + * Parse the URL path into separate path components. + * + */ +static +CURLcode ftp_parse_url_path(struct connectdata *conn) +{ + CURLcode retcode = CURLE_OK; + struct SessionHandle *data = conn->data; + /* the ftp struct is already inited in ftp_connect() */ + struct FTP *ftp = data->reqdata.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + size_t dlen; + char *slash_pos; /* position of the first '/' char in curpos */ + char *path_to_use = data->reqdata.path; + char *cur_pos; + + cur_pos = path_to_use; /* current position in path. point at the begin + of next path component */ + + ftpc->ctl_valid = FALSE; + ftpc->cwdfail = FALSE; + + switch(data->set.ftp_filemethod) { + case FTPFILE_NOCWD: + /* fastest, but less standard-compliant */ + ftp->file = data->reqdata.path; /* this is a full file path */ + break; + + case FTPFILE_SINGLECWD: + /* get the last slash */ + slash_pos=strrchr(cur_pos, '/'); + if(slash_pos || !cur_pos || !*cur_pos) { + ftpc->dirdepth = 1; /* we consider it to be a single dir */ + ftpc->dirs = (char **)calloc(1, sizeof(ftpc->dirs[0])); + if(!ftpc->dirs) + return CURLE_OUT_OF_MEMORY; + + ftpc->dirs[0] = curl_easy_unescape(conn->data, slash_pos ? cur_pos : "/", + slash_pos?(int)(slash_pos-cur_pos):1, + NULL); + if(!ftpc->dirs[0]) { + free(ftpc->dirs); + return CURLE_OUT_OF_MEMORY; + } + ftp->file = slash_pos ? slash_pos+1 : cur_pos; /* rest is file name */ + } + else + ftp->file = cur_pos; /* this is a file name only */ + break; + + default: /* allow pretty much anything */ + case FTPFILE_MULTICWD: + ftpc->dirdepth = 0; + ftpc->diralloc = 5; /* default dir depth to allocate */ + ftpc->dirs = (char **)calloc(ftpc->diralloc, sizeof(ftpc->dirs[0])); + if(!ftpc->dirs) + return CURLE_OUT_OF_MEMORY; + + /* parse the URL path into separate path components */ + while ((slash_pos = strchr(cur_pos, '/')) != NULL) { + /* 1 or 0 to indicate absolute directory */ + bool absolute_dir = (bool)((cur_pos - data->reqdata.path > 0) && + (ftpc->dirdepth == 0)); + + /* seek out the next path component */ + if (slash_pos-cur_pos) { + /* we skip empty path components, like "x//y" since the FTP command + CWD requires a parameter and a non-existant parameter a) doesn't + work on many servers and b) has no effect on the others. */ + int len = (int)(slash_pos - cur_pos + absolute_dir); + ftpc->dirs[ftpc->dirdepth] = curl_easy_unescape(conn->data, + cur_pos - absolute_dir, + len, NULL); + if (!ftpc->dirs[ftpc->dirdepth]) { /* run out of memory ... */ + failf(data, "no memory"); + freedirs(conn); + return CURLE_OUT_OF_MEMORY; + } + if (isBadFtpString(ftpc->dirs[ftpc->dirdepth])) { + freedirs(conn); + return CURLE_URL_MALFORMAT; + } + } + else { + cur_pos = slash_pos + 1; /* jump to the rest of the string */ + continue; + } + + if(!retcode) { + cur_pos = slash_pos + 1; /* jump to the rest of the string */ + if(++ftpc->dirdepth >= ftpc->diralloc) { + /* enlarge array */ + char *bigger; + ftpc->diralloc *= 2; /* double the size each time */ + bigger = realloc(ftpc->dirs, ftpc->diralloc * sizeof(ftpc->dirs[0])); + if(!bigger) { + ftpc->dirdepth--; + freedirs(conn); + return CURLE_OUT_OF_MEMORY; + } + ftpc->dirs = (char **)bigger; + } + } + } + + ftp->file = cur_pos; /* the rest is the file name */ + } + + if(*ftp->file) { + ftp->file = curl_easy_unescape(conn->data, ftp->file, 0, NULL); + if(NULL == ftp->file) { + freedirs(conn); + failf(data, "no memory"); + return CURLE_OUT_OF_MEMORY; + } + if (isBadFtpString(ftp->file)) { + freedirs(conn); + return CURLE_URL_MALFORMAT; + } + } + else + ftp->file=NULL; /* instead of point to a zero byte, we make it a NULL + pointer */ + + if(data->set.upload && !ftp->file && + (!ftp->no_transfer || conn->bits.no_body)) { + /* We need a file name when uploading. Return error! */ + failf(data, "Uploading to a URL without a file name!"); + return CURLE_URL_MALFORMAT; + } + + ftpc->cwddone = FALSE; /* default to not done */ + + if(ftpc->prevpath) { + /* prevpath is "raw" so we convert the input path before we compare the + strings */ + char *path = curl_easy_unescape(conn->data, data->reqdata.path, 0, NULL); + if(!path) + return CURLE_OUT_OF_MEMORY; + + dlen = strlen(path) - (ftp->file?strlen(ftp->file):0); + if((dlen == strlen(ftpc->prevpath)) && + curl_strnequal(path, ftpc->prevpath, dlen)) { + infof(data, "Request has same path as previous transfer\n"); + ftpc->cwddone = TRUE; + } + free(path); + } + + return retcode; +} + +/* 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->data->reqdata.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + 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; + return result; + } + + if(ftp->no_transfer) + /* no data to transfer */ + result=Curl_setup_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; + + ftpc->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 */); + + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + return result; +} + +/*********************************************************************** + * + * ftp_regular_transfer() + * + * The input argument is already checked for validity. + * + * 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, + bool *dophase_done) +{ + CURLcode result=CURLE_OK; + bool connected=0; + struct SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + data->reqdata.size = -1; /* make sure this is unknown at this point */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, 0); + Curl_pgrsSetDownloadSize(data, 0); + + ftpc->ctl_valid = TRUE; /* starts good */ + + result = ftp_perform(conn, + &connected, /* have we connected after PASV/PORT */ + dophase_done); /* all commands in the DO-phase done? */ + + if(CURLE_OK == result) { + + 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(conn); + + return result; +} + +#endif /* CURL_DISABLE_FTP */ |