summaryrefslogtreecommitdiff
path: root/Build/source/libs/curl/lib/ftp.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/libs/curl/lib/ftp.c')
-rw-r--r--Build/source/libs/curl/lib/ftp.c2138
1 files changed, 2138 insertions, 0 deletions
diff --git a/Build/source/libs/curl/lib/ftp.c b/Build/source/libs/curl/lib/ftp.c
new file mode 100644
index 00000000000..8044c22df8b
--- /dev/null
+++ b/Build/source/libs/curl/lib/ftp.c
@@ -0,0 +1,2138 @@
+/*****************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2001, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * In order to be useful for every potential user, curl and libcurl are
+ * dual-licensed under the MPL and the MIT/X-derivate licenses.
+ *
+ * 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 MPL or the MIT/X-derivate
+ * licenses. You may pick one of these licenses.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id: ftp.c,v 1.130 2002/02/28 23:31:23 bagder Exp $
+ *****************************************************************************/
+
+#include "setup.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
+#include <winsock.h>
+#else /* 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
+#include <sys/utsname.h>
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef VMS
+#include <in.h>
+#include <inet.h>
+#endif
+#endif
+
+#if defined(WIN32) && defined(__GNUC__) || defined(__MINGW32__)
+#include <errno.h>
+#endif
+
+#include <curl/curl.h>
+#include "urldata.h"
+#include "sendf.h"
+
+#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 KRB4
+#include "security.h"
+#include "krb4.h"
+#endif
+
+#include "strequal.h"
+#include "ssluse.h"
+#include "connect.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 MALLOCDEBUG
+#include "memdebug.h"
+#endif
+
+/* Local API functions */
+static CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote);
+static CURLcode ftp_cwd(struct connectdata *conn, char *path);
+
+/* easy-to-use macro: */
+#define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z))) return result
+
+/***********************************************************************
+ *
+ * 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 SessionHandle *data,
+ struct connectdata *conn,
+ int sock)
+{
+ fd_set rdset;
+ struct timeval dt;
+
+ FD_ZERO(&rdset);
+
+ FD_SET(sock, &rdset);
+
+ /* we give the server 10 seconds to connect to us */
+ dt.tv_sec = 10;
+ dt.tv_usec = 0;
+
+ switch (select(sock+1, &rdset, NULL, NULL, &dt)) {
+ 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 */
+ {
+ int s;
+ size_t size = sizeof(struct sockaddr_in);
+ struct sockaddr_in add;
+
+ getsockname(sock, (struct sockaddr *) &add, (socklen_t *)&size);
+ s=accept(sock, (struct sockaddr *) &add, (socklen_t *)&size);
+
+ sclose(sock); /* close the first socket */
+
+ if (-1 == s) {
+ /* DIE! */
+ failf(data, "Error accept()ing server connect");
+ return CURLE_FTP_PORT_FAILED;
+ }
+ infof(data, "Connection accepted from server\n");
+
+ conn->secondarysocket = s;
+ }
+ break;
+ }
+ return CURLE_OK;
+}
+
+
+/* --- 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.
+ */
+
+int Curl_GetFTPResponse(char *buf,
+ struct connectdata *conn,
+ int *ftpcode)
+{
+ /* Brand new implementation.
+ * We cannot read just one byte per read() and then go back to select()
+ * as it seems that the OpenSSL read() stuff doesn't grok that properly.
+ *
+ * Alas, read as much as possible, split up into lines, use the ending
+ * line in a response or continue reading. */
+
+ int sockfd = conn->firstsocket;
+ int nread; /* total size read */
+ int perline; /* count bytes per line */
+ bool keepon=TRUE;
+ ssize_t gotbytes;
+ char *ptr;
+ int timeout = 3600; /* default timeout in seconds */
+ struct timeval interval;
+ fd_set rkeepfd;
+ fd_set readfd;
+ struct SessionHandle *data = conn->data;
+ char *line_start;
+ int code=0; /* default "error code" to return */
+
+#define SELECT_OK 0
+#define SELECT_ERROR 1 /* select() problems */
+#define SELECT_TIMEOUT 2 /* took too long */
+#define SELECT_MEMORY 3 /* no available memory */
+#define SELECT_CALLBACK 4 /* aborted by callback */
+
+ int error = SELECT_OK;
+
+ struct FTP *ftp = conn->proto.ftp;
+
+ if (ftpcode)
+ *ftpcode = 0; /* 0 for errors */
+
+ 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 */
+ if(timeout <=0 ) {
+ failf(data, "Transfer aborted due to timeout");
+ return -SELECT_TIMEOUT; /* already too little time */
+ }
+ }
+
+ FD_ZERO (&readfd); /* clear it */
+ FD_SET (sockfd, &readfd); /* read socket */
+
+ /* get this in a backup variable to be able to restore it on each lap in the
+ select() loop */
+ rkeepfd = readfd;
+
+ ptr=buf;
+ line_start = buf;
+
+ nread=0;
+ perline=0;
+ keepon=TRUE;
+
+ while((nread<BUFSIZE) && (keepon && !error)) {
+ readfd = rkeepfd; /* set every lap */
+ interval.tv_sec = timeout;
+ interval.tv_usec = 0;
+
+ if(!ftp->cache)
+ switch (select (sockfd+1, &readfd, NULL, NULL, &interval)) {
+ case -1: /* select() error, stop reading */
+ error = SELECT_ERROR;
+ failf(data, "Transfer aborted due to select() error");
+ break;
+ case 0: /* timeout */
+ error = SELECT_TIMEOUT;
+ failf(data, "Transfer aborted due to timeout");
+ break;
+ default:
+ error = SELECT_OK;
+ break;
+ }
+ if(SELECT_OK == error) {
+ /*
+ * 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(ftp->cache) {
+ /* we had data in the "cache", copy that instead of doing an actual
+ read */
+ memcpy(ptr, ftp->cache, ftp->cache_size);
+ gotbytes = ftp->cache_size;
+ free(ftp->cache); /* free the cache */
+ ftp->cache = NULL; /* clear the pointer */
+ ftp->cache_size = 0; /* zero the size just in case */
+ }
+ else {
+ int res = Curl_read(conn, sockfd, ptr,
+ BUFSIZE-nread, &gotbytes);
+ if(res < 0)
+ /* EWOULDBLOCK */
+ continue; /* go looping again */
+
+ if(CURLE_OK != res)
+ keepon = FALSE;
+ }
+
+ if(!keepon)
+ ;
+ else if(gotbytes <= 0) {
+ keepon = FALSE;
+ error = SELECT_ERROR;
+ failf(data, "Connection aborted");
+ }
+ 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;
+
+ nread += 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 */
+ CURLcode result;
+
+ /* output debug output if that is requested */
+ if(data->set.verbose) {
+ fputs("< ", data->set.err);
+ fwrite(line_start, perline, 1, data->set.err);
+ /* no need to output LF here, it is part of the data */
+ }
+
+ /*
+ * We pass all response-lines to the callback function registered
+ * for "headers". The response lines can be seen as a kind of
+ * headers.
+ */
+ result = Curl_client_write(data, CLIENTWRITE_HEADER,
+ line_start, perline);
+ if(result)
+ return -SELECT_CALLBACK;
+
+#define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \
+ isdigit((int)line[2]) && (' ' == line[3]))
+
+ if(perline>3 && lastline(line_start)) {
+ /* This is the end of the last line, copy the last
+ * line to the start of the buffer and zero terminate,
+ * for old times sake (and krb4)! */
+ char *meow;
+ int n;
+ for(meow=line_start, n=0; 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. */
+ ftp->cache_size = gotbytes - i;
+ ftp->cache = (char *)malloc(ftp->cache_size);
+ if(ftp->cache)
+ memcpy(ftp->cache, line_start, ftp->cache_size);
+ else
+ return -SELECT_MEMORY; /**BANG**/
+ }
+ } /* there was data */
+ } /* if(no error) */
+ } /* while there's buffer left and loop is requested */
+
+ if(!error)
+ code = atoi(buf);
+
+#ifdef 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(error)
+ return -error;
+
+ if(ftpcode)
+ *ftpcode=code; /* return the initial number like this */
+
+ return nread; /* total amount of bytes read */
+}
+
+#ifndef ENABLE_IPV6
+/*
+ * This function is only used by code that works on IPv4. When we add proper
+ * support for that functionality with IPv6, this function can go in again.
+ */
+/* -- who are we? -- */
+static char *getmyhost(char *buf, int buf_size)
+{
+#if defined(HAVE_GETHOSTNAME)
+ gethostname(buf, buf_size);
+#elif defined(HAVE_UNAME)
+ struct utsname ugnm;
+ strncpy(buf, uname(&ugnm) < 0 ? "localhost" : ugnm.nodename, buf_size - 1);
+ buf[buf_size - 1] = '\0';
+#else
+ /* We have no means of finding the local host name! */
+ strncpy(buf, "localhost", buf_size);
+ buf[buf_size - 1] = '\0';
+#endif
+ return buf;
+}
+
+#endif /* ipv4-only function */
+
+
+/* ftp_connect() should do everything that is to be considered a part
+ of the connection phase. */
+CURLcode Curl_ftp_connect(struct connectdata *conn)
+{
+ /* this is FTP and no proxy */
+ int nread;
+ struct SessionHandle *data=conn->data;
+ char *buf = data->state.buffer; /* this is our buffer */
+ struct FTP *ftp;
+ CURLcode result;
+ int ftpcode;
+
+ ftp = (struct FTP *)malloc(sizeof(struct FTP));
+ if(!ftp)
+ return CURLE_OUT_OF_MEMORY;
+
+ memset(ftp, 0, sizeof(struct FTP));
+ conn->proto.ftp = ftp;
+
+ /* We always support persistant connections on ftp */
+ conn->bits.close = FALSE;
+
+ /* get some initial data into the ftp struct */
+ ftp->bytecountp = &conn->bytecount;
+
+ /* no need to duplicate them, the data struct won't change */
+ ftp->user = data->state.user;
+ ftp->passwd = data->state.passwd;
+
+ if (data->set.tunnel_thru_httpproxy) {
+ /* We want "seamless" FTP operations through HTTP proxy tunnel */
+ result = Curl_ConnectHTTPProxyTunnel(conn, conn->firstsocket,
+ conn->hostname, conn->remote_port);
+ if(CURLE_OK != result)
+ return result;
+ }
+
+ if(conn->protocol & PROT_FTPS) {
+ /* FTPS is simply ftp with SSL for the control channel */
+ /* now, perform the SSL initialization for this socket */
+ result = Curl_SSLConnect(conn);
+ if(result)
+ return result;
+ }
+
+
+ /* The first thing we do is wait for the "220*" line: */
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode != 220) {
+ failf(data, "This doesn't seem like a nice ftp-server response");
+ return CURLE_FTP_WEIRD_SERVER_REPLY;
+ }
+
+#ifdef KRB4
+ /* if not anonymous login, try a secure login */
+ if(data->set.krb4) {
+
+ /* request data protection level (default is 'clear') */
+ Curl_sec_request_prot(conn, "private");
+
+ /* We set private first as default, in case the line below fails to
+ set a valid level */
+ Curl_sec_request_prot(conn, data->set.krb4_level);
+
+ if(Curl_sec_login(conn) != 0)
+ infof(data, "Logging in with password in cleartext!\n");
+ else
+ infof(data, "Authentication successful\n");
+ }
+#endif
+
+ /* send USER */
+ FTPSENDF(conn, "USER %s", ftp->user);
+
+ /* wait for feedback */
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode == 530) {
+ /* 530 User ... access denied
+ (the server denies to log the specified user) */
+ failf(data, "Access denied: %s", &buf[4]);
+ return CURLE_FTP_ACCESS_DENIED;
+ }
+ else if(ftpcode == 331) {
+ /* 331 Password required for ...
+ (the server requires to send the user's password too) */
+ FTPSENDF(conn, "PASS %s", ftp->passwd);
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode == 530) {
+ /* 530 Login incorrect.
+ (the username and/or the password are incorrect) */
+ failf(data, "the username and/or the password are incorrect");
+ return CURLE_FTP_USER_PASSWORD_INCORRECT;
+ }
+ else if(ftpcode == 230) {
+ /* 230 User ... logged in.
+ (user successfully logged in) */
+
+ infof(data, "We have successfully logged in\n");
+ }
+ else {
+ failf(data, "Odd return code after PASS");
+ return CURLE_FTP_WEIRD_PASS_REPLY;
+ }
+ }
+ else if(buf[0] == '2') {
+ /* 230 User ... logged in.
+ (the user logged in without password) */
+ infof(data, "We have successfully logged in\n");
+#ifdef KRB4
+ /* we are logged in (with Kerberos)
+ * now set the requested protection level
+ */
+ if(conn->sec_complete)
+ Curl_sec_set_protection_level(conn);
+
+ /* we may need to issue a KAUTH here to have access to the files
+ * do it if user supplied a password
+ */
+ if(data->state.passwd && *data->state.passwd)
+ Curl_krb_kauth(conn);
+#endif
+ }
+ else {
+ failf(data, "Odd return code after USER");
+ return CURLE_FTP_WEIRD_USER_REPLY;
+ }
+
+ /* send PWD to discover our entry point */
+ FTPSENDF(conn, "PWD", NULL);
+
+ /* wait for feedback */
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode == 257) {
+ char *dir = (char *)malloc(nread+1);
+ char *store=dir;
+ char *ptr=&buf[4]; /* start on the first letter */
+
+ /* 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++;
+ }
+ ftp->entrypath =dir; /* remember this */
+ infof(data, "Entry path is '%s'\n", ftp->entrypath);
+ }
+ else {
+ /* couldn't get the path */
+ }
+
+ }
+ else {
+ /* We couldn't read the PWD response! */
+ }
+
+ return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * 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)
+{
+ struct SessionHandle *data = conn->data;
+ struct FTP *ftp = conn->proto.ftp;
+ ssize_t nread;
+ char *buf = data->state.buffer; /* this is our buffer */
+ int ftpcode;
+ CURLcode result=CURLE_OK;
+
+ if(data->set.upload) {
+ if((-1 != data->set.infilesize) && (data->set.infilesize != *ftp->bytecountp)) {
+ failf(data, "Wrote only partial file (%d out of %d bytes)",
+ *ftp->bytecountp, data->set.infilesize);
+ return CURLE_PARTIAL_FILE;
+ }
+ }
+ else {
+ if((-1 != conn->size) && (conn->size != *ftp->bytecountp) &&
+ (conn->maxdownload != *ftp->bytecountp)) {
+ failf(data, "Received only partial file: %d bytes", *ftp->bytecountp);
+ return CURLE_PARTIAL_FILE;
+ }
+ else if(!conn->bits.resume_done &&
+ !data->set.no_body &&
+ (0 == *ftp->bytecountp)) {
+ /* We consider this an error, but there's no true FTP error received
+ why we need to continue to "read out" the server response too.
+ We don't want to leave a "waiting" server reply if we'll get told
+ to make a second request on this same connection! */
+ failf(data, "No data was received!");
+ result = CURLE_FTP_COULDNT_RETR_FILE;
+ }
+ }
+
+#ifdef KRB4
+ Curl_sec_fflush_fd(conn, conn->secondarysocket);
+#endif
+ /* shut down the socket to inform the server we're done */
+ sclose(conn->secondarysocket);
+ conn->secondarysocket = -1;
+
+ if(!data->set.no_body && !conn->bits.resume_done) {
+ /* now let's see what the server says about the transfer we
+ just performed: */
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ /* 226 Transfer complete, 250 Requested file action okay, completed. */
+ if((ftpcode != 226) && (ftpcode != 250)) {
+ failf(data, "server did not report OK, got %d", ftpcode);
+ return CURLE_FTP_WRITE_ERROR;
+ }
+ }
+
+ conn->bits.resume_done = FALSE; /* clean this for next connection */
+
+ /* Send any post-transfer QUOTE strings? */
+ if(!result && 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);
+
+ nread = Curl_GetFTPResponse(conn->data->state.buffer, conn, &ftpcode);
+ if (nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ 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_cwd()
+ *
+ * Send 'CWD' to the remote server to Change Working Directory.
+ * It is the ftp version of the unix 'cd' command.
+ */
+static
+CURLcode ftp_cwd(struct connectdata *conn, char *path)
+{
+ ssize_t nread;
+ int ftpcode;
+ CURLcode result;
+
+ FTPSENDF(conn, "CWD %s", path);
+ nread = Curl_GetFTPResponse(
+ conn->data->state.buffer, conn, &ftpcode);
+ if (nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if (ftpcode != 250) {
+ failf(conn->data, "Couldn't cd to %s", path);
+ return CURLE_FTP_ACCESS_DENIED;
+ }
+
+ return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * ftp_getfiletime()
+ *
+ * Get the timestamp of the given file.
+ */
+static
+CURLcode ftp_getfiletime(struct connectdata *conn, char *file)
+{
+ CURLcode result=CURLE_OK;
+ int ftpcode; /* for ftp status */
+ ssize_t nread;
+ char *buf = conn->data->state.buffer;
+
+ /* we have requested to get the modified-time of the file, this is yet
+ again a grey area as the MDTM is not kosher RFC959 */
+ FTPSENDF(conn, "MDTM %s", file);
+
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode == 213) {
+ /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the
+ last .sss part is optional and means fractions of a second */
+ int year, month, day, hour, minute, second;
+ if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d",
+ &year, &month, &day, &hour, &minute, &second)) {
+ /* we have a time, reformat it */
+ time_t secs=time(NULL);
+ sprintf(buf, "%04d%02d%02d %02d:%02d:%02d",
+ year, month, day, hour, minute, second);
+ /* now, convert this into a time() value: */
+ conn->data->info.filetime = curl_getdate(buf, &secs);
+ }
+ else {
+ infof(conn->data, "unsupported MDTM reply format\n");
+ }
+ }
+ return result;
+}
+
+/***********************************************************************
+ *
+ * ftp_transfertype()
+ *
+ * Set transfer type. We only deal with ASCII or BINARY so this function
+ * sets one of them.
+ */
+static CURLcode ftp_transfertype(struct connectdata *conn,
+ bool ascii)
+{
+ struct SessionHandle *data = conn->data;
+ int ftpcode;
+ ssize_t nread;
+ char *buf=data->state.buffer;
+ CURLcode result;
+
+ FTPSENDF(conn, "TYPE %s", ascii?"A":"I");
+
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode != 200) {
+ failf(data, "Couldn't set %s mode",
+ ascii?"ASCII":"binary");
+ return ascii? CURLE_FTP_COULDNT_SET_ASCII:CURLE_FTP_COULDNT_SET_BINARY;
+ }
+
+ return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * ftp_getsize()
+ *
+ * Returns the file size (in bytes) of the given remote file.
+ */
+
+static
+CURLcode ftp_getsize(struct connectdata *conn, char *file,
+ ssize_t *size)
+{
+ struct SessionHandle *data = conn->data;
+ int ftpcode;
+ ssize_t nread;
+ char *buf=data->state.buffer;
+ CURLcode result;
+
+ FTPSENDF(conn, "SIZE %s", file);
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode == 213) {
+ /* get the size from the ascii string: */
+ *size = atoi(buf+4);
+ }
+ else
+ return CURLE_FTP_COULDNT_GET_SIZE;
+
+ 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_ipconnect *addr,
+ char *newhost, /* ascii version */
+ int port)
+{
+#ifndef ENABLE_IPV6
+ /*****************************************************************
+ *
+ * IPv4-only code section
+ */
+
+ struct in_addr in;
+ struct hostent * answer;
+
+#ifdef HAVE_INET_NTOA_R
+ char ntoa_buf[64];
+#endif
+ /* The array size trick below is to make this a large chunk of memory
+ suitably 8-byte aligned on 64-bit platforms. This was thoughtfully
+ suggested by Philip Gladstone. */
+ long bigbuf[9000 / sizeof(long)];
+
+#if defined(HAVE_INET_ADDR)
+ in_addr_t address;
+# if defined(HAVE_GETHOSTBYADDR_R)
+ int h_errnop;
+# endif
+ char *hostent_buf = (char *)bigbuf; /* get a char * to the buffer */
+
+ address = inet_addr(newhost);
+# ifdef HAVE_GETHOSTBYADDR_R
+
+# ifdef HAVE_GETHOSTBYADDR_R_5
+ /* AIX, Digital Unix (OSF1, Tru64) style:
+ extern int gethostbyaddr_r(char *addr, size_t len, int type,
+ struct hostent *htent, struct hostent_data *ht_data); */
+
+ /* Fred Noz helped me try this out, now it at least compiles! */
+
+ /* Bjorn Reese (November 28 2001):
+ The Tru64 man page on gethostbyaddr_r() says that
+ the hostent struct must be filled with zeroes before the call to
+ gethostbyaddr_r(). */
+
+ memset(hostent_buf, 0, sizeof(struct hostent));
+
+ if(gethostbyaddr_r((char *) &address,
+ sizeof(address), AF_INET,
+ (struct hostent *)hostent_buf,
+ hostent_buf + sizeof(*answer)))
+ answer=NULL;
+
+# endif
+# ifdef HAVE_GETHOSTBYADDR_R_7
+ /* Solaris and IRIX */
+ answer = gethostbyaddr_r((char *) &address, sizeof(address), AF_INET,
+ (struct hostent *)bigbuf,
+ hostent_buf + sizeof(*answer),
+ sizeof(hostent_buf) - sizeof(*answer),
+ &h_errnop);
+# endif
+# ifdef HAVE_GETHOSTBYADDR_R_8
+ /* Linux style */
+ if(gethostbyaddr_r((char *) &address, sizeof(address), AF_INET,
+ (struct hostent *)hostent_buf,
+ hostent_buf + sizeof(*answer),
+ sizeof(hostent_buf) - sizeof(*answer),
+ &answer,
+ &h_errnop))
+ answer=NULL; /* error */
+# endif
+
+# else
+ answer = gethostbyaddr((char *) &address, sizeof(address), AF_INET);
+# endif
+#else
+ answer = NULL;
+#endif
+ (void) memcpy(&in.s_addr, addr, sizeof (Curl_ipconnect));
+ infof(conn->data, "Connecting to %s (%s) port %u\n",
+ answer?answer->h_name:newhost,
+#if defined(HAVE_INET_NTOA_R)
+ inet_ntoa_r(in, ntoa_buf, sizeof(ntoa_buf)),
+#else
+ inet_ntoa(in),
+#endif
+ port);
+
+#else
+ /*****************************************************************
+ *
+ * IPv6-only code section
+ */
+ char hbuf[NI_MAXHOST]; /* ~1KB */
+ char nbuf[NI_MAXHOST]; /* ~1KB */
+ char sbuf[NI_MAXSERV]; /* around 32 */
+#ifdef NI_WITHSCOPEID
+ const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
+#else
+ const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
+#endif
+ port = 0; /* unused, prevent warning */
+ if (getnameinfo(addr->ai_addr, addr->ai_addrlen,
+ nbuf, sizeof(nbuf), sbuf, sizeof(sbuf), niflags)) {
+ snprintf(nbuf, sizeof(nbuf), "?");
+ snprintf(sbuf, sizeof(sbuf), "?");
+ }
+
+ if (getnameinfo(addr->ai_addr, addr->ai_addrlen,
+ hbuf, sizeof(hbuf), NULL, 0, 0)) {
+ infof(conn->data, "Connecting to %s (%s) port %s\n", nbuf, newhost, sbuf);
+ }
+ else {
+ infof(conn->data, "Connecting to %s (%s) port %s\n", hbuf, nbuf, sbuf);
+ }
+#endif
+}
+
+/***********************************************************************
+ *
+ * ftp_use_port()
+ *
+ * Send the proper PORT command. PORT is the ftp client's way of telling the
+ * server that *WE* open a port that we listen on an awaits the server to
+ * connect to. This is the opposite of PASV.
+ */
+
+static
+CURLcode ftp_use_port(struct connectdata *conn)
+{
+ struct SessionHandle *data=conn->data;
+ int portsock=-1;
+ ssize_t nread;
+ char *buf = data->state.buffer; /* this is our buffer */
+ int ftpcode; /* receive FTP response codes in this */
+ CURLcode result;
+
+#ifdef ENABLE_IPV6
+ /******************************************************************
+ *
+ * Here's a piece of IPv6-specific code coming up
+ *
+ */
+
+ struct addrinfo hints, *res, *ai;
+ struct sockaddr_storage ss;
+ socklen_t sslen;
+ char hbuf[NI_MAXHOST];
+
+ struct sockaddr *sa=(struct sockaddr *)&ss;
+#ifdef NI_WITHSCOPEID
+ const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
+#else
+ const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
+#endif
+ unsigned char *ap;
+ unsigned char *pp;
+ int alen, plen;
+ char portmsgbuf[4096], tmp[4096];
+
+ const char *mode[] = { "EPRT", "LPRT", "PORT", NULL };
+ char **modep;
+
+ /*
+ * we should use Curl_if2ip? given pickiness of recent ftpd,
+ * I believe we should use the same address as the control connection.
+ */
+ sslen = sizeof(ss);
+ if (getsockname(conn->firstsocket, (struct sockaddr *)&ss, &sslen) < 0)
+ return CURLE_FTP_PORT_FAILED;
+
+ if (getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL, 0,
+ niflags))
+ return CURLE_FTP_PORT_FAILED;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = sa->sa_family;
+ /*hints.ai_family = ss.ss_family;
+ this way can be used if sockaddr_storage is properly defined, as glibc
+ 2.1.X doesn't do*/
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ if (getaddrinfo(hbuf, (char *)"0", &hints, &res))
+ return CURLE_FTP_PORT_FAILED;
+
+ portsock = -1;
+ for (ai = res; ai; ai = ai->ai_next) {
+ portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (portsock < 0)
+ continue;
+
+ if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) {
+ sclose(portsock);
+ portsock = -1;
+ continue;
+ }
+
+ if (listen(portsock, 1) < 0) {
+ sclose(portsock);
+ portsock = -1;
+ continue;
+ }
+
+ break;
+ }
+ freeaddrinfo(res);
+ if (portsock < 0) {
+ failf(data, strerror(errno));
+ return CURLE_FTP_PORT_FAILED;
+ }
+
+ sslen = sizeof(ss);
+ if (getsockname(portsock, sa, &sslen) < 0) {
+ failf(data, strerror(errno));
+ return CURLE_FTP_PORT_FAILED;
+ }
+
+ for (modep = (char **)mode; modep && *modep; modep++) {
+ int lprtaf, eprtaf;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr;
+ alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr);
+ pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port;
+ plen = sizeof(((struct sockaddr_in *)&ss)->sin_port);
+ lprtaf = 4;
+ eprtaf = 1;
+ break;
+ case AF_INET6:
+ ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr;
+ alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr);
+ pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port;
+ plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port);
+ lprtaf = 6;
+ eprtaf = 2;
+ break;
+ default:
+ ap = pp = NULL;
+ lprtaf = eprtaf = -1;
+ break;
+ }
+
+ if (strcmp(*modep, "EPRT") == 0) {
+ if (eprtaf < 0)
+ continue;
+ if (getnameinfo((struct sockaddr *)&ss, sslen,
+ portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp), niflags))
+ continue;
+
+ /* do not transmit IPv6 scope identifier to the wire */
+ if (sa->sa_family == AF_INET6) {
+ char *q = strchr(portmsgbuf, '%');
+ if (q)
+ *q = '\0';
+ }
+
+ result = Curl_ftpsendf(conn, "%s |%d|%s|%s|", *modep, eprtaf,
+ portmsgbuf, tmp);
+ if(result)
+ return result;
+ } else if (strcmp(*modep, "LPRT") == 0 ||
+ strcmp(*modep, "PORT") == 0) {
+ int i;
+
+ if (strcmp(*modep, "LPRT") == 0 && lprtaf < 0)
+ continue;
+ if (strcmp(*modep, "PORT") == 0 && sa->sa_family != AF_INET)
+ continue;
+
+ portmsgbuf[0] = '\0';
+ if (strcmp(*modep, "LPRT") == 0) {
+ snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen);
+ if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+ sizeof(portmsgbuf)) {
+ continue;
+ }
+ }
+
+ for (i = 0; i < alen; i++) {
+ if (portmsgbuf[0])
+ snprintf(tmp, sizeof(tmp), ",%u", ap[i]);
+ else
+ snprintf(tmp, sizeof(tmp), "%u", ap[i]);
+
+ if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+ sizeof(portmsgbuf)) {
+ continue;
+ }
+ }
+
+ if (strcmp(*modep, "LPRT") == 0) {
+ snprintf(tmp, sizeof(tmp), ",%d", plen);
+
+ if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf))
+ continue;
+ }
+
+ for (i = 0; i < plen; i++) {
+ snprintf(tmp, sizeof(tmp), ",%u", pp[i]);
+
+ if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+ sizeof(portmsgbuf)) {
+ continue;
+ }
+ }
+
+ result = Curl_ftpsendf(conn, "%s %s", *modep, portmsgbuf);
+ if(result)
+ return result;
+ }
+
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if (ftpcode != 200) {
+ failf(data, "Server does not grok %s", *modep);
+ continue;
+ }
+ else
+ break;
+ }
+
+ if (!*modep) {
+ sclose(portsock);
+ return CURLE_FTP_PORT_FAILED;
+ }
+ /* we set the secondary socket variable to this for now, it
+ is only so that the cleanup function will close it in case
+ we fail before the true secondary stuff is made */
+ conn->secondarysocket = portsock;
+
+#else
+ /******************************************************************
+ *
+ * Here's a piece of IPv4-specific code coming up
+ *
+ */
+ struct sockaddr_in sa;
+ struct hostent *h=NULL;
+ char *hostdataptr=NULL;
+ unsigned short porttouse;
+ char myhost[256] = "";
+
+ if(data->set.ftpport) {
+ if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) {
+ h = Curl_resolv(data, myhost, 0, &hostdataptr);
+ }
+ else {
+ int len = strlen(data->set.ftpport);
+ if(len>1)
+ h = Curl_resolv(data, data->set.ftpport, 0, &hostdataptr);
+ if(h)
+ strcpy(myhost, data->set.ftpport); /* buffer overflow risk */
+ }
+ }
+ if(! *myhost) {
+ char *tmp_host = getmyhost(myhost, sizeof(myhost));
+ h=Curl_resolv(data, tmp_host, 0, &hostdataptr);
+ }
+ infof(data, "We connect from %s\n", myhost);
+
+ if ( h ) {
+ if( (portsock = socket(AF_INET, SOCK_STREAM, 0)) >= 0 ) {
+ int size;
+
+ /* we set the secondary socket variable to this for now, it
+ is only so that the cleanup function will close it in case
+ we fail before the true secondary stuff is made */
+ conn->secondarysocket = portsock;
+
+ memset((char *)&sa, 0, sizeof(sa));
+ memcpy((char *)&sa.sin_addr,
+ h->h_addr,
+ h->h_length);
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = INADDR_ANY;
+ sa.sin_port = 0;
+ size = sizeof(sa);
+
+ if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
+ /* we succeeded to bind */
+ struct sockaddr_in add;
+ socklen_t socksize = sizeof(add);
+
+ if(getsockname(portsock, (struct sockaddr *) &add,
+ &socksize)<0) {
+ failf(data, "getsockname() failed");
+ return CURLE_FTP_PORT_FAILED;
+ }
+ porttouse = ntohs(add.sin_port);
+
+ if ( listen(portsock, 1) < 0 ) {
+ failf(data, "listen(2) failed on socket");
+ free(hostdataptr);
+ return CURLE_FTP_PORT_FAILED;
+ }
+ }
+ else {
+ failf(data, "bind(2) failed on socket");
+ free(hostdataptr);
+ return CURLE_FTP_PORT_FAILED;
+ }
+ }
+ else {
+ failf(data, "socket(2) failed (%s)");
+ free(hostdataptr);
+ return CURLE_FTP_PORT_FAILED;
+ }
+ }
+ else {
+ failf(data, "could't find my own IP address (%s)", myhost);
+ return CURLE_FTP_PORT_FAILED;
+ }
+ {
+#ifdef HAVE_INET_NTOA_R
+ char ntoa_buf[64];
+#endif
+ struct in_addr in;
+ unsigned short ip[5];
+ (void) memcpy(&in.s_addr, *h->h_addr_list, sizeof (in.s_addr));
+#ifdef HAVE_INET_NTOA_R
+ /* ignore the return code from inet_ntoa_r() as it is int or
+ char * depending on system */
+ inet_ntoa_r(in, ntoa_buf, sizeof(ntoa_buf));
+ sscanf( ntoa_buf, "%hu.%hu.%hu.%hu",
+ &ip[0], &ip[1], &ip[2], &ip[3]);
+#else
+ sscanf( inet_ntoa(in), "%hu.%hu.%hu.%hu",
+ &ip[0], &ip[1], &ip[2], &ip[3]);
+#endif
+ result=Curl_ftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d",
+ ip[0], ip[1], ip[2], ip[3],
+ porttouse >> 8,
+ porttouse & 255);
+ if(result)
+ return result;
+ }
+
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode != 200) {
+ failf(data, "Server does not grok PORT, try without it!");
+ return CURLE_FTP_PORT_FAILED;
+ }
+#endif /* end of ipv4-specific code */
+
+ return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * ftp_use_pasv()
+ *
+ * Send the PASV command. PASV is the ftp client's way of asking the server to
+ * open a second port that we can connect to (for the data transfer). This is
+ * the opposite of PORT.
+ */
+
+static
+CURLcode ftp_use_pasv(struct connectdata *conn)
+{
+ struct SessionHandle *data = conn->data;
+ ssize_t nread;
+ char *buf = data->state.buffer; /* this is our buffer */
+ int ftpcode; /* receive FTP response codes in this */
+ CURLcode result;
+ Curl_addrinfo *addr=NULL;
+ Curl_ipconnect *conninfo;
+
+ /*
+ 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|)
+
+ */
+
+#if 1
+ const char *mode[] = { "EPSV", "PASV", NULL };
+ int results[] = { 229, 227, 0 };
+#else
+#if 0
+ char *mode[] = { "EPSV", "LPSV", "PASV", NULL };
+ int results[] = { 229, 228, 227, 0 };
+#else
+ const char *mode[] = { "PASV", NULL };
+ int results[] = { 227, 0 };
+#endif
+#endif
+ int modeoff;
+ unsigned short connectport; /* the local port connect() should use! */
+ unsigned short newport; /* remote port, not necessary the local one */
+ char *hostdataptr=NULL;
+
+ /* newhost must be able to hold a full IP-style address in ASCII, which
+ in the IPv6 case means 5*8-1 = 39 letters */
+ char newhost[48];
+ char *newhostp=NULL;
+
+ for (modeoff = (data->set.ftp_use_epsv?0:1);
+ mode[modeoff]; modeoff++) {
+ result = Curl_ftpsendf(conn, mode[modeoff]);
+ if(result)
+ return result;
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+ if (ftpcode == results[modeoff])
+ break;
+ }
+
+ if (!mode[modeoff]) {
+ failf(data, "Odd return code after PASV");
+ return CURLE_FTP_WEIRD_PASV_REPLY;
+ }
+ else if (227 == results[modeoff]) {
+ int ip[4];
+ int port[2];
+ char *str=buf;
+
+ /*
+ * New 227-parser June 3rd 1999.
+ * It now scans for a sequence of six comma-separated numbers and
+ * will take them as IP+port indicators.
+ *
+ * Found reply-strings include:
+ * "227 Entering Passive Mode (127,0,0,1,4,51)"
+ * "227 Data transfer will passively listen to 127,0,0,1,4,51"
+ * "227 Entering passive mode. 127,0,0,1,4,51"
+ */
+
+ while(*str) {
+ if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d",
+ &ip[0], &ip[1], &ip[2], &ip[3],
+ &port[0], &port[1]))
+ break;
+ str++;
+ }
+
+ if(!*str) {
+ failf(data, "Couldn't interpret this 227-reply: %s", buf);
+ return CURLE_FTP_WEIRD_227_FORMAT;
+ }
+
+ sprintf(newhost, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
+ newhostp = newhost;
+ newport = (port[0]<<8) + port[1];
+ }
+#if 1
+ else if (229 == results[modeoff]) {
+ char *ptr = strchr(buf, '(');
+ 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])) {
+ /* the four separators should be identical */
+ newport = num;
+
+ /* we should use the same host we already are connected to */
+ newhostp = conn->name;
+ }
+ else
+ ptr=NULL;
+ }
+ if(!ptr) {
+ failf(data, "Weirdly formatted EPSV reply");
+ return CURLE_FTP_WEIRD_PASV_REPLY;
+ }
+ }
+#endif
+ else
+ return CURLE_FTP_CANT_RECONNECT;
+
+ if(data->change.proxy) {
+ /*
+ * This is a tunnel through a http proxy and we need to connect to the
+ * proxy again here. We already have the name info for it since the
+ * previous lookup.
+ */
+ addr = conn->hostaddr;
+ connectport =
+ (unsigned short)conn->port; /* we connect to the proxy's port */
+ }
+ else {
+ /* normal, direct, ftp connection */
+ addr = Curl_resolv(data, newhostp, newport, &hostdataptr);
+ if(!addr) {
+ failf(data, "Can't resolve new host %s", newhost);
+ return CURLE_FTP_CANT_GET_HOST;
+ }
+ connectport = newport; /* we connect to the remote port */
+ }
+
+ result = Curl_connecthost(conn,
+ addr,
+ connectport,
+ &conn->secondarysocket,
+ &conninfo);
+
+ if((CURLE_OK == result) &&
+ data->set.verbose)
+ /* this just dumps information about this second connection */
+ ftp_pasv_verbose(conn, conninfo, newhost, connectport);
+
+ if(CURLE_OK != result)
+ return result;
+
+ if (data->set.tunnel_thru_httpproxy) {
+ /* We want "seamless" FTP operations through HTTP proxy tunnel */
+ result = Curl_ConnectHTTPProxyTunnel(conn, conn->secondarysocket,
+ newhost, newport);
+ if(CURLE_OK != result)
+ return result;
+ }
+
+ return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * 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)
+{
+ /* this is FTP and no proxy */
+ ssize_t nread;
+ CURLcode result;
+ struct SessionHandle *data=conn->data;
+ char *buf = data->state.buffer; /* this is our buffer */
+
+ /* the ftp struct is already inited in ftp_connect() */
+ struct FTP *ftp = conn->proto.ftp;
+
+ long *bytecountp = ftp->bytecountp;
+ int ftpcode; /* for ftp status */
+
+ /* Send any QUOTE strings? */
+ if(data->set.quote) {
+ if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK)
+ return result;
+ }
+
+ /* This is a re-used connection. Since we change directory to where the
+ transfer is taking place, we must now get back to the original dir
+ where we ended up after login: */
+ if (conn->bits.reuse) {
+ if ((result = ftp_cwd(conn, ftp->entrypath)) != CURLE_OK)
+ return result;
+ }
+
+ /* change directory first! */
+ if(ftp->dir && ftp->dir[0]) {
+ if ((result = ftp_cwd(conn, ftp->dir)) != CURLE_OK)
+ return result;
+ }
+
+ /* Requested time of file? */
+ if(data->set.get_filetime && ftp->file) {
+ result = ftp_getfiletime(conn, ftp->file);
+ if(result)
+ return result;
+ }
+
+ /* 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(data->set.no_body && data->set.include_header) {
+ /* 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! */
+ ssize_t filesize;
+
+ /* Some servers return different sizes for different modes, and thus we
+ must set the proper type before we check the size */
+ result = ftp_transfertype(conn, data->set.ftp_ascii);
+ if(result)
+ return result;
+
+ /* failing to get size is not a serious error */
+ result = ftp_getsize(conn, ftp->file, &filesize);
+
+ if(CURLE_OK == result) {
+ sprintf(buf, "Content-Length: %d\r\n", filesize);
+ result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
+ if(result)
+ return result;
+ }
+
+ /* If we asked for a time of the file and we actually got one as
+ well, we "emulate" a HTTP-style header in our output. */
+
+#ifdef HAVE_STRFTIME
+ if(data->set.get_filetime && data->info.filetime) {
+ struct tm *tm;
+#ifdef HAVE_LOCALTIME_R
+ struct tm buffer;
+ tm = (struct tm *)localtime_r(&data->info.filetime, &buffer);
+#else
+ tm = localtime((unsigned long *)&data->info.filetime);
+#endif
+ /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
+ strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S %Z\r\n",
+ tm);
+ result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
+ if(result)
+ return result;
+ }
+#endif
+
+ return CURLE_OK;
+ }
+
+ if(data->set.no_body)
+ /* don't transfer the data */
+ ;
+ /* Get us a second connection up and connected */
+ else if(data->set.ftp_use_port) {
+ /* We have chosen to use the PORT command */
+ result = ftp_use_port(conn);
+ if(CURLE_OK == result)
+ /* we have the data connection ready */
+ infof(data, "Connected the data stream with PORT!\n");
+ }
+ else {
+ /* We have chosen (this is default) to use the PASV command */
+ result = ftp_use_pasv(conn);
+ if(CURLE_OK == result)
+ infof(data, "Connected the data stream with PASV!\n");
+ }
+
+ if(result)
+ return result;
+
+ if(data->set.upload) {
+
+ /* Set type to binary (unless specified ASCII) */
+ result = ftp_transfertype(conn, data->set.ftp_ascii);
+ if(result)
+ return result;
+
+ /* Send any PREQUOTE strings after transfer type is set? (Wesley Laxton)*/
+ if(data->set.prequote) {
+ if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK)
+ return result;
+ }
+
+ if(conn->resume_from) {
+ /* we're about to continue the uploading of a file */
+ /* 1. get already existing file's size. We use the SIZE
+ command for this which may not exist in the server!
+ The SIZE command is not in RFC959. */
+
+ /* 2. This used to set REST. But since we can do append, we
+ don't another ftp command. We just skip the source file
+ offset and then we APPEND the rest on the file instead */
+
+ /* 3. pass file-size number of bytes in the source file */
+ /* 4. lower the infilesize counter */
+ /* => transfer as usual */
+
+ if(conn->resume_from < 0 ) {
+ /* we could've got a specified offset from the command line,
+ but now we know we didn't */
+ ssize_t gottensize;
+
+ if(CURLE_OK != ftp_getsize(conn, ftp->file, &gottensize)) {
+ failf(data, "Couldn't get remote file size");
+ return CURLE_FTP_COULDNT_GET_SIZE;
+ }
+ conn->resume_from = gottensize;
+ }
+
+ if(conn->resume_from) {
+ /* do we still game? */
+ int passed=0;
+ /* enable append instead */
+ data->set.ftp_append = 1;
+
+ /* Now, let's read off the proper amount of bytes from the
+ input. If we knew it was a proper file we could've just
+ fseek()ed but we only have a stream here */
+ do {
+ int readthisamountnow = (conn->resume_from - passed);
+ int actuallyread;
+
+ if(readthisamountnow > BUFSIZE)
+ readthisamountnow = BUFSIZE;
+
+ actuallyread =
+ data->set.fread(data->state.buffer, 1, readthisamountnow,
+ data->set.in);
+
+ passed += actuallyread;
+ if(actuallyread != readthisamountnow) {
+ failf(data, "Could only read %d bytes from the input", passed);
+ return CURLE_FTP_COULDNT_USE_REST;
+ }
+ }
+ while(passed != conn->resume_from);
+
+ /* now, decrease the size of the read */
+ if(data->set.infilesize>0) {
+ data->set.infilesize -= conn->resume_from;
+
+ if(data->set.infilesize <= 0) {
+ infof(data, "File already completely uploaded\n");
+
+ /* no data to transfer */
+ result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+
+ /* Set resume done so that we won't get any error in
+ * Curl_ftp_done() because we didn't transfer the amount of bytes
+ * that the local file file obviously is */
+ conn->bits.resume_done = TRUE;
+
+ return CURLE_OK;
+ }
+ }
+ /* we've passed, proceed as normal */
+ }
+ }
+
+ /* Send everything on data->set.in to the socket */
+ if(data->set.ftp_append) {
+ /* we append onto the file instead of rewriting it */
+ FTPSENDF(conn, "APPE %s", ftp->file);
+ }
+ else {
+ FTPSENDF(conn, "STOR %s", ftp->file);
+ }
+
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode>=400) {
+ failf(data, "Failed FTP upload:%s", buf+3);
+ /* oops, we never close the sockets! */
+ return CURLE_FTP_COULDNT_STOR_FILE;
+ }
+
+ if(data->set.ftp_use_port) {
+ /* PORT means we are now awaiting the server to connect to us. */
+ result = AllowServerConnect(data, conn, conn->secondarysocket);
+ if( result )
+ return result;
+ }
+
+ *bytecountp=0;
+
+ /* When we know we're uploading a specified file, we can get the file
+ size prior to the actual upload. */
+
+ Curl_pgrsSetUploadSize(data, data->set.infilesize);
+
+ result = Curl_Transfer(conn, -1, -1, FALSE, NULL, /* no download */
+ conn->secondarysocket, bytecountp);
+ if(result)
+ return result;
+
+ }
+ else if(!data->set.no_body) {
+ /* Retrieve file or directory */
+ bool dirlist=FALSE;
+ long downloadsize=-1;
+
+ if(conn->bits.use_range && conn->range) {
+ long from, to;
+ int totalsize=-1;
+ char *ptr;
+ char *ptr2;
+
+ from=strtol(conn->range, &ptr, 0);
+ while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
+ ptr++;
+ to=strtol(ptr, &ptr2, 0);
+ if(ptr == ptr2) {
+ /* we didn't get any digit */
+ to=-1;
+ }
+ if((-1 == to) && (from>=0)) {
+ /* X - */
+ conn->resume_from = from;
+ infof(data, "FTP RANGE %d to end of file\n", from);
+ }
+ else if(from < 0) {
+ /* -Y */
+ totalsize = -from;
+ conn->maxdownload = -from;
+ conn->resume_from = from;
+ infof(data, "FTP RANGE the last %d bytes\n", totalsize);
+ }
+ else {
+ /* X-Y */
+ totalsize = to-from;
+ conn->maxdownload = totalsize+1; /* include the last mentioned byte */
+ conn->resume_from = from;
+ infof(data, "FTP RANGE from %d getting %d bytes\n", from,
+ conn->maxdownload);
+ }
+ infof(data, "range-download from %d to %d, totally %d bytes\n",
+ from, to, totalsize);
+ }
+
+ if((data->set.ftp_list_only) || !ftp->file) {
+ /* The specified path ends with a slash, and therefore we think this
+ is a directory that is requested, use LIST. But before that we
+ need to set ASCII transfer mode. */
+ dirlist = TRUE;
+
+ /* Set type to ASCII */
+ result = ftp_transfertype(conn, TRUE /* ASCII enforced */);
+ if(result)
+ return result;
+
+ /* if this output is to be machine-parsed, the NLST command will be
+ better used since the LIST command output is not specified or
+ standard in any way */
+
+ FTPSENDF(conn, "%s",
+ data->set.customrequest?data->set.customrequest:
+ (data->set.ftp_list_only?"NLST":"LIST"));
+ }
+ else {
+ ssize_t foundsize;
+
+ /* Set type to binary (unless specified ASCII) */
+ result = ftp_transfertype(conn, data->set.ftp_ascii);
+ if(result)
+ return result;
+
+ /* Send any PREQUOTE strings after transfer type is set? (Wesley Laxton)*/
+ if(data->set.prequote) {
+ if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK)
+ return result;
+ }
+
+ /* Attempt to get the size, it'll be useful in some cases: for resumed
+ downloads and when talking to servers that don't give away the size
+ in the RETR response line. */
+ result = ftp_getsize(conn, ftp->file, &foundsize);
+ if(CURLE_OK == result)
+ downloadsize = foundsize;
+
+ if(conn->resume_from) {
+
+ /* Daniel: (August 4, 1999)
+ *
+ * We start with trying to use the SIZE command to figure out the size
+ * of the file we're gonna get. If we can get the size, this is by far
+ * the best way to know if we're trying to resume beyond the EOF.
+ *
+ * Daniel, November 28, 2001. We *always* get the size on downloads
+ * now, so it is done before this even when not doing resumes. I saved
+ * the comment above for nostalgical reasons! ;-)
+ */
+ if(CURLE_OK != result) {
+ infof(data, "ftp server doesn't support SIZE\n");
+ /* We couldn't get the size and therefore we can't know if there
+ really is a part of the file left to get, although the server
+ will just close the connection when we start the connection so it
+ won't cause us any harm, just not make us exit as nicely. */
+ }
+ else {
+ /* We got a file size report, so we check that there actually is a
+ part of the file left to get, or else we go home. */
+ if(conn->resume_from< 0) {
+ /* We're supposed to download the last abs(from) bytes */
+ if(foundsize < -conn->resume_from) {
+ failf(data, "Offset (%d) was beyond file size (%d)",
+ conn->resume_from, foundsize);
+ return CURLE_FTP_BAD_DOWNLOAD_RESUME;
+ }
+ /* convert to size to download */
+ downloadsize = -conn->resume_from;
+ /* download from where? */
+ conn->resume_from = foundsize - downloadsize;
+ }
+ else {
+ if(foundsize < conn->resume_from) {
+ failf(data, "Offset (%d) was beyond file size (%d)",
+ conn->resume_from, foundsize);
+ return CURLE_FTP_BAD_DOWNLOAD_RESUME;
+ }
+ /* Now store the number of bytes we are expected to download */
+ downloadsize = foundsize-conn->resume_from;
+ }
+ }
+
+ if (downloadsize == 0) {
+ /* no data to transfer */
+ result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+ infof(data, "File already completely downloaded\n");
+
+ /* Set resume done so that we won't get any error in Curl_ftp_done()
+ * because we didn't transfer the amount of bytes that the remote
+ * file obviously is */
+ conn->bits.resume_done = TRUE;
+
+ return CURLE_OK;
+ }
+
+ /* Set resume file transfer offset */
+ infof(data, "Instructs server to resume from offset %d\n",
+ conn->resume_from);
+
+ FTPSENDF(conn, "REST %d", conn->resume_from);
+
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ if(ftpcode != 350) {
+ failf(data, "Couldn't use REST: %s", buf+4);
+ return CURLE_FTP_COULDNT_USE_REST;
+ }
+ }
+
+ FTPSENDF(conn, "RETR %s", ftp->file);
+ }
+
+ nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
+ if(nread < 0)
+ return CURLE_OPERATION_TIMEOUTED;
+
+ 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. */
+
+ int size=-1; /* default unknown size */
+
+ if(!dirlist &&
+ !data->set.ftp_ascii &&
+ (-1 == downloadsize)) {
+ /*
+ * 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--) {
+ int index=bytes-buf;
+ /* this is a hint there is size information in there! ;-) */
+ while(--index) {
+ /* scan for the parenthesis and break there */
+ if('(' == *bytes)
+ break;
+ /* if only skip digits, or else we're in deep trouble */
+ if(!isdigit((int)*bytes)) {
+ bytes=NULL;
+ break;
+ }
+ /* one more estep backwards */
+ bytes--;
+ }
+ /* only if we have nothing but digits: */
+ if(bytes++) {
+ /* get the number! */
+ size = atoi(bytes);
+ }
+
+ }
+ }
+ else if(downloadsize > -1)
+ size = downloadsize;
+
+ if(data->set.ftp_use_port) {
+ result = AllowServerConnect(data, conn, conn->secondarysocket);
+ if( result )
+ return result;
+ }
+
+ infof(data, "Getting file with size: %d\n", size);
+
+ /* FTP download: */
+ result=Curl_Transfer(conn, conn->secondarysocket, size, FALSE,
+ bytecountp,
+ -1, NULL); /* no upload here */
+ if(result)
+ return result;
+ }
+ else {
+ failf(data, "%s", buf+4);
+ return CURLE_FTP_COULDNT_RETR_FILE;
+ }
+
+ }
+ /* end of transfer */
+
+ return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * 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)
+{
+ CURLcode retcode;
+
+ struct SessionHandle *data = conn->data;
+ struct FTP *ftp;
+ int dirlength=0; /* 0 forces strlen() */
+
+ /* the ftp struct is already inited in ftp_connect() */
+ ftp = conn->proto.ftp;
+
+ /* We split the path into dir and file parts *before* we URLdecode
+ it */
+ ftp->file = strrchr(conn->ppath, '/');
+ if(ftp->file) {
+ if(ftp->file != conn->ppath)
+ dirlength=ftp->file-conn->ppath; /* don't count the traling slash */
+
+ ftp->file++; /* point to the first letter in the file name part or
+ remain NULL */
+ }
+ else {
+ ftp->file = conn->ppath; /* there's only a file part */
+ }
+
+ if(*ftp->file) {
+ ftp->file = curl_unescape(ftp->file, 0);
+ if(NULL == ftp->file) {
+ failf(data, "no memory");
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+ else
+ ftp->file=NULL; /* instead of point to a zero byte, we make it a NULL
+ pointer */
+
+ ftp->urlpath = conn->ppath;
+ if(dirlength) {
+ ftp->dir = curl_unescape(ftp->urlpath, dirlength);
+ if(NULL == ftp->dir) {
+ if(ftp->file)
+ free(ftp->file);
+ failf(data, "no memory");
+ return CURLE_OUT_OF_MEMORY; /* failure */
+ }
+ }
+ else
+ ftp->dir = NULL;
+
+ retcode = ftp_perform(conn);
+
+ /* clean up here, success or error doesn't matter */
+ if(ftp->file)
+ free(ftp->file);
+ if(ftp->dir)
+ free(ftp->dir);
+
+ ftp->file = ftp->dir = NULL; /* zero */
+
+ return retcode;
+}
+
+/***********************************************************************
+ *
+ * Curl_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!
+ */
+CURLcode Curl_ftpsendf(struct connectdata *conn,
+ const char *fmt, ...)
+{
+ ssize_t bytes_written;
+ char s[256];
+ ssize_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);
+
+ if(conn->data->set.verbose)
+ fprintf(conn->data->set.err, "> %s\n", s);
+
+ strcat(s, "\r\n"); /* append a trailing CRLF */
+
+ bytes_written=0;
+ write_len = strlen(s);
+
+ do {
+ res = Curl_write(conn, conn->firstsocket, sptr, write_len,
+ &bytes_written);
+
+ if(CURLE_OK != res)
+ break;
+
+ if(bytes_written != write_len) {
+ write_len -= bytes_written;
+ sptr += bytes_written;
+ }
+ else
+ break;
+ } while(1);
+
+ return res;
+}
+
+/***********************************************************************
+ *
+ * Curl_ftp_disconnect()
+ *
+ * Disconnect from an FTP server. Cleanup protocol-specific per-connection
+ * resources
+ */
+CURLcode Curl_ftp_disconnect(struct connectdata *conn)
+{
+ struct FTP *ftp= conn->proto.ftp;
+
+ /* The FTP session may or may not have been allocated/setup at this point! */
+ if(ftp) {
+ if(ftp->entrypath)
+ free(ftp->entrypath);
+ if(ftp->cache)
+ free(ftp->cache);
+ }
+ return CURLE_OK;
+}
+
+/*
+ * local variables:
+ * eval: (load-file "../curl-mode.el")
+ * end:
+ * vim600: fdm=marker
+ * vim: et sw=2 ts=2 sts=2 tw=78
+ */