Skip to content
ftp.c 109 KiB
Newer Older
  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);
  /* 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_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 = conn->proto.ftp;
  char *buf = data->state.buffer;
  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.
     */
    if((instate != FTP_LIST) &&
       !data->set.ftp_ascii &&
       (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(size > conn->maxdownload && conn->maxdownload > 0)
      size = conn->size = conn->maxdownload;
    if(instate != FTP_LIST)
      infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
    /* FTP download: */
    result=Curl_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 */
    else {
      failf(data, "%s", buf+4);
      return CURLE_FTP_COULDNT_RETR_FILE;
  return result;
}

/* after USER, PASS and ACCT */
static CURLcode ftp_state_loggedin(struct connectdata *conn)
{
  CURLcode result = CURLE_OK;
  struct SessionHandle *data = conn->data;
  infof(data, "We have successfully logged in\n");

#ifdef HAVE_KRB4
  if(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 = conn->proto.ftp;
  (void)instate; /* no use for this yet */

  if((ftpcode == 331) && (ftp->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) */
    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 *ftp = conn->proto.ftp;
  static const char * const ftpauth[]  = {
    "SSL", "TLS"
  };
  size_t nread;
  if(ftp->sendleft) {
    /* we have a piece of a command still left to send */
    ssize_t written;
    result = Curl_write(conn, sock, ftp->sendthis + ftp->sendsize -
                        ftp->sendleft, ftp->sendleft, &written);
    if(result)
      return result;
    if(written != (ssize_t)ftp->sendleft) {
      /* only a fraction was sent */
      ftp->sendleft -= written;
    else {
      free(ftp->sendthis);
      ftp->sendthis=NULL;
      ftp->sendleft = ftp->sendsize = 0;
      ftp->response = Curl_tvnow();
    }
    return CURLE_OK;
  }
  /* 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 */
    switch(ftp->state) {
    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 */

        ftp->count3=0;
        switch(data->set.ftpsslauth) {
        case CURLFTPAUTH_DEFAULT:
        case CURLFTPAUTH_SSL:
          ftp->count2 = 1; /* add one to get next */
          ftp->count1 = 0;
          break;
        case CURLFTPAUTH_TLS:
          ftp->count2 = -1; /* subtract one to get next */
          ftp->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[ftp->count1]);
        state(conn, FTP_AUTH);
      else {
        ftp_state_user(conn);
        if(result)
          return result;
    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(ftp->count3 < 1) {
        ftp->count3++;
        ftp->count1 += ftp->count2; /* get next attempt */
        result = Curl_nbftpsendf(conn, "AUTH %s", ftpauth[ftp->count1]);
    case FTP_USER:
    case FTP_PASS:
      result = ftp_state_user_resp(conn, ftpcode, ftp->state);
      break;
    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", '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! */
        conn->ssl[SECONDARYSOCKET].use = TRUE;
      /* 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++;
          }
          ftp->entrypath =dir; /* remember this */
          infof(data, "Entry path is '%s'\n", ftp->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! */
      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, ftp->state);
      if(result)
        return result;
    case FTP_CWD:
      if(ftpcode/100 != 2) {
        /* failure to CWD there */
        if(conn->data->set.ftp_create_missing_dirs &&
           ftp->count1 && !ftp->count2) {
          /* try making it */
          ftp->count2++; /* counter to prevent CWD-MKD loops */
          NBFTPSENDF(conn, "MKD %s", ftp->dirs[ftp->count1 - 1]);
          state(conn, FTP_MKD);
        }
        else
          /* return failure */
          return CURLE_FTP_ACCESS_DENIED;
      }
      else {
        /* success */
        ftp->count2=0;
        if(++ftp->count1 <= ftp->dirdepth) {
          /* send next CWD */
          NBFTPSENDF(conn, "CWD %s", ftp->dirs[ftp->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", ftp->dirs[ftp->count1 - 1]);
      break;
    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, ftp->state);
      break;
    case FTP_SIZE:
    case FTP_RETR_SIZE:
    case FTP_STOR_SIZE:
      result = ftp_state_size_resp(conn, ftpcode, ftp->state);
      break;
Daniel Stenberg's avatar
Daniel Stenberg committed

    case FTP_REST:
    case FTP_RETR_REST:
      result = ftp_state_rest_resp(conn, ftpcode, ftp->state);
      break;
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;
    case FTP_LIST:
    case FTP_RETR:
      result = ftp_state_get_resp(conn, ftpcode, ftp->state);
      break;
    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 *ftp = conn->proto.ftp;
  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(), ftp->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 = ftp->response_time*1000 -
      Curl_tvdiff(Curl_tvnow(), ftp->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 *ftp = conn->proto.ftp;
  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(ftp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
                   ftp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
                   0);
  if(rc == -1) {
    failf(data, "select error");
    return CURLE_OUT_OF_MEMORY;
  else if(rc != 0) {
    result = ftp_statemach_act(conn);
    *done = (ftp->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 *ftp = conn->proto.ftp;
  CURLcode result = CURLE_OK;
  while(ftp->state != FTP_STOP) {
    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(ftp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
                     ftp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
                     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;
/*
 * 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 */
{
  struct FTP *ftp;
  CURLcode result;
  *done = FALSE; /* default to not done yet */
  ftp = (struct FTP *)calloc(sizeof(struct FTP), 1);
  if(!ftp)
    return CURLE_OUT_OF_MEMORY;
  /* We always support persistant connections on ftp */
  conn->bits.close = FALSE;
  /* get some initial data into the ftp struct */
  ftp->bytecountp = &conn->bytecount;
  /* no need to duplicate them, 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;
  ftp->response_time = 3600; /* set default response time-out */
  if (conn->bits.tunnel_proxy) {
    /* BLOCKING */
    /* We want "seamless" FTP operations through HTTP proxy tunnel */
    result = Curl_ConnectHTTPProxyTunnel(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 */
  state(conn, FTP_WAIT220);
  ftp->response = Curl_tvnow(); /* start response time-out now! */

  if(conn->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;
  ssize_t nread;
  int ftpcode;
  CURLcode result=CURLE_OK;
  bool was_ctl_valid = ftp->ctl_valid;
  size_t flen;
  size_t dlen;
  char *path;
  /* now store a copy of the directory we are in */
  if(ftp->prevpath)
    free(ftp->prevpath);
Daniel Stenberg's avatar
Daniel Stenberg committed

  path = curl_unescape(conn->path, 0); /* get the "raw" path */
  if(!path)
    return CURLE_OUT_OF_MEMORY;
Daniel Stenberg's avatar
Daniel Stenberg committed

  flen = ftp->file?strlen(ftp->file):0; /* file is "raw" already */
  dlen = strlen(path)-flen;
  if(dlen) {
    ftp->prevpath = path;
    if(flen)
      /* if 'path' is not the whole string */
      ftp->prevpath[dlen]=0; /* terminate */
    infof(data, "Remembering we are in dir %s\n", ftp->prevpath);
  }
  else {
    ftp->prevpath = NULL; /* no path */
    free(path);
  }
  /* free the dir tree and file parts */
  freedirs(ftp);
Daniel Stenberg's avatar
Daniel Stenberg committed

Daniel Stenberg's avatar
Daniel Stenberg committed

  if(data->set.upload) {
    if((-1 != data->set.infilesize) &&
       (data->set.infilesize != *ftp->bytecountp) &&
Daniel Stenberg's avatar
Daniel Stenberg committed
       !ftp->no_transfer) {
      failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T
            " out of %" FORMAT_OFF_T " bytes)",
            *ftp->bytecountp, data->set.infilesize);
      conn->bits.close = TRUE; /* close this connection since we don't
                                  know what state this error leaves us in */
      return CURLE_PARTIAL_FILE;
    }
  }
  else {
    if((-1 != conn->size) && (conn->size != *ftp->bytecountp) &&
       (conn->maxdownload != *ftp->bytecountp)) {
      failf(data, "Received only partial file: %" FORMAT_OFF_T " bytes",
            *ftp->bytecountp);
      conn->bits.close = TRUE; /* close this connection since we don't
                                  know what state this error leaves us in */
      return CURLE_PARTIAL_FILE;
    }
    else if(!ftp->dont_check &&
            !*ftp->bytecountp &&
            (conn->size>0)) {
      /* We consider this an error, but there's no true FTP error received
         why we need to continue to "read out" the server response too.
         We don't want to leave a "waiting" server reply if we'll get told
         to make a second request on this same connection! */
      failf(data, "No data was received!");
      result = CURLE_FTP_COULDNT_RETR_FILE;
    }
  }
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_ACCESS_DENIED:
    /* the connection stays alive fine even though this happened */
    /* fall-through */
  case CURLE_OK: /* doesn't affect the control connection's status */
    ftp->ctl_valid = was_ctl_valid;
    break;
  default:       /* by default, an error means the control connection is
                    wedged and should not be used anymore */
    ftp->ctl_valid = FALSE;
    break;
  }
Daniel Stenberg's avatar
Daniel Stenberg committed

#ifdef HAVE_KRB4
  Curl_sec_fflush_fd(conn, conn->sock[SECONDARYSOCKET]);
#endif
Daniel Stenberg's avatar
Daniel Stenberg committed

  /* shut down the socket to inform the server we're done */
#ifdef _WIN32_WCE
  shutdown(conn->sock[SECONDARYSOCKET],2);  /* SD_BOTH */
#endif
  conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
Daniel Stenberg's avatar
Daniel Stenberg committed

  if(!ftp->no_transfer && !status) {
    /* Let's see what the server says about the transfer we just performed,
     * but lower the timeout as sometimes this connection has died while the
     * data has been transfered. This happens when doing through NATs etc that
     * abandon old silent connections.
     */
    ftp->response_time = 60; /* give it only a minute for now */
Daniel Stenberg's avatar
Daniel Stenberg committed

    result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
Daniel Stenberg's avatar
Daniel Stenberg committed

    ftp->response_time = 3600; /* set this back to one hour waits */
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) {
      failf(data, "control connection looks dead");
      return result;
Daniel Stenberg's avatar
Daniel Stenberg committed
    if(result)
      return result;
    if(!ftp->dont_check) {
      /* 226 Transfer complete, 250 Requested file action okay, completed. */
      if((ftpcode != 226) && (ftpcode != 250)) {
        failf(data, "server did not report OK, got %d", ftpcode);
        return CURLE_FTP_WRITE_ERROR;
  /* clear these for next connection */
  ftp->no_transfer = FALSE;
  ftp->dont_check = FALSE;
  if (!result && conn->sec_conn) {   /* 3rd party transfer */
    /* "done" with the secondary connection */
    result = Curl_ftp_done(conn->sec_conn, status);
  }
Daniel Stenberg's avatar
Daniel Stenberg committed

  /* Send any post-transfer QUOTE strings? */
  if(!status && !result && data->set.postquote)
    result = ftp_sendquote(conn, data->set.postquote);
Daniel Stenberg's avatar
Daniel Stenberg committed

/***********************************************************************
 *
 * ftp_sendquote()
 *
 * Where a 'quote' means a list of custom commands to send to the server.
 * The quote list is passed as an argument.
 */
static
CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote)
{
  struct curl_slist *item;
  ssize_t nread;
  int ftpcode;
  CURLcode result;
Daniel Stenberg's avatar
Daniel Stenberg committed

  item = quote;
  while (item) {
    if (item->data) {
      FTPSENDF(conn, "%s", item->data);
Daniel Stenberg's avatar
Daniel Stenberg committed

      result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
      if (result)
        return result;
Daniel Stenberg's avatar
Daniel Stenberg committed

      if (ftpcode >= 400) {
        failf(conn->data, "QUOT string not accepted: %s", item->data);
        return CURLE_FTP_QUOTE_ERROR;
Daniel Stenberg's avatar
Daniel Stenberg committed

Daniel Stenberg's avatar
Daniel Stenberg committed

/***********************************************************************
 *
 * ftp_transfertype()
 *
 * Set transfer type. We only deal with ASCII or BINARY so this function
 * sets one of them.
 */
static CURLcode ftp_transfertype(struct connectdata *conn,
                                  bool ascii)
{
  struct SessionHandle *data = conn->data;
  int ftpcode;
  ssize_t nread;
  CURLcode result;
  FTPSENDF(conn, "TYPE %s", ascii?"A":"I");
Daniel Stenberg's avatar
Daniel Stenberg committed

  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
  if(result)
    return result;
Daniel Stenberg's avatar
Daniel Stenberg committed

  if(ftpcode != 200) {
    failf(data, "Couldn't set %s mode",
          ascii?"ASCII":"binary");
    return ascii? CURLE_FTP_COULDNT_SET_ASCII:CURLE_FTP_COULDNT_SET_BINARY;
  }
Daniel Stenberg's avatar
Daniel Stenberg committed

/***************************************************************************
 *
 * ftp_pasv_verbose()