]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
FTP code turned into state machine. Not completely yet, but a good start.
authorDaniel Stenberg <daniel@haxx.se>
Wed, 9 Feb 2005 13:06:40 +0000 (13:06 +0000)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 9 Feb 2005 13:06:40 +0000 (13:06 +0000)
The tag 'before_ftp_statemachine' was set just before this commit in case
of future need.

34 files changed:
include/curl/curl.h
lib/dict.c
lib/dict.h
lib/file.c
lib/file.h
lib/ftp.c
lib/ftp.h
lib/hostares.c
lib/hostasyn.c
lib/hostip.c
lib/hostip.h
lib/hostip6.c
lib/hostsyn.c
lib/hostthre.c
lib/http.c
lib/http.h
lib/ldap.c
lib/ldap.h
lib/multi.c
lib/multiif.h
lib/sendf.h
lib/telnet.c
lib/telnet.h
lib/transfer.c
lib/url.c
lib/url.h
lib/urldata.h
src/main.c
tests/data/test113
tests/data/test114
tests/data/test190
tests/data/test195
tests/data/test196
tests/libtest/lib511.c

index 019ef08a08b2a2fc6a20601e7c48af21ee47dbe7..a8fb925b52742e581766145c55804b7f38596263 100644 (file)
@@ -245,7 +245,9 @@ typedef enum {
   CURLE_COULDNT_RESOLVE_HOST,    /* 6 */
   CURLE_COULDNT_CONNECT,         /* 7 */
   CURLE_FTP_WEIRD_SERVER_REPLY,  /* 8 */
-  CURLE_FTP_ACCESS_DENIED,       /* 9 */
+  CURLE_FTP_ACCESS_DENIED,       /* 9 a service was denied by the FTP server
+                                    due to lack of access - when login fails
+                                    this is not returned. */
   CURLE_FTP_USER_PASSWORD_INCORRECT, /* 10 */
   CURLE_FTP_WEIRD_PASS_REPLY,    /* 11 */
   CURLE_FTP_WEIRD_USER_REPLY,    /* 12 */
@@ -305,6 +307,8 @@ typedef enum {
   CURLE_SEND_FAIL_REWIND,        /* 65 - Sending the data requires a rewind
                                     that failed */
   CURLE_SSL_ENGINE_INITFAILED,   /* 66 - failed to initialise ENGINE */
+  CURLE_LOGIN_DENIED,            /* 67 - user, password or similar was not
+                                    accepted and we failed to login */
   CURL_LAST /* never use! */
 } CURLcode;
 
index ef82e0b85ba9f7dcd48a71422badd23dcb64bb74..1c687292f3695704c2f9fdbca51d82fe29ded67d 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -85,7 +85,7 @@
 #define _MPRINTF_REPLACE /* use our functions only */
 #include <curl/mprintf.h>
 
-CURLcode Curl_dict(struct connectdata *conn)
+CURLcode Curl_dict(struct connectdata *conn, bool *done)
 {
   char *word;
   char *ppath;
@@ -100,6 +100,8 @@ CURLcode Curl_dict(struct connectdata *conn)
   char *path = conn->path;
   curl_off_t *bytecount = &conn->bytecount;
 
+  *done = TRUE; /* unconditionally */
+
   if(conn->bits.user_passwd) {
     /* AUTH is missing */
   }
index 4301f0176cf8672e4069898aae29a04a3fa5c37a..d3da1936f60f89004a07fc405fdc7fbf463580f9 100644 (file)
@@ -2,18 +2,18 @@
 #define __DICT_H
 
 /***************************************************************************
- *                                  _   _ ____  _     
- *  Project                     ___| | | |  _ \| |    
- *                             / __| | | | |_) | |    
- *                            | (__| |_| |  _ <| |___ 
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
  * are also available at http://curl.haxx.se/docs/copyright.html.
- * 
+ *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
  * furnished to do so, under the terms of the COPYING file.
@@ -24,7 +24,7 @@
  * $Id$
  ***************************************************************************/
 #ifndef CURL_DISABLE_DICT
-CURLcode Curl_dict(struct connectdata *conn);
+CURLcode Curl_dict(struct connectdata *conn, bool *done);
 CURLcode Curl_dict_done(struct connectdata *conn);
 #endif
 #endif
index 267a99ba0935b878d1d186c0e348388451e4f62e..5bebf32876f867b6a1d4ee49b9407112a70e8645 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -266,7 +266,7 @@ static CURLcode file_upload(struct connectdata *conn)
  * opposed to sockets) we instead perform the whole do-operation in this
  * function.
  */
-CURLcode Curl_file(struct connectdata *conn)
+CURLcode Curl_file(struct connectdata *conn, bool *done)
 {
   /* This implementation ignores the host name in conformance with
      RFC 1738. Only local files (reachable via the standard file system)
@@ -286,6 +286,8 @@ CURLcode Curl_file(struct connectdata *conn)
   int fd;
   struct timeval now = Curl_tvnow();
 
+  *done = TRUE; /* unconditionally */
+
   Curl_readwrite_init(conn);
   Curl_initinfo(data);
   Curl_pgrsStartNow(data);
index 689b8aeca7cf1288ec2f6552d27e338e3ba7043a..f78acfe63b3dcdd55f0d1be40c8d0c9d05033fb9 100644 (file)
@@ -2,18 +2,18 @@
 #define __FILE_H
 
 /***************************************************************************
- *                                  _   _ ____  _     
- *  Project                     ___| | | |  _ \| |    
- *                             / __| | | | |_) | |    
- *                            | (__| |_| |  _ <| |___ 
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
  * are also available at http://curl.haxx.se/docs/copyright.html.
- * 
+ *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
  * furnished to do so, under the terms of the COPYING file.
@@ -24,7 +24,7 @@
  * $Id$
  ***************************************************************************/
 #ifndef CURL_DISABLE_FILE
-CURLcode Curl_file(struct connectdata *);
+CURLcode Curl_file(struct connectdata *, bool *done);
 CURLcode Curl_file_done(struct connectdata *, CURLcode);
 CURLcode Curl_file_connect(struct connectdata *);
 #endif
index b29ae5363a980b400b486e7c41e766c2915d5af8..45ec88ba42151bab31071ce806b3d6483df664a7 100644 (file)
--- a/lib/ftp.c
+++ b/lib/ftp.c
@@ -124,11 +124,20 @@ static CURLcode ftp_3rdparty_pretransfer(struct connectdata *conn);
 static CURLcode ftp_3rdparty_transfer(struct connectdata *conn);
 static CURLcode ftp_parse_url_path(struct connectdata *conn);
 static CURLcode ftp_cwd_and_create_path(struct connectdata *conn);
-static CURLcode ftp_regular_transfer(struct connectdata *conn);
+static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done);
 static CURLcode ftp_3rdparty(struct connectdata *conn);
+static void ftp_pasv_verbose(struct connectdata *conn,
+                             Curl_addrinfo *ai,
+                             char *newhost, /* ascii version */
+                             int port);
+static CURLcode ftp_state_post_rest(struct connectdata *conn);
+static CURLcode ftp_state_post_cwd(struct connectdata *conn);
+static CURLcode ftp_state_quote(struct connectdata *conn,
+                                bool init, ftpstate instate);
 
 /* easy-to-use macro: */
 #define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z))) return result
+#define NBFTPSENDF(x,y,z) if((result = Curl_nbftpsendf(x,y,z))) return result
 
 static void freedirs(struct FTP *ftp)
 {
@@ -226,6 +235,166 @@ static CURLcode AllowServerConnect(struct connectdata *conn)
 }
 
 
+static CURLcode ftp_readresp(curl_socket_t sockfd,
+                             struct connectdata *conn,
+                             int *ftpcode, /* return the ftp-code if done */
+                             size_t *size) /* size of the response */
+{
+  int perline; /* count bytes per line */
+  bool keepon=TRUE;
+  ssize_t gotbytes;
+  char *ptr;
+  struct SessionHandle *data = conn->data;
+  char *line_start;
+  char *buf = data->state.buffer;
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  int code = 0;
+
+  if (ftpcode)
+    *ftpcode = 0; /* 0 for errors or not done */
+
+  ptr=buf;
+  line_start = buf;
+
+  perline=0;
+  keepon=TRUE;
+
+  while((ftp->nread_resp<BUFSIZE) && (keepon && !result)) {
+
+    if(ftp->cache) {
+      /* we had data in the "cache", copy that instead of doing an actual
+       * read
+       *
+       * ftp->cache_size is cast to int here.  This should be safe,
+       * because it would have been populated with something of size
+       * int to begin with, even though its datatype may be larger
+       * than an int.
+       */
+      memcpy(ptr, ftp->cache, (int)ftp->cache_size);
+      gotbytes = (int)ftp->cache_size;
+      free(ftp->cache);    /* free the cache */
+      ftp->cache = NULL;   /* clear the pointer */
+      ftp->cache_size = 0; /* zero the size just in case */
+    }
+    else {
+      int res = Curl_read(conn, sockfd, ptr, BUFSIZE-ftp->nread_resp,
+                          &gotbytes);
+      if(res < 0)
+        /* EWOULDBLOCK */
+        return CURLE_OK; /* return */
+
+      if(CURLE_OK != res)
+        keepon = FALSE;
+    }
+
+    if(!keepon)
+      ;
+    else if(gotbytes <= 0) {
+      keepon = FALSE;
+      result = CURLE_RECV_ERROR;
+      failf(data, "FTP response reading failed");
+    }
+    else {
+      /* we got a whole chunk of data, which can be anything from one
+       * byte to a set of lines and possible just a piece of the last
+       * line */
+      int i;
+
+      conn->headerbytecount += gotbytes;
+
+      ftp->nread_resp += gotbytes;
+      for(i = 0; i < gotbytes; ptr++, i++) {
+        perline++;
+        if(*ptr=='\n') {
+          /* a newline is CRLF in ftp-talk, so the CR is ignored as
+             the line isn't really terminated until the LF comes */
+
+          /* output debug output if that is requested */
+          if(data->set.verbose)
+            Curl_debug(data, CURLINFO_HEADER_IN, line_start, perline, conn);
+
+          /*
+           * We pass all response-lines to the callback function registered
+           * for "headers". The response lines can be seen as a kind of
+           * headers.
+           */
+          result = Curl_client_write(data, CLIENTWRITE_HEADER,
+                                     line_start, perline);
+          if(result)
+            return result;
+
+#define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \
+                        isdigit((int)line[2]) && (' ' == line[3]))
+
+          if(perline>3 && lastline(line_start)) {
+            /* This is the end of the last line, copy the last line to the
+               start of the buffer and zero terminate, for old times sake (and
+               krb4)! */
+            char *meow;
+            int n;
+            for(meow=line_start, n=0; meow<ptr; meow++, n++)
+              buf[n] = *meow;
+            *meow=0; /* zero terminate */
+            keepon=FALSE;
+            line_start = ptr+1; /* advance pointer */
+            i++; /* skip this before getting out */
+
+            *size = ftp->nread_resp; /* size of the response */
+            ftp->nread_resp = 0; /* restart */
+            break;
+          }
+          perline=0; /* line starts over here */
+          line_start = ptr+1;
+        }
+      }
+      if(!keepon && (i != gotbytes)) {
+        /* We found the end of the response lines, but we didn't parse the
+           full chunk of data we have read from the server. We therefore need
+           to store the rest of the data to be checked on the next invoke as
+           it may actually contain another end of response already! */
+        ftp->cache_size = gotbytes - i;
+        ftp->cache = (char *)malloc((int)ftp->cache_size);
+        if(ftp->cache)
+          memcpy(ftp->cache, line_start, (int)ftp->cache_size);
+        else
+          return CURLE_OUT_OF_MEMORY; /**BANG**/
+      }
+    } /* there was data */
+
+  } /* while there's buffer left and loop is requested */
+
+  if(!result)
+    code = atoi(buf);
+
+#ifdef HAVE_KRB4
+  /* handle the security-oriented responses 6xx ***/
+  /* FIXME: some errorchecking perhaps... ***/
+  switch(code) {
+  case 631:
+    Curl_sec_read_msg(conn, buf, prot_safe);
+    break;
+  case 632:
+    Curl_sec_read_msg(conn, buf, prot_private);
+    break;
+  case 633:
+    Curl_sec_read_msg(conn, buf, prot_confidential);
+    break;
+  default:
+    /* normal ftp stuff we pass through! */
+    break;
+  }
+#endif
+
+  *ftpcode=code; /* return the initial number like this */
+
+
+  /* store the latest code for later retrieval */
+  conn->data->info.httpcode=code;
+
+  return result;
+}
+
 /* --- parse FTP server responses --- */
 
 /*
@@ -238,9 +407,9 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
                              struct connectdata *conn,
                              int *ftpcode) /* return the ftp-code */
 {
-  /* Brand new implementation.
-   * We cannot read just one byte per read() and then go back to select()
-   * as it seems that the OpenSSL read() stuff doesn't grok that properly.
+  /*
+   * We cannot read just one byte per read() and then go back to select() as
+   * the OpenSSL read() doesn't grok that properly.
    *
    * Alas, read as much as possible, split up into lines, use the ending
    * line in a response or continue reading.  */
@@ -450,1723 +619,2490 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
   return result;
 }
 
