Skip to content
Snippets Groups Projects
ftp.c 57.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Daniel Stenberg's avatar
    Daniel Stenberg committed
    /*****************************************************************************
     *                                  _   _ ____  _     
     *  Project                     ___| | | |  _ \| |    
     *                             / __| | | | |_) | |    
     *                            | (__| |_| |  _ <| |___ 
     *                             \___|\___/|_| \_\_____|
     *
    
     * Copyright (C) 2001, Daniel Stenberg, <daniel@haxx.se>, et al.
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     *
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     * In order to be useful for every potential user, curl and libcurl are
     * dual-licensed under the MPL and the MIT/X-derivate licenses.
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     *
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     * 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.
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     *
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     * KIND, either express or implied.
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     *
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     * $Id$
     *****************************************************************************/
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    #include "setup.h"
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #include <stdarg.h>
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #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 */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #ifdef HAVE_SYS_SOCKET_H
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #include <sys/socket.h>
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #endif
    #include <sys/types.h>
    
    #ifdef HAVE_NETINET_IN_H
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #include <netinet/in.h>
    
    #endif
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #ifdef HAVE_ARPA_INET_H
    #include <arpa/inet.h>
    #endif
    #include <sys/utsname.h>
    
    #ifdef HAVE_NETDB_H
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #include <netdb.h>
    #endif
    
    #endif
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    #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"
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #include "escape.h"
    
    #include "http.h" /* for HTTP proxy tunnel stuff */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    #ifdef KRB4
    #include "security.h"
    
    #include "strequal.h"
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #include "ssluse.h"
    
    #include "connect.h"
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #define _MPRINTF_REPLACE /* use our functions only */
    #include <curl/mprintf.h>
    
    
    /* The last #include file should be: */
    #ifdef MALLOCDEBUG
    #include "memdebug.h"
    #endif
    
    Sterling Hughes's avatar
    Sterling Hughes committed
    /* Local API functions */
    
    static CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote);
    static CURLcode ftp_cwd(struct connectdata *conn, char *path);
    
    #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,
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
                                       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;
    
    
    Sterling Hughes's avatar
    Sterling Hughes committed
      switch (select(sock+1, &rdset, NULL, NULL, &dt)) {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      case -1: /* error */
        /* let's die here */
        failf(data, "Error while waiting for server connect");
    
        return CURLE_FTP_PORT_FAILED;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      case 0:  /* timeout */
        /* let's die here */
        failf(data, "Timeout while waiting for server connect");
    
        return CURLE_FTP_PORT_FAILED;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      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);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
          sclose(sock); /* close the first socket */
    
    
          if (-1 == s) {
            /* DIE! */
            failf(data, "Error accept()ing server connect");
          	return CURLE_FTP_PORT_FAILED;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          }
          infof(data, "Connection accepted from server\n");
    
    
          conn->secondarysocket = s;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        }
        break;
      }
    
      return CURLE_OK;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    }
    
    
    /* --- 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.
     */
    
                            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 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;
    
      int code=0; /* default "error code" to return */
    
    
    #define SELECT_OK      0
    #define SELECT_ERROR   1
    #define SELECT_TIMEOUT 2
      int error = SELECT_OK;
    
    
      if (ftpcode)
        *ftpcode = 0; /* 0 for errors */
    
        /* 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;
    
        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:
          /*
           * 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(CURLE_OK != Curl_read(conn, sockfd, ptr, BUFSIZE-nread, &gotbytes))
            keepon = FALSE;
          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 */
    
                /* 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 */
                }
    
    
    #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)! */
    
    Sterling Hughes's avatar
    Sterling Hughes committed
                  char *meow;
    
    Sterling Hughes's avatar
    Sterling Hughes committed
                  for(meow=line_start, i=0; meow<ptr; meow++, i++)
                    buf[i] = *meow;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
                  *meow=0; /* zero terminate */
    
                  keepon=FALSE;
                  break;
                }
                perline=0; /* line starts over here */
                line_start = ptr+1;
              }
            }
          }
          break;
        } /* switch */
      } /* while there's buffer left and loop is requested */
    
      if(!error)
        code = atoi(buf);
    
    #if KRB4
      /* handle the security-oriented responses 6xx ***/
      /* FIXME: some errorchecking perhaps... ***/
      switch(code) {
      case 631:
    
        Curl_sec_read_msg(conn, buf, prot_safe);
    
        Curl_sec_read_msg(conn, buf, prot_private);
    
        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.
     */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    /* -- who are we? -- */
    
    static char *getmyhost(char *buf, int buf_size)
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #if defined(HAVE_GETHOSTNAME)
      gethostname(buf, buf_size);
    #elif defined(HAVE_UNAME)
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      struct utsname ugnm;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      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! */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      strncpy(buf, "localhost", buf_size);
      buf[buf_size - 1] = '\0';
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #endif
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      return buf;
    
    /* ftp_connect() should do everything that is to be considered a part
       of the connection phase. */
    
    CURLcode Curl_ftp_connect(struct connectdata *conn)
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    {
      /* this is FTP and no proxy */
    
      struct SessionHandle *data=conn->data;
      char *buf = data->state.buffer; /* this is our buffer */
    
      struct FTP *ftp;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
      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;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
      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;
      }
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      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;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      /* The first thing we do is wait for the "220*" line: */
    
      nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
    
      if(nread < 0)
        return CURLE_OPERATION_TIMEOUTED;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        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 */
    
    
        /* 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);
    
          infof(data, "Logging in with password in cleartext!\n");
        else
          infof(data, "Authentication successful\n");
      }
    #endif
      
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      /* send USER */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
      /* wait for feedback */
    
      nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
    
      if(nread < 0)
        return CURLE_OPERATION_TIMEOUTED;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        /* 530 User ... access denied
           (the server denies to log the specified user) */
        failf(data, "Access denied: %s", &buf[4]);
    
        return CURLE_FTP_ACCESS_DENIED;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      }
    
      else if(ftpcode == 331) {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        /* 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;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          /* 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;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        }
    
        else if(ftpcode == 230) {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          /* 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;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        }
      }
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        /* 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
         */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        if(data->state.passwd && *data->state.passwd)
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      }
      else {
        failf(data, "Odd return code after USER");
    
        return CURLE_FTP_WEIRD_USER_REPLY;
      }
    
    
      /* send PWD to discover our entry point */
    
    
      /* 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! */
      }
    
    
    /***********************************************************************
     *
     * 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;
    
      char *buf = data->state.buffer; /* this is our buffer */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      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;
        }
    
          /* 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!");
    
      Curl_sec_fflush_fd(conn, conn->secondarysocket);
    
      /* 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);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    /***********************************************************************
     *
     * ftp_sendquote()
     *
     * Where a 'quote' means a list of custom commands to send to the server.
     * The quote list is passed as an argument.
     */
    
    Sterling Hughes's avatar
    Sterling Hughes committed
    static 
    
    CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote)
    
    {
      struct curl_slist *item;
    
    
      item = quote;
      while (item) {
        if (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;
          }
        }
    
    
    /***********************************************************************
     *
     * ftp_cwd()
     *
     * Send 'CWD' to the remote server to Change Working Directory.
     * It is the ftp version of the unix 'cd' command.
     */
    
    Sterling Hughes's avatar
    Sterling Hughes committed
    static 
    
    CURLcode ftp_cwd(struct connectdata *conn, char *path)
    
    Sterling Hughes's avatar
    Sterling Hughes committed
    {
      ssize_t nread;
      int     ftpcode;
    
                                  conn->data->state.buffer, conn, &ftpcode);
    
    Sterling Hughes's avatar
    Sterling Hughes committed
      if (nread < 0)
        return CURLE_OPERATION_TIMEOUTED;
    
      if (ftpcode != 250) {
    
        failf(conn->data, "Couldn't cd to %s", path);
    
    Sterling Hughes's avatar
    Sterling Hughes committed
        return CURLE_FTP_ACCESS_DENIED;
      }
    
    Sterling Hughes's avatar
    Sterling Hughes committed
    
      return CURLE_OK;
    
    /***********************************************************************
     *
     * ftp_getfiletime()
     *
     * Get the timestamp of the given file.
     */
    
    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 */
    
      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,
    
      struct SessionHandle *data = conn->data;
    
      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.
     */
    
    
    CURLcode ftp_getsize(struct connectdata *conn, char *file,
    
      struct SessionHandle *data = conn->data;
    
      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;
    
    
      char ntoa_buf[64];
    #endif
      char hostent_buf[8192];
    
    #if defined(HAVE_INET_ADDR)
    
    # if defined(HAVE_GETHOSTBYADDR_R)
      int h_errnop;
    # endif
    
      address = inet_addr(newhost);
    # ifdef HAVE_GETHOSTBYADDR_R
    
    #  ifdef HAVE_GETHOSTBYADDR_R_5
      /* AIX, Digital Unix 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! */
    
      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 *)hostent_buf,
                               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
      /*****************************************************************
       *
       * 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.
    
    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 */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    #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;
    
      const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
    
      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;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
      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;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      freeaddrinfo(res);
    
      if (portsock < 0) {
        failf(data, strerror(errno));
        return CURLE_FTP_PORT_FAILED;
      }
    
      sslen = sizeof(ss);
      if (getsockname(portsock, sa, &sslen) < 0) {