Skip to content
Snippets Groups Projects
ftp.c 111 KiB
Newer Older
  • Learn to ignore specific revisions
  • Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    static CURLcode ftp_state_size_resp(struct connectdata *conn,
                                        int ftpcode,
                                        ftpstate instate)
    
      CURLcode result = CURLE_OK;
      struct SessionHandle *data=conn->data;
      curl_off_t filesize;
      char *buf = data->state.buffer;
    
      /* get the size from the ascii string: */
      filesize = (ftpcode == 213)?curlx_strtoofft(buf+4, NULL, 0):-1;
    
      if(instate == FTP_SIZE) {
        if(-1 != filesize) {
          snprintf(buf, sizeof(data->state.buffer),
                   "Content-Length: %" FORMAT_OFF_T "\r\n", filesize);
    
          result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
    
        result = ftp_state_post_size(conn);
      }
      else if(instate == FTP_RETR_SIZE)
        result = ftp_state_post_retr_size(conn, filesize);
      else if(instate == FTP_STOR_SIZE) {
    
        data->reqdata.resume_from = filesize;
    
        result = ftp_state_ul_setup(conn, TRUE);
    
    static CURLcode ftp_state_rest_resp(struct connectdata *conn,
                                        int ftpcode,
                                        ftpstate instate)
    
      struct FTP *ftp = conn->data->reqdata.proto.ftp;
    
      switch(instate) {
      case FTP_REST:
      default:
        if (ftpcode == 350) {
    
          result = Curl_client_write(conn, CLIENTWRITE_BOTH,
    
                                   (char *)"Accept-ranges: bytes\r\n", 0);
          if(result)
            return result;
        }
    
        result = ftp_state_post_rest(conn);
        break;
    
      case FTP_RETR_REST:
        if (ftpcode != 350) {
          failf(conn->data, "Couldn't use REST");
          result = CURLE_FTP_COULDNT_USE_REST;
        }
        else {
          NBFTPSENDF(conn, "RETR %s", ftp->file);
          state(conn, FTP_RETR);
    
    static CURLcode ftp_state_stor_resp(struct connectdata *conn,
                                        int ftpcode)
    
      struct SessionHandle *data = conn->data;
    
      struct FTP *ftp = data->reqdata.proto.ftp;
    
      if(ftpcode>=400) {
        failf(data, "Failed FTP upload: %0d", ftpcode);
        /* oops, we never close the sockets! */
        return CURLE_FTP_COULDNT_STOR_FILE;
      }
    
      if(data->set.ftp_use_port) {
        /* BLOCKING */
        /* PORT means we are now awaiting the server to connect to us. */
        result = AllowServerConnect(conn);
        if( result )
          return result;
      }
    
      if(conn->ssl[SECONDARYSOCKET].use) {
        /* since we only have a plaintext TCP connection here, we must now
           do the TLS stuff */
        infof(data, "Doing the SSL/TLS handshake on the data stream\n");
        /* BLOCKING */
        result = Curl_ssl_connect(conn, SECONDARYSOCKET);
        if(result)
          return result;
      }
    
    
      /* 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_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */
                                   SECONDARYSOCKET, ftp->bytecountp);
    
      state(conn, FTP_STOP);
    
      return result;
    }
    
    /* for LIST and RETR responses */
    static CURLcode ftp_state_get_resp(struct connectdata *conn,
                                        int ftpcode,
                                        ftpstate instate)
    
      struct SessionHandle *data = conn->data;
    
      struct FTP *ftp = data->reqdata.proto.ftp;
    
      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. */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
        curl_off_t size=-1; /* default unknown size */
    
        /*
         * It appears that there are FTP-servers that return size 0 for files when
         * SIZE is used on the file while being in BINARY mode. To work around
         * that (stupid) behavior, we attempt to parse the RETR response even if
         * the SIZE returned size zero.
         *
         * Debugging help from Salvatore Sorrentino on February 26, 2003.
         */
    
           (ftp->downloadsize < 1)) {
          /*
           * 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--) {
    
            /* this is a hint there is size information in there! ;-) */
            while(--in) {
              /* scan for the left parenthesis and break there */
              if('(' == *bytes)
                break;
              /* skip only digits */
              if(!isdigit((int)*bytes)) {
                bytes=NULL;
                break;
              }
              /* one more estep backwards */
              bytes--;
            }
            /* if we have nothing but digits: */
            if(bytes++) {
              /* get the number! */
              size = curlx_strtoofft(bytes, NULL, 0);
            }
          }
        }
        else if(ftp->downloadsize > -1)
          size = ftp->downloadsize;
    
        if(data->set.ftp_use_port) {
          /* BLOCKING */
          result = AllowServerConnect(conn);
          if( result )
            return result;
    
        if(conn->ssl[SECONDARYSOCKET].use) {
          /* since we only have a plaintext TCP connection here, we must now
             do the TLS stuff */
          infof(data, "Doing the SSL/TLS handshake on the data stream\n");
          result = Curl_ssl_connect(conn, SECONDARYSOCKET);
          if(result)
            return result;
        }
    
    
        if(size > data->reqdata.maxdownload && data->reqdata.maxdownload > 0)
          size = data->reqdata.size = data->reqdata.maxdownload;
    
        infof(data, "Maxdownload = %" FORMAT_OFF_T "\n", data->reqdata.maxdownload);
    
        if(instate != FTP_LIST)
          infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
    
        result=Curl_setup_transfer(conn, SECONDARYSOCKET, size, FALSE,
                                   ftp->bytecountp,
                                   -1, NULL); /* no upload here */
    
        if(result)
          return result;
    
        state(conn, FTP_STOP);
      }
      else {
        if((instate == FTP_LIST) && (ftpcode == 450)) {
          /* simply no matching files in the dir listing */
          ftp->no_transfer = TRUE; /* don't download anything */
          state(conn, FTP_STOP); /* this phase is over */
    
          failf(data, "RETR response: %03d", ftpcode);
    
      return result;
    }
    
    /* after USER, PASS and ACCT */
    static CURLcode ftp_state_loggedin(struct connectdata *conn)
    {
      CURLcode result = CURLE_OK;
    
    #ifdef HAVE_KRB4
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      if(conn->data->set.krb4) {
    
        /* We are logged in, asked to use Kerberos. Set the requested
         * protection level
         */
        if(conn->sec_complete)
          /* BLOCKING */
          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(conn->passwd && *conn->passwd) {
          /* BLOCKING */
          result = Curl_krb_kauth(conn);
          if(result)
            return result;
        }
    
      }
    #endif
      if(conn->ssl[FIRSTSOCKET].use) {
        /* PBSZ = PROTECTION BUFFER SIZE.
    
        The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says:
    
        Specifically, the PROT command MUST be preceded by a PBSZ
        command and a PBSZ command MUST be preceded by a successful
        security data exchange (the TLS negotiation in this case)
    
        ... (and on page 8):
    
        Thus the PBSZ command must still be issued, but must have a
        parameter of '0' to indicate that no buffering is taking place
        and the data connection should not be encapsulated.
        */
        NBFTPSENDF(conn, "PBSZ %d", 0);
        state(conn, FTP_PBSZ);
      }
      else {
        result = ftp_state_pwd(conn);
      }
      return result;
    }
    
    /* for USER and PASS responses */
    static CURLcode ftp_state_user_resp(struct connectdata *conn,
                                        int ftpcode,
                                        ftpstate instate)
    {
      CURLcode result = CURLE_OK;
      struct SessionHandle *data = conn->data;
    
      struct FTP *ftp = data->reqdata.proto.ftp;
      struct ftp_conn *ftpc = &conn->proto.ftpc;
    
      if((ftpcode == 331) && (ftpc->state == FTP_USER)) {
    
        /* 331 Password required for ...
           (the server requires to send the user's password too) */
        NBFTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:"");
        state(conn, FTP_PASS);
      }
      else if(ftpcode/100 == 2) {
        /* 230 User ... logged in.
           (the user logged in with or without password) */
        result = ftp_state_loggedin(conn);
      }
      else if(ftpcode == 332) {
        if(data->set.ftp_account) {
          NBFTPSENDF(conn, "ACCT %s", data->set.ftp_account);
          state(conn, FTP_ACCT);
        }
        else {
    
          failf(data, "ACCT requested but none available");
    
        530 User ... access denied
        (the server denies to log the specified user) */
    
    
        if (conn->data->set.ftp_alternative_to_user &&
            !conn->data->state.ftp_trying_alternative) {
          /* Ok, USER failed.  Let's try the supplied command. */
          NBFTPSENDF(conn, "%s", conn->data->set.ftp_alternative_to_user);
          conn->data->state.ftp_trying_alternative = TRUE;
          state(conn, FTP_USER);
          result = CURLE_OK;
        }
        else {
          failf(data, "Access denied: %03d", ftpcode);
          result = CURLE_LOGIN_DENIED;
        }
    
    /* for ACCT response */
    static CURLcode ftp_state_acct_resp(struct connectdata *conn,
                                        int ftpcode)
    {
      CURLcode result = CURLE_OK;
      struct SessionHandle *data = conn->data;
      if(ftpcode != 230) {
        failf(data, "ACCT rejected by server: %03d", ftpcode);
        result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */
    
    static CURLcode ftp_statemach_act(struct connectdata *conn)
    {
      CURLcode result;
      curl_socket_t sock = conn->sock[FIRSTSOCKET];
      struct SessionHandle *data=conn->data;
      int ftpcode;
    
      struct ftp_conn *ftpc = &conn->proto.ftpc;
    
      static const char * const ftpauth[]  = {
        "SSL", "TLS"
      };
    
        /* we have a piece of a command still left to send */
        ssize_t written;
    
        result = Curl_write(conn, sock, ftpc->sendthis + ftpc->sendsize -
                            ftpc->sendleft, ftpc->sendleft, &written);
    
        if(written != (ssize_t)ftpc->sendleft) {
    
          free(ftpc->sendthis);
          ftpc->sendthis=NULL;
          ftpc->sendleft = ftpc->sendsize = 0;
          ftpc->response = Curl_tvnow();
    
      /* we read a piece of response */
      result = ftp_readresp(sock, conn, &ftpcode, &nread);
      if(result)
        return result;
    
      if(ftpcode) {
        /* we have now received a full FTP server response */
    
        case FTP_WAIT220:
          if(ftpcode != 220) {
            failf(data, "This doesn't seem like a nice ftp-server response");
            return CURLE_FTP_WEIRD_SERVER_REPLY;
    
          /* We have received a 220 response fine, now we proceed. */
    #ifdef HAVE_KRB4
          if(data->set.krb4) {
            /* If not anonymous login, try a secure login. Note that this
               procedure is still BLOCKING. */
    
            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");
    
              infof(data, "Authentication successful\n");
          }
    #endif
    
          if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
            /* We don't have a SSL/TLS connection yet, but FTPS is
               requested. Try a FTPS connection now */
    
    
            switch(data->set.ftpsslauth) {
            case CURLFTPAUTH_DEFAULT:
            case CURLFTPAUTH_SSL:
    
              ftpc->count2 = 1; /* add one to get next */
              ftpc->count1 = 0;
    
              ftpc->count2 = -1; /* subtract one to get next */
              ftpc->count1 = 1;
    
              break;
            default:
              failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d\n",
                    data->set.ftpsslauth);
              return CURLE_FAILED_INIT; /* we don't know what to do */
    
            NBFTPSENDF(conn, "AUTH %s", ftpauth[ftpc->count1]);
    
            result = ftp_state_user(conn);
    
        case FTP_AUTH:
          /* we have gotten the response to a previous AUTH command */
    
          /* RFC2228 (page 5) says:
           *
           * If the server is willing to accept the named security mechanism,
           * and does not require any security data, it must respond with
           * reply code 234/334.
           */
    
          if((ftpcode == 234) || (ftpcode == 334)) {
    
            /* Curl_ssl_connect is BLOCKING */
            result = Curl_ssl_connect(conn, FIRSTSOCKET);
    
            if(CURLE_OK == result) {
              conn->protocol |= PROT_FTPS;
              conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */
              result = ftp_state_user(conn);
            }
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          }
    
          else if(ftpc->count3 < 1) {
            ftpc->count3++;
            ftpc->count1 += ftpc->count2; /* get next attempt */
            result = Curl_nbftpsendf(conn, "AUTH %s", ftpauth[ftpc->count1]);
    
          else {
            if(data->set.ftp_ssl > CURLFTPSSL_TRY)
              /* we failed and CURLFTPSSL_CONTROL or CURLFTPSSL_ALL is set */
              result = CURLE_FTP_SSL_FAILED;
            else
              /* ignore the failure and continue */
              result = ftp_state_user(conn);
          }
    
          result = ftp_state_user_resp(conn, ftpcode, ftpc->state);
    
        case FTP_ACCT:
          result = ftp_state_acct_resp(conn, ftpcode);
          break;
    
        case FTP_PBSZ:
          /* FIX: check response code */
    
          /* For TLS, the data connection can have one of two security levels.
    
          2)Private (requested by 'PROT P')
          */
          if(!conn->ssl[SECONDARYSOCKET].use) {
    
            NBFTPSENDF(conn, "PROT %c",
                       data->set.ftp_ssl == CURLFTPSSL_CONTROL ? 'C' : 'P');
    
            state(conn, FTP_PROT);
          }
          else {
            result = ftp_state_pwd(conn);
            if(result)
              return result;
          }
    
        case FTP_PROT:
          if(ftpcode/100 == 2)
            /* We have enabled SSL for the data connection! */
    
    Yang Tse's avatar
    Yang Tse committed
              (bool)(data->set.ftp_ssl != CURLFTPSSL_CONTROL);
    
          /* FTP servers typically responds with 500 if they decide to reject
             our 'P' request */
          else if(data->set.ftp_ssl> CURLFTPSSL_CONTROL)
            /* we failed and bails out */
            return CURLE_FTP_SSL_FAILED;
    
          result = ftp_state_pwd(conn);
          if(result)
            return result;
          break;
    
        case FTP_PWD:
          if(ftpcode == 257) {
            char *dir = (char *)malloc(nread+1);
            char *store=dir;
            char *ptr=&data->state.buffer[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++;
              }
    
              ftpc->entrypath =dir; /* remember this */
              infof(data, "Entry path is '%s'\n", ftpc->entrypath);
    
              /* also save it where getinfo can access it: */
    
              data->state.most_recent_ftp_entrypath = ftpc->entrypath;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
            }
    
            else {
              /* couldn't get the path */
              free(dir);
              infof(data, "Failed to figure out path\n");
    
          state(conn, FTP_STOP); /* we are done with the CONNECT phase! */
    
          DEBUGF(infof(data, "protocol connect phase DONE\n"));
    
          break;
    
        case FTP_QUOTE:
        case FTP_POSTQUOTE:
        case FTP_RETR_PREQUOTE:
        case FTP_STOR_PREQUOTE:
          if(ftpcode >= 400) {
            failf(conn->data, "QUOT command failed with %03d", ftpcode);
            return CURLE_FTP_QUOTE_ERROR;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          }
    
          result = ftp_state_quote(conn, FALSE, ftpc->state);
    
        case FTP_CWD:
          if(ftpcode/100 != 2) {
            /* failure to CWD there */
            if(conn->data->set.ftp_create_missing_dirs &&
    
              ftpc->count2++; /* counter to prevent CWD-MKD loops */
              NBFTPSENDF(conn, "MKD %s", ftpc->dirs[ftpc->count1 - 1]);
    
              failf(data, "Server denied you to change to the given directory");
    
              ftpc->cwdfail = TRUE; /* don't remember this path as we failed
                                       to enter it */
    
            ftpc->count2=0;
            if(++ftpc->count1 <= ftpc->dirdepth) {
    
              NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
    
            }
            else {
              result = ftp_state_post_cwd(conn);
              if(result)
                return result;
            }
          }
          break;
    
        case FTP_MKD:
          if(ftpcode/100 != 2) {
            /* failure to MKD the dir */
            failf(data, "Failed to MKD dir: %03d", ftpcode);
            return CURLE_FTP_ACCESS_DENIED;
          }
          state(conn, FTP_CWD);
          /* send CWD */
    
          NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
    
        case FTP_MDTM:
          result = ftp_state_mdtm_resp(conn, ftpcode);
          break;
    
        case FTP_TYPE:
        case FTP_LIST_TYPE:
        case FTP_RETR_TYPE:
        case FTP_STOR_TYPE:
    
          result = ftp_state_type_resp(conn, ftpcode, ftpc->state);
    
        case FTP_SIZE:
        case FTP_RETR_SIZE:
        case FTP_STOR_SIZE:
    
          result = ftp_state_size_resp(conn, ftpcode, ftpc->state);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
          result = ftp_state_rest_resp(conn, ftpcode, ftpc->state);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
        case FTP_PASV:
          result = ftp_state_pasv_resp(conn, ftpcode);
          break;
    
        case FTP_PORT:
          result = ftp_state_port_resp(conn, ftpcode);
          break;
    
          result = ftp_state_get_resp(conn, ftpcode, ftpc->state);
    
        case FTP_STOR:
          result = ftp_state_stor_resp(conn, ftpcode);
          break;
    
        case FTP_QUIT:
          /* fallthrough, just stop! */
        default:
          /* internal error */
          state(conn, FTP_STOP);
          break;
        }
      } /* if(ftpcode) */
    
    /* Returns timeout in ms. 0 or negative number means the timeout has already
       triggered */
    static long ftp_state_timeout(struct connectdata *conn)
    {
      struct SessionHandle *data=conn->data;
    
      struct ftp_conn *ftpc = &conn->proto.ftpc;
    
      long timeout_ms=360000; /* in milliseconds */
    
      if(data->set.ftp_response_timeout )
        /* if CURLOPT_FTP_RESPONSE_TIMEOUT is set, use that to determine remaining
           time.  Also, use ftp->response because FTP_RESPONSE_TIMEOUT is supposed
           to govern the response for any given ftp response, not for the time
           from connect to the given ftp response. */
        timeout_ms = data->set.ftp_response_timeout*1000 - /* timeout time */
    
          Curl_tvdiff(Curl_tvnow(), ftpc->response); /* spent time */
    
      else if(data->set.timeout)
        /* if timeout is requested, find out how much remaining time we have */
        timeout_ms = data->set.timeout*1000 - /* timeout time */
          Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
      else
        /* Without a requested timeout, we only wait 'response_time' seconds for
           the full response to arrive before we bail out */
    
        timeout_ms = ftpc->response_time*1000 -
          Curl_tvdiff(Curl_tvnow(), ftpc->response); /* spent time */
    
    /* called repeatedly until done from multi.c */
    CURLcode Curl_ftp_multi_statemach(struct connectdata *conn,
                                      bool *done)
    {
      curl_socket_t sock = conn->sock[FIRSTSOCKET];
      int rc;
      struct SessionHandle *data=conn->data;
    
      struct ftp_conn *ftpc = &conn->proto.ftpc;
    
      CURLcode result = CURLE_OK;
      long timeout_ms = ftp_state_timeout(conn);
    
      *done = FALSE; /* default to not done yet */
    
      if(timeout_ms <= 0) {
        failf(data, "FTP response timeout");
        return CURLE_OPERATION_TIMEDOUT;
      }
    
      rc = Curl_select(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
                       ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
    
      if(rc == -1) {
        failf(data, "select error");
        return CURLE_OUT_OF_MEMORY;
    
      else if(rc != 0) {
        result = ftp_statemach_act(conn);
    
    Yang Tse's avatar
    Yang Tse committed
        *done = (bool)(ftpc->state == FTP_STOP);
    
      /* if rc == 0, then select() timed out */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
    static CURLcode ftp_easy_statemach(struct connectdata *conn)
    {
      curl_socket_t sock = conn->sock[FIRSTSOCKET];
      int rc;
      struct SessionHandle *data=conn->data;
    
      struct ftp_conn *ftpc = &conn->proto.ftpc;
    
        long timeout_ms = ftp_state_timeout(conn);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
        if(timeout_ms <=0 ) {
          failf(data, "FTP response timeout");
          return CURLE_OPERATION_TIMEDOUT; /* already too little time */
        }
    
        rc = Curl_select(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
                         ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
    
                         (int)timeout_ms);
    
        if(rc == -1) {
          failf(data, "select error");
          return CURLE_OUT_OF_MEMORY;
    
        else if(rc == 0) {
          result = CURLE_OPERATION_TIMEDOUT;
          break;
        }
        else {
          result = ftp_statemach_act(conn);
          if(result)
            break;
    
    /*
     * Allocate and initialize the struct FTP for the current SessionHandle.  If
     * need be.
     */
    static CURLcode ftp_init(struct connectdata *conn)
    {
      struct SessionHandle *data = conn->data;
      struct FTP *ftp;
      if(data->reqdata.proto.ftp)
        return CURLE_OK;
    
      ftp = (struct FTP *)calloc(sizeof(struct FTP), 1);
      if(!ftp)
        return CURLE_OUT_OF_MEMORY;
    
      data->reqdata.proto.ftp = ftp;
    
      /* get some initial data into the ftp struct */
      ftp->bytecountp = &data->reqdata.keep.bytecount;
    
      /* no need to duplicate them, this connectdata struct won't change */
      ftp->user = conn->user;
      ftp->passwd = conn->passwd;
      if (isBadFtpString(ftp->user) || isBadFtpString(ftp->passwd))
        return CURLE_URL_MALFORMAT;
    
      return CURLE_OK;
    }
    
    
    /*
     * Curl_ftp_connect() should do everything that is to be considered a part of
     * the connection phase.
     *
     * The variable 'done' points to will be TRUE if the protocol-layer connect
     * phase is done when this function returns, or FALSE is not. When called as
     * a part of the easy interface, it will always be TRUE.
     */
    CURLcode Curl_ftp_connect(struct connectdata *conn,
                              bool *done) /* see description above */
    {
      CURLcode result;
    
    #ifndef CURL_DISABLE_HTTP
      /* for FTP over HTTP proxy */
      struct HTTP http_proxy;
      struct FTP *ftp_save;
    #endif   /* CURL_DISABLE_HTTP */
    
      struct ftp_conn *ftpc = &conn->proto.ftpc;
      struct SessionHandle *data=conn->data;
    
      *done = FALSE; /* default to not done yet */
    
      if (data->reqdata.proto.ftp) {
        Curl_ftp_disconnect(conn);
        free(data->reqdata.proto.ftp);
        data->reqdata.proto.ftp = NULL;
      }
    
      result = ftp_init(conn);
      if(result)
        return result;
    
      /* We always support persistant connections on ftp */
      conn->bits.close = FALSE;
    
      ftpc->response_time = 3600; /* set default response time-out */
    
      if (conn->bits.tunnel_proxy && conn->bits.httpproxy) {
    
        /* We want "seamless" FTP operations through HTTP proxy tunnel */
    
    
        /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member
         * conn->proto.http; we want FTP through HTTP and we have to change the
         * member temporarily for connecting to the HTTP proxy. After
         * Curl_proxyCONNECT we have to set back the member to the original struct
         * FTP pointer
         */
    
        memset(&http_proxy, 0, sizeof(http_proxy));
    
        data->reqdata.proto.http = &http_proxy;
    
        result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
                                   conn->host.name, conn->remote_port);
    
        if(CURLE_OK != result)
          return result;
    
      if(conn->protocol & PROT_FTPS) {
        /* BLOCKING */
        /* FTPS is simply ftp with SSL for the control channel */
        /* now, perform the SSL initialization for this socket */
    
        result = Curl_ssl_connect(conn, FIRSTSOCKET);
    
        if(result)
          return result;
      }
    
      /* When we connect, we start in the state where we await the 220
         response */
    
      ftp_respinit(conn); /* init the response reader stuff */
    
      ftpc->response = Curl_tvnow(); /* start response time-out now! */
    
      if(data->state.used_interface == Curl_if_multi)
    
        result = Curl_ftp_multi_statemach(conn, done);
      else {
        result = ftp_easy_statemach(conn);
        if(!result)
          *done = TRUE;
      }
    
      return result;
    
    /***********************************************************************
     *
     * 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, CURLcode status)
    
      struct SessionHandle *data = conn->data;
    
      struct FTP *ftp = data->reqdata.proto.ftp;
      struct ftp_conn *ftpc = &conn->proto.ftpc;
    
      ssize_t nread;
      int ftpcode;
      CURLcode result=CURLE_OK;
    
      bool was_ctl_valid = ftpc->ctl_valid;
    
      char *path_to_use = data->reqdata.path;
      struct Curl_transfer_keeper *k = &data->reqdata.keep;
    
    
      if(!ftp)
        /* When the easy handle is removed from the multi while libcurl is still
         * trying to resolve the host name, it seems that the ftp struct is not
         * yet initialized, but the removal action calls Curl_done() which calls
         * this function. So we simply return success if no ftp pointer is set.
         */
        return CURLE_OK;
    
    
      /* now store a copy of the directory we are in */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
      path = curl_easy_unescape(data, path_to_use, 0, NULL);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
      flen = ftp->file?strlen(ftp->file):0; /* file is "raw" already */
      dlen = strlen(path)-flen;
    
      if(dlen && !ftpc->cwdfail) {
        ftpc->prevpath = path;
    
        if(flen)
          /* if 'path' is not the whole string */
    
          ftpc->prevpath[dlen]=0; /* terminate */
        infof(data, "Remembering we are in dir %s\n", ftpc->prevpath);
    
        free(path);
      }
      /* free the dir tree and file parts */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
      switch(status) {
      case CURLE_BAD_DOWNLOAD_RESUME:
      case CURLE_FTP_WEIRD_PASV_REPLY:
      case CURLE_FTP_PORT_FAILED:
      case CURLE_FTP_COULDNT_SET_BINARY:
      case CURLE_FTP_COULDNT_RETR_FILE:
    
      case CURLE_FTP_COULDNT_STOR_FILE:
    
      case CURLE_FTP_ACCESS_DENIED:
        /* the connection stays alive fine even though this happened */
        /* fall-through */
      case CURLE_OK: /* doesn't affect the control connection's status */
    
        break;
      default:       /* by default, an error means the control connection is
                        wedged and should not be used anymore */