summaryrefslogtreecommitdiff
path: root/Utilities/cmcurl/ftp.c
diff options
context:
space:
mode:
Diffstat (limited to 'Utilities/cmcurl/ftp.c')
-rw-r--r--Utilities/cmcurl/ftp.c3865
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 */