Skip to content
ftp.c 125 KiB
Newer Older
      infof(data, "File already completely downloaded\n");
      /* Set ->transfer so that we won't get any error in ftp_done()
       * because we didn't transfer the any file */
      ftp->transfer = FTPTRANSFER_NONE;
    /* Set resume file transfer offset */
    infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T
          "\n", data->state.resume_from);
    PPSENDF(&ftpc->pp, "REST %" FORMAT_OFF_T, data->state.resume_from);
    PPSENDF(&ftpc->pp, "RETR %s", ftpc->file);
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;
#ifdef CURL_FTP_HTTPSTYLE_HEAD
    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);
    Curl_pgrsSetDownloadSize(data, filesize);
  else if(instate == FTP_RETR_SIZE) {
    Curl_pgrsSetDownloadSize(data, filesize);
    result = ftp_state_post_retr_size(conn, filesize);
    result = ftp_state_ul_setup(conn, TRUE);
static CURLcode ftp_state_rest_resp(struct connectdata *conn,
                                    int ftpcode,
                                    ftpstate instate)
  struct ftp_conn *ftpc = &conn->proto.ftpc;
#ifdef CURL_FTP_HTTPSTYLE_HEAD
      char buffer[24]= { "Accept-ranges: bytes\r\n" };
      result = Curl_client_write(conn, CLIENTWRITE_BOTH, buffer, 0);
    result = ftp_state_post_rest(conn);
    break;
      failf(conn->data, "Couldn't use REST");
      result = CURLE_FTP_COULDNT_USE_REST;
    }
    else {
      PPSENDF(&ftpc->pp, "RETR %s", ftpc->file);
static CURLcode ftp_state_stor_resp(struct connectdata *conn,
                                    int ftpcode)
  struct SessionHandle *data = conn->data;
  if(ftpcode>=400) {
    failf(data, "Failed FTP upload: %0d", ftpcode);
    /* oops, we never close the sockets! */
  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);

  /* set the SO_SNDBUF for the secondary socket for those who need it */
  Curl_sndbufset(conn->sock[SECONDARYSOCKET]);

  Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */
                      SECONDARYSOCKET, ftp->bytecountp);
  conn->proto.ftpc.pp.pending_resp = TRUE; /* expect a server response */
  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;
  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->req.maxdownload && data->req.maxdownload > 0)
      size = data->req.size = data->req.maxdownload;
    else if((instate != FTP_LIST) && (data->set.prefer_ascii))
      size = -1; /* kludge for servers that understate ASCII mode file size */
    infof(data, "Maxdownload = %" FORMAT_OFF_T "\n", data->req.maxdownload);
      infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
    Curl_setup_transfer(conn, SECONDARYSOCKET, size, FALSE,
                        ftp->bytecountp, -1, NULL); /* no upload here */
    conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */
    state(conn, FTP_STOP);
  }
  else {
    if((instate == FTP_LIST) && (ftpcode == 450)) {
      /* simply no matching files in the dir listing */
      ftp->transfer = FTPTRANSFER_NONE; /* don't download anything */
      state(conn, FTP_STOP); /* this phase is over */
      failf(data, "RETR response: %03d", ftpcode);
      return instate == FTP_RETR && ftpcode == 550?
        CURLE_REMOTE_FILE_NOT_FOUND:
        CURLE_FTP_COULDNT_RETR_FILE;
  return result;
}

/* after USER, PASS and ACCT */
static CURLcode ftp_state_loggedin(struct connectdata *conn)
{
  CURLcode result = CURLE_OK;

#ifdef HAVE_KRB4
    /* 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.
    */
    PPSENDF(&conn->proto.ftpc.pp, "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_conn *ftpc = &conn->proto.ftpc;
  /* some need password anyway, and others just return 2xx ignored */
  if((ftpcode == 331) && (ftpc->state == FTP_USER)) {
    /* 331 Password required for ...
       (the server requires to send the user's password too) */
    PPSENDF(&ftpc->pp, "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.str[STRING_FTP_ACCOUNT]) {
      PPSENDF(&ftpc->pp, "ACCT %s", data->set.str[STRING_FTP_ACCOUNT]);
      failf(data, "ACCT requested but none available");
    530 User ... access denied
    (the server denies to log the specified user) */
    if(conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] &&
        !conn->data->state.ftp_trying_alternative) {
      /* Ok, USER failed.  Let's try the supplied command. */
      PPSENDF(&conn->proto.ftpc.pp, "%s",
              conn->data->set.str[STRING_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 ftpauth[][4]  = { "SSL", "TLS" };
  if(pp->sendleft)
    return Curl_pp_flushsend(pp);
  result = ftp_readresp(sock, pp, &ftpcode, &nread);
  if(ftpcode) {
    /* we have now received a full FTP server response */
        failf(data, "Got a %03d ftp-server response when 220 was expected",
              ftpcode);
      /* We have received a 220 response fine, now we proceed. */
#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
      if(data->set.krb) {
        /* 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.str[STRING_KRB_LEVEL]);
        if(Curl_sec_login(conn) != CURLE_OK)
          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;
          failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
Yang Tse's avatar
 
Yang Tse committed
                (int)data->set.ftpsslauth);
          return CURLE_FAILED_INIT; /* we don't know what to do */
        PPSENDF(&ftpc->pp, "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->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_pp_sendf(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]);
        if(data->set.ftp_ssl > CURLUSESSL_TRY)
          /* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
          result = CURLE_USE_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;
      PPSENDF(&ftpc->pp, "PROT %c",
              data->set.ftp_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
    case FTP_PROT:
      if(ftpcode/100 == 2)
        /* We have enabled SSL for the data connection! */
          (bool)(data->set.ftp_ssl != CURLUSESSL_CONTROL);
      /* FTP servers typically responds with 500 if they decide to reject
         our 'P' request */
      else if(data->set.ftp_ssl > CURLUSESSL_CONTROL)
        state(conn, FTP_CCC);
      }
      else {
        result = ftp_state_pwd(conn);
        if(result)
          return result;
      }
      break;

    case FTP_CCC:
        /* 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 *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++;
            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;

          /* If the path name does not look like an absolute path (i.e.: it
             does not start with a '/'), we probably need some server-dependent
             adjustments. For example, this is the case when connecting to
             an OS400 FTP server: this server supports two name syntaxes,
             the default one being incompatible with standard pathes. In
             addition, this server switches automatically to the regular path
             syntax when one is encountered in a command: this results in
             having an entrypath in the wrong syntax when later used in CWD.
               The method used here is to check the server OS: we do it only
             if the path name looks strange to minimize overhead on other
             systems. */

          if(!ftpc->server_os && ftpc->entrypath[0] != '/') {
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"));
    case FTP_SYST:
      if(ftpcode == 215) {
        char *ptr=&data->state.buffer[4];  /* start on the first letter */
        char *os;
        char *store;

        os = malloc(nread + 1);
        if(!os)
          return CURLE_OUT_OF_MEMORY;

        /* Reply format is like
           215<space><OS-name><space><commentary>
        */
        while (*ptr == ' ')
          ptr++;
        for (store = os; *ptr && *ptr != ' ';)
          *store++ = *ptr++;
        *store = '\0'; /* zero terminate */
        ftpc->server_os = os;

        /* Check for special servers here. */

        if(strequal(ftpc->server_os, "OS/400")) {
          /* Force OS400 name format 1. */
          PPSENDF(&ftpc->pp, "SITE NAMEFMT 1", NULL);
          state(conn, FTP_NAMEFMT);
          break;
        }
      else {
        /* Nothing special for the target server. */
       }
      }
      else {
        /* Cannot identify server OS. Continue anyway and cross fingers. */
      }

      state(conn, FTP_STOP); /* we are done with the CONNECT phase! */
      DEBUGF(infof(data, "protocol connect phase DONE\n"));
      break;

    case FTP_NAMEFMT:
      if(ftpcode == 250) {
        /* Name format change successful: reload initial path. */
        ftp_state_pwd(conn);
        break;
      }

      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) && !ftpc->count2) {
        /* failure reponse code, and not allowed to fail */
        failf(conn->data, "QUOT command failed with %03d", ftpcode);
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 */
          PPSENDF(&ftpc->pp, "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) {
          PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
        }
        else {
          result = ftp_state_post_cwd(conn);
          if(result)
            return result;
        }
      }
      break;
      if((ftpcode/100 != 2) && !ftpc->count3--) {
        /* failure to MKD the dir */
        failf(data, "Failed to MKD dir: %03d", ftpcode);
      PPSENDF(&ftpc->pp, "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_PRET:
      if(ftpcode != 200) {
       /* there only is this one standard OK return code. */
        failf(data, "PRET command not accepted: %03d", ftpcode);
        return CURLE_FTP_PRET_FAILED;
      }
      result = ftp_state_use_pasv(conn);
      break;

    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) */
/* called repeatedly until done from multi.c */
static CURLcode ftp_multi_statemach(struct connectdata *conn,
  struct ftp_conn *ftpc = &conn->proto.ftpc;
  CURLcode result = Curl_pp_multi_statemach(&ftpc->pp);
Daniel Stenberg's avatar
Daniel Stenberg committed

  /* Check for the state outside of the Curl_socket_ready() return code checks
     since at times we are in fact already in this state when this function
     gets called. */
  *done = (bool)(ftpc->state == FTP_STOP);

static CURLcode ftp_easy_statemach(struct connectdata *conn)
{
  struct ftp_conn *ftpc = &conn->proto.ftpc;
/*
 * Allocate and initialize the struct FTP for the current SessionHandle.  If
 * need be.
 */
Yang Tse's avatar
 
Yang Tse committed

#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \
    defined(__OPTIMIZE__) && defined(__unix__) && defined(__i386__)
  /* workaround icc 9.1 optimizer issue */
Yang Tse's avatar
 
Yang Tse committed
#pragma optimize("", off)
Yang Tse's avatar
 
Yang Tse committed
#endif

static CURLcode ftp_init(struct connectdata *conn)
{
Yang Tse's avatar
 
Yang Tse committed
  struct FTP *ftp;

  if(NULL == conn->data->state.proto.ftp) {
    conn->data->state.proto.ftp = malloc(sizeof(struct FTP));
    if(NULL == conn->data->state.proto.ftp)
  ftp = conn->data->state.proto.ftp;

  /* get some initial data into the ftp struct */
  ftp->bytecountp = &conn->data->req.bytecount;
  ftp->transfer = FTPTRANSFER_BODY;
  ftp->downloadsize = 0;
  /* No need to duplicate user+password, the connectdata struct won't change
     during a session, but we re-init them here since on subsequent inits
     since the conn struct may have changed or been replaced.
  */
  ftp->user = conn->user;
  ftp->passwd = conn->passwd;
  if(TRUE == isBadFtpString(ftp->user))
    return CURLE_URL_MALFORMAT;
  if(TRUE == isBadFtpString(ftp->passwd))
  conn->proto.ftpc.known_filesize = -1; /* unknown size for now */

Yang Tse's avatar
 
Yang Tse committed
#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \
    defined(__OPTIMIZE__) && defined(__unix__) && defined(__i386__)
  /* workaround icc 9.1 optimizer issue */
#pragma optimize("", on)
#endif

 * 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.
 */
static CURLcode ftp_connect(struct connectdata *conn,
                                 bool *done) /* see description above */
  struct ftp_conn *ftpc = &conn->proto.ftpc;
  struct SessionHandle *data=conn->data;
  *done = FALSE; /* default to not done yet */
  /* If there already is a protocol-specific struct allocated for this
     sessionhandle, deal with it */
  Curl_reset_reqproto(conn);
  if(CURLE_OK != result)
  /* We always support persistant connections on ftp */
  conn->bits.close = FALSE;
  pp->response_time = RESP_TIMEOUT; /* set default response time-out */
  pp->statemach_act = ftp_statemach_act;
  pp->endofresp = ftp_endofresp;
  pp->conn = conn;
  if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
    /* for FTP over HTTP proxy */
    struct HTTP http_proxy;
    struct FTP *ftp_save;

    /* 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));
    result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
                               conn->host.name, conn->remote_port);
    if(CURLE_OK != result)
      return result;
  if(conn->handler->protocol & CURLPROTO_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);
  Curl_pp_init(pp); /* init the generic pingpong data */

  /* When we connect, we start in the state where we await the 220
     response */
  state(conn, FTP_WAIT220);

  if(data->state.used_interface == Curl_if_multi)
    result = ftp_multi_statemach(conn, done);
  else {
    result = ftp_easy_statemach(conn);
    if(!result)
      *done = TRUE;
  }

  return result;
/***********************************************************************
 *
 *
 * The DONE function. This does what needs to be done after a single DO has
 * performed.
 * Input argument is already checked for validity.
static CURLcode ftp_done(struct connectdata *conn, CURLcode status,
  struct SessionHandle *data = conn->data;
  struct ftp_conn *ftpc = &conn->proto.ftpc;
  ssize_t nread;
  int ftpcode;
  CURLcode result=CURLE_OK;
  bool was_ctl_valid = ftpc->ctl_valid;
  const char *path_to_use = data->state.path;