diff options
author | Andy Cedilnik <andy.cedilnik@kitware.com> | 2003-01-07 02:13:39 (GMT) |
---|---|---|
committer | Andy Cedilnik <andy.cedilnik@kitware.com> | 2003-01-07 02:13:39 (GMT) |
commit | baeba76200afb19cf8ea4e67025c1916ac210b13 (patch) | |
tree | f9fb8746b534b90c89e1d54e7a16a0b14873f894 /Source/CTest/Curl/ftp.c | |
parent | 0f14e027b5263a78b2bd1d41a319f16dacaa16af (diff) | |
download | CMake-baeba76200afb19cf8ea4e67025c1916ac210b13.zip CMake-baeba76200afb19cf8ea4e67025c1916ac210b13.tar.gz CMake-baeba76200afb19cf8ea4e67025c1916ac210b13.tar.bz2 |
Initial import
Diffstat (limited to 'Source/CTest/Curl/ftp.c')
-rw-r--r-- | Source/CTest/Curl/ftp.c | 2138 |
1 files changed, 2138 insertions, 0 deletions
diff --git a/Source/CTest/Curl/ftp.c b/Source/CTest/Curl/ftp.c new file mode 100644 index 0000000..127971b --- /dev/null +++ b/Source/CTest/Curl/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$ + *****************************************************************************/ + +#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 + */ |