|
|
@@ -0,0 +1,3864 @@
|
|
|
+/***************************************************************************
|
|
|
+ * _ _ ____ _
|
|
|
+ * Project ___| | | | _ \| |
|
|
|
+ * / __| | | | |_) | |
|
|
|
+ * | (__| |_| | _ <| |___
|
|
|
+ * \___|\___/|_| \_\_____|
|
|
|
+ *
|
|
|
+ * Copyright (C) 1998 - 2007, Daniel Stenberg, <[email protected]>, 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 */
|