| /***************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) 1998 - 2002, Daniel Stenberg, <[email protected]>, 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(bigbuf) - 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(bigbuf) - 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=CURLE_OK; |
| 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 && ftp->file) { |
| /* 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 |
| */ |