Skip to content
Snippets Groups Projects
ftp.c 124 KiB
Newer Older
  • Learn to ignore specific revisions
  •         /* using the good old yacc/bison yuck */
            snprintf(buf, sizeof(conn->data->state.buffer),
                     "%04d%02d%02d %02d:%02d:%02d GMT",
                     year, month, day, hour, minute, second);
            /* now, convert this into a time() value: */
    
            data->info.filetime = (long)curl_getdate(buf, &secs);
    
    #ifdef CURL_FTP_HTTPSTYLE_HEAD
    
          /* If we asked for a time of the file and we actually got one as well,
             we "emulate" a HTTP-style header in our output. */
    
             ftpc->file &&
    
             data->set.get_filetime &&
             (data->info.filetime>=0) ) {
    
            time_t filetime = (time_t)data->info.filetime;
    
            tm = (struct tm *)gmtime_r(&filetime, &buffer);
    
    #endif
            /* format: "Tue, 15 Nov 1994 12:45:26" */
    
            snprintf(buf, BUFSIZE-1,
                     "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
                     Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
                     tm->tm_mday,
                     Curl_month[tm->tm_mon],
                     tm->tm_year + 1900,
                     tm->tm_hour,
                     tm->tm_min,
                     tm->tm_sec);
    
            result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
    
          } /* end of a ridiculous amount of conditionals */
    
        break;
      default:
        infof(data, "unsupported MDTM reply format\n");
        break;
      case 550: /* "No such file or directory" */
        failf(data, "Given file does not exist");
        result = CURLE_FTP_COULDNT_RETR_FILE;
        break;
    
    
      if(data->set.timecondition) {
        if((data->info.filetime > 0) && (data->set.timevalue > 0)) {
          switch(data->set.timecondition) {
          case CURL_TIMECOND_IFMODSINCE:
          default:
    
            if(data->info.filetime <= data->set.timevalue) {
    
              infof(data, "The requested document is not new enough\n");
    
              ftp->transfer = FTPTRANSFER_NONE; /* mark this to not transfer data */
    
              state(conn, FTP_STOP);
              return CURLE_OK;
            }
            break;
          case CURL_TIMECOND_IFUNMODSINCE:
            if(data->info.filetime > data->set.timevalue) {
              infof(data, "The requested document is not old enough\n");
    
              ftp->transfer = FTPTRANSFER_NONE; /* mark this to not transfer data */
    
              state(conn, FTP_STOP);
              return CURLE_OK;
            }
            break;
          } /* switch */
    
        else {
          infof(data, "Skipping time comparison\n");
    
      if(!result)
        result = ftp_state_post_mdtm(conn);
    
    static CURLcode ftp_state_type_resp(struct connectdata *conn,
                                        int ftpcode,
                                        ftpstate instate)
    {
      CURLcode result = CURLE_OK;
      struct SessionHandle *data=conn->data;
    
      if(ftpcode/100 != 2) {
        /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a
           successful 'TYPE I'. While that is not as RFC959 says, it is still a
           positive response code and we allow that. */
    
        failf(data, "Couldn't set desired mode");
    
      if(ftpcode != 200)
        infof(data, "Got a %03d response code instead of the assumed 200\n",
              ftpcode);
    
    
      if(instate == FTP_TYPE)
        result = ftp_state_post_type(conn);
      else if(instate == FTP_LIST_TYPE)
        result = ftp_state_post_listtype(conn);
      else if(instate == FTP_RETR_TYPE)
        result = ftp_state_post_retrtype(conn);
      else if(instate == FTP_STOR_TYPE)
        result = ftp_state_post_stortype(conn);
    
    static CURLcode ftp_state_post_retr_size(struct connectdata *conn,
                                             curl_off_t filesize)
    {
      CURLcode result = CURLE_OK;
      struct SessionHandle *data=conn->data;
    
      struct ftp_conn *ftpc = &conn->proto.ftpc;
    
      if(data->set.max_filesize && (filesize > data->set.max_filesize)) {
    
        failf(data, "Maximum file size exceeded");
        return CURLE_FILESIZE_EXCEEDED;
      }
      ftp->downloadsize = filesize;
    
        /* We always (attempt to) get the size of downloads, so it is done before
           this even when not doing resumes. */
        if(filesize == -1) {
          infof(data, "ftp server doesn't support SIZE\n");
          /* We couldn't get the size and therefore we can't know if there really
             is a part of the file left to get, although the server will just
             close the connection when we start the connection so it won't cause
             us any harm, just not make us exit as nicely. */
        }
        else {
          /* We got a file size report, so we check that there actually is a
             part of the file left to get, or else we go home.  */
    
            /* We're supposed to download the last abs(from) bytes */
    
            if(filesize < -data->state.resume_from) {
    
              failf(data, "Offset (%" FORMAT_OFF_T
                    ") was beyond file size (%" FORMAT_OFF_T ")",
    
              return CURLE_BAD_DOWNLOAD_RESUME;
            }
            /* convert to size to download */
    
            ftp->downloadsize = -data->state.resume_from;
    
            data->state.resume_from = filesize - ftp->downloadsize;
    
              failf(data, "Offset (%" FORMAT_OFF_T
                    ") was beyond file size (%" FORMAT_OFF_T ")",
    
              return CURLE_BAD_DOWNLOAD_RESUME;
            }
            /* Now store the number of bytes we are expected to download */
    
            ftp->downloadsize = filesize-data->state.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 ->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);
    
        NBFTPSENDF(conn, "REST %" FORMAT_OFF_T, data->state.resume_from);
    
        NBFTPSENDF(conn, "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 {
    
          NBFTPSENDF(conn, "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);
    
    
      result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */
                                   SECONDARYSOCKET, ftp->bytecountp);
    
      conn->proto.ftpc.pending_resp = TRUE; /* we expect a server response more */
    
    
      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;
    
        infof(data, "Maxdownload = %" FORMAT_OFF_T "\n", data->req.maxdownload);
    
          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 */
    
        conn->proto.ftpc.pending_resp = TRUE; /* we expect a server response more */
    
        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.
        */
        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_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) */
        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.str[STRING_FTP_ACCOUNT]) {
          NBFTPSENDF(conn, "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. */
    
          NBFTPSENDF(conn, "%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" };
    
        /* 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 */
    
            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) != 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;
    
              failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
    
                    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]);
    
            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;
    
          NBFTPSENDF(conn, "PROT %c",
                     data->set.ftp_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
          state(conn, FTP_PROT);
    
        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)
    
            /* 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:
    
            /* 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;
    
            char *dir = 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);
    
    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;
    
          if((ftpcode/100 != 2) && !ftpc->count3--) {
    
            /* failure to MKD the dir */
            failf(data, "Failed to MKD dir: %03d", ftpcode);
    
          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 */
    
    static CURLcode ftp_multi_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;
    
      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_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
                             ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
                             0);
    
    Yang Tse's avatar
    Yang Tse committed
        failf(data, "select/poll error");
    
      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_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
                               ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
                               (int)timeout_ms);
    
    Yang Tse's avatar
    Yang Tse committed
          failf(data, "select/poll error");