Skip to content
Snippets Groups Projects
ftp.c 55.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Daniel Stenberg's avatar
    Daniel Stenberg committed
    /*****************************************************************************
     *                                  _   _ ____  _     
     *  Project                     ___| | | |  _ \| |    
     *                             / __| | | | |_) | |    
     *                            | (__| |_| |  _ <| |___ 
     *                             \___|\___/|_| \_\_____|
     *
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     * Copyright (C) 2000, 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"
    
    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
    
    /* easy-to-use macro: */
    #define ftpsendf Curl_ftpsendf
    
    
    static CURLcode AllowServerConnect(struct UrlData *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;
    
      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;
    
    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 */
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          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 --- */
    
    #define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \
    			isdigit((int)line[2]) && (' ' == line[3]))
    
    
    
    int Curl_GetFTPResponse(int sockfd,
                            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 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 UrlData *data = conn->data;
      char *line_start;
      int code;
    
    #define SELECT_OK      0
    #define SELECT_ERROR   1
    #define SELECT_TIMEOUT 2
      int error = SELECT_OK;
    
      if(ftpcode)
        *ftpcode=0; /* 0 for errors */
    
      if(data->timeout) {
        /* if timeout is requested, find out how much remaining time we have */
        timeout = data->timeout - /* timeout time */
          (Curl_tvlong(Curl_tvnow()) - Curl_tvlong(conn->now)); /* 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->bits.verbose) {
                  fputs("< ", data->err);
                  fwrite(line_start, 1, perline, data->err);
                  /* no need to output LF here, it is part of the data */
                }
    
                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 *moo;
                  int i;
                  for(moo=line_start, i=0; moo<ptr; moo++, i++)
                    buf[i] = *moo;
                  moo[i]=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:
        sec_read_msg(conn, buf, prot_safe);
        break;
      case 632:
        sec_read_msg(conn, buf, prot_private);
        break;
      case 633:
        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 */
    }
    
    #if 0
    
    /*
     * We allow the ftpcode pointer to be NULL if no reply integer is wanted
     */
    
    int Curl_GetFTPResponse(int sockfd, char *buf,
                            struct connectdata *conn,
                            int *ftpcode)
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    {
      int nread;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      char *ptr;
    
      int timeout = 3600; /* in seconds */
      struct timeval interval;
      fd_set rkeepfd;
      fd_set readfd;
      struct UrlData *data = conn->data;
    
    #define SELECT_OK      0
    #define SELECT_ERROR   1
    #define SELECT_TIMEOUT 2
      int error = SELECT_OK;
    
    
      if(ftpcode)
        *ftpcode=0; /* 0 for errors */
    
      if(data->timeout) {
        /* if timeout is requested, find out how much remaining time we have */
        timeout = data->timeout - /* timeout time */
    
          (Curl_tvlong(Curl_tvnow()) - Curl_tvlong(conn->now)); /* 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;
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      do {
        ptr=buf;
    
        /* get us a full line, terminated with a newline */
    
        nread=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, 1, &keepon))
              keepon = FALSE;
    
            else if(keepon <= 0) {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
              error = SELECT_ERROR;
    
              failf(data, "Connection aborted");
            }
    
            else if ((*ptr == '\n') || (*ptr == '\r'))
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        }
        *ptr=0; /* zero terminate */
    
    
    #if KRB4
        { /* handle the security-oriented responses 6xx ***/
          /* FIXME: some errorchecking perhaps... ***/
          if(strncmp(buf, "631", 3) == 0)
            sec_read_msg(conn, buf, prot_safe);
          else if(strncmp(buf, "632", 3) == 0)
            sec_read_msg(conn, buf, prot_private);
          else if(strncmp(buf, "633", 3) == 0)
            sec_read_msg(conn, buf, prot_confidential);
          nread = strlen(buf);
        }
    #endif
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          fputs("< ", data->err);
          fwrite(buf, 1, nread, data->err);
          fputs("\n", data->err);
        }
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    	  (nread<4 || !lastline(buf)) );
    
      if(ftpcode)
        *ftpcode=atoi(buf); /* return the initial number like this */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      return nread;
    }
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    /* -- who are we? -- */
    
    char *Curl_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 UrlData *data=conn->data;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      char *buf = data->buffer; /* this is our buffer */
    
      struct FTP *ftp;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
      myalarm(0); /* switch off the alarm stuff */
    
    
      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;
    
    
      /* duplicate to keep them even when the data struct changes */
      ftp->user = strdup(data->user);
      ftp->passwd = strdup(data->passwd);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
      if (data->bits.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(conn->firstsocket, 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 */
      if(data->bits.krb4) {
    
        /* request data protection level (default is 'clear') */
        sec_request_prot(conn, "private");
    
        /* We set private first as default, in case the line below fails to
           set a valid level */
        sec_request_prot(conn, data->krb4_level);
    
        if(sec_login(conn) != 0)
          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 */
    
      ftpsendf(conn->firstsocket, conn, "USER %s", ftp->user);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
      /* wait for feedback */
    
      nread = Curl_GetFTPResponse(conn->firstsocket, 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->firstsocket, conn, "PASS %s", ftp->passwd);
        nread = Curl_GetFTPResponse(conn->firstsocket, 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)
          sec_set_protection_level(conn);
    
        /* we may need to issue a KAUTH here to have access to the files
         * do it if user supplied a password
         */
        if(conn->data->passwd && *conn->data->passwd)
          krb_kauth(conn);
    #endif
    
    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 */
      ftpsendf(conn->firstsocket, conn, "PWD");
    
      /* wait for feedback */
      nread = Curl_GetFTPResponse(conn->firstsocket, 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;
    }
    
    
    /* argument is already checked for validity */
    
    CURLcode Curl_ftp_done(struct connectdata *conn)
    
    {
      struct UrlData *data = conn->data;
    
      struct FTP *ftp = conn->proto.ftp;
    
      size_t nread;
      char *buf = data->buffer; /* this is our buffer */
      struct curl_slist *qitem; /* QUOTE item */
    
    
      if(data->bits.upload) {
        if((-1 != data->infilesize) && (data->infilesize != *ftp->bytecountp)) {
          failf(data, "Wrote only partial file (%d out of %d bytes)",
                *ftp->bytecountp, data->infilesize);
          return CURLE_PARTIAL_FILE;
        }
      }
      else {
        if((-1 != conn->size) && (conn->size != *ftp->bytecountp) &&
    
           (conn->maxdownload != *ftp->bytecountp)) {
    
          failf(data, "Received only partial file");
          return CURLE_PARTIAL_FILE;
        }
    
        else if(!conn->bits.resume_done &&
                !data->bits.no_body &&
                (0 == *ftp->bytecountp)) {
    
          failf(data, "No data was received!");
          return CURLE_FTP_COULDNT_RETR_FILE;
        }
      }
    
      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->bits.no_body && !conn->bits.resume_done) {  
    
        /* now let's see what the server says about the transfer we
           just performed: */
    
        nread = Curl_GetFTPResponse(conn->firstsocket, 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, "%s", buf+4);
          return CURLE_FTP_WRITE_ERROR;
        }
    
      conn->bits.resume_done = FALSE; /* clean this for next connection */
    
    
      /* Send any post-transfer QUOTE strings? */
      if(data->postquote) {
        qitem = data->postquote;
        /* Send all QUOTE strings in same order as on command-line */
        while (qitem) {
          /* Send string */
          if (qitem->data) {
    
            ftpsendf(conn->firstsocket, conn, "%s", qitem->data);
    
            nread = Curl_GetFTPResponse(conn->firstsocket, buf, conn, &ftpcode);
    
            if(nread < 0)
              return CURLE_OPERATION_TIMEOUTED;
    
              failf(data, "QUOT string not accepted: %s",
                    qitem->data);
              return CURLE_FTP_QUOTE_ERROR;
            }
          }
          qitem = qitem->next;
        }
    
      return CURLE_OK;
    }
    
    
    
    static
    CURLcode _ftp(struct connectdata *conn)
    {
      /* this is FTP and no proxy */
      size_t nread;
      CURLcode result;
      struct UrlData *data=conn->data;
      char *buf = data->buffer; /* this is our buffer */
      /* for the ftp PORT mode */
      int portsock=-1;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #if defined (HAVE_INET_NTOA_R)
      char ntoa_buf[64];
    #endif
    
    #ifdef ENABLE_IPV6
      struct addrinfo *ai;
    
    #else
      struct sockaddr_in serv_addr;
      char hostent_buf[8192];
    
    
      struct curl_slist *qitem; /* QUOTE item */
      /* the ftp struct is already inited in ftp_connect() */
    
      struct FTP *ftp = conn->proto.ftp;
    
    
      long *bytecountp = ftp->bytecountp;
    
      int ftpcode; /* for ftp status */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      /* Send any QUOTE strings? */
      if(data->quote) {
        qitem = data->quote;
        /* Send all QUOTE strings in same order as on command-line */
        while (qitem) {
          /* Send string */
          if (qitem->data) {
    
            ftpsendf(conn->firstsocket, conn, "%s", qitem->data);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
            nread = Curl_GetFTPResponse(conn->firstsocket, buf, conn, &ftpcode);
    
            if(nread < 0)
              return CURLE_OPERATION_TIMEOUTED;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
              failf(data, "QUOT string not accepted: %s",
                    qitem->data);
    
              return CURLE_FTP_QUOTE_ERROR;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
            }
          }
          qitem = qitem->next;
        }
      }
    
    
      if(conn->bits.reuse) {
        /* 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: */
        ftpsendf(conn->firstsocket, conn, "CWD %s", ftp->entrypath);
        nread = Curl_GetFTPResponse(conn->firstsocket, buf, conn, &ftpcode);
        if(nread < 0)
          return CURLE_OPERATION_TIMEOUTED;
        
        if(ftpcode != 250) {
          failf(data, "Couldn't change back to directory %s", ftp->entrypath);
          return CURLE_FTP_ACCESS_DENIED;
        }
      }
    
    
    
    
      /* change directory first! */
      if(ftp->dir && ftp->dir[0]) {
    
        ftpsendf(conn->firstsocket, conn, "CWD %s", ftp->dir);
        nread = Curl_GetFTPResponse(conn->firstsocket, buf, conn, &ftpcode);
    
        if(nread < 0)
          return CURLE_OPERATION_TIMEOUTED;
    
    
          failf(data, "Couldn't change to directory %s", ftp->dir);
          return CURLE_FTP_ACCESS_DENIED;
        }
      }
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      if(data->bits.get_filetime && ftp->file) {
    
        /* we have requested to get the modified-time of the file, this is yet
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
           again a grey area as the MDTM is not kosher RFC959 */
    
        ftpsendf(conn->firstsocket, conn, "MDTM %s", ftp->file);
    
        nread = Curl_GetFTPResponse(conn->firstsocket, 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: */
            data->progress.filetime = curl_getdate(buf, &secs);
          }
          else {
            infof(data, "unsupported MDTM reply format\n");
          }
        }
    
      }
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      /* If we have selected NOBODY, it means that we only want file information.
         Which in FTP can't be much more than the file size! */
    
      if(data->bits.no_body) {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        /* 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! */
        int filesize;
    
    
        /* Some servers return different sizes for different modes, and thus we
           must set the proper type before we check the size */
    
        ftpsendf(conn->firstsocket, conn, "TYPE %s",
    
        nread = Curl_GetFTPResponse(conn->firstsocket, buf, conn, &ftpcode);
    
        if(nread < 0)
          return CURLE_OPERATION_TIMEOUTED;
    
    
          failf(data, "Couldn't set %s mode",
                (data->bits.ftp_ascii)?"ASCII":"binary");
          return (data->bits.ftp_ascii)? CURLE_FTP_COULDNT_SET_ASCII:
            CURLE_FTP_COULDNT_SET_BINARY;
        }
    
    
        ftpsendf(conn->firstsocket, conn, "SIZE %s", ftp->file);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
        nread = Curl_GetFTPResponse(conn->firstsocket, buf, conn, &ftpcode);
    
        if(nread < 0)
          return CURLE_OPERATION_TIMEOUTED;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
          /* get the size from the ascii string: */
          filesize = atoi(buf+4);
    
          sprintf(buf, "Content-Length: %d\r\n", filesize);
          result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
          if(result)
            return result;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    #ifdef HAVE_STRFTIME
    
          if(data->bits.get_filetime && data->progress.filetime) {
            struct tm *tm;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #ifdef HAVE_LOCALTIME_R
    
            struct tm buffer;
            tm = (struct tm *)localtime_r(&data->progress.filetime, &buffer);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #else
    
            tm = localtime(&data->progress.filetime);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #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;
          }
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #endif
    
        return CURLE_OK;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      }
    
      /* We have chosen to use the PORT command */
    
      if(data->bits.ftp_use_port) {
    
    #ifdef ENABLE_IPV6
        struct addrinfo hints, *res, *ai;
        struct sockaddr_storage ss;
    
        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];
    
        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, "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) {
    
    	portsock = -1;
    	continue;
          }
    
          if (listen(portsock, 1) < 0) {
    
    	portsock = -1;
    	continue;
          }
    
          break;
        }
        if (portsock < 0) {
          failf(data, strerror(errno));
          freeaddrinfo(res);
          return CURLE_FTP_PORT_FAILED;
        }
    
        sslen = sizeof(ss);
    
        if (getsockname(portsock, sa, &sslen) < 0) {
    
          failf(data, strerror(errno));
          freeaddrinfo(res);
          return CURLE_FTP_PORT_FAILED;
        }
    
        for (modep = mode; modep && *modep; modep++) {
          int lprtaf, eprtaf;
    
    
    	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, '%');