Skip to content
ftp.c 113 KiB
Newer Older
        ftp->downloadsize = filesize-data->reqdata.resume_from;
    if(ftp->downloadsize == 0) {
      /* no data to transfer */
      result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
      infof(data, "File already completely downloaded\n");
      /* Set no_transfer so that we won't get any error in Curl_ftp_done()
       * because we didn't transfer the any file */
      ftp->no_transfer = TRUE;
      state(conn, FTP_STOP);
      return CURLE_OK;
    /* Set resume file transfer offset */
    infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T
    NBFTPSENDF(conn, "REST %" FORMAT_OFF_T, data->reqdata.resume_from);
  else {
    /* no resume */
    NBFTPSENDF(conn, "RETR %s", ftp->file);
    state(conn, FTP_RETR);
  }
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 */
            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;
      if(data->set.ftp_use_ccc) {
        /* CCC - Clear Command Channel
         */
        NBFTPSENDF(conn, "CCC", NULL);
        state(conn, FTP_CCC);
      }
      else {
        result = ftp_state_pwd(conn);
        if(result)
          return result;
      }
      break;

    case FTP_CCC:
      if (ftpcode < 500) {
	/* First shut down the SSL layer (note: this call will block) */
	result = Curl_ssl_shutdown(conn, FIRSTSOCKET);

	if(result) {
	  failf(conn->data, "Failed to clear the command channel (CCC)");
	  return result;
	}
      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 - /* 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 - /* 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 */
      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 = 3600000; /* 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, bool premature)
  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;