-/*
- * Curl_ftp_connect() should do everything that is to be considered a part of
- * the connection phase.
- */
-CURLcode Curl_ftp_connect(struct connectdata *conn)
+/* This is the ONLY way to change FTP state! */
+static void state(struct connectdata *conn,
+                  ftpstate state)
 {
-  /* this is FTP and no proxy */
-  ssize_t nread;
-  struct SessionHandle *data=conn->data;
-  char *buf = data->state.buffer; /* this is our buffer */
-  struct FTP *ftp;
-  CURLcode result;
-  int ftpcode, trynum;
-  static const char * const ftpauth[]  = {
-    "SSL", "TLS", NULL
+#ifdef CURLDEBUG
+  /* for debug purposes */
+  const char *names[]={
+    "STOP",
+    "WAIT220",
+    "AUTH",
+    "USER",
+    "PASS",
+    "ACCT",
+    "PBSZ",
+    "PROT",
+    "PWD",
+    "QUOTE",
+    "RETR_PREQUOTE",
+    "STOR_PREQUOTE",
+    "POSTQUOTE",
+    "CWD",
+    "MKD",
+    "MDTM",
+    "TYPE",
+    "LIST_TYPE",
+    "RETR_TYPE",
+    "STOR_TYPE",
+    "SIZE",
+    "RETR_SIZE",
+    "STOR_SIZE",
+    "REST",
+    "RETR_REST",
+    "PORT",
+    "PASV",
+    "LIST",
+    "RETR",
+    "STOR",
+    "QUIT"
   };
-
-  ftp = (struct FTP *)calloc(sizeof(struct FTP), 1);
-  if(!ftp)
-    return CURLE_OUT_OF_MEMORY;
-
-  conn->proto.ftp = ftp;
-
-  /* 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 */
-
-#ifndef CURL_DISABLE_HTTP
-  if (conn->bits.tunnel_proxy) {
-    /* 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;
-  }
-#endif   /* CURL_DISABLE_HTTP */
-
-  if(conn->protocol & PROT_FTPS) {
-    /* FTPS is simply ftp with SSL for the control channel */
-    /* now, perform the SSL initialization for this socket */
-    result = Curl_SSLConnect(conn, FIRSTSOCKET);
-    if(result)
-      return result;
-  }
-
-  /* The first thing we do is wait for the "220*" line: */
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
-
-  if(ftpcode != 220) {
-    failf(data, "This doesn't seem like a nice ftp-server response");
-    return CURLE_FTP_WEIRD_SERVER_REPLY;
-  }
-
-#ifdef HAVE_KRB4
-  /* if not anonymous login, try a secure login */
-  if(data->set.krb4) {
-
-    /* request data protection level (default is 'clear') */
-    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");
-    else
-      infof(data, "Authentication successful\n");
-  }
 #endif
+  struct FTP *ftp = conn->proto.ftp;
+#ifdef CURLDEBUG
+  if(ftp->state != state)
+    infof(conn->data, "FTP %p state change from %s to %s\n",
+          ftp, names[ftp->state], names[state]);
+#endif
+  ftp->state = state;
+}
 
-  if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
-    /* we don't have a SSL/TLS connection, try a FTPS connection now */
-    int start;
-    int trynext;
-    int count=0;
-
-    switch(data->set.ftpsslauth) {
-    case CURLFTPAUTH_DEFAULT:
-    case CURLFTPAUTH_SSL:
-      start = 0;
-      trynext = 1;
-      break;
-    case CURLFTPAUTH_TLS:
-      start = 1;
-      trynext = 0;
-      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 */
-    }
+static CURLcode ftp_state_user(struct connectdata *conn)
+{
+  CURLcode result;
+  struct FTP *ftp = conn->proto.ftp;
+  /* send USER */
+  NBFTPSENDF(conn, "USER %s", ftp->user?ftp->user:"");
 
-    for (trynum = start; ftpauth[count]; trynum=trynext, count++) {
+  state(conn, FTP_USER);
 
-      FTPSENDF(conn, "AUTH %s", ftpauth[trynum]);
+  return CURLE_OK;
+}
 
-      result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+static CURLcode ftp_state_pwd(struct connectdata *conn)
+{
+  CURLcode result;
 
-      if(result)
-        return result;
+  /* send PWD to discover our entry point */
+  NBFTPSENDF(conn, "PWD", NULL);
+  state(conn, FTP_PWD);
 
-      /* 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.
-       */
+  return CURLE_OK;
+}
 
-      if((ftpcode == 234) || (ftpcode == 334)) {
-        result = Curl_SSLConnect(conn, FIRSTSOCKET);
-        if(result)
-          return result;
-        conn->protocol |= PROT_FTPS;
-        conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */
-        break;
-      }
-    }
-  }
+/* For the FTP "protocol connect" and "doing" phases only */
+CURLcode Curl_ftp_fdset(struct connectdata *conn,
+                        fd_set *read_fd_set,
+                        fd_set *write_fd_set,
+                        int *max_fdp)
+{
+  struct FTP *ftp = conn->proto.ftp;
+  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
 
-  /* send USER */
-  FTPSENDF(conn, "USER %s", ftp->user?ftp->user:"");
+  if(ftp->sendleft)
+    /* write mode */
+    FD_SET(sockfd, write_fd_set);
+  else
+    /* read mode */
+    FD_SET(sockfd, read_fd_set);
 
-  /* wait for feedback */
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
+  if((int)sockfd > *max_fdp)
+    *max_fdp = (int)sockfd;
 
-  if(ftpcode == 530) {
-    /* 530 User ... access denied
-       (the server denies to log the specified user) */
-    failf(data, "Access denied: %03d", ftpcode);
-    return CURLE_FTP_ACCESS_DENIED;
-  }
-  else if(ftpcode == 331) {
-    /* 331 Password required for ...
-       (the server requires to send the user's password too) */
-    FTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:"");
-    result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-    if(result)
-      return result;
+  return CURLE_OK;
+}
 
-    if(ftpcode == 530) {
-      /* 530 Login incorrect.
-         (the username and/or the password are incorrect)
-      or
-         530 Sorry, the maximum number of allowed users are already connected
-      */
-      failf(data, "not logged in: %03d", ftpcode);
-      return CURLE_FTP_USER_PASSWORD_INCORRECT;
-    }
-    else if(ftpcode/100 == 2) {
-      /* 230 User ... logged in.
-         (user successfully logged in)
+/* This is called after the FTP_QUOTE state is passed.
 
-         Apparently, proftpd with SSL returns 232 here at times. */
+   ftp_state_cwd() sends the range of PWD commands to the server to change to
+   the correct directory. It may also need to send MKD commands to create
+   missing ones, if that option is enabled.
+*/
+static CURLcode ftp_state_cwd(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
 
-      infof(data, "We have successfully logged in\n");
+  if(ftp->cwddone)
+    /* already done and fine */
+    result = ftp_state_post_cwd(conn);
+  else {
+    ftp->count2 = 0;
+    if (conn->bits.reuse && ftp->entrypath) {
+      /* This is a re-used connection. Since we change directory to where the
+         transfer is taking place, we must first get back to the original dir
+         where we ended up after login: */
+      ftp->count1 = 0; /* we count this as the first path, then we add one
+                          for all upcoming ones in the ftp->dirs[] array */
+      NBFTPSENDF(conn, "CWD %s", ftp->entrypath);
+      state(conn, FTP_CWD);
     }
-    else if(ftpcode == 332) {
-      /* 332 Please provide account info */
-      if(data->set.ftp_account) {
-        FTPSENDF(conn, "ACCT %s", data->set.ftp_account);
-        result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-        if(!result && (ftpcode != 230)) {
-          failf(data, "ACCT rejected by server: %03d", ftpcode);
-          result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */
-        }
+    else {
+      if(ftp->dirdepth) {
+        ftp->count1 = 1;
+        /* issue the first CWD, the rest is sent when the CWD responses are
+           received... */
+        NBFTPSENDF(conn, "CWD %s", ftp->dirs[ftp->count1 -1]);
+        state(conn, FTP_CWD);
       }
       else {
-        failf(data, "ACCT requested by none available");
-        result = CURLE_FTP_WEIRD_PASS_REPLY;
+        /* No CWD necessary */
+        result = ftp_state_post_cwd(conn);
       }
-      if(result)
-        return result;
-    }
-    else {
-      failf(data, "Odd return code after PASS");
-      return CURLE_FTP_WEIRD_PASS_REPLY;
     }
   }
-  else if(buf[0] == '2') {
-    /* 230 User ... logged in.
-       (the user logged in without password) */
-    infof(data, "We have successfully logged in\n");
-    if (conn->ssl[FIRSTSOCKET].use) {
-#ifdef HAVE_KRB4
-      /* We are logged in with Kerberos, now set the requested protection
-       * level
-       */
-      if(conn->sec_complete)
-        Curl_sec_set_protection_level(conn);
+  return result;
+}
 
-      /* 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) {
-        result = Curl_krb_kauth(conn);
-        if(result)
-          return result;
-      }
-#endif
-    }
-  }
-  else {
-    failf(data, "Odd return code after USER");
-    return CURLE_FTP_WEIRD_USER_REPLY;
-  }
+typedef enum { EPRT, LPRT, PORT, DONE } ftpport;
 
-  if(conn->ssl[FIRSTSOCKET].use) {
-    /* PBSZ = PROTECTION BUFFER SIZE.
+static CURLcode ftp_state_use_port(struct connectdata *conn,
+                                   ftpport fcmd) /* start with this */
 
-       The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says:
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data=conn->data;
+  curl_socket_t portsock= CURL_SOCKET_BAD;
 
-       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)
+#ifdef ENABLE_IPV6
+  /******************************************************************
+   * IPv6-specific section
+   */
 
-       ... (and on page 8):
+  struct addrinfo *res, *ai;
+  struct sockaddr_storage ss;
+  socklen_t sslen;
+  char hbuf[NI_MAXHOST];
+  struct sockaddr *sa=(struct sockaddr *)&ss;
+  unsigned char *ap;
+  unsigned char *pp;
+  char portmsgbuf[1024], tmp[1024];
+  const char *mode[] = { "EPRT", "LPRT", "PORT", NULL };
+  int rc;
+  int error;
+  char *host=NULL;
+  struct Curl_dns_entry *h=NULL;
 
-       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.
-    */
-    FTPSENDF(conn, "PBSZ %d", 0);
-    result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-    if(result)
-      return result;
+  if(data->set.ftpport && (strlen(data->set.ftpport) > 1)) {
+    /* attempt to get the address of the given interface name */
+    if(!Curl_if2ip(data->set.ftpport, hbuf, sizeof(hbuf)))
+      /* not an interface, use the given string as host name instead */
+      host = data->set.ftpport;
+    else
+      host = hbuf; /* use the hbuf for host name */
+  } /* data->set.ftpport */
 
-    /* For TLS, the data connection can have one of two security levels.
+  if(!host) {
+    /* not an interface and not a host name, get default by extracting
+       the IP from the control connection */
 
-       1)Clear (requested by 'PROT C')
+    sslen = sizeof(ss);
+    rc = getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen);
+    if(rc < 0) {
+      failf(data, "getsockname() returned %d\n", rc);
+      return CURLE_FTP_PORT_FAILED;
+    }
+
+    rc = getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL,
+                     0, NIFLAGS);
+    if(rc) {
+      failf(data, "getnameinfo() returned %d\n", rc);
+      return CURLE_FTP_PORT_FAILED;
+    }
+    host = hbuf; /* use this host name */
+  }
+
+  rc = Curl_resolv(conn, host, 0, &h);
+  if(rc == CURLRESOLV_PENDING)
+    rc = Curl_wait_for_resolv(conn, &h);
+  if(h) {
+    res = h->addr;
+    /* when we return from this function, we can forget about this entry
+       to we can unlock it now already */
+    Curl_resolv_unlock(data, h);
+  } /* (h) */
+  else
+    res = NULL; /* failure! */
+
+  portsock = CURL_SOCKET_BAD;
+  error = 0;
+  for (ai = res; ai; ai = ai->ai_next) {
+    /*
+     * Workaround for AIX5 getaddrinfo() problem (it doesn't set ai_socktype):
+     */
+    if (ai->ai_socktype == 0)
+      ai->ai_socktype = SOCK_STREAM;
+
+    portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+    if (portsock == CURL_SOCKET_BAD) {
+      error = Curl_ourerrno();
+      continue;
+    }
+
+    if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) {
+      error = Curl_ourerrno();
+      sclose(portsock);
+      portsock = CURL_SOCKET_BAD;
+      continue;
+    }
+
+    if (listen(portsock, 1) < 0) {
+      error = Curl_ourerrno();
+      sclose(portsock);
+      portsock = CURL_SOCKET_BAD;
+      continue;
+    }
+
+    break;
+  }
+
+  if (portsock == CURL_SOCKET_BAD) {
+    failf(data, "%s", Curl_strerror(conn,error));
+    return CURLE_FTP_PORT_FAILED;
+  }
+
+  sslen = sizeof(ss);
+  if (getsockname(portsock, sa, &sslen) < 0) {
+    failf(data, "%s", Curl_strerror(conn,Curl_ourerrno()));
+    return CURLE_FTP_PORT_FAILED;
+  }
+
+#ifdef PF_INET6
+  if(!conn->bits.ftp_use_eprt && conn->bits.ipv6)
+    /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the
+       request and enable EPRT again! */
+    conn->bits.ftp_use_eprt = TRUE;
+#endif
+
+  for (; fcmd != DONE; fcmd++) {
+    int lprtaf, eprtaf;
+    int alen=0, plen=0;
+
+    if(!conn->bits.ftp_use_eprt && (EPRT == fcmd))
+      /* if disabled, goto next */
+      continue;
+
+    if(!conn->bits.ftp_use_lprt && (LPRT == fcmd))
+      /* if disabled, goto next */
+      continue;
+
+    switch (sa->sa_family) {
+    case AF_INET:
+      ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr;
+      alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr);
+      pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port;
+      plen = sizeof(((struct sockaddr_in *)&ss)->sin_port);
+      lprtaf = 4;
+      eprtaf = 1;
+      break;
+    case AF_INET6:
+      ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr;
+      alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr);
+      pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port;
+      plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port);
+      lprtaf = 6;
+      eprtaf = 2;
+      break;
+    default:
+      ap = pp = NULL;
+      lprtaf = eprtaf = -1;
+      break;
+    }
+
+    if (EPRT == fcmd) {
+      if (eprtaf < 0)
+        continue;
+      if (getnameinfo((struct sockaddr *)&ss, sslen,
+                      portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp),
+                      NIFLAGS))
+        continue;
+
+      /* do not transmit IPv6 scope identifier to the wire */
+      if (sa->sa_family == AF_INET6) {
+        char *q = strchr(portmsgbuf, '%');
+        if (q)
+          *q = '\0';
+      }
+
+      result = Curl_nbftpsendf(conn, "%s |%d|%s|%s|", mode[fcmd], eprtaf,
+                               portmsgbuf, tmp);
+      if(result)
+        return result;
+      break;
+    }
+    else if ((LPRT == fcmd) || (PORT == fcmd)) {
+      int i;
+
+      if ((LPRT == fcmd) && lprtaf < 0)
+        continue;
+      if ((PORT == fcmd) && sa->sa_family != AF_INET)
+        continue;
+
+      portmsgbuf[0] = '\0';
+      if (LPRT == fcmd) {
+        snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen);
+        if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+            sizeof(portmsgbuf)) {
+          continue;
+        }
+      }
+
+      for (i = 0; i < alen; i++) {
+        if (portmsgbuf[0])
+          snprintf(tmp, sizeof(tmp), ",%u", ap[i]);
+        else
+          snprintf(tmp, sizeof(tmp), "%u", ap[i]);
+
+        if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+            sizeof(portmsgbuf)) {
+          continue;
+        }
+      }
+
+      if (LPRT == fcmd) {
+        snprintf(tmp, sizeof(tmp), ",%d", plen);
+
+        if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf))
+          continue;
+      }
+
+      for (i = 0; i < plen; i++) {
+        snprintf(tmp, sizeof(tmp), ",%u", pp[i]);
+
+        if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+            sizeof(portmsgbuf)) {
+          continue;
+        }
+      }
+
+      result = Curl_nbftpsendf(conn, "%s %s", mode[fcmd], portmsgbuf);
+      if(result)
+        return result;
+      break;
+    }
+  }
+
+  /* store which command was sent */
+  ftp->count1 = fcmd;
+
+  /* we set the secondary socket variable to this for now, it is only so that
+     the cleanup function will close it in case we fail before the true
+     secondary stuff is made */
+  if(-1 != conn->sock[SECONDARYSOCKET])
+    sclose(conn->sock[SECONDARYSOCKET]);
+  conn->sock[SECONDARYSOCKET] = portsock;
+
+#else
+  /******************************************************************
+   * IPv4-specific section
+   */
+  struct sockaddr_in sa;
+  unsigned short porttouse;
+  char myhost[256] = "";
+  bool sa_filled_in = FALSE;
+  Curl_addrinfo *addr = NULL;
+  unsigned short ip[4];
+  (void)fcmd; /* not used in the IPv4 code */
+  if(data->set.ftpport) {
+    in_addr_t in;
+
+    /* First check if the given name is an IP address */
+    in=inet_addr(data->set.ftpport);
+
+    if(in != CURL_INADDR_NONE)
+      /* this is an IPv4 address */
+      addr = Curl_ip2addr(in, data->set.ftpport, 0);
+    else {
+      if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) {
+        /* The interface to IP conversion provided a dotted address */
+        in=inet_addr(myhost);
+        addr = Curl_ip2addr(in, myhost, 0);
+      }
+      else if(strlen(data->set.ftpport)> 1) {
+        /* might be a host name! */
+        struct Curl_dns_entry *h=NULL;
+        int rc = Curl_resolv(conn, myhost, 0, &h);
+        if(rc == CURLRESOLV_PENDING)
+          /* BLOCKING */
+          rc = Curl_wait_for_resolv(conn, &h);
+        if(h) {
+          addr = h->addr;
+          /* when we return from this function, we can forget about this entry
+             so we can unlock it now already */
+          Curl_resolv_unlock(data, h);
+        } /* (h) */
+      } /* strlen */
+    } /* CURL_INADDR_NONE */
+  } /* data->set.ftpport */
+
+  if(!addr) {
+    /* pick a suitable default here */
+
+    socklen_t sslen;
+
+    sslen = sizeof(sa);
+    if (getsockname(conn->sock[FIRSTSOCKET],
+                    (struct sockaddr *)&sa, &sslen) < 0) {
+      failf(data, "getsockname() failed");
+      return CURLE_FTP_PORT_FAILED;
+    }
+
+    sa_filled_in = TRUE; /* the sa struct is filled in */
+  }
+
+  if (addr || sa_filled_in) {
+    portsock = socket(AF_INET, SOCK_STREAM, 0);
+    if(CURL_SOCKET_BAD != portsock) {
+      socklen_t size;
+
+      /* we set the secondary socket variable to this for now, it
+         is only so that the cleanup function will close it in case
+         we fail before the true secondary stuff is made */
+      if(-1 != conn->sock[SECONDARYSOCKET])
+        sclose(conn->sock[SECONDARYSOCKET]);
+      conn->sock[SECONDARYSOCKET] = portsock;
+
+      if(!sa_filled_in) {
+        memcpy(&sa, addr->ai_addr, sizeof(sa));
+        sa.sin_addr.s_addr = INADDR_ANY;
+      }
+
+      sa.sin_port = 0;
+      size = sizeof(sa);
+
+      if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
+        /* we succeeded to bind */
+        struct sockaddr_in add;
+        socklen_t socksize = sizeof(add);
+
+        if(getsockname(portsock, (struct sockaddr *) &add,
+                       &socksize)<0) {
+          failf(data, "getsockname() failed");
+          return CURLE_FTP_PORT_FAILED;
+        }
+        porttouse = ntohs(add.sin_port);
+
+        if ( listen(portsock, 1) < 0 ) {
+          failf(data, "listen(2) failed on socket");
+          return CURLE_FTP_PORT_FAILED;
+        }
+      }
+      else {
+        failf(data, "bind(2) failed on socket");
+        return CURLE_FTP_PORT_FAILED;
+      }
+    }
+    else {
+      failf(data, "socket(2) failed (%s)");
+      return CURLE_FTP_PORT_FAILED;
+    }
+  }
+  else {
+    failf(data, "could't find IP address to use");
+    return CURLE_FTP_PORT_FAILED;
+  }
+
+  if(sa_filled_in)
+    Curl_inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr,
+                   myhost, sizeof(myhost));
+  else
+    Curl_printable_address(addr, myhost, sizeof(myhost));
+
+  if(4 == sscanf(myhost, "%hu.%hu.%hu.%hu",
+                 &ip[0], &ip[1], &ip[2], &ip[3])) {
+
+    infof(data, "Telling server to connect to %d.%d.%d.%d:%d\n",
+          ip[0], ip[1], ip[2], ip[3], porttouse);
+
+    result=Curl_nbftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d",
+                           ip[0], ip[1], ip[2], ip[3],
+                           porttouse >> 8, porttouse & 255);
+    if(result)
+      return result;
+  }
+  else
+    return CURLE_FTP_PORT_FAILED;
+
+  Curl_freeaddrinfo(addr);
+
+  ftp->count1 = PORT;
+
+#endif /* end of ipv4-specific code */
+
+  state(conn, FTP_PORT);
+  return result;
+}
+
+static CURLcode ftp_state_use_pasv(struct connectdata *conn)
+{
+  struct FTP *ftp = conn->proto.ftp;
+  CURLcode result = CURLE_OK;
+  /*
+    Here's the excecutive summary on what to do:
+
+    PASV is RFC959, expect:
+    227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)
+
+    LPSV is RFC1639, expect:
+    228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2)
+
+    EPSV is RFC2428, expect:
+    229 Entering Extended Passive Mode (|||port|)
+
+  */
+
+  const char *mode[] = { "EPSV", "PASV", NULL };
+  int modeoff;
+
+#ifdef PF_INET6
+  if(!conn->bits.ftp_use_epsv && conn->bits.ipv6)
+    /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the
+       request and enable EPSV again! */
+    conn->bits.ftp_use_epsv = TRUE;
+#endif
+
+  modeoff = conn->bits.ftp_use_epsv?0:1;
+
+  result = Curl_nbftpsendf(conn, "%s", mode[modeoff]);
+  if(result)
+    return result;
+
+  ftp->count1 = modeoff;
+  state(conn, FTP_PASV);
+  infof(conn->data, "Connect data stream passively\n");
+
+  return result;
+}
+
+/* REST is the last command in the chain of commands when a "head"-like
+   request is made. Thus, if an actual transfer is to be made this is where
+   we take off for real. */
+static CURLcode ftp_state_post_rest(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+
+  if(ftp->no_transfer || conn->bits.no_body) {
+    /* then we're done with a "head"-like request, goto STOP */
+    state(conn, FTP_STOP);
+
+    /* doesn't transfer any data */
+    ftp->no_transfer = TRUE;
+  }
+  else if(data->set.ftp_use_port) {
+    /* We have chosen to use the PORT (or similar) command */
+    result = ftp_state_use_port(conn, EPRT);
+  }
+  else {
+    /* We have chosen (this is default) to use the PASV (or similar) command */
+    result = ftp_state_use_pasv(conn);
+  }
+  return result;
+}
+
+static CURLcode ftp_state_post_size(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+
+  if(ftp->no_transfer) {
+    /* if a "head"-like request is being made */
+
+    /* Determine if server can respond to REST command and therefore
+       whether it supports range */
+    NBFTPSENDF(conn, "REST %d", 0);
+
+    state(conn, FTP_REST);
+  }
+  else
+    result = ftp_state_post_rest(conn);
+
+  return result;
+}
+
+static CURLcode ftp_state_post_type(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+
+  if(ftp->no_transfer) {
+    /* if a "head"-like request is being made */
+
+    /* we know ftp->file is a valid pointer to a file name */
+    NBFTPSENDF(conn, "SIZE %s", ftp->file);
+
+    state(conn, FTP_SIZE);
+  }
+  else
+    result = ftp_state_post_size(conn);
+
+  return result;
+}
+
+static CURLcode ftp_state_post_listtype(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+
+  /* If this output is to be machine-parsed, the NLST command might be better
+     to use, since the LIST command output is not specified or standard in any
+     way. It has turned out that the NLST list output is not the same on all
+     servers either... */
+
+  NBFTPSENDF(conn, "%s",
+             data->set.customrequest?data->set.customrequest:
+             (data->set.ftp_list_only?"NLST":"LIST"));
+
+  state(conn, FTP_LIST);
+
+  return result;
+}
+
+static CURLcode ftp_state_post_retrtype(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+
+  /* We've sent the TYPE, now we must send the list of prequote strings */
+
+  result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE);
+
+  return result;
+}
+
+static CURLcode ftp_state_post_stortype(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+
+  /* We've sent the TYPE, now we must send the list of prequote strings */
 
-       2)Private (requested by 'PROT P')
-    */
-    if(!conn->ssl[SECONDARYSOCKET].use) {
-      FTPSENDF(conn, "PROT %c", 'P');
-      result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-      if(result)
-        return result;
+  result = ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE);
 
-      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;
+  return result;
+}
+
+static CURLcode ftp_state_post_mdtm(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+
+  /* If we have selected NOBODY and HEADER, it means that we only want file
+     information. Which in FTP can't be much more than the file size and
+     date. */
+  if(conn->bits.no_body && data->set.include_header && ftp->file) {
+    /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
+       may not support it! It is however the only way we have to get a file's
+       size! */
+
+    ftp->no_transfer = TRUE; /* this means no actual transfer will be made */
+
+    /* Some servers return different sizes for different modes, and thus we
+       must set the proper type before we check the size */
+    NBFTPSENDF(conn, "TYPE %c",
+               data->set.ftp_ascii?'A':'I');
+    state(conn, FTP_TYPE);
+  }
+  else
+    result = ftp_state_post_type(conn);
+
+  return result;
+}
+
+/* This is called after the CWD commands have been done in the beginning of
+   the DO phase */
+static CURLcode ftp_state_post_cwd(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+
+  /* Requested time of file or time-depended transfer? */
+  if((data->set.get_filetime || data->set.timecondition) && ftp->file) {
+
+    /* we have requested to get the modified-time of the file, this is a white
+       spot as the MDTM is not mentioned in RFC959 */
+    NBFTPSENDF(conn, "MDTM %s", ftp->file);
+
+    state(conn, FTP_MDTM);
+  }
+  else
+    result = ftp_state_post_mdtm(conn);
+
+  return result;
+}
+
+
+/* This is called after the TYPE and possible quote commands have been sent */
+static CURLcode ftp_state_ul_setup(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+  curl_off_t passed=0;
+
+  if(conn->resume_from) {
+    /* we're about to continue the uploading of a file */
+    /* 1. get already existing file's size. We use the SIZE command for this
+       which may not exist in the server!  The SIZE command is not in
+       RFC959. */
+
+    /* 2. This used to set REST. But since we can do append, we
+       don't another ftp command. We just skip the source file
+       offset and then we APPEND the rest on the file instead */
+
+    /* 3. pass file-size number of bytes in the source file */
+    /* 4. lower the infilesize counter */
+    /* => transfer as usual */
+
+    if(conn->resume_from < 0 ) {
+      /* Got no given size to start from, figure it out */
+      NBFTPSENDF(conn, "SIZE %s", ftp->file);
+      state(conn, FTP_STOR_SIZE);
+      return result;
+    }
+
+    /* enable append */
+    data->set.ftp_append = TRUE;
+
+    /* Let's read off the proper amount of bytes from the input. If we knew it
+       was a proper file we could've just fseek()ed but we only have a stream
+       here */
+
+    /* TODO: allow the ioctlfunction to provide a fast forward function that
+       can be used here and use this method only as a fallback! */
+    do {
+      curl_off_t readthisamountnow = (conn->resume_from - passed);
+      curl_off_t actuallyread;
+
+      if(readthisamountnow > BUFSIZE)
+        readthisamountnow = BUFSIZE;
+
+      actuallyread = (curl_off_t)
+        conn->fread(data->state.buffer, 1, (size_t)readthisamountnow,
+                    conn->fread_in);
+
+      passed += actuallyread;
+      if(actuallyread != readthisamountnow) {
+        failf(data, "Could only read %" FORMAT_OFF_T
+              " bytes from the input", passed);
+        return CURLE_FTP_COULDNT_USE_REST;
+      }
+    } while(passed != conn->resume_from);
+
+    /* now, decrease the size of the read */
+    if(data->set.infilesize>0) {
+      data->set.infilesize -= conn->resume_from;
+
+      if(data->set.infilesize <= 0) {
+        infof(data, "File already completely uploaded\n");
+
+        /* no data to transfer */
+        result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+
+        /* Set no_transfer so that we won't get any error in
+         * Curl_ftp_done() because we didn't transfer anything! */
+        ftp->no_transfer = TRUE;
+
+        state(conn, FTP_STOP);
+        return CURLE_OK;
+      }
     }
+    /* we've passed, proceed as normal */
+  } /* resume_from */
+
+  NBFTPSENDF(conn, data->set.ftp_append?"APPE %s":"STOR %s",
+             ftp->file);
+
+  state(conn, FTP_STOR);
+
+  return result;
+}
+
+static CURLcode ftp_state_quote(struct connectdata *conn,
+                                bool init,
+                                ftpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+  bool quote=FALSE;
+  struct curl_slist *item;
+
+  switch(instate) {
+  case FTP_QUOTE:
+  default:
+    item = data->set.quote;
+    break;
+  case FTP_RETR_PREQUOTE:
+  case FTP_STOR_PREQUOTE:
+    item = data->set.prequote;
+    break;
+  case FTP_POSTQUOTE:
+    item = data->set.postquote;
+    break;
   }
 
-  /* send PWD to discover our entry point */
-  FTPSENDF(conn, "PWD", NULL);
+  if(init)
+    ftp->count1 = 0;
+  else
+    ftp->count1++;
+
+  if(item) {
+    int i = 0;
+
+    /* Skip count1 items in the linked list */
+    while((i< ftp->count1) && item) {
+      item = item->next;
+      i++;
+    }
+    if(item) {
+      NBFTPSENDF(conn, "%s", item->data);
+      state(conn, instate);
+      quote = TRUE;
+    }
+  }
+
+  if(!quote) {
+    /* No more quote to send, continue to ... */
+    switch(instate) {
+    case FTP_QUOTE:
+    default:
+      result = ftp_state_cwd(conn);
+      break;
+    case FTP_RETR_PREQUOTE:
+      NBFTPSENDF(conn, "SIZE %s", ftp->file);
+      state(conn, FTP_RETR_SIZE);
+      break;
+    case FTP_STOR_PREQUOTE:
+      result = ftp_state_ul_setup(conn);
+      break;
+    case FTP_POSTQUOTE:
+      break;
+    }
+  }
+
+  return result;
+}
+
+static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
+                                    int ftpcode)
+{
+  struct FTP *ftp = conn->proto.ftp;
+  CURLcode result;
+  struct SessionHandle *data=conn->data;
+  Curl_addrinfo *conninfo;
+  struct Curl_dns_entry *addr=NULL;
+  int rc;
+  unsigned short connectport; /* the local port connect() should use! */
+  unsigned short newport=0; /* remote port */
+  bool connected;
+
+  /* newhost must be able to hold a full IP-style address in ASCII, which
+     in the IPv6 case means 5*8-1 = 39 letters */
+#define NEWHOST_BUFSIZE 48
+  char newhost[NEWHOST_BUFSIZE];
+  char *str=&data->state.buffer[4];  /* start on the first letter */
+
+  if((ftp->count1 == 0) &&
+     (ftpcode == 229)) {
+    /* positive EPSV response */
+    char *ptr = strchr(str, '(');
+    if(ptr) {
+      unsigned int num;
+      char separator[4];
+      ptr++;
+      if(5  == sscanf(ptr, "%c%c%c%u%c",
+                      &separator[0],
+                      &separator[1],
+                      &separator[2],
+                      &num,
+                      &separator[3])) {
+        char sep1 = separator[0];
+        int i;
+
+        /* The four separators should be identical, or else this is an oddly
+           formatted reply and we bail out immediately. */
+        for(i=1; i<4; i++) {
+          if(separator[i] != sep1) {
+            ptr=NULL; /* set to NULL to signal error */
+            break;
+          }
+        }
+        if(ptr) {
+          newport = num;
+
+          /* use the same IP we are already connected to */
+          snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str, newhost);
+        }
+      }
+      else
+        ptr=NULL;
+    }
+    if(!ptr) {
+      failf(data, "Weirdly formatted EPSV reply");
+      return CURLE_FTP_WEIRD_PASV_REPLY;
+    }
+  }
+  else if((ftp->count1 == 1) &&
+          (ftpcode == 227)) {
+    /* positive PASV response */
+    int ip[4];
+    int port[2];
+
+    /*
+     * Scan for a sequence of six comma-separated numbers and use them as
+     * IP+port indicators.
+     *
+     * Found reply-strings include:
+     * "227 Entering Passive Mode (127,0,0,1,4,51)"
+     * "227 Data transfer will passively listen to 127,0,0,1,4,51"
+     * "227 Entering passive mode. 127,0,0,1,4,51"
+     */
+    while(*str) {
+      if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d",
+                      &ip[0], &ip[1], &ip[2], &ip[3],
+                      &port[0], &port[1]))
+        break;
+      str++;
+    }
+
+    if(!*str) {
+      failf(data, "Couldn't interpret the 227-response");
+      return CURLE_FTP_WEIRD_227_FORMAT;
+    }
+
+    snprintf(newhost, sizeof(newhost),
+             "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
+    newport = (port[0]<<8) + port[1];
+  }
+  else if(ftp->count1 == 0) {
+    /* EPSV failed, move on to PASV */
+
+    /* disable it for next transfer */
+    conn->bits.ftp_use_epsv = FALSE;
+    infof(data, "disabling EPSV usage\n");
+
+    NBFTPSENDF(conn, "PASV", NULL);
+    ftp->count1++;
+    /* remain in the FTP_PASV state */
+    return result;
+  }
+  else {
+    failf(data, "Bad PASV/EPSV response: %03d", ftpcode);
+    return CURLE_FTP_WEIRD_PASV_REPLY;
+  }
+
+  /* we got OK from server */
+
+  if(data->change.proxy && *data->change.proxy) {
+    /*
+     * This is a tunnel through a http proxy and we need to connect to the
+     * proxy again here.
+     *
+     * We don't want to rely on a former host lookup that might've expired
+     * now, instead we remake the lookup here and now!
+     */
+    rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr);
+    if(rc == CURLRESOLV_PENDING)
+      /* BLOCKING */
+      rc = Curl_wait_for_resolv(conn, &addr);
+
+    connectport =
+      (unsigned short)conn->port; /* we connect to the proxy's port */
+
+  }
+  else {
+    /* normal, direct, ftp connection */
+    rc = Curl_resolv(conn, newhost, newport, &addr);
+    if(rc == CURLRESOLV_PENDING)
+      /* BLOCKING */
+      rc = Curl_wait_for_resolv(conn, &addr);
+
+    if(!addr) {
+      failf(data, "Can't resolve new host %s:%d", newhost, newport);
+      return CURLE_FTP_CANT_GET_HOST;
+    }
+    connectport = newport; /* we connect to the remote port */
+  }
+
+  result = Curl_connecthost(conn,
+                            addr,
+                            &conn->sock[SECONDARYSOCKET],
+                            &conninfo,
+                            &connected);
+
+  Curl_resolv_unlock(data, addr); /* we're done using this address */
 
-  /* wait for feedback */
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
   if(result)
     return result;
 
-  if(ftpcode == 257) {
-    char *dir = (char *)malloc(nread+1);
-    char *store=dir;
-    char *ptr=&buf[4]; /* start on the first letter */
+  conn->bits.tcpconnect = connected; /* simply TRUE or FALSE */
 
-    if(!dir)
-      return CURLE_OUT_OF_MEMORY;
+  /*
+   * When this is used from the multi interface, this might've returned with
+   * the 'connected' set to FALSE and thus we are now awaiting a non-blocking
+   * connect to connect and we should not be "hanging" here waiting.
+   */
+
+  if(data->set.verbose)
+    /* this just dumps information about this second connection */
+    ftp_pasv_verbose(conn, conninfo, newhost, connectport);
+
+#ifndef CURL_DISABLE_HTTP
+  if(conn->bits.tunnel_proxy) {
+    /* FIX: this MUST wait for a proper connect first if 'connected' is
+     * FALSE */
+
+    /* BLOCKING */
+    /* We want "seamless" FTP operations through HTTP proxy tunnel */
+    result = Curl_ConnectHTTPProxyTunnel(conn, SECONDARYSOCKET,
+                                         newhost, newport);
+    if(CURLE_OK != result)
+      return result;
+  }
+#endif   /* CURL_DISABLE_HTTP */
+
+  state(conn, FTP_STOP); /* this phase is completed */
+
+  return result;
+}
+
+static CURLcode ftp_state_port_resp(struct connectdata *conn,
+                                    int ftpcode)
+{
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+  ftpport fcmd = ftp->count1;
+  CURLcode result = CURLE_OK;
 
-    /* Reply format is like
-       257<space>"<directory-name>"<space><commentary> and the RFC959 says
+  if(ftpcode != 200) {
+    /* the command failed */
 
-       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);
+    if (EPRT == fcmd) {
+      infof(data, "disabling EPRT usage\n");
+      conn->bits.ftp_use_eprt = FALSE;
     }
-    else {
-      /* couldn't get the path */
-      free(dir);
-      infof(data, "Failed to figure out path\n");
+    else if (LPRT == fcmd) {
+      infof(data, "disabling LPRT usage\n");
+      conn->bits.ftp_use_lprt = FALSE;
     }
+    fcmd++;
 
+    if(fcmd == DONE) {
+      failf(data, "Failed to do PORT");
+      result = CURLE_FTP_PORT_FAILED;
+    }
+    else
+      /* try next */
+      result = ftp_state_use_port(conn, fcmd);
   }
   else {
-    /* We couldn't read the PWD response! */
+    infof(data, "Connect data stream actively\n");
+    state(conn, FTP_STOP); /* end of DO phase */
   }
 
-  return CURLE_OK;
+  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)
+static CURLcode ftp_state_mdtm_resp(struct connectdata *conn,
+                                    int ftpcode)
 {
-  struct SessionHandle *data = conn->data;
+  CURLcode result = CURLE_OK;
   struct FTP *ftp = conn->proto.ftp;
-  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);
-
-  path = curl_unescape(conn->path, 0); /* get the "raw" path */
-  if(!path)
-    return CURLE_OUT_OF_MEMORY;
+  struct SessionHandle *data=conn->data;
 
-  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);
+  switch(ftpcode) {
+  case 213:
+    {
+      /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the
+         last .sss part is optional and means fractions of a second */
+      int year, month, day, hour, minute, second;
+      char *buf = data->state.buffer;
+      if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d",
+                     &year, &month, &day, &hour, &minute, &second)) {
+        /* we have a time, reformat it */
+        time_t secs=time(NULL);
+        /* 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 = curl_getdate(buf, &secs);
+      }
 
-  ftp->ctl_valid = FALSE;
+      /* 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. */
 
-  if(data->set.upload) {
-    if((-1 != data->set.infilesize) &&
-       (data->set.infilesize != *ftp->bytecountp) &&
-       !data->set.crlf) {
-      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;
+#ifdef HAVE_STRFTIME
+      if(data->set.get_filetime && (data->info.filetime>=0) ) {
+        struct tm *tm;
+        time_t clock = (time_t)data->info.filetime;
+#ifdef HAVE_GMTIME_R
+        struct tm buffer;
+        tm = (struct tm *)gmtime_r(&clock, &buffer);
+#else
+        tm = gmtime(&clock);
+#endif
+        /* format: "Tue, 15 Nov 1994 12:45:26" */
+        strftime(buf, BUFSIZE-1,
+                 "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n", tm);
+        result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
+        if(result)
+          return result;
+      }
+#endif
     }
+    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;
   }
-  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;
+
+  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->no_transfer = TRUE; /* 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->no_transfer = TRUE; /* mark this to not transfer data */
+          state(conn, FTP_STOP);
+          return CURLE_OK;
+        }
+        break;
+      } /* switch */
     }
-    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;
+    else {
+      infof(data, "Skipping time comparison\n");
     }
   }
 
-  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;
-  }
+  if(!result)
+    result = ftp_state_post_mdtm(conn);
 
-#ifdef HAVE_KRB4
-  Curl_sec_fflush_fd(conn, conn->sock[SECONDARYSOCKET]);
-#endif
+  return result;
+}
 
-  /* shut down the socket to inform the server we're done */
+static CURLcode ftp_state_type_resp(struct connectdata *conn,
+                                    int ftpcode,
+                                    ftpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data=conn->data;
 
-#ifdef _WIN32_WCE
-  shutdown(conn->sock[SECONDARYSOCKET],2);  /* SD_BOTH */
-#endif
+  if(ftpcode != 200) {
+    failf(data, "Couldn't set desired mode");
+    return CURLE_FTP_COULDNT_SET_BINARY; /* FIX */
+  }
+  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);
 
-  sclose(conn->sock[SECONDARYSOCKET]);
+  return result;
+}
 
-  conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
+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 *ftp = conn->proto.ftp;
 
-  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 */
+  if (data->set.max_filesize && (filesize > data->set.max_filesize)) {
+    failf(data, "Maximum file size exceeded");
+    return CURLE_FILESIZE_EXCEEDED;
+  }
+  ftp->downloadsize = filesize;
 
-    result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+  if(conn->resume_from) {
+    /* 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.  */
+      if(conn->resume_from< 0) {
+        /* We're supposed to download the last abs(from) bytes */
+        if(filesize < -conn->resume_from) {
+          failf(data, "Offset (%" FORMAT_OFF_T
+                ") was beyond file size (%" FORMAT_OFF_T ")",
+                conn->resume_from, filesize);
+          return CURLE_BAD_DOWNLOAD_RESUME;
+        }
+        /* convert to size to download */
+        ftp->downloadsize = -conn->resume_from;
+        /* download from where? */
+        conn->resume_from = filesize - ftp->downloadsize;
+      }
+      else {
+        if(filesize < conn->resume_from) {
+          failf(data, "Offset (%" FORMAT_OFF_T
+                ") was beyond file size (%" FORMAT_OFF_T ")",
+                conn->resume_from, filesize);
+          return CURLE_BAD_DOWNLOAD_RESUME;
+        }
+        /* Now store the number of bytes we are expected to download */
+        ftp->downloadsize = filesize-conn->resume_from;
+      }
+    }
 
-    ftp->response_time = 3600; /* set this back to one hour waits */
+    if(ftp->downloadsize == 0) {
+      /* no data to transfer */
+      result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+      infof(data, "File already completely downloaded\n");
 
-    if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) {
-      failf(data, "control connection looks dead");
-      return result;
+      /* 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;
     }
 
-    if(result)
-      return result;
+    /* Set resume file transfer offset */
+    infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T
+          "\n", conn->resume_from);
 
-    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;
-      }
-    }
-  }
+    NBFTPSENDF(conn, "REST %" FORMAT_OFF_T, conn->resume_from);
 
-  /* clear these for next connection */
-  ftp->no_transfer = FALSE;
-  ftp->dont_check = FALSE;
+    state(conn, FTP_RETR_REST);
 
-  if (!result && conn->sec_conn) {   /* 3rd party transfer */
-    /* "done" with the secondary connection */
-    result = Curl_ftp_done(conn->sec_conn, status);
   }
-
-  /* Send any post-transfer QUOTE strings? */
-  if(!status && !result && data->set.postquote)
-    result = ftp_sendquote(conn, data->set.postquote);
+  else {
+    /* no resume */
+    NBFTPSENDF(conn, "RETR %s", ftp->file);
+    state(conn, FTP_RETR);
+  }
 
   return result;
 }
 
-/***********************************************************************
- *
- * 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)
+static CURLcode ftp_state_size_resp(struct connectdata *conn,
+                                    int ftpcode,
+                                    ftpstate instate)
 {
-  struct curl_slist *item;
-  ssize_t nread;
-  int ftpcode;
-  CURLcode result;
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data=conn->data;
+  curl_off_t filesize;
+  char *buf = data->state.buffer;
 
-  item = quote;
-  while (item) {
-    if (item->data) {
-      FTPSENDF(conn, "%s", item->data);
+  /* get the size from the ascii string: */
+  filesize = (ftpcode == 213)?curlx_strtoofft(buf+4, NULL, 0):-1;
 
-      result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-      if (result)
+  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(data, CLIENTWRITE_BOTH, buf, 0);
+      if(result)
         return result;
-
-      if (ftpcode >= 400) {
-        failf(conn->data, "QUOT string not accepted: %s", item->data);
-        return CURLE_FTP_QUOTE_ERROR;
-      }
     }
-
-    item = item->next;
+    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) {
+    conn->resume_from = filesize;
+    result = ftp_state_ul_setup(conn);
   }
 
-  return CURLE_OK;
+  return result;
 }
 
-/***********************************************************************
- *
- * ftp_getfiletime()
- *
- * Get the timestamp of the given file.
- */
-static
-CURLcode ftp_getfiletime(struct connectdata *conn, char *file)
+static CURLcode ftp_state_rest_resp(struct connectdata *conn,
+                                    int ftpcode,
+                                    ftpstate instate)
 {
-  CURLcode result=CURLE_OK;
-  int ftpcode; /* for ftp status */
-  ssize_t nread;
-  char *buf = conn->data->state.buffer;
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
 
-  /* we have requested to get the modified-time of the file, this is yet
-     again a grey area as the MDTM is not kosher RFC959 */
-  FTPSENDF(conn, "MDTM %s", file);
+  switch(instate) {
+  case FTP_REST:
+  default:
+    if (ftpcode == 350) {
+      result = Curl_client_write(conn->data, CLIENTWRITE_BOTH,
+                               (char *)"Accept-ranges: bytes\r\n", 0);
+      if(result)
+        return result;
+    }
 
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
+    result = ftp_state_post_rest(conn);
+    break;
 
-  switch(ftpcode) {
-  case 213:
-    {
-      /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the
-         last .sss part is optional and means fractions of a second */
-      int year, month, day, hour, minute, second;
-      if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d",
-                     &year, &month, &day, &hour, &minute, &second)) {
-        /* we have a time, reformat it */
-        time_t secs=time(NULL);
-        /* 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: */
-        conn->data->info.filetime = curl_getdate(buf, &secs);
-      }
+  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);
     }
-    break;
-  default:
-    infof(conn->data, "unsupported MDTM reply format\n");
-    break;
-  case 550: /* "No such file or directory" */
-    failf(conn->data, "Given file does not exist");
-    result = CURLE_FTP_COULDNT_RETR_FILE;
     break;
   }
-  return  result;
+
+  return result;
 }
 
-/***********************************************************************
- *
- * 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)
+static CURLcode ftp_state_stor_resp(struct connectdata *conn,
+                                    int ftpcode)
 {
+  CURLcode result = CURLE_OK;
   struct SessionHandle *data = conn->data;
-  int ftpcode;
-  ssize_t nread;
-  CURLcode result;
+  struct FTP *ftp = conn->proto.ftp;
 
-  FTPSENDF(conn, "TYPE %s", ascii?"A":"I");
+  if(ftpcode>=400) {
+    failf(data, "Failed FTP upload: %0d", ftpcode);
+    /* oops, we never close the sockets! */
+    return CURLE_FTP_COULDNT_STOR_FILE;
+  }
 
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
+  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(ftpcode != 200) {
-    failf(data, "Couldn't set %s mode",
-          ascii?"ASCII":"binary");
-    return ascii? CURLE_FTP_COULDNT_SET_ASCII:CURLE_FTP_COULDNT_SET_BINARY;
+  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_SSLConnect(conn, SECONDARYSOCKET);
+    if(result)
+      return result;
   }
 
-  return CURLE_OK;
-}
+  *(ftp->bytecountp)=0;
 
-/***********************************************************************
- *
- * ftp_getsize()
- *
- * Returns the file size (in bytes) of the given remote file.
- */
+  /* When we know we're uploading a specified file, we can get the file
+     size prior to the actual upload. */
 
-static
-CURLcode ftp_getsize(struct connectdata *conn, char *file,
-                     curl_off_t *size)
+  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)
 {
+  CURLcode result = CURLE_OK;
   struct SessionHandle *data = conn->data;
-  int ftpcode;
-  ssize_t nread;
-  char *buf=data->state.buffer;
-  CURLcode result;
+  struct FTP *ftp = conn->proto.ftp;
+  char *buf = data->state.buffer;
 
-  FTPSENDF(conn, "SIZE %s", file);
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
+  if((ftpcode == 150) || (ftpcode == 125)) {
 
-  if(ftpcode == 213) {
-    /* get the size from the ascii string: */
-    *size = curlx_strtoofft(buf+4, NULL, 0);
-  }
-  else
-    return CURLE_FTP_COULDNT_GET_SIZE;
+    /*
+      A;
+      150 Opening BINARY mode data connection for /etc/passwd (2241
+      bytes).  (ok, the file is being transfered)
 
-  return CURLE_OK;
-}
+      B:
+      150 Opening ASCII mode data connection for /bin/ls
 
-/***************************************************************************
- *
- * ftp_pasv_verbose()
- *
- * This function only outputs some informationals about this second connection
- * when we've issued a PASV command before and thus we have connected to a
- * possibly new IP address.
- *
- */
-static void
-ftp_pasv_verbose(struct connectdata *conn,
-                 Curl_addrinfo *ai,
-                 char *newhost, /* ascii version */
-                 int port)
-{
-  char buf[256];
-  Curl_printable_address(ai, buf, sizeof(buf));
-  infof(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port);
-}
+      C:
+      150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes).
 
-/***********************************************************************
- *
- * ftp_use_port()
- *
- * Send the proper PORT command. PORT is the ftp client's way of telling the
- * server that *WE* open a port that we listen on an awaits the server to
- * connect to. This is the opposite of PASV.
- */
+      D:
+      150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes).
 
-static
-CURLcode ftp_use_port(struct connectdata *conn)
-{
-  struct SessionHandle *data=conn->data;
-  curl_socket_t portsock= CURL_SOCKET_BAD;
-  ssize_t nread;
-  int ftpcode; /* receive FTP response codes in this */
-  CURLcode result;
+      E:
+      125 Data connection already open; Transfer starting. */
 
-#ifdef ENABLE_IPV6
-  /******************************************************************
-   *
-   * Here's a piece of IPv6-specific code coming up
-   *
-   */
+    curl_off_t size=-1; /* default unknown size */
 
-  struct addrinfo *res, *ai;
-  struct sockaddr_storage ss;
-  socklen_t sslen;
-  char hbuf[NI_MAXHOST]="";
-  struct sockaddr *sa=(struct sockaddr *)&ss;
-  unsigned char *ap;
-  unsigned char *pp;
-  char portmsgbuf[1024], tmp[1024];
-  enum ftpcommand { EPRT, LPRT, PORT, DONE } fcmd;
-  const char *mode[] = { "EPRT", "LPRT", "PORT", NULL };
-  int rc;
-  int error;
-  char *host=NULL;
-  struct Curl_dns_entry *h=NULL;
 
-  if(data->set.ftpport && (strlen(data->set.ftpport) > 1)) {
-    /* attempt to get the address of the given interface name */
-    if(!Curl_if2ip(data->set.ftpport, hbuf, sizeof(hbuf)))
-      /* not an interface, use the given string as host name instead */
-      host = data->set.ftpport;
-    else
-      host = hbuf; /* use the hbuf for host name */
-  } /* data->set.ftpport */
+    /*
+     * 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(!host) {
-    /* not an interface and not a host name, get default by extracting
-       the IP from the control connection */
+    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--) {
+        long in=bytes-buf;
+        /* 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;
 
-    sslen = sizeof(ss);
-    rc = getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen);
-    if(rc < 0) {
-      failf(data, "getsockname() returned %d\n", rc);
-      return CURLE_FTP_PORT_FAILED;
+    if(data->set.ftp_use_port) {
+      /* BLOCKING */
+      result = AllowServerConnect(conn);
+      if( result )
+        return result;
     }
 
-    rc = getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL,
-                     0, NIFLAGS);
-    if(rc) {
-      failf(data, "getnameinfo() returned %d\n", rc);
-      return CURLE_FTP_PORT_FAILED;
+    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_SSLConnect(conn, SECONDARYSOCKET);
+      if(result)
+        return result;
     }
-    host = hbuf; /* use this host name */
-  }
 
-  rc = Curl_resolv(conn, host, 0, &h);
-  if(rc == CURLRESOLV_PENDING)
-    rc = Curl_wait_for_resolv(conn, &h);
-  if(h) {
-    res = h->addr;
-    /* when we return from this function, we can forget about this entry
-       to we can unlock it now already */
-    Curl_resolv_unlock(data, h);
-  } /* (h) */
-  else
-    res = NULL; /* failure! */
+    if(size > conn->maxdownload && conn->maxdownload > 0)
+      size = conn->size = conn->maxdownload;
 
-  portsock = CURL_SOCKET_BAD;
-  error = 0;
-  for (ai = res; ai; ai = ai->ai_next) {
-    /*
-     * Workaround for AIX5 getaddrinfo() problem (it doesn't set ai_socktype):
-     */
-    if (ai->ai_socktype == 0)
-      ai->ai_socktype = SOCK_STREAM;
+    if(instate != FTP_LIST)
+      infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
 
-    portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-    if (portsock == CURL_SOCKET_BAD) {
-      error = Curl_ourerrno();
-      continue;
+    /* 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 */
     }
-    if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) {
-      error = Curl_ourerrno();
-      sclose(portsock);
-      portsock = CURL_SOCKET_BAD;
-      continue;
+    else {
+      failf(data, "%s", buf+4);
+      return CURLE_FTP_COULDNT_RETR_FILE;
     }
+  }
 
-    if (listen(portsock, 1) < 0) {
-      error = Curl_ourerrno();
-      sclose(portsock);
-      portsock = CURL_SOCKET_BAD;
-      continue;
+  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
+  /* We are logged in with Kerberos, now 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 by none available");
+      result = CURLE_LOGIN_DENIED;
     }
-
-    break;
   }
+  else {
+    /* All other response codes, like:
 
-  if (portsock == CURL_SOCKET_BAD) {
-    failf(data, "%s", Curl_strerror(conn,error));
-    return CURLE_FTP_PORT_FAILED;
+    530 User ... access denied
+    (the server denies to log the specified user) */
+    failf(data, "Access denied: %03d", ftpcode);
+    result = CURLE_LOGIN_DENIED;
   }
+  return result;
+}
 
-  sslen = sizeof(ss);
-  if (getsockname(portsock, sa, &sslen) < 0) {
-    failf(data, "%s", Curl_strerror(conn,Curl_ourerrno()));
-    return CURLE_FTP_PORT_FAILED;
+/* 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 */
   }
+  else
+    result = ftp_state_loggedin(conn);
 
-#ifdef PF_INET6
-  if(!conn->bits.ftp_use_eprt && conn->bits.ipv6)
-    /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the
-       request and enable EPRT again! */
-    conn->bits.ftp_use_eprt = TRUE;
-#endif
+  return result;
+}
 
-  for (fcmd = EPRT; fcmd != DONE; fcmd++) {
-    int lprtaf, eprtaf;
-    int alen=0, plen=0;
 
-    if(!conn->bits.ftp_use_eprt && (EPRT == fcmd))
-      /* if disabled, goto next */
-      continue;
+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(!conn->bits.ftp_use_lprt && (LPRT == fcmd))
-      /* if disabled, goto next */
-      continue;
+  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;
 
-    switch (sa->sa_family) {
-    case AF_INET:
-      ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr;
-      alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr);
-      pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port;
-      plen = sizeof(((struct sockaddr_in *)&ss)->sin_port);
-      lprtaf = 4;
-      eprtaf = 1;
-      break;
-    case AF_INET6:
-      ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr;
-      alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr);
-      pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port;
-      plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port);
-      lprtaf = 6;
-      eprtaf = 2;
-      break;
-    default:
-      ap = pp = NULL;
-      lprtaf = eprtaf = -1;
-      break;
+    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;
+  }
 
-    if (EPRT == fcmd) {
-      if (eprtaf < 0)
-        continue;
-      if (getnameinfo((struct sockaddr *)&ss, sslen,
-                      portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp),
-                      NIFLAGS))
-        continue;
+  /* we read a piece of response */
+  result = ftp_readresp(sock, conn, &ftpcode, &nread);
+  if(result)
+    return result;
 
-      /* do not transmit IPv6 scope identifier to the wire */
-      if (sa->sa_family == AF_INET6) {
-        char *q = strchr(portmsgbuf, '%');
-        if (q)
-          *q = '\0';
+  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;
       }
 
-      result = Curl_ftpsendf(conn, "%s |%d|%s|%s|", mode[fcmd], eprtaf,
-                             portmsgbuf, tmp);
-      if(result)
-        return result;
-    }
-    else if ((LPRT == fcmd) || (PORT == fcmd)) {
-      int i;
-
-      if ((LPRT == fcmd) && lprtaf < 0)
-        continue;
-      if ((PORT == fcmd) && sa->sa_family != AF_INET)
-        continue;
+      /* 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. */
 
-      portmsgbuf[0] = '\0';
-      if (LPRT == fcmd) {
-        snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen);
-        if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
-            sizeof(portmsgbuf)) {
-          continue;
-        }
-      }
+        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);
 
-      for (i = 0; i < alen; i++) {
-        if (portmsgbuf[0])
-          snprintf(tmp, sizeof(tmp), ",%u", ap[i]);
+        if(Curl_sec_login(conn) != 0)
+          infof(data, "Logging in with password in cleartext!\n");
         else
-          snprintf(tmp, sizeof(tmp), "%u", ap[i]);
+          infof(data, "Authentication successful\n");
+      }
+#endif
 
-        if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
-            sizeof(portmsgbuf)) {
-          continue;
+      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);
       }
-
-      if (LPRT == fcmd) {
-        snprintf(tmp, sizeof(tmp), ",%d", plen);
-
-        if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf))
-          continue;
+      else {
+        ftp_state_user(conn);
+        if(result)
+          return result;
       }
 
-      for (i = 0; i < plen; i++) {
-        snprintf(tmp, sizeof(tmp), ",%u", pp[i]);
-
-        if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
-            sizeof(portmsgbuf)) {
-          continue;
-        }
-      }
+      break;
 
-      result = Curl_ftpsendf(conn, "%s %s", mode[fcmd], portmsgbuf);
-      if(result)
-        return result;
-    }
+    case FTP_AUTH:
+      /* we have gotten the response to a previous AUTH command */
 
-    result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-    if(result)
-      return result;
+      /* 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 != 200) {
-      if (EPRT == fcmd) {
-        infof(data, "disabling EPRT usage\n");
-        conn->bits.ftp_use_eprt = FALSE;
+      if((ftpcode == 234) || (ftpcode == 334)) {
+        /* Curl_SSLConnect is BLOCKING */
+        result = Curl_SSLConnect(conn, FIRSTSOCKET);
+        if(result)
+          return result;
+        conn->protocol |= PROT_FTPS;
+        conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */
       }
-      else if (LPRT == fcmd) {
-        infof(data, "disabling LPRT usage\n");
-        conn->bits.ftp_use_lprt = FALSE;
+      else if(ftp->count3 < 1) {
+        ftp->count3++;
+        ftp->count1 += ftp->count2; /* get next attempt */
+        NBFTPSENDF(conn, "AUTH %s", ftpauth[ftp->count1]);
+        /* remain in this same state */
+      }
+      else {
+        result = ftp_state_user(conn);
+        if(result)
+          return result;
       }
-      continue;
-    }
-    else
       break;
-  }
-
-  if (fcmd == DONE) {
-    sclose(portsock);
-    failf(data, "PORT command attempts failed");
-    return CURLE_FTP_PORT_FAILED;
-  }
-  /* we set the secondary socket variable to this for now, it
-     is only so that the cleanup function will close it in case
-     we fail before the true secondary stuff is made */
-  conn->sock[SECONDARYSOCKET] = portsock;
-
-#else
-  /******************************************************************
-   *
-   * Here's a piece of IPv4-specific code coming up
-   *
-   */
-  struct sockaddr_in sa;
-  unsigned short porttouse;
-  char myhost[256] = "";
-  bool sa_filled_in = FALSE;
-  Curl_addrinfo *addr = NULL;
-  unsigned short ip[4];
 
-  if(data->set.ftpport) {
-    in_addr_t in;
+    case FTP_USER:
+    case FTP_PASS:
+      result = ftp_state_user_resp(conn, ftpcode, ftp->state);
+      break;
 
-    /* First check if the given name is an IP address */
-    in=inet_addr(data->set.ftpport);
+    case FTP_ACCT:
+      result = ftp_state_acct_resp(conn, ftpcode);
+      break;
 
-    if(in != CURL_INADDR_NONE)
-      /* this is an IPv4 address */
-      addr = Curl_ip2addr(in, data->set.ftpport, 0);
-    else {
-      if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) {
-        /* The interface to IP conversion provided a dotted address */
-        in=inet_addr(myhost);
-        addr = Curl_ip2addr(in, myhost, 0);
-      }
-      else if(strlen(data->set.ftpport)> 1) {
-        /* might be a host name! */
-        struct Curl_dns_entry *h=NULL;
-        int rc = Curl_resolv(conn, myhost, 0, &h);
-        if(rc == CURLRESOLV_PENDING)
-          rc = Curl_wait_for_resolv(conn, &h);
-        if(h) {
-          addr = h->addr;
-          /* when we return from this function, we can forget about this entry
-             to we can unlock it now already */
-          Curl_resolv_unlock(data, h);
-        } /* (h) */
-      } /* strlen */
-    } /* CURL_INADDR_NONE */
-  } /* data->set.ftpport */
+    case FTP_PBSZ:
+      /* FIX: check response code */
 
-  if(!addr) {
-    /* pick a suitable default here */
+      /* For TLS, the data connection can have one of two security levels.
 
-    socklen_t sslen;
+      1) Clear (requested by 'PROT C')
 
-    sslen = sizeof(sa);
-    if (getsockname(conn->sock[FIRSTSOCKET],
-                    (struct sockaddr *)&sa, &sslen) < 0) {
-      failf(data, "getsockname() failed");
-      return CURLE_FTP_PORT_FAILED;
-    }
+      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;
+      }
 
-    sa_filled_in = TRUE; /* the sa struct is filled in */
-  }
+      break;
 
-  if (addr || sa_filled_in) {
-    portsock = socket(AF_INET, SOCK_STREAM, 0);
-    if(CURL_SOCKET_BAD != portsock) {
-      socklen_t size;
+    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;
 
-      /* we set the secondary socket variable to this for now, it
-         is only so that the cleanup function will close it in case
-         we fail before the true secondary stuff is made */
-      conn->sock[SECONDARYSOCKET] = portsock;
+      result = ftp_state_pwd(conn);
+      if(result)
+        return result;
+      break;
 
-      if(!sa_filled_in) {
-        memcpy(&sa, addr->ai_addr, sizeof(sa));
-        sa.sin_addr.s_addr = INADDR_ANY;
-      }
+    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 */
 
-      sa.sin_port = 0;
-      size = sizeof(sa);
+        if(!dir)
+          return CURLE_OUT_OF_MEMORY;
 
-      if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
-        /* we succeeded to bind */
-        struct sockaddr_in add;
-        socklen_t socksize = sizeof(add);
+        /* Reply format is like
+           257<space>"<directory-name>"<space><commentary> and the RFC959
+           says
 
-        if(getsockname(portsock, (struct sockaddr *) &add,
-                       &socksize)<0) {
-          failf(data, "getsockname() failed");
-          return CURLE_FTP_PORT_FAILED;
+           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);
         }
-        porttouse = ntohs(add.sin_port);
-
-        if ( listen(portsock, 1) < 0 ) {
-          failf(data, "listen(2) failed on socket");
-          return CURLE_FTP_PORT_FAILED;
+        else {
+          /* couldn't get the path */
+          free(dir);
+          infof(data, "Failed to figure out path\n");
         }
       }
-      else {
-        failf(data, "bind(2) failed on socket");
-        return CURLE_FTP_PORT_FAILED;
+      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;
       }
-    }
-    else {
-      failf(data, "socket(2) failed (%s)");
-      return CURLE_FTP_PORT_FAILED;
-    }
-  }
-  else {
-    failf(data, "could't find IP address to use");
-    return CURLE_FTP_PORT_FAILED;
-  }
+      result = ftp_state_quote(conn, FALSE, ftp->state);
+      if(result)
+        return result;
 
-  if(sa_filled_in)
-    Curl_inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr,
-                   myhost, sizeof(myhost));
-  else
-    Curl_printable_address(addr, myhost, sizeof(myhost));
+      break;
 
-  if(4 == sscanf(myhost, "%hu.%hu.%hu.%hu",
-                 &ip[0], &ip[1], &ip[2], &ip[3])) {
+    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;
 
-    infof(data, "Telling server to connect to %d.%d.%d.%d:%d\n",
-          ip[0], ip[1], ip[2], ip[3], porttouse);
+    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;
 
-    result=Curl_ftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d",
-                         ip[0], ip[1], ip[2], ip[3],
-                         porttouse >> 8,
-                         porttouse & 255);
-    if(result)
-      return result;
+    case FTP_MDTM:
+      result = ftp_state_mdtm_resp(conn, ftpcode);
+      break;
 
-  }
-  else
-    return CURLE_FTP_PORT_FAILED;
+    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;
 
-  Curl_freeaddrinfo(addr);
+    case FTP_SIZE:
+    case FTP_RETR_SIZE:
+    case FTP_STOR_SIZE:
+      result = ftp_state_size_resp(conn, ftpcode, ftp->state);
+      break;
 
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
+    case FTP_REST:
+    case FTP_RETR_REST:
+      result = ftp_state_rest_resp(conn, ftpcode, ftp->state);
+      break;
 
-  if(ftpcode != 200) {
-    failf(data, "Server does not grok PORT, try without it!");
-    return CURLE_FTP_PORT_FAILED;
-  }
-#endif /* end of ipv4-specific code */
+    case FTP_PASV:
+      result = ftp_state_pasv_resp(conn, ftpcode);
+      break;
 
-  return CURLE_OK;
-}
+    case FTP_PORT:
+      result = ftp_state_port_resp(conn, ftpcode);
+      break;
 
-/***********************************************************************
- *
- * ftp_use_pasv()
- *
- * Send the PASV command. PASV is the ftp client's way of asking the server to
- * open a second port that we can connect to (for the data transfer). This is
- * the opposite of PORT.
- */
+    case FTP_LIST:
+    case FTP_RETR:
+      result = ftp_state_get_resp(conn, ftpcode, ftp->state);
+      break;
 
-static
-CURLcode ftp_use_pasv(struct connectdata *conn,
-                      bool *connected)
-{
-  struct SessionHandle *data = conn->data;
-  ssize_t nread;
-  char *buf = data->state.buffer; /* this is our buffer */
-  int ftpcode; /* receive FTP response codes in this */
-  CURLcode result;
-  struct Curl_dns_entry *addr=NULL;
-  Curl_addrinfo *conninfo;
-  int rc;
+    case FTP_STOR:
+      result = ftp_state_stor_resp(conn, ftpcode);
+      break;
 
-  /*
-    Here's the excecutive summary on what to do:
+    case FTP_QUIT:
+      /* fallthrough, just stop! */
+    default:
+      /* internal error */
+      state(conn, FTP_STOP);
+      break;
+    }
+  } /* if(ftpcode) */
 
-    PASV is RFC959, expect:
-    227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)
+  return result;
+}
 
-    LPSV is RFC1639, expect:
-    228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2)
+/* 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 */
 
-    EPSV is RFC2428, expect:
-    229 Entering Extended Passive Mode (|||port|)
+  return timeout_ms;
+}
 
-  */
 
-  const char *mode[] = { "EPSV", "PASV", NULL };
-  int results[] = { 229, 227, 0 };
-  int modeoff;
-  unsigned short connectport; /* the local port connect() should use! */
-  unsigned short newport=0; /* remote port, not necessary the local one */
+/* 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);
 
-  /* newhost must be able to hold a full IP-style address in ASCII, which
-     in the IPv6 case means 5*8-1 = 39 letters */
-#define NEWHOST_BUFSIZE 48
-  char newhost[NEWHOST_BUFSIZE];
+  *done = FALSE; /* default to not done yet */
 
-#ifdef PF_INET6
-  if(!conn->bits.ftp_use_epsv && conn->bits.ipv6)
-    /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the
-       request and enable EPSV again! */
-    conn->bits.ftp_use_epsv = TRUE;
-#endif
+  if(timeout_ms <= 0) {
+    failf(data, "FTP response timeout");
+    return CURLE_OPERATION_TIMEDOUT;
+  }
 
-  for (modeoff = (conn->bits.ftp_use_epsv?0:1);
-       mode[modeoff]; modeoff++) {
-    result = Curl_ftpsendf(conn, "%s", mode[modeoff]);
-    if(result)
-      return result;
-    result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-    if(result)
-      return result;
-    if (ftpcode == results[modeoff])
-      break;
+  rc = Curl_select(ftp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
+                   ftp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
+                   0);
 
-    if(modeoff == 0) {
-      /* EPSV is not supported, disable it for next transfer */
-      conn->bits.ftp_use_epsv = FALSE;
-      infof(data, "disabling EPSV usage\n");
-    }
+  if(rc == -1) {
+    failf(data, "select error");
+    return CURLE_OUT_OF_MEMORY;
   }
-
-  if (!mode[modeoff]) {
-    failf(data, "Odd return code after PASV");
-    return CURLE_FTP_WEIRD_PASV_REPLY;
+  else if(rc != 0) {
+    result = ftp_statemach_act(conn);
+    *done = (ftp->state == FTP_STOP);
   }
-  else if (227 == results[modeoff]) {
-    int ip[4];
-    int port[2];
-    char *str=buf;
+  /* if rc == 0, then select() timed out */
 
-    /*
-     * New 227-parser June 3rd 1999.
-     * It now scans for a sequence of six comma-separated numbers and
-     * will take them as IP+port indicators.
-     *
-     * Found reply-strings include:
-     * "227 Entering Passive Mode (127,0,0,1,4,51)"
-     * "227 Data transfer will passively listen to 127,0,0,1,4,51"
-     * "227 Entering passive mode. 127,0,0,1,4,51"
-     */
+  return result;
+}
 
-    while(*str) {
-      if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d",
-                      &ip[0], &ip[1], &ip[2], &ip[3],
-                      &port[0], &port[1]))
-        break;
-      str++;
-    }
+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;
 
-    if(!*str) {
-      failf(data, "Couldn't interpret the 227-reply");
-      return CURLE_FTP_WEIRD_227_FORMAT;
-    }
+  while(ftp->state != FTP_STOP) {
+    long timeout_ms = ftp_state_timeout(conn);
 
-    snprintf(newhost, sizeof(newhost),
-             "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
-    newport = (port[0]<<8) + port[1];
-  }
-  else if (229 == results[modeoff]) {
-    char *ptr = strchr(buf, '(');
-    if(ptr) {
-      unsigned int num;
-      char separator[4];
-      ptr++;
-      if(5  == sscanf(ptr, "%c%c%c%u%c",
-                      &separator[0],
-                      &separator[1],
-                      &separator[2],
-                      &num,
-                      &separator[3])) {
-        char sep1 = separator[0];
-        int i;
+    if(timeout_ms <=0 ) {
+      failf(data, "FTP response timeout");
+      return CURLE_OPERATION_TIMEDOUT; /* already too little time */
+    }
 
-        /* The four separators should be identical, or else this is an oddly
-           formatted reply and we bail out immediately. */
-        for(i=1; i<4; i++) {
-          if(separator[i] != sep1) {
-            ptr=NULL; /* set to NULL to signal error */
-            break;
-          }
-        }
-        if(ptr) {
-          newport = num;
+    rc = Curl_select(ftp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
+                     ftp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
+                     timeout_ms);
 
-          /* We must use the same IP we are already connected to */
-          snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str);
-        }
-      }
-      else
-        ptr=NULL;
+    if(rc == -1) {
+      failf(data, "select error");
+      return CURLE_OUT_OF_MEMORY;
     }
-    if(!ptr) {
-      failf(data, "Weirdly formatted EPSV reply");
-      return CURLE_FTP_WEIRD_PASV_REPLY;
+    else if(rc == 0) {
+      result = CURLE_OPERATION_TIMEDOUT;
+      break;
+    }
+    else {
+      result = ftp_statemach_act(conn);
+      if(result)
+        break;
     }
   }
-  else
-    return CURLE_FTP_CANT_RECONNECT;
 
-  if(data->change.proxy && *data->change.proxy) {
-    /*
-     * This is a tunnel through a http proxy and we need to connect to the
-     * proxy again here.
-     *
-     * We don't want to rely on a former host lookup that might've expired
-     * now, instead we remake the lookup here and now!
-     */
-    rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr);
-    if(rc == CURLRESOLV_PENDING)
-      rc = Curl_wait_for_resolv(conn, &addr);
+  return result;
+}
 
-    connectport =
-      (unsigned short)conn->port; /* we connect to the proxy's port */
+/*
+ * 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;
 
-  }
-  else {
-    /* normal, direct, ftp connection */
-    rc = Curl_resolv(conn, newhost, newport, &addr);
-    if(rc == CURLRESOLV_PENDING)
-      rc = Curl_wait_for_resolv(conn, &addr);
+  *done = FALSE; /* default to not done yet */
 
-    if(!addr) {
-      failf(data, "Can't resolve new host %s:%d", newhost, newport);
-      return CURLE_FTP_CANT_GET_HOST;
-    }
-    connectport = newport; /* we connect to the remote port */
-  }
+  ftp = (struct FTP *)calloc(sizeof(struct FTP), 1);
+  if(!ftp)
+    return CURLE_OUT_OF_MEMORY;
 
-  result = Curl_connecthost(conn,
-                            addr,
-                            &conn->sock[SECONDARYSOCKET],
-                            &conninfo,
-                            connected);
+  conn->proto.ftp = ftp;
 
-  Curl_resolv_unlock(data, addr); /* we're done using this address */
+  /* We always support persistant connections on ftp */
+  conn->bits.close = FALSE;
 
-  if(result)
-    return result;
+  /* get some initial data into the ftp struct */
+  ftp->bytecountp = &conn->bytecount;
 
-  /*
-   * When this is used from the multi interface, this might've returned with
-   * the 'connected' set to FALSE and thus we are now awaiting a non-blocking
-   * connect to connect and we should not be "hanging" here waiting.
-   */
+  /* 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;
 
-  if(data->set.verbose)
-    /* this just dumps information about this second connection */
-    ftp_pasv_verbose(conn, conninfo, newhost, connectport);
+  ftp->response_time = 3600; /* set default response time-out */
 
 #ifndef CURL_DISABLE_HTTP
-  if(conn->bits.tunnel_proxy) {
+  if (conn->bits.tunnel_proxy) {
+    /* BLOCKING */
     /* We want "seamless" FTP operations through HTTP proxy tunnel */
-    result = Curl_ConnectHTTPProxyTunnel(conn, SECONDARYSOCKET,
-                                         newhost, newport);
+    result = Curl_ConnectHTTPProxyTunnel(conn, FIRSTSOCKET,
+                                         conn->host.name, conn->remote_port);
     if(CURLE_OK != result)
       return result;
   }
 #endif   /* CURL_DISABLE_HTTP */
 
-  return CURLE_OK;
+  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_SSLConnect(conn, FIRSTSOCKET);
+    if(result)
+      return result;
+  }
+
+  /* When we connect, we start in the state where we await the 220
+     response */
+  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_nextconnect()
+/***********************************************************************
+ *
+ * Curl_ftp_done()
+ *
+ * The DONE function. This does what needs to be done after a single DO has
+ * performed.
  *
- * This function shall be called when the second FTP connection has been
- * established and is confirmed connected.
+ * Input argument is already checked for validity.
  */
-
-CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
+CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status)
 {
-  struct SessionHandle *data=conn->data;
-  char *buf = data->state.buffer; /* this is our buffer */
-  CURLcode result;
-  ssize_t nread;
-  int ftpcode; /* for ftp status */
-
-  /* the ftp struct is already inited in Curl_ftp_connect() */
+  struct SessionHandle *data = conn->data;
   struct FTP *ftp = conn->proto.ftp;
-  curl_off_t *bytecountp = ftp->bytecountp;
-
-  if(data->set.upload) {
-
-    /* Set type to binary (unless specified ASCII) */
-    result = ftp_transfertype(conn, data->set.ftp_ascii);
-    if(result)
-      return result;
-
-    /* Send any PREQUOTE strings after transfer type is set? (Wesley Laxton)*/
-    if(data->set.prequote) {
-      if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK)
-        return result;
-    }
+  ssize_t nread;
+  int ftpcode;
+  CURLcode result=CURLE_OK;
+  bool was_ctl_valid = ftp->ctl_valid;
+  size_t flen;
+  size_t dlen;
+  char *path;
 
-    if(conn->resume_from) {
-      /* we're about to continue the uploading of a file */
-      /* 1. get already existing file's size. We use the SIZE
-         command for this which may not exist in the server!
-         The SIZE command is not in RFC959. */
+  /* now store a copy of the directory we are in */
+  if(ftp->prevpath)
+    free(ftp->prevpath);
 
-      /* 2. This used to set REST. But since we can do append, we
-         don't another ftp command. We just skip the source file
-         offset and then we APPEND the rest on the file instead */
+  path = curl_unescape(conn->path, 0); /* get the "raw" path */
+  if(!path)
+    return CURLE_OUT_OF_MEMORY;
 
-      /* 3. pass file-size number of bytes in the source file */
-      /* 4. lower the infilesize counter */
-      /* => transfer as usual */
+  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);
 
-      if(conn->resume_from < 0 ) {
-        /* we could've got a specified offset from the command line,
-           but now we know we didn't */
-        curl_off_t gottensize;
+  ftp->ctl_valid = FALSE;
 
-        if(CURLE_OK != ftp_getsize(conn, ftp->file, &gottensize)) {
-          failf(data, "Couldn't get remote file size");
-          return CURLE_FTP_COULDNT_GET_SIZE;
-        }
-        conn->resume_from = gottensize;
-      }
+  if(data->set.upload) {
+    if((-1 != data->set.infilesize) &&
+       (data->set.infilesize != *ftp->bytecountp) &&
+       !data->set.crlf) {
+      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;
+    }
+  }
 
-      if(conn->resume_from) {
-        /* do we still game? */
-        curl_off_t passed=0;
-        /* enable append instead */
-        data->set.ftp_append = 1;
-
-        /* Now, let's read off the proper amount of bytes from the
-           input. If we knew it was a proper file we could've just
-           fseek()ed but we only have a stream here */
-        do {
-          curl_off_t readthisamountnow = (conn->resume_from - passed);
-          curl_off_t actuallyread;
-
-          if(readthisamountnow > BUFSIZE)
-            readthisamountnow = BUFSIZE;
-
-          actuallyread = (curl_off_t)
-            conn->fread(data->state.buffer, 1, (size_t)readthisamountnow,
-                        conn->fread_in);
-
-          passed += actuallyread;
-          if(actuallyread != readthisamountnow) {
-            failf(data, "Could only read %" FORMAT_OFF_T
-                  " bytes from the input", passed);
-            return CURLE_FTP_COULDNT_USE_REST;
-          }
-        }
-        while(passed != conn->resume_from);
+  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;
+  }
 
-        /* now, decrease the size of the read */
-        if(data->set.infilesize>0) {
-          data->set.infilesize -= conn->resume_from;
+#ifdef HAVE_KRB4
+  Curl_sec_fflush_fd(conn, conn->sock[SECONDARYSOCKET]);
+#endif
 
-          if(data->set.infilesize <= 0) {
-            infof(data, "File already completely uploaded\n");
+  /* shut down the socket to inform the server we're done */
 
-            /* no data to transfer */
-            result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+#ifdef _WIN32_WCE
+  shutdown(conn->sock[SECONDARYSOCKET],2);  /* SD_BOTH */
+#endif
 
-            /* Set no_transfer so that we won't get any error in
-             * Curl_ftp_done() because we didn't transfer anything! */
-            ftp->no_transfer = TRUE;
+  sclose(conn->sock[SECONDARYSOCKET]);
 
-            return CURLE_OK;
-          }
-        }
-        /* we've passed, proceed as normal */
-      }
-    }
+  conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
 
-    /* Send everything on data->state.in to the socket */
-    if(data->set.ftp_append) {
-      /* we append onto the file instead of rewriting it */
-      FTPSENDF(conn, "APPE %s", ftp->file);
-    }
-    else {
-      FTPSENDF(conn, "STOR %s", ftp->file);
-    }
+  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 */
 
     result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-    if(result)
-      return result;
 
-    if(ftpcode>=400) {
-      failf(data, "Failed FTP upload: %03d", ftpcode);
-      /* oops, we never close the sockets! */
-      return CURLE_FTP_COULDNT_STOR_FILE;
-    }
-
-    if(data->set.ftp_use_port) {
-      /* PORT means we are now awaiting the server to connect to us. */
-      result = AllowServerConnect(conn);
-      if( result )
-        return result;
-    }
+    ftp->response_time = 3600; /* set this back to one hour waits */
 
-    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_SSLConnect(conn, SECONDARYSOCKET);
-      if(result)
-        return result;
+    if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) {
+      failf(data, "control connection looks dead");
+      return result;
     }
 
-    *bytecountp=0;
-
-    /* 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, bytecountp);
     if(result)
       return result;
 
-  }
-  else if(!conn->bits.no_body) {
-    /* Retrieve file or directory */
-    bool dirlist=FALSE;
-    curl_off_t downloadsize=-1;
-
-    if(conn->bits.use_range && conn->range) {
-      curl_off_t from, to;
-      curl_off_t totalsize=-1;
-      char *ptr;
-      char *ptr2;
-
-      from=curlx_strtoofft(conn->range, &ptr, 0);
-      while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
-        ptr++;
-      to=curlx_strtoofft(ptr, &ptr2, 0);
-      if(ptr == ptr2) {
-        /* we didn't get any digit */
-        to=-1;
-      }
-      if((-1 == to) && (from>=0)) {
-        /* X - */
-        conn->resume_from = from;
-        infof(data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", from);
-      }
-      else if(from < 0) {
-        /* -Y */
-        totalsize = -from;
-        conn->maxdownload = -from;
-        conn->resume_from = from;
-        infof(data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", totalsize);
-      }
-      else {
-        /* X-Y */
-        totalsize = to-from;
-        conn->maxdownload = totalsize+1; /* include the last mentioned byte */
-        conn->resume_from = from;
-        infof(data, "FTP RANGE from %" FORMAT_OFF_T
-              " getting %" FORMAT_OFF_T " bytes\n", from, conn->maxdownload);
+    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;
       }
-      infof(data, "range-download from %" FORMAT_OFF_T
-            " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
-            from, to, conn->maxdownload);
-      ftp->dont_check = TRUE; /* dont check for successful transfer */
-    }
-
-    if((data->set.ftp_list_only) || !ftp->file) {
-      /* The specified path ends with a slash, and therefore we think this
-         is a directory that is requested, use LIST. But before that we
-         need to set ASCII transfer mode. */
-      dirlist = TRUE;
-
-      /* Set type to ASCII */
-      result = ftp_transfertype(conn, TRUE /* ASCII enforced */);
-      if(result)
-        return result;
-
-      /* if this output is to be machine-parsed, the NLST command will be
-         better used since the LIST command output is not specified or
-         standard in any way */
-
-      FTPSENDF(conn, "%s",
-            data->set.customrequest?data->set.customrequest:
-            (data->set.ftp_list_only?"NLST":"LIST"));
     }
-    else {
-      curl_off_t foundsize;
-
-      /* Set type to binary (unless specified ASCII) */
-      result = ftp_transfertype(conn, data->set.ftp_ascii);
-      if(result)
-        return result;
-
-      /* Send any PREQUOTE strings after transfer type is set? */
-      if(data->set.prequote) {
-        if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK)
-          return result;
-      }
+  }
 
-      /* Attempt to get the size, it'll be useful in some cases: for resumed
-         downloads and when talking to servers that don't give away the size
-         in the RETR response line. */
-      result = ftp_getsize(conn, ftp->file, &foundsize);
-      if(CURLE_OK == result) {
-        if (data->set.max_filesize && foundsize > data->set.max_filesize) {
-          failf(data, "Maximum file size exceeded");
-          return CURLE_FILESIZE_EXCEEDED;
-        }
-        downloadsize = foundsize;
-      }
+  /* clear these for next connection */
+  ftp->no_transfer = FALSE;
+  ftp->dont_check = FALSE;
 
-      if(conn->resume_from) {
+  if (!result && conn->sec_conn) {   /* 3rd party transfer */
+    /* "done" with the secondary connection */
+    result = Curl_ftp_done(conn->sec_conn, status);
+  }
 
-        /* Daniel: (August 4, 1999)
-         *
-         * We start with trying to use the SIZE command to figure out the size
-         * of the file we're gonna get. If we can get the size, this is by far
-         * the best way to know if we're trying to resume beyond the EOF.
-         *
-         * Daniel, November 28, 2001. We *always* get the size on downloads
-         * now, so it is done before this even when not doing resumes. I saved
-         * the comment above for nostalgical reasons! ;-)
-         */
-        if(CURLE_OK != result) {
-          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.  */
-          if(conn->resume_from< 0) {
-            /* We're supposed to download the last abs(from) bytes */
-            if(foundsize < -conn->resume_from) {
-              failf(data, "Offset (%" FORMAT_OFF_T
-                    ") was beyond file size (%" FORMAT_OFF_T ")",
-                    conn->resume_from, foundsize);
-              return CURLE_BAD_DOWNLOAD_RESUME;
-            }
-            /* convert to size to download */
-            downloadsize = -conn->resume_from;
-            /* download from where? */
-            conn->resume_from = foundsize - downloadsize;
-          }
-          else {
-            if(foundsize < conn->resume_from) {
-              failf(data, "Offset (%" FORMAT_OFF_T
-                    ") was beyond file size (%" FORMAT_OFF_T ")",
-                    conn->resume_from, foundsize);
-              return CURLE_BAD_DOWNLOAD_RESUME;
-            }
-            /* Now store the number of bytes we are expected to download */
-            downloadsize = foundsize-conn->resume_from;
-          }
-        }
+  /* Send any post-transfer QUOTE strings? */
+  if(!status && !result && data->set.postquote)
+    result = ftp_sendquote(conn, data->set.postquote);
 
-        if (downloadsize == 0) {
-          /* no data to transfer */
-          result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
-          infof(data, "File already completely downloaded\n");
+  return result;
+}
 
-          /* 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;
-          return CURLE_OK;
-        }
+/***********************************************************************
+ *
+ * ftp_sendquote()
+ *
+ * Where a 'quote' means a list of custom commands to send to the server.
+ * The quote list is passed as an argument.
+ */
 
-        /* Set resume file transfer offset */
-        infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T
-              "\n",
-              conn->resume_from);
+static
+CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote)
+{
+  struct curl_slist *item;
+  ssize_t nread;
+  int ftpcode;
+  CURLcode result;
 
-        FTPSENDF(conn, "REST %" FORMAT_OFF_T, conn->resume_from);
+  item = quote;
+  while (item) {
+    if (item->data) {
+      FTPSENDF(conn, "%s", item->data);
 
-        result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-        if(result)
-          return result;
+      result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+      if (result)
+        return result;
 
-        if(ftpcode != 350) {
-          failf(data, "Couldn't use REST: %03d", ftpcode);
-          return CURLE_FTP_COULDNT_USE_REST;
-        }
+      if (ftpcode >= 400) {
+        failf(conn->data, "QUOT string not accepted: %s", item->data);
+        return CURLE_FTP_QUOTE_ERROR;
       }
-
-      FTPSENDF(conn, "RETR %s", ftp->file);
     }
 
-    result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-    if(result)
-      return result;
+    item = item->next;
+  }
 
-    if((ftpcode == 150) || (ftpcode == 125)) {
+  return CURLE_OK;
+}
 
-      /*
-        A;
-        150 Opening BINARY mode data connection for /etc/passwd (2241
-        bytes).  (ok, the file is being transfered)
+/***********************************************************************
+ *
+ * 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;
 
-        B:
-        150 Opening ASCII mode data connection for /bin/ls
+  FTPSENDF(conn, "TYPE %s", ascii?"A":"I");
 
-        C:
-        150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes).
+  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+  if(result)
+    return result;
 
-        D:
-        150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes).
+  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;
+  }
 
-        E:
-        125 Data connection already open; Transfer starting. */
+  return CURLE_OK;
+}
 
-      curl_off_t size=-1; /* default unknown size */
+/***************************************************************************
+ *
+ * ftp_pasv_verbose()
+ *
+ * This function only outputs some informationals about this second connection
+ * when we've issued a PASV command before and thus we have connected to a
+ * possibly new IP address.
+ *
+ */
+static void
+ftp_pasv_verbose(struct connectdata *conn,
+                 Curl_addrinfo *ai,
+                 char *newhost, /* ascii version */
+                 int port)
+{
+  char buf[256];
+  Curl_printable_address(ai, buf, sizeof(buf));
+  infof(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port);
+}
 
+/*
+  Check if this is a range download, and if so, set the internal variables
+  properly.
+ */
 
-      /*
-       * 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.
-       */
+static CURLcode ftp_range(struct connectdata *conn)
+{
+  curl_off_t from, to;
+  curl_off_t totalsize=-1;
+  char *ptr;
+  char *ptr2;
+  struct SessionHandle *data = conn->data;
+  struct FTP *ftp = conn->proto.ftp;
 
-      if(!dirlist &&
-         !data->set.ftp_ascii &&
-         (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--) {
-          long in=bytes-buf;
-          /* this is a hint there is size information in there! ;-) */
-          while(--in) {
-            /* scan for the parenthesis and break there */
-            if('(' == *bytes)
-              break;
-            /* if only skip digits, or else we're in deep trouble */
-            if(!isdigit((int)*bytes)) {
-              bytes=NULL;
-              break;
-            }
-            /* one more estep backwards */
-            bytes--;
-          }
-          /* only if we have nothing but digits: */
-          if(bytes++) {
-            /* get the number! */
-            size = curlx_strtoofft(bytes, NULL, 0);
-          }
+  if(conn->bits.use_range && conn->range) {
+    from=curlx_strtoofft(conn->range, &ptr, 0);
+    while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
+      ptr++;
+    to=curlx_strtoofft(ptr, &ptr2, 0);
+    if(ptr == ptr2) {
+      /* we didn't get any digit */
+      to=-1;
+    }
+    if((-1 == to) && (from>=0)) {
+      /* X - */
+      conn->resume_from = from;
+      infof(data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", from);
+    }
+    else if(from < 0) {
+      /* -Y */
+      totalsize = -from;
+      conn->maxdownload = -from;
+      conn->resume_from = from;
+      infof(data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", totalsize);
+    }
+    else {
+      /* X-Y */
+      totalsize = to-from;
+      conn->maxdownload = totalsize+1; /* include the last mentioned byte */
+      conn->resume_from = from;
+      infof(data, "FTP RANGE from %" FORMAT_OFF_T
+            " getting %" FORMAT_OFF_T " bytes\n", from, conn->maxdownload);
+    }
+    infof(data, "range-download from %" FORMAT_OFF_T
+          " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
+          from, to, conn->maxdownload);
+    ftp->dont_check = TRUE; /* dont check for successful transfer */
+  }
+  return CURLE_OK;
+}
 
-        }
-      }
-      else if(downloadsize > -1)
-        size = downloadsize;
 
-      if(data->set.ftp_use_port) {
-        result = AllowServerConnect(conn);
-        if( result )
-          return result;
-      }
+/*
+ * Curl_ftp_nextconnect()
+ *
+ * This function shall be called when the second FTP (data) connection is
+ * connected.
+ */
 
-      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_SSLConnect(conn, SECONDARYSOCKET);
-        if(result)
-          return result;
-      }
+CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
+{
+  struct SessionHandle *data=conn->data;
+  CURLcode result = CURLE_OK;
+
+  /* the ftp struct is inited in Curl_ftp_connect() */
+  struct FTP *ftp = conn->proto.ftp;
 
-      if(size > conn->maxdownload && conn->maxdownload > 0)
-        size = conn->size = conn->maxdownload;
+  infof(data, "DO-MORE phase starts\n");
 
-      infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
+  if(!ftp->no_transfer && !conn->bits.no_body) {
+    /* a transfer is about to take place */
 
-      /* FTP download: */
-      result=Curl_Transfer(conn, SECONDARYSOCKET, size, FALSE,
-                           bytecountp,
-                           -1, NULL); /* no upload here */
-      if(result)
-        return result;
+    if(data->set.upload) {
+      NBFTPSENDF(conn, "TYPE %c", data->set.ftp_ascii?'A':'I');
+      state(conn, FTP_STOR_TYPE);
     }
     else {
-      if(dirlist && (ftpcode == 450)) {
-        /* simply no matching files */
-        ftp->no_transfer = TRUE; /* don't think we should download anything */
+      /* download */
+      ftp->downloadsize = -1; /* unknown as of yet */
+
+      result = ftp_range(conn);
+      if(result)
+        ;
+      else if((data->set.ftp_list_only) || !ftp->file) {
+        /* The specified path ends with a slash, and therefore we think this
+           is a directory that is requested, use LIST. But before that we
+           need to set ASCII transfer mode. */
+        NBFTPSENDF(conn, "TYPE A", NULL);
+        state(conn, FTP_LIST_TYPE);
       }
       else {
-        failf(data, "RETR failed: %03d", ftpcode);
-        return CURLE_FTP_COULDNT_RETR_FILE;
+        NBFTPSENDF(conn, "TYPE %c", data->set.ftp_ascii?'A':'I');
+        state(conn, FTP_RETR_TYPE);
       }
     }
-
+    result = ftp_easy_statemach(conn);
   }
+
+  if(ftp->no_transfer)
+    /* no data to transfer. FIX: it feels like a kludge to have this here
+       too! */
+    result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+
   /* end of transfer */
+  infof(data, "DO-MORE phase ends\n");
 
-  return CURLE_OK;
+  return result;
 }
 
+
+
 /***********************************************************************
  *
  * ftp_perform()
@@ -2177,151 +3113,33 @@ CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
 
 static
 CURLcode ftp_perform(struct connectdata *conn,
-                     bool *connected)  /* for the TCP connect status after
-                                          PASV / PORT */
+                     bool *connected,  /* connect status after PASV / PORT */
+                     bool *dophase_done)
 {
   /* this is FTP and no proxy */
   CURLcode result=CURLE_OK;
   struct SessionHandle *data=conn->data;
-  char *buf = data->state.buffer; /* this is our buffer */
 
-  /* the ftp struct is already inited in Curl_ftp_connect() */
-  struct FTP *ftp = conn->proto.ftp;
+  infof(data, "DO phase starts\n");
 
-  /* Send any QUOTE strings? */
-  if(data->set.quote) {
-    if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK)
-      return result;
-  }
+  *dophase_done = FALSE; /* not done yet */
 
-  result = ftp_cwd_and_create_path(conn);
-  if (result)
+  /* start the first command in the DO phase */
+  result = ftp_state_quote(conn, TRUE, FTP_QUOTE);
+  if(result)
     return result;
 
-  /* Requested time of file or time-depended transfer? */
-  if((data->set.get_filetime || data->set.timecondition) &&
-     ftp->file) {
-    result = ftp_getfiletime(conn, ftp->file);
-    switch( result )
-      {
-      case CURLE_FTP_COULDNT_RETR_FILE:
-      case CURLE_OK:
-        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->no_transfer = TRUE; /* mark this to not transfer data */
-                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->no_transfer = TRUE; /* mark this to not transfer data */
-                return CURLE_OK;
-              }
-              break;
-            } /* switch */
-          }
-          else {
-            infof(data, "Skipping time comparison\n");
-          }
-        }
-        break;
-      default:
-        return result;
-      } /* switch */
-  }
-
-  /* If we have selected NOBODY and HEADER, it means that we only want file
-     information. Which in FTP can't be much more than the file size and
-     date. */
-  if(conn->bits.no_body && data->set.include_header && ftp->file) {
-    /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
-       may not support it! It is however the only way we have to get a file's
-       size! */
-    curl_off_t filesize;
-    ssize_t nread;
-    int ftpcode;
-
-    ftp->no_transfer = TRUE; /* this means no actual transfer is made */
-
-    /* Some servers return different sizes for different modes, and thus we
-       must set the proper type before we check the size */
-    result = ftp_transfertype(conn, data->set.ftp_ascii);
-    if(result)
-      return result;
-
-    /* failing to get size is not a serious error */
-    result = ftp_getsize(conn, ftp->file, &filesize);
-
-    if(CURLE_OK == result) {
-      snprintf(buf, sizeof(data->state.buffer),
-               "Content-Length: %" FORMAT_OFF_T "\r\n", filesize);
-      result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
-      if(result)
-        return result;
-    }
-
-    /* Determine if server can respond to REST command and therefore
-       whether it can do a range */
-    FTPSENDF(conn, "REST 0", NULL);
-    result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-
-    if ((CURLE_OK == result) && (ftpcode == 350)) {
-      result = Curl_client_write(data, CLIENTWRITE_BOTH,
-                                 (char *)"Accept-ranges: bytes\r\n", 0);
-      if(result)
-        return result;
-    }
-
-    /* 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. */
-
-#ifdef HAVE_STRFTIME
-    if(data->set.get_filetime && (data->info.filetime>=0) ) {
-      struct tm *tm;
-      time_t clock = (time_t)data->info.filetime;
-#ifdef HAVE_GMTIME_R
-      struct tm buffer;
-      tm = (struct tm *)gmtime_r(&clock, &buffer);
-#else
-      tm = gmtime(&clock);
-#endif
-      /* format: "Tue, 15 Nov 1994 12:45:26" */
-      strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n",
-               tm);
-      result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
-      if(result)
-        return result;
-    }
-#endif
-
-    return CURLE_OK;
-  }
-
-  if(conn->bits.no_body)
-    /* doesn't really transfer any data */
-    ftp->no_transfer = TRUE;
-  /* Get us a second connection up and connected */
-  else if(data->set.ftp_use_port) {
-    /* We have chosen to use the PORT command */
-    result = ftp_use_port(conn);
-    if(CURLE_OK == result) {
-      /* we have the data connection ready */
-      infof(data, "Ordered connect of the data stream with PORT!\n");
-      *connected = TRUE; /* mark us "still connected" */
-    }
-  }
+  /* run the state-machine */
+  if(conn->data->state.used_interface == Curl_if_multi)
+    result = Curl_ftp_multi_statemach(conn, dophase_done);
   else {
-    /* We have chosen (this is default) to use the PASV command */
-    result = ftp_use_pasv(conn, connected);
-    if(CURLE_OK == result && *connected)
-      infof(data, "Connected the data stream with PASV!\n");
+    result = ftp_easy_statemach(conn);
+    *dophase_done = TRUE; /* with the easy interface we are done here */
   }
+  *connected = conn->bits.tcpconnect;
+
+  if(*dophase_done)
+    infof(data, "DO phase is comlete\n");
 
   return result;
 }
@@ -2335,31 +3153,89 @@ CURLcode ftp_perform(struct connectdata *conn,
  *
  * The input argument is already checked for validity.
  */
-CURLcode Curl_ftp(struct connectdata *conn)
+CURLcode Curl_ftp(struct connectdata *conn, bool *done)
 {
   CURLcode retcode = CURLE_OK;
 
+  *done = FALSE; /* default to false */
+
   retcode = ftp_parse_url_path(conn);
   if (retcode)
     return retcode;
 
-  if (conn->sec_conn) /* 3rd party transfer */
+  if (conn->sec_conn) {
+    /* 3rd party transfer */
+    *done = TRUE; /* BLOCKING */
     retcode = ftp_3rdparty(conn);
+  }
   else
-    retcode = ftp_regular_transfer(conn);
+    retcode = ftp_regular_transfer(conn, done);
 
   return retcode;
 }
 
 /***********************************************************************
  *
- * Curl_ftpsendf()
+ * Curl_(nb)ftpsendf()
  *
  * Sends the formated string as a ftp command to a ftp server
  *
  * NOTE: we build the command in a fixed-length buffer, which sets length
  * restrictions on the command!
+ *
+ * The "nb" version is made to Never Block.
  */
+CURLcode Curl_nbftpsendf(struct connectdata *conn,
+                       const char *fmt, ...)
+{
+  ssize_t bytes_written;
+  char s[256];
+  size_t write_len;
+  char *sptr=s;
+  CURLcode res = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+
+  va_list ap;
+  va_start(ap, fmt);
+  vsnprintf(s, 250, fmt, ap);
+  va_end(ap);
+
+  strcat(s, "\r\n"); /* append a trailing CRLF */
+
+  bytes_written=0;
+  write_len = strlen(s);
+
+  res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len,
+                   &bytes_written);
+
+  if(CURLE_OK != res)
+    return res;
+
+  if(conn->data->set.verbose)
+    Curl_debug(conn->data, CURLINFO_HEADER_OUT, sptr, bytes_written,
+               conn);
+
+  if(bytes_written != (ssize_t)write_len) {
+    /* the whole chunk was not sent, store the rest of the data */
+    write_len -= bytes_written;
+    sptr += bytes_written;
+    ftp->sendthis = malloc(write_len);
+    if(ftp->sendthis) {
+      memcpy(ftp->sendthis, sptr, write_len);
+      ftp->sendsize=ftp->sendleft=write_len;
+    }
+    else {
+      failf(data, "out of memory");
+      res = CURLE_OUT_OF_MEMORY;
+    }
+  }
+  else
+    ftp->response = Curl_tvnow();
+
+  return res;
+}
+
 CURLcode Curl_ftpsendf(struct connectdata *conn,
                        const char *fmt, ...)
 {
@@ -2412,17 +3288,16 @@ CURLcode Curl_ftpsendf(struct connectdata *conn,
  */
 static CURLcode ftp_quit(struct connectdata *conn)
 {
-  ssize_t nread;
-  int ftpcode;
-  CURLcode ret = CURLE_OK;
+  CURLcode result = CURLE_OK;
 
   if(conn->proto.ftp->ctl_valid) {
-    ret = Curl_ftpsendf(conn, "%s", "QUIT");
-    if(CURLE_OK == ret)
-      ret = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+    NBFTPSENDF(conn, "QUIT", NULL);
+    state(conn, FTP_QUIT);
+
+    result = ftp_easy_statemach(conn);
   }
 
-  return ret;
+  return result;
 }
 
 /***********************************************************************
@@ -2430,7 +3305,7 @@ static CURLcode ftp_quit(struct connectdata *conn)
  * Curl_ftp_disconnect()
  *
  * Disconnect from an FTP server. Cleanup protocol-specific per-connection
- * resources
+ * resources. BLOCKING.
  */
 CURLcode Curl_ftp_disconnect(struct connectdata *conn)
 {
@@ -2881,21 +3756,66 @@ CURLcode ftp_cwd_and_create_path(struct connectdata *conn)
   return result;
 }
 
+/* call this when the DO phase has completed */
+static CURLcode ftp_dophase_done(struct connectdata *conn,
+                                 bool connected)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+
+  if(connected)
+    result = Curl_ftp_nextconnect(conn);
+
+  if(result && (conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD)) {
+    /* Failure detected, close the second socket if it was created already */
+    sclose(conn->sock[SECONDARYSOCKET]);
+    conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
+  }
+
+  if(ftp->no_transfer)
+    /* no data to transfer */
+    result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+  else if(!connected)
+    /* since we didn't connect now, we want do_more to get called */
+    conn->bits.do_more = TRUE;
+
+  ftp->ctl_valid = TRUE; /* seems good */
+
+  return result;
+}
+
+/* called from multi.c while DOing */
+CURLcode Curl_ftp_doing(struct connectdata *conn,
+                        bool *dophase_done)
+{
+  CURLcode result;
+  result = Curl_ftp_multi_statemach(conn, dophase_done);
+
+  if(*dophase_done) {
+    result = ftp_dophase_done(conn, FALSE /* not connected */);
+
+    infof(conn->data, "DO phase is comlete\n");
+  }
+  return result;
+}
 
 /***********************************************************************
  *
  * ftp_regular_transfer()
  *
  * The input argument is already checked for validity.
- * Performs a regular transfer between local and remote hosts.
+ *
+ * Performs all commands done before a regular transfer between a local and a
+ * remote host.
  *
  * ftp->ctl_valid starts out as FALSE, and gets set to TRUE if we reach the
  * Curl_ftp_done() function without finding any major problem.
  */
 static
-CURLcode ftp_regular_transfer(struct connectdata *conn)
+CURLcode ftp_regular_transfer(struct connectdata *conn,
+                              bool *dophase_done)
 {
-  CURLcode retcode=CURLE_OK;
+  CURLcode result=CURLE_OK;
   bool connected=0;
   struct SessionHandle *data = conn->data;
   struct FTP *ftp;
@@ -2909,31 +3829,26 @@ CURLcode ftp_regular_transfer(struct connectdata *conn)
   Curl_pgrsSetUploadSize(data, 0);
   Curl_pgrsSetDownloadSize(data, 0);
 
-  retcode = ftp_perform(conn, &connected);
+  ftp->ctl_valid = TRUE; /* starts good */
 
-  if(CURLE_OK == retcode) {
-    if(connected)
-      retcode = Curl_ftp_nextconnect(conn);
+  result = ftp_perform(conn,
+                       &connected, /* have we connected after PASV/PORT */
+                       dophase_done); /* all commands in the DO-phase done? */
 
-    if(retcode && (conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD)) {
-      /* Failure detected, close the second socket if it was created already */
-      sclose(conn->sock[SECONDARYSOCKET]);
-      conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
-    }
+  if(CURLE_OK == result) {
 
-    if(ftp->no_transfer)
-      /* no data to transfer */
-      retcode=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
-    else if(!connected)
-      /* since we didn't connect now, we want do_more to get called */
-      conn->bits.do_more = TRUE;
+    if(!*dophase_done)
+      /* the DO phase has not completed yet */
+      return CURLE_OK;
+
+    result = ftp_dophase_done(conn, connected);
+    if(result)
+      return result;
   }
   else
     freedirs(ftp);
 
-  ftp->ctl_valid = TRUE; /* seems good */
-
-  return retcode;
+  return result;
 }
 
 
@@ -2947,16 +3862,16 @@ CURLcode ftp_regular_transfer(struct connectdata *conn)
  */
 static CURLcode ftp_3rdparty(struct connectdata *conn)
 {
-  CURLcode retcode = CURLE_OK;
+  CURLcode result = CURLE_OK;
 
   conn->proto.ftp->ctl_valid = conn->sec_conn->proto.ftp->ctl_valid = TRUE;
   conn->size = conn->sec_conn->size = -1;
 
-  retcode = ftp_3rdparty_pretransfer(conn);
-  if (!retcode)
-    retcode = ftp_3rdparty_transfer(conn);
+  result = ftp_3rdparty_pretransfer(conn);
+  if (!result)
+    result = ftp_3rdparty_transfer(conn);
 
-  return retcode;
+  return result;
 }
 
 #endif /* CURL_DISABLE_FTP */
index dc7cf79c0d832da6e79c7c50c240fc86efc9288b..3c26cc38b54a41f931f45480aa3321734fc96f28 100644 (file)
--- a/lib/ftp.h
+++ b/lib/ftp.h
@@ -1,18 +1,18 @@
 #ifndef __FTP_H
 #define __FTP_H
 /***************************************************************************
- *                                  _   _ ____  _     
- *  Project                     ___| | | |  _ \| |    
- *                             / __| | | | |_) | |    
- *                            | (__| |_| |  _ <| |___ 
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
  * are also available at http://curl.haxx.se/docs/copyright.html.
- * 
+ *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
  * furnished to do so, under the terms of the COPYING file.
  ***************************************************************************/
 
 #ifndef CURL_DISABLE_FTP
-CURLcode Curl_ftp(struct connectdata *conn);
+CURLcode Curl_ftp(struct connectdata *conn, bool *done);
 CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode);
-CURLcode Curl_ftp_connect(struct connectdata *conn);
+CURLcode Curl_ftp_connect(struct connectdata *conn, bool *done);
 CURLcode Curl_ftp_disconnect(struct connectdata *conn);
 CURLcode Curl_ftpsendf(struct connectdata *, const char *fmt, ...);
+CURLcode Curl_nbftpsendf(struct connectdata *, const char *fmt, ...);
 CURLcode Curl_GetFTPResponse(ssize_t *nread, struct connectdata *conn,
                              int *ftpcode);
 CURLcode Curl_ftp_nextconnect(struct connectdata *conn);
-#endif
-
-#endif
+CURLcode Curl_ftp_multi_statemach(struct connectdata *conn, bool *done);
+CURLcode Curl_ftp_fdset(struct connectdata *conn,
+                        fd_set *read_fd_set,
+                        fd_set *write_fd_set,
+                        int *max_fdp);
+CURLcode Curl_ftp_doing(struct connectdata *conn,
+                        bool *dophase_done);
+#endif /* CURL_DISABLE_FTP */
+#endif /* __FTP_H */
index c17e991e25f80877771f1354e4af0ab05279a3eb..13c0dc2370fdac925bcfb621a9d3abdcd646aa54 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -99,7 +99,7 @@
 #ifdef CURLRES_ARES
 
 /*
- * Curl_fdset() is called when someone from the outside world (using
+ * Curl_resolv_fdset() is called when someone from the outside world (using
  * curl_multi_fdset()) wants to get our fd_set setup and we're talking with
  * ares. The caller must make sure that this function is only called when we
  * have a working ares channel.
  * Returns: CURLE_OK always!
  */
 
-CURLcode Curl_fdset(struct connectdata *conn,
-                    fd_set *read_fd_set,
-                    fd_set *write_fd_set,
-                    int *max_fdp)
+CURLcode Curl_resolv_fdset(struct connectdata *conn,
+                           fd_set *read_fd_set,
+                           fd_set *write_fd_set,
+                           int *max_fdp)
 
 {
   int max = ares_fds(conn->data->state.areschannel,
index ec7a34a43060080dba9d6a2ee12abf7e700fbc32..e31219030e88b8006340e3d7eed3af0dcc959394 100644 (file)
 #include <process.h>
 #endif
 
-#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
-#undef in_addr_t
-#define in_addr_t unsigned long
-#endif
-
 #include "urldata.h"
 #include "sendf.h"
 #include "hostip.h"
index 460b8f626d223a7c8ed852b3bc30b4094f746876..233d81cf368a36d6f55443eb3c01d3dcb67e576c 100644 (file)
 #include <process.h>
 #endif
 
-#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
-#undef in_addr_t
-#define in_addr_t unsigned long
-#endif
-
 #include "urldata.h"
 #include "sendf.h"
 #include "hostip.h"
index 4fd03c1afeb484bfa5d9f557fb848ad59c4e8dee..0b06bae6a4158b4150b7dd2f2499b635e6e08b4f 100644 (file)
@@ -159,13 +159,13 @@ CURLcode Curl_is_resolved(struct connectdata *conn,
 CURLcode Curl_wait_for_resolv(struct connectdata *conn,
                               struct Curl_dns_entry **dnsentry);
 
-/* Curl_fdset() is a generic function that exists in multiple versions
+/* Curl_resolv_fdset() is a generic function that exists in multiple versions
    depending on what name resolve technology we've built to use. The function
    is called from the curl_multi_fdset() function */
-CURLcode Curl_fdset(struct connectdata *conn,
-                    fd_set *read_fd_set,
-                    fd_set *write_fd_set,
-                    int *max_fdp);
+CURLcode Curl_resolv_fdset(struct connectdata *conn,
+                           fd_set *read_fd_set,
+                           fd_set *write_fd_set,
+                           int *max_fdp);
 /* unlock a previously resolved dns entry */
 void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns);
 
index e1a08f78ae985175e8bf101a86a1a5231ce490b5..4da05078f1c070158a2488f18e496adc1653bd40 100644 (file)
 #include <process.h>
 #endif
 
-#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
-#undef in_addr_t
-#define in_addr_t unsigned long
-#endif
-
 #include "urldata.h"
 #include "sendf.h"
 #include "hostip.h"
index d2343b9623a6bde50342bbb5e1a424de968395d2..38030b7f0abd1084abe4b95c3621542f279799e1 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
 #include <process.h>
 #endif
 
-#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
-#undef in_addr_t
-#define in_addr_t unsigned long
-#endif
-
 #include "urldata.h"
 #include "sendf.h"
 #include "hostip.h"
@@ -133,10 +128,10 @@ CURLcode Curl_is_resolved(struct connectdata *conn,
  * It is present here to keep #ifdefs out from multi.c
  */
 
-CURLcode Curl_fdset(struct connectdata *conn,
-                    fd_set *read_fd_set,
-                    fd_set *write_fd_set,
-                    int *max_fdp)
+CURLcode Curl_resolv_fdset(struct connectdata *conn,
+                           fd_set *read_fd_set,
+                           fd_set *write_fd_set,
+                           int *max_fdp)
 {
   (void)conn;
   (void)read_fd_set;
index 47c9ea1c84d8477576c0d0509dcd88f148c4e6d5..ed653fed6db33a78f329723b84f47522ccbabcfb 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -155,7 +155,7 @@ struct thread_data {
   HANDLE thread_hnd;
   unsigned thread_id;
   DWORD  thread_status;
-  curl_socket_t dummy_sock;   /* dummy for Curl_fdset() */
+  curl_socket_t dummy_sock;   /* dummy for Curl_resolv_fdset() */
   FILE *stderr_file;
   HANDLE mutex_waiting;  /* marks that we are still waiting for a resolve */
   HANDLE event_resolved; /* marks that the thread obtained the information */
@@ -404,9 +404,9 @@ static bool init_resolve_thread (struct connectdata *conn,
      destroy_thread_data(&conn->async);
      return FALSE;
   }
-  /* This socket is only to keep Curl_fdset() and select() happy; should never
-   * become signalled for read/write since it's unbound but Windows needs
-   * atleast 1 socket in select().
+  /* This socket is only to keep Curl_resolv_fdset() and select() happy;
+   * should never become signalled for read/write since it's unbound but
+   * Windows needs atleast 1 socket in select().
    */
   td->dummy_sock = socket(AF_INET, SOCK_DGRAM, 0);
   return TRUE;
@@ -541,10 +541,10 @@ CURLcode Curl_is_resolved(struct connectdata *conn,
   return CURLE_OK;
 }
 
-CURLcode Curl_fdset(struct connectdata *conn,
-                    fd_set *read_fd_set,
-                    fd_set *write_fd_set,
-                    int *max_fdp)
+CURLcode Curl_resolv_fdset(struct connectdata *conn,
+                           fd_set *read_fd_set,
+                           fd_set *write_fd_set,
+                           int *max_fdp)
 {
   const struct thread_data *td =
     (const struct thread_data *) conn->async.os_specific;
index 3a7af8f6e914d09c3dd269daee4df2fd02e2534e..0516431261289d16b5bc2dfa9eac82f9f1f178ff 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -1222,7 +1222,7 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn,
  * Curl_http_connect() performs HTTP stuff to do at connect-time, called from
  * the generic Curl_connect().
  */
-CURLcode Curl_http_connect(struct connectdata *conn)
+CURLcode Curl_http_connect(struct connectdata *conn, bool *done)
 {
   struct SessionHandle *data;
   CURLcode result;
@@ -1261,6 +1261,8 @@ CURLcode Curl_http_connect(struct connectdata *conn)
     data->state.first_host = strdup(conn->host.name);
   }
 
+  *done = TRUE;
+
   return CURLE_OK;
 }
 
@@ -1328,7 +1330,7 @@ CURLcode Curl_http_done(struct connectdata *conn,
  * request is to be performed. This creates and sends a properly constructed
  * HTTP request.
  */
-CURLcode Curl_http(struct connectdata *conn)
+CURLcode Curl_http(struct connectdata *conn, bool *done)
 {
   struct SessionHandle *data=conn->data;
   char *buf = data->state.buffer; /* this is a short cut to the buffer */
@@ -1342,6 +1344,11 @@ CURLcode Curl_http(struct connectdata *conn)
   Curl_HttpReq httpreq = data->set.httpreq;
   char *addcookies = NULL;
 
+  /* Always consider the DO phase done after this function call, even if there
+     may be parts of the request that is not yet sent, since we can deal with
+     the rest of the request in the PERFORM phase. */
+  *done = TRUE;
+
   if(!conn->proto.http) {
     /* Only allocate this struct if we don't already have it! */
 
index 57f1d115e1b0eebafcbfd3e9801d6ba6b15120df..23afc9115c810846024bccd763d17b212b8039ec 100644 (file)
@@ -8,7 +8,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -34,9 +34,9 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn,
                                      char *hostname, int remote_port);
 
 /* protocol-specific functions set up to be called by the main engine */
-CURLcode Curl_http(struct connectdata *conn);
+CURLcode Curl_http(struct connectdata *conn, bool *done);
 CURLcode Curl_http_done(struct connectdata *, CURLcode);
-CURLcode Curl_http_connect(struct connectdata *conn);
+CURLcode Curl_http_connect(struct connectdata *conn, bool *done);
 
 /* The following functions are defined in http_chunks.c */
 void Curl_httpchunk_init(struct connectdata *conn);
index 1042688921cdbcea3982e2a159428d03449d099b..3afa813521eaa6b98bb3d71706b7117e0c97ad7f 100644 (file)
@@ -5,7 +5,7 @@
  *                | (__| |_| |  _ <| |___
  *                \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -224,7 +224,7 @@ static void (*ldap_free_urldesc)(LDAPURLDesc *) = _ldap_free_urldesc;
 #endif
 
 
-CURLcode Curl_ldap(struct connectdata *conn)
+CURLcode Curl_ldap(struct connectdata *conn, bool *done)
 {
   CURLcode status = CURLE_OK;
   int rc = 0;
@@ -256,6 +256,7 @@ CURLcode Curl_ldap(struct connectdata *conn)
   int num = 0;
   struct SessionHandle *data=conn->data;
 
+  *done = TRUE; /* unconditionally */
   infof(data, "LDAP local: %s\n", data->change.url);
 
   if (!DynaOpen(&mod_name)) {
index b95cf74ad293360dcf8dd34309f14cfeef337d57..b2d4f3973ad4990bd799b1032e4ce7561bfda9c5 100644 (file)
@@ -2,18 +2,18 @@
 #define __LDAP_H
 
 /***************************************************************************
- *                                  _   _ ____  _     
- *  Project                     ___| | | |  _ \| |    
- *                             / __| | | | |_) | |    
- *                            | (__| |_| |  _ <| |___ 
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
  * are also available at http://curl.haxx.se/docs/copyright.html.
- * 
+ *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
  * furnished to do so, under the terms of the COPYING file.
@@ -24,6 +24,6 @@
  * $Id$
  ***************************************************************************/
 #ifndef CURL_DISABLE_LDAP
-CURLcode Curl_ldap(struct connectdata *conn);
+CURLcode Curl_ldap(struct connectdata *conn, bool *done);
 #endif
 #endif /* __LDAP_H */
index 159e51e53ca66c7084b08fa6693f35381e9b39e2..643f6362ff5fe695fdf4a1ae33c1b625ea70abe9 100644 (file)
@@ -45,6 +45,7 @@
 #include "memory.h"
 #include "easyif.h"
 #include "multiif.h"
+#include "sendf.h"
 
 /* The last #include file should be: */
 #include "memdebug.h"
@@ -56,11 +57,14 @@ struct Curl_message {
 };
 
 typedef enum {
-  CURLM_STATE_INIT,
+  CURLM_STATE_INIT,        /* start in this state */
   CURLM_STATE_CONNECT,     /* resolve/connect has been sent off */
-  CURLM_STATE_WAITRESOLVE, /* we're awaiting the resolve to finalize */
-  CURLM_STATE_WAITCONNECT, /* we're awaiting the connect to finalize */
-  CURLM_STATE_DO,          /* send off the request (part 1) */
+  CURLM_STATE_WAITRESOLVE, /* awaiting the resolve to finalize */
+  CURLM_STATE_WAITCONNECT, /* awaiting the connect to finalize */
+  CURLM_STATE_PROTOCONNECT, /* completing the protocol-specific connect
+                               phase */
+  CURLM_STATE_DO,          /* start send off the request (part 1) */
+  CURLM_STATE_DOING,       /* sending off the request (part 1) */
   CURLM_STATE_DO_MORE,     /* send off the request (part 2) */
   CURLM_STATE_PERFORM,     /* transfer data */
   CURLM_STATE_DONE,        /* post data transfer operation */
@@ -111,6 +115,33 @@ struct Curl_multi {
   struct curl_hash *hostcache;
 };
 
+/* always use this function to change state, to make debugging easier */
+static void multistate(struct Curl_one_easy *easy, CURLMstate state)
+{
+#ifdef CURLDEBUG
+  const char *statename[]={
+    "INIT",
+    "CONNECT",
+    "WAITRESOLVE",
+    "WAITCONNECT",
+    "PROTOCONNECT",
+    "DO",
+    "DOING",
+    "DO_MORE",
+    "PERFORM",
+    "DONE",
+    "COMPLETED",
+  };
+  CURLMstate oldstate = easy->state;
+#endif
+  easy->state = state;
+
+#ifdef CURLDEBUG
+  infof(easy->easy_handle,
+        "STATE: %s => %s handle %p: \n",
+        statename[oldstate], statename[easy->state], (char *)easy);
+#endif
+}
 
 CURLM *curl_multi_init(void)
 {
@@ -158,7 +189,7 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
 
   /* set the easy handle */
   easy->easy_handle = easy_handle;
-  easy->state = CURLM_STATE_INIT;
+  multistate(easy, CURLM_STATE_INIT);
 
   /* for multi interface connections, we share DNS cache automaticly */
   easy->easy_handle->hostcache = multi->hostcache;
@@ -258,7 +289,22 @@ CURLMcode curl_multi_fdset(CURLM *multi_handle,
       break;
     case CURLM_STATE_WAITRESOLVE:
       /* waiting for a resolve to complete */
-      Curl_fdset(easy->easy_conn, read_fd_set, write_fd_set, &this_max_fd);
+      Curl_resolv_fdset(easy->easy_conn, read_fd_set, write_fd_set,
+                        &this_max_fd);
+      if(this_max_fd > *max_fd)
+        *max_fd = this_max_fd;
+      break;
+
+    case CURLM_STATE_PROTOCONNECT:
+      Curl_protocol_fdset(easy->easy_conn, read_fd_set, write_fd_set,
+                          &this_max_fd);
+      if(this_max_fd > *max_fd)
+        *max_fd = this_max_fd;
+      break;
+
+    case CURLM_STATE_DOING:
+      Curl_doing_fdset(easy->easy_conn, read_fd_set, write_fd_set,
+                       &this_max_fd);
       if(this_max_fd > *max_fd)
         *max_fd = this_max_fd;
       break;
@@ -318,6 +364,8 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
   struct Curl_message *msg = NULL;
   bool connected;
   bool async;
+  bool protocol_connect;
+  bool dophase_done;
 
   *running_handles = 0; /* bump this once for every living handle */
 
@@ -326,10 +374,6 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
 
   easy=multi->easy.next;
   while(easy) {
-#if 0
-    fprintf(stderr, "HANDLE %p: State: %x\n",
-            (char *)easy, easy->state);
-#endif
     do {
       if (CURLM_STATE_WAITCONNECT <= easy->state &&
           easy->state <= CURLM_STATE_DO &&
@@ -344,13 +388,13 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
             easy->easy_handle->change.url_changed = FALSE;
             easy->result = Curl_follow(easy->easy_handle, gotourl, FALSE);
             if(CURLE_OK == easy->result)
-              easy->state = CURLM_STATE_CONNECT;
+              multistate(easy, CURLM_STATE_CONNECT);
             else
               free(gotourl);
           }
           else {
             easy->result = CURLE_OUT_OF_MEMORY;
-            easy->state = CURLM_STATE_COMPLETED;
+            multistate(easy, CURLM_STATE_COMPLETED);
             break;
           }
         }
@@ -365,7 +409,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
 
         if(CURLE_OK == easy->result) {
           /* after init, go CONNECT */
-          easy->state = CURLM_STATE_CONNECT;
+          multistate(easy, CURLM_STATE_CONNECT);
           result = CURLM_CALL_MULTI_PERFORM;
 
           easy->easy_handle->state.used_interface = Curl_if_multi;
@@ -376,16 +420,22 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
         /* Connect. We get a connection identifier filled in. */
         Curl_pgrsTime(easy->easy_handle, TIMER_STARTSINGLE);
         easy->result = Curl_connect(easy->easy_handle, &easy->easy_conn,
-                                    &async);
+                                    &async, &protocol_connect);
 
         if(CURLE_OK == easy->result) {
           if(async)
             /* We're now waiting for an asynchronous name lookup */
-            easy->state = CURLM_STATE_WAITRESOLVE;
+            multistate(easy, CURLM_STATE_WAITRESOLVE);
           else {
-            /* after the connect has been sent off, go WAITCONNECT */
-            easy->state = CURLM_STATE_WAITCONNECT;
+            /* after the connect has been sent off, go WAITCONNECT unless the
+               protocol connect is already done and we can go directly to
+               DO! */
             result = CURLM_CALL_MULTI_PERFORM;
+
+            if(protocol_connect)
+              multistate(easy, CURLM_STATE_DO);
+            else
+              multistate(easy, CURLM_STATE_WAITCONNECT);
           }
         }
         break;
@@ -401,14 +451,17 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
         if(dns) {
           /* Perform the next step in the connection phase, and then move on
              to the WAITCONNECT state */
-          easy->result = Curl_async_resolved(easy->easy_conn);
+          easy->result = Curl_async_resolved(easy->easy_conn,
+                                             &protocol_connect);
 
           if(CURLE_OK != easy->result)
             /* if Curl_async_resolved() returns failure, the connection struct
                is already freed and gone */
             easy->easy_conn = NULL;           /* no more connection */
-
-          easy->state = CURLM_STATE_WAITCONNECT;
+          else {
+            /* FIX: what if protocol_connect is TRUE here?! */
+            multistate(easy, CURLM_STATE_WAITCONNECT);
+          }
         }
 
         if(CURLE_OK != easy->result) {
@@ -425,7 +478,8 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
         easy->result = Curl_is_connected(easy->easy_conn, FIRSTSOCKET,
                                          &connected);
         if(connected)
-          easy->result = Curl_protocol_connect(easy->easy_conn);
+          easy->result = Curl_protocol_connect(easy->easy_conn,
+                                               &protocol_connect);
 
         if(CURLE_OK != easy->result) {
           /* failure detected */
@@ -435,29 +489,64 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
         }
 
         if(connected) {
+          if(!protocol_connect) {
+            /* We have a TCP connection, but 'protocol_connect' may be false
+               and then we continue to 'STATE_PROTOCONNECT'. If protocol
+               connect is TRUE, we move on to STATE_DO. */
+            multistate(easy, CURLM_STATE_PROTOCONNECT);
+            fprintf(stderr, "WAITCONNECT => PROTOCONNECT\n");
+          }
+          else {
+            /* after the connect has completed, go DO */
+            multistate(easy, CURLM_STATE_DO);
+            result = CURLM_CALL_MULTI_PERFORM;
+          }
+        }
+        break;
+
+      case CURLM_STATE_PROTOCONNECT:
+        /* protocol-specific connect phase */
+        easy->result = Curl_protocol_connecting(easy->easy_conn,
+                                                &protocol_connect);
+        if(protocol_connect) {
           /* after the connect has completed, go DO */
-          easy->state = CURLM_STATE_DO;
+          multistate(easy, CURLM_STATE_DO);
           result = CURLM_CALL_MULTI_PERFORM;
         }
+        else if(easy->result) {
+          /* failure detected */
+          Curl_posttransfer(easy->easy_handle);
+          Curl_done(&easy->easy_conn, easy->result);
+          Curl_disconnect(easy->easy_conn); /* close the connection */
+          easy->easy_conn = NULL;           /* no more connection */
+        }
         break;
 
       case CURLM_STATE_DO:
-        /* Do the fetch or put request */
-        easy->result = Curl_do(&easy->easy_conn);
+        /* Perform the protocol's DO action */
+        easy->result = Curl_do(&easy->easy_conn, &dophase_done);
+
         if(CURLE_OK == easy->result) {
 
-          /* after do, go PERFORM... or DO_MORE */
-          if(easy->easy_conn->bits.do_more) {
+          if(!dophase_done) {
+            /* DO was not completed in one function call, we must continue
+               DOING... */
+            multistate(easy, CURLM_STATE_DOING);
+            result = CURLM_OK;
+          }
+
+          /* after DO, go PERFORM... or DO_MORE */
+          else if(easy->easy_conn->bits.do_more) {
             /* we're supposed to do more, but we need to sit down, relax
                and wait a little while first */
-            easy->state = CURLM_STATE_DO_MORE;
+            multistate(easy, CURLM_STATE_DO_MORE);
             result = CURLM_OK;
           }
           else {
             /* we're done with the DO, now PERFORM */
             easy->result = Curl_readwrite_init(easy->easy_conn);
             if(CURLE_OK == easy->result) {
-              easy->state = CURLM_STATE_PERFORM;
+              multistate(easy, CURLM_STATE_PERFORM);
               result = CURLM_CALL_MULTI_PERFORM;
             }
           }
@@ -471,10 +560,39 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
         }
         break;
 
+      case CURLM_STATE_DOING:
+        /* we continue DOING until the DO phase is complete */
+        easy->result = Curl_protocol_doing(easy->easy_conn, &dophase_done);
+        if(CURLE_OK == easy->result) {
+          if(dophase_done) {
+            /* after DO, go PERFORM... or DO_MORE */
+            if(easy->easy_conn->bits.do_more) {
+              /* we're supposed to do more, but we need to sit down, relax
+                 and wait a little while first */
+              multistate(easy, CURLM_STATE_DO_MORE);
+              result = CURLM_OK;
+            }
+            else {
+              /* we're done with the DO, now PERFORM */
+              easy->result = Curl_readwrite_init(easy->easy_conn);
+              if(CURLE_OK == easy->result) {
+                multistate(easy, CURLM_STATE_PERFORM);
+                result = CURLM_CALL_MULTI_PERFORM;
+              }
+            }
+          } /* dophase_done */
+        }
+        else {
+          /* failure detected */
+          Curl_posttransfer(easy->easy_handle);
+          Curl_done(&easy->easy_conn, easy->result);
+          Curl_disconnect(easy->easy_conn); /* close the connection */
+          easy->easy_conn = NULL;           /* no more connection */
+        }
+        break;
+
       case CURLM_STATE_DO_MORE:
-        /*
-         * First, check if we really are ready to do more.
-         */
+        /* Ready to do more? */
         easy->result = Curl_is_connected(easy->easy_conn, SECONDARYSOCKET,
                                          &connected);
         if(connected) {
@@ -487,7 +605,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
             easy->result = Curl_readwrite_init(easy->easy_conn);
 
           if(CURLE_OK == easy->result) {
-            easy->state = CURLM_STATE_PERFORM;
+            multistate(easy, CURLM_STATE_PERFORM);
             result = CURLM_CALL_MULTI_PERFORM;
           }
         }
@@ -532,7 +650,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
             if(easy->result == CURLE_OK)
               easy->result = Curl_follow(easy->easy_handle, newurl, retry);
             if(CURLE_OK == easy->result) {
-              easy->state = CURLM_STATE_CONNECT;
+              multistate(easy, CURLM_STATE_CONNECT);
               result = CURLM_CALL_MULTI_PERFORM;
             }
             else
@@ -542,7 +660,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
           }
           else {
             /* after the transfer is done, go DONE */
-            easy->state = CURLM_STATE_DONE;
+            multistate(easy, CURLM_STATE_DONE);
             result = CURLM_CALL_MULTI_PERFORM;
           }
         }
@@ -553,7 +671,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
 
         /* after we have DONE what we're supposed to do, go COMPLETED, and
            it doesn't matter what the Curl_done() returned! */
-        easy->state = CURLM_STATE_COMPLETED;
+        multistate(easy, CURLM_STATE_COMPLETED);
         break;
 
       case CURLM_STATE_COMPLETED:
@@ -571,7 +689,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
           /*
            * If an error was returned, and we aren't in completed state now,
            * then we go to completed and consider this transfer aborted.  */
-          easy->state = CURLM_STATE_COMPLETED;
+          multistate(easy, CURLM_STATE_COMPLETED);
         }
         else
           /* this one still lives! */
@@ -600,7 +718,6 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
 
       multi->num_msgs++; /* increase message counter */
     }
-
     easy = easy->next; /* operate on next handle */
   }
 
index 5c3a9c53f0af2bedabe245c9416798885c2ddb6c..dc6632c6aa8ec3cfc339c0d78340cdd0f2444219 100644 (file)
@@ -27,5 +27,4 @@
  * Prototypes for library-wide functions provided by multi.c
  */
 void Curl_multi_rmeasy(void *multi, CURL *data);
-
 #endif /* __MULTIIF_H */
index 77ff52e3aed4a3b0f4c2a63d0c68f7662863fedb..ae96af3e07d1b0b39d09a731df4ba185d1411117 100644 (file)
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
index 9fbf85ff3615b5ae7544be84023b8a8b575d6e0d..d528615a505a0a9aa5d78a57e7978920a376655e 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -1065,7 +1065,7 @@ CURLcode Curl_telnet_done(struct connectdata *conn, CURLcode status)
   return CURLE_OK;
 }
 
-CURLcode Curl_telnet(struct connectdata *conn)
+CURLcode Curl_telnet(struct connectdata *conn, bool *done)
 {
   CURLcode code;
   struct SessionHandle *data = conn->data;
@@ -1093,6 +1093,8 @@ CURLcode Curl_telnet(struct connectdata *conn)
   char *buf = data->state.buffer;
   struct TELNET *tn;
 
+  *done = TRUE; /* uncontionally */
+
   code = init_telnet(conn);
   if(code)
     return code;
index 86dd99b6f340cda9fd5b5659f5c01cdca65fe4fe..bb30f083fff350a2cd196ea0b2965ca44f661ada 100644 (file)
@@ -2,18 +2,18 @@
 #define __TELNET_H
 
 /***************************************************************************
- *                                  _   _ ____  _     
- *  Project                     ___| | | |  _ \| |    
- *                             / __| | | | |_) | |    
- *                            | (__| |_| |  _ <| |___ 
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
  * are also available at http://curl.haxx.se/docs/copyright.html.
- * 
+ *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
  * furnished to do so, under the terms of the COPYING file.
@@ -24,7 +24,7 @@
  * $Id$
  ***************************************************************************/
 #ifndef CURL_DISABLE_TELNET
-CURLcode Curl_telnet(struct connectdata *conn);
+CURLcode Curl_telnet(struct connectdata *conn, bool *done);
 CURLcode Curl_telnet_done(struct connectdata *conn, CURLcode);
 #endif
 #endif
index ea7c5dbe34df87f3844414175419c3d2f899eaaf..9b37988de7f10773093d93d4aaad0d90d5581f4a 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -2027,9 +2027,11 @@ Curl_connect_host(struct SessionHandle *data,
 
   do {
     bool async;
+    bool protocol_done=TRUE; /* will be TRUE always since this is only used
+                                within the easy interface */
     Curl_pgrsTime(data, TIMER_STARTSINGLE);
     data->change.url_changed = FALSE;
-    res = Curl_connect(data, conn, &async);
+    res = Curl_connect(data, conn, &async, &protocol_done);
 
     if((CURLE_OK == res) && async) {
       /* Now, if async is TRUE here, we need to wait for the name
@@ -2037,8 +2039,9 @@ Curl_connect_host(struct SessionHandle *data,
       res = Curl_wait_for_resolv(*conn, NULL);
       if(CURLE_OK == res)
         /* Resolved, continue with the connection */
-        res = Curl_async_resolved(*conn);
+        res = Curl_async_resolved(*conn, &protocol_done);
       else
+        /* if we can't resolve, we kill this "connection" now */
         (void)Curl_disconnect(*conn);
     }
     if(res)
@@ -2126,8 +2129,8 @@ CURLcode Curl_perform(struct SessionHandle *data)
     }
 
     if(res == CURLE_OK) {
-
-      res = Curl_do(&conn);
+      bool do_done;
+      res = Curl_do(&conn, &do_done);
 
       /* for non 3rd party transfer only */
       if(res == CURLE_OK && !data->set.source_url) {
index d25f43ebc252d2a3a550d005e40abb8ddee2fba5..7de06ff3fd34924af725bc277b234540581b6108 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -1989,41 +1989,117 @@ static void verboseconnect(struct connectdata *conn)
         conn->ip_addr_str, conn->port);
 }
 
+CURLcode Curl_protocol_fdset(struct connectdata *conn,
+                             fd_set *read_fd_set,
+                             fd_set *write_fd_set,
+                             int *max_fdp)
+{
+  CURLcode res = CURLE_OK;
+  if(conn->curl_proto_fdset)
+    res = conn->curl_proto_fdset(conn, read_fd_set, write_fd_set, max_fdp);
+  return res;
+}
+
+CURLcode Curl_doing_fdset(struct connectdata *conn,
+                          fd_set *read_fd_set,
+                          fd_set *write_fd_set,
+                          int *max_fdp)
+{
+  CURLcode res = CURLE_OK;
+  if(conn && conn->curl_doing_fdset)
+    res = conn->curl_doing_fdset(conn, read_fd_set, write_fd_set, max_fdp);
+  return res;
+}
+
+/*
+ * We are doing protocol-specific connecting and this is being called over and
+ * over from the multi interface until the connection phase is done on
+ * protocol layer.
+ */
+
+CURLcode Curl_protocol_connecting(struct connectdata *conn, bool *done)
+{
+  CURLcode result=CURLE_OK;
+
+  if(conn && conn->curl_connecting) {
+    *done = FALSE;
+    result = conn->curl_connecting(conn, done);
+  }
+  else
+    *done = TRUE;
+
+  return result;
+}
+
+/*
+ * We are DOING this is being called over and over from the multi interface
+ * until the DOING phase is done on protocol layer.
+ */
+
+CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done)
+{
+  CURLcode result=CURLE_OK;
+
+  if(conn && conn->curl_doing) {
+    *done = FALSE;
+    result = conn->curl_doing(conn, done);
+  }
+  else
+    *done = TRUE;
+
+  return result;
+}
+
 /*
  * We have discovered that the TCP connection has been successful, we can now
  * proceed with some action.
  *
- * If we're using the multi interface, this host address pointer is most
- * likely NULL at this point as we can't keep the resolved info around. This
- * may call for some reworking, like a reference counter in the struct or
- * something.
  */
-CURLcode Curl_protocol_connect(struct connectdata *conn)
+CURLcode Curl_protocol_connect(struct connectdata *conn, bool *protocol_done)
 {
   struct SessionHandle *data = conn->data;
   CURLcode result=CURLE_OK;
 
-  if(conn->bits.tcpconnect)
+  *protocol_done = FALSE;
+
+  if(conn->bits.tcpconnect && conn->bits.protoconnstart) {
     /* We already are connected, get back. This may happen when the connect
        worked fine in the first call, like when we connect to a local server
-       or proxy. */
+       or proxy. Note that we don't know if the protocol is actually done.
+
+       Unless this protocol doesn't have any protocol-connect callback, as
+       then we know we're done. */
+    if(!conn->curl_connecting)
+      *protocol_done = TRUE;
+
     return CURLE_OK;
+  }
+
+  if(!conn->bits.tcpconnect) {
 
-  Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
+    Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
 
-  if(data->set.verbose)
-    verboseconnect(conn);
+    if(data->set.verbose)
+      verboseconnect(conn);
+  }
 
-  if(conn->curl_connect) {
-    /* is there a protocol-specific connect() procedure? */
+  if(!conn->bits.protoconnstart) {
+    if(conn->curl_connect) {
+      /* is there a protocol-specific connect() procedure? */
 
-    /* set start time here for timeout purposes in the
-     * connect procedure, it is later set again for the
-     * progress meter purpose */
-    conn->now = Curl_tvnow();
+      /* Set start time here for timeout purposes in the connect procedure, it
+         is later set again for the progress meter purpose */
+      conn->now = Curl_tvnow();
 
-    /* Call the protocol-specific connect function */
-    result = conn->curl_connect(conn);
+      /* Call the protocol-specific connect function */
+      result = conn->curl_connect(conn, protocol_done);
+    }
+    else
+      *protocol_done = TRUE;
+
+    /* it has started, possibly even completed but that knowledge isn't stored
+       in this bit! */
+    conn->bits.protoconnstart = TRUE;
   }
 
   return result; /* pass back status */
@@ -2733,6 +2809,10 @@ static CURLcode CreateConnection(struct SessionHandle *data,
       conn->curl_do_more = Curl_ftp_nextconnect;
       conn->curl_done = Curl_ftp_done;
       conn->curl_connect = Curl_ftp_connect;
+      conn->curl_connecting = Curl_ftp_multi_statemach;
+      conn->curl_doing = Curl_ftp_doing;
+      conn->curl_proto_fdset = Curl_ftp_fdset;
+      conn->curl_doing_fdset = Curl_ftp_fdset;
       conn->curl_disconnect = Curl_ftp_disconnect;
     }
 
@@ -3385,17 +3465,21 @@ static CURLcode CreateConnection(struct SessionHandle *data,
  */
 
 static CURLcode SetupConnection(struct connectdata *conn,
-                                struct Curl_dns_entry *hostaddr)
+                                struct Curl_dns_entry *hostaddr,
+                                bool *protocol_done)
 {
   struct SessionHandle *data = conn->data;
   CURLcode result=CURLE_OK;
 
   Curl_pgrsTime(data, TIMER_NAMELOOKUP);
 
-  if(conn->protocol & PROT_FILE)
+  if(conn->protocol & PROT_FILE) {
     /* There's nothing in this function to setup if we're only doing
        a file:// transfer */
+    *protocol_done = TRUE;
     return result;
+  }
+  *protocol_done = FALSE; /* default to not done */
 
   /*************************************************************
    * Send user-agent to HTTP proxies even if the target protocol
@@ -3416,13 +3500,13 @@ static CURLcode SetupConnection(struct connectdata *conn,
   conn->headerbytecount = 0;
 
   if(CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET]) {
-    bool connected;
+    bool connected = FALSE;
 
     /* Connect only if not already connected! */
     result = ConnectPlease(conn, hostaddr, &connected);
 
     if(connected) {
-      result = Curl_protocol_connect(conn);
+      result = Curl_protocol_connect(conn, protocol_done);
       if(CURLE_OK == result)
         conn->bits.tcpconnect = TRUE;
     }
@@ -3436,6 +3520,7 @@ static CURLcode SetupConnection(struct connectdata *conn,
   else {
     Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */
     conn->bits.tcpconnect = TRUE;
+    *protocol_done = TRUE;
     if(data->set.verbose)
       verboseconnect(conn);
   }
@@ -3460,7 +3545,8 @@ static CURLcode SetupConnection(struct connectdata *conn,
 
 CURLcode Curl_connect(struct SessionHandle *data,
                       struct connectdata **in_connect,
-                      bool *asyncp)
+                      bool *asyncp,
+                      bool *protocol_done)
 {
   CURLcode code;
   struct Curl_dns_entry *dns;
@@ -3476,7 +3562,7 @@ CURLcode Curl_connect(struct SessionHandle *data,
       /* If an address is available it means that we already have the name
          resolved, OR it isn't async.
          If so => continue connecting from here */
-      code = SetupConnection(*in_connect, dns);
+      code = SetupConnection(*in_connect, dns, protocol_done);
     /* else
          response will be received and treated async wise */
   }
@@ -3494,12 +3580,16 @@ CURLcode Curl_connect(struct SessionHandle *data,
 }
 
 /* Call this function after Curl_connect() has returned async=TRUE and
-   then a successful name resolve has been received */
-CURLcode Curl_async_resolved(struct connectdata *conn)
+   then a successful name resolve has been received.
+
+   Note: this function disconnects and frees the conn data in case of
+   resolve failure */
+CURLcode Curl_async_resolved(struct connectdata *conn,
+                             bool *protocol_done)
 {
 #if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) || \
     defined(USE_THREADING_GETADDRINFO)
-  CURLcode code = SetupConnection(conn, conn->async.dns);
+  CURLcode code = SetupConnection(conn, conn->async.dns, protocol_done);
 
   if(code)
     /* We're not allowed to return failure with memory left allocated
@@ -3573,7 +3663,7 @@ CURLcode Curl_done(struct connectdata **connp,
   return result;
 }
 
-CURLcode Curl_do(struct connectdata **connp)
+CURLcode Curl_do(struct connectdata **connp, bool *done)
 {
   CURLcode result=CURLE_OK;
   struct connectdata *conn = *connp;
@@ -3583,7 +3673,7 @@ CURLcode Curl_do(struct connectdata **connp)
 
   if(conn->curl_do) {
     /* generic protocol-specific function pointer set in curl_connect() */
-    result = conn->curl_do(conn);
+    result = conn->curl_do(conn, done);
 
     /* This was formerly done in transfer.c, but we better do it here */
 
@@ -3603,8 +3693,10 @@ CURLcode Curl_do(struct connectdata **connp)
 
       if(CURLE_OK == result) {
         bool async;
+        bool protocol_done = TRUE;
+
         /* Now, redo the connect and get a new connection */
-        result = Curl_connect(data, connp, &async);
+        result = Curl_connect(data, connp, &async, &protocol_done);
         if(CURLE_OK == result) {
           /* We have connected or sent away a name resolve query fine */
 
@@ -3617,13 +3709,13 @@ CURLcode Curl_do(struct connectdata **connp)
               return result;
 
             /* Resolved, continue with the connection */
-            result = Curl_async_resolved(conn);
+            result = Curl_async_resolved(conn, &protocol_done);
             if(result)
               return result;
           }
 
           /* ... finally back to actually retry the DO phase */
-          result = conn->curl_do(conn);
+          result = conn->curl_do(conn, done);
         }
       }
     }
index 8fd4795e95e49c38f72693c309fce9d3fdc6c275..5a1dff2662798377d003999d269bea0d18ac54a6 100644 (file)
--- a/lib/url.h
+++ b/lib/url.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -31,17 +31,28 @@ CURLcode Curl_open(struct SessionHandle **curl);
 CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, ...);
 CURLcode Curl_close(struct SessionHandle *data); /* opposite of curl_open() */
 CURLcode Curl_connect(struct SessionHandle *, struct connectdata **,
-                      bool *async);
-CURLcode Curl_async_resolved(struct connectdata *conn);
-CURLcode Curl_do(struct connectdata **);
+                      bool *async, bool *protocol_connect);
+CURLcode Curl_async_resolved(struct connectdata *conn,
+                             bool *protocol_connect);
+CURLcode Curl_do(struct connectdata **, bool *done);
 CURLcode Curl_do_more(struct connectdata *);
 CURLcode Curl_done(struct connectdata **, CURLcode);
 CURLcode Curl_disconnect(struct connectdata *);
-CURLcode Curl_protocol_connect(struct connectdata *conn);
+CURLcode Curl_protocol_connect(struct connectdata *conn, bool *done);
+CURLcode Curl_protocol_connecting(struct connectdata *conn, bool *done);
+CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done);
 bool Curl_ssl_config_matches(struct ssl_config_data* data,
                              struct ssl_config_data* needle);
 bool Curl_clone_ssl_config(struct ssl_config_data* source,
                            struct ssl_config_data* dest);
 void Curl_free_ssl_config(struct ssl_config_data* sslc);
 void Curl_safefree(void *ptr);
+CURLcode Curl_protocol_fdset(struct connectdata *conn,
+                             fd_set *read_fd_set,
+                             fd_set *write_fd_set,
+                             int *max_fdp);
+CURLcode Curl_doing_fdset(struct connectdata *conn,
+                          fd_set *read_fd_set,
+                          fd_set *write_fd_set,
+                          int *max_fdp);
 #endif
index 2068c9a7015cc04d8b333ee74b15c4d87464fbf2..e270334c0bc4733fa01a0742224b82f78d6df84d 100644 (file)
@@ -243,6 +243,42 @@ struct HTTP {
 /****************************************************************************
  * FTP unique setup
  ***************************************************************************/
+typedef enum {
+  FTP_STOP,    /* do nothing state, stops the state machine */
+  FTP_WAIT220, /* waiting for the inintial 220 response immediately after
+                  a connect */
+  FTP_AUTH,
+  FTP_USER,
+  FTP_PASS,
+  FTP_ACCT,
+  FTP_PBSZ,
+  FTP_PROT,
+  FTP_PWD,
+  FTP_QUOTE, /* waiting for a response to a command sent in a quote list */
+  FTP_RETR_PREQUOTE,
+  FTP_STOR_PREQUOTE,
+  FTP_POSTQUOTE,
+  FTP_CWD,  /* change dir */
+  FTP_MKD,  /* if the dir didn't exist */
+  FTP_MDTM, /* to figure out the datestamp */
+  FTP_TYPE, /* to set type when doing a head-like request */
+  FTP_LIST_TYPE, /* set type when about to do a dir list */
+  FTP_RETR_TYPE, /* set type when about to RETR a file */
+  FTP_STOR_TYPE, /* set type when about to STOR a file */
+  FTP_SIZE, /* get the remote file's size for head-like request */
+  FTP_RETR_SIZE, /* get the remote file's size for RETR */
+  FTP_STOR_SIZE, /* get the size for (resumed) STOR */
+  FTP_REST, /* when used to check if the server supports it in head-like */
+  FTP_RETR_REST, /* when asking for "resume" in for RETR */
+  FTP_PORT, /* generic state for PORT, LPRT and EPRT, check count1 */
+  FTP_PASV, /* generic state for PASV and EPSV, check count1 */
+  FTP_LIST, /* generic state for LIST, NLST or a custom list command */
+  FTP_RETR,
+  FTP_STOR, /* generic state for STOR and APPE */
+  FTP_QUIT,
+  FTP_LAST  /* never used */
+} ftpstate;
+
 struct FTP {
   curl_off_t *bytecountp;
   char *user;    /* user name string */
@@ -271,6 +307,18 @@ struct FTP {
   bool cwddone;     /* if it has been determined that the proper CWD combo
                        already has been done */
   char *prevpath;   /* conn->path from the previous transfer */
+  size_t nread_resp; /* number of bytes currently read of a server response */
+  int count1; /* general purpose counter for the state machine */
+  int count2; /* general purpose counter for the state machine */
+  int count3; /* general purpose counter for the state machine */
+  char *sendthis; /* allocated pointer to a buffer that is to be sent to the
+                     ftp server */
+  size_t sendleft; /* number of bytes left to send from the sendthis buffer */
+  size_t sendsize; /* total size of the sendthis buffer */
+  struct timeval response; /* set to Curl_tvnow() when a command has been sent
+                              off, used to time-out response reading */
+  ftpstate state; /* always use ftp.c:state() to change state! */
+  curl_off_t downloadsize;
 };
 
 /****************************************************************************
@@ -309,9 +357,12 @@ struct ConnectBits {
   bool forbidchunk;   /* used only to explicitly forbid chunk-upload for
                          specific upload buffers. See readmoredata() in
                          http.c for details. */
-  bool tcpconnect;    /* the tcp stream (or simimlar) is connected, this
-                         is set the first time on the first connect function
-                         call */
+
+  bool tcpconnect;    /* the TCP layer (or simimlar) is connected, this is set
+                         the first time on the first connect function call */
+  bool protoconnstart;/* the protocol layer has STARTED its operation after
+                         the TCP layer connect */
+
   bool retry;         /* this connection is about to get closed and then
                          re-attempted at another connection. */
   bool no_body;       /* CURLOPT_NO_BODY (or similar) was set */
@@ -510,7 +561,7 @@ struct connectdata {
 
   /* These two functions MUST be set by the curl_connect() function to be
      be protocol dependent */
-  CURLcode (*curl_do)(struct connectdata *);
+  CURLcode (*curl_do)(struct connectdata *, bool *done);
   CURLcode (*curl_done)(struct connectdata *, CURLcode);
 
   /* If the curl_do() function is better made in two halves, this
@@ -521,8 +572,29 @@ struct connectdata {
 
   /* This function *MAY* be set to a protocol-dependent function that is run
    * after the connect() and everything is done, as a step in the connection.
+   * The 'done' pointer points to a bool that should be set to TRUE if the
+   * function completes before return. If it doesn't complete, the caller
+   * should call the curl_connecting() function until it is.
    */
-  CURLcode (*curl_connect)(struct connectdata *);
+  CURLcode (*curl_connect)(struct connectdata *, bool *done);
+
+  /* See above. Currently only used for FTP. */
+  CURLcode (*curl_connecting)(struct connectdata *, bool *done);
+  CURLcode (*curl_doing)(struct connectdata *, bool *done);
+
+  /* Called from the multi interface during the PROTOCONNECT phase, and it
+     should then return a proper fd set */
+  CURLcode (*curl_proto_fdset)(struct connectdata *conn,
+                               fd_set *read_fd_set,
+                               fd_set *write_fd_set,
+                               int *max_fdp);
+
+  /* Called from the multi interface during the DOING phase, and it should
+     then return a proper fd set */
+  CURLcode (*curl_doing_fdset)(struct connectdata *conn,
+                               fd_set *read_fd_set,
+                               fd_set *write_fd_set,
+                               int *max_fdp);
 
   /* This function *MAY* be set to a protocol-dependent function that is run
    * by the curl_disconnect(), as a step in the disconnection.
index 1558d18f2b6b1cb29df0e9dbbefa819a4e102fb8..c1317ad27816f2d278625d1d632346c702f476ad 100644 (file)
@@ -21,7 +21,6 @@
  * $Id$
  ***************************************************************************/
 
-/* This is now designed to have its own local setup.h */
 #include "setup.h"
 
 #include <stdio.h>
@@ -3726,7 +3725,8 @@ operate(struct Configurable *config, int argc, char *argv[])
                 }
               }
             } /* if CURLE_OK */
-            else if(CURLE_FTP_USER_PASSWORD_INCORRECT == res) {
+            else if((CURLE_FTP_USER_PASSWORD_INCORRECT == res) ||
+                    (CURLE_LOGIN_DENIED == res)) {
               curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
 
               if(response/100 == 5)
index 19dcc558250d52fef1843dee5d460e7ed2867c6e..c80fcca0ec6d3b63b1adf73a6060b778080e5d0c 100644 (file)
@@ -21,7 +21,7 @@ REPLY USER 314 bluah you fewl!
 # Verify data after the test has been "shot"
 <verify>
 <errorcode>
-12
+67
 </errorcode>
 <protocol>
 USER anonymous\r
index 05555abf9b2b13142079f1517943a8d826fab1f6..2f24e55f008a3fc603e7279c9cc12560bf56fe64 100644 (file)
@@ -21,7 +21,7 @@ REPLY PASS 314 bluah you f00l!
 # Verify data after the test has been "shot"
 <verify>
 <errorcode>
-11
+67
 </errorcode>
 <protocol>
 USER anonymous\r
index d5c48fb6c77b06a07343e201ce1ac37ebac95d0c..78b179e823217e2ba20a4fe8684ce910426a2b98 100644 (file)
@@ -15,7 +15,7 @@ ftp
 FTP download with strict timeout and slow CWD
  </name>
  <command>
-ftp://%HOSTIP:%FTPPORT/path/to/file/126 -m 3
+ftp://%HOSTIP:%FTPPORT/path/to/file/190 -m 3
 </command>
 <file name="log/ftpserver.cmd">
 DELAY CWD 15
@@ -24,6 +24,7 @@ DELAY CWD 15
 
 # Verify data after the test has been "shot"
 <verify>
+# 28 is CURLE_OPERATION_TIMEOUTED
 <errorcode>
 28
 </errorcode>
index 39b0744ed9ffd5a482dce67b5566e51038d1f477..cadfb3d6f873a55e54a49ab87004df22c8f7748b 100644 (file)
@@ -21,7 +21,7 @@ REPLY PASS 530 temporarily not available
 # Verify data after the test has been "shot"
 <verify>
 <errorcode>
-10
+67
 </errorcode>
 <protocol>
 USER anonymous\r
index ec43918b0ad5d77cd785d7e42898cdf9b2d6ed05..b47cf5d6bdcdfee95db5afa22f9d35516fedbccb 100644 (file)
@@ -20,8 +20,9 @@ REPLY PASS 530 temporarily not available
 
 # Verify data after the test has been "shot"
 <verify>
+# 67 is CURLE_LOGIN_DENIED
 <errorcode>
-10
+67
 </errorcode>
 <protocol>
 USER anonymous\r
index 4afb1dc9fe6a116ad5abc15810665e04e600f20f..c0861a0531c98367802669d34ece11b1d600eaa8 100644 (file)
@@ -9,7 +9,6 @@ int test(char *URL)
   curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
   curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
   res = curl_easy_perform(curl);
-  curl_easy_cleanup(curl);  
+  curl_easy_cleanup(curl);
   return (int)res;
 }
-