Index: lib/ftp.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/ftp.c,v
retrieving revision 1.290
diff -u -r1.290 ftp.c
--- lib/ftp.c	17 Dec 2004 10:09:32 -0000	1.290
+++ lib/ftp.c	5 Jan 2005 14:14:02 -0000
@@ -129,6 +129,7 @@
 
 /* easy-to-use macro: */
 #define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z))) return result
+#define NEWFTPSENDF(x,y,z) if((result = Curl_newftpsendf(x,y,z))) return result
 
 static void freedirs(struct FTP *ftp)
 {
@@ -218,6 +219,166 @@
 }
 
 
+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->host.dispname);
+
+          /*
+           * 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 --- */
 
 /*
@@ -442,307 +603,407 @@
   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)
+static CURLcode ftp_state_user(struct connectdata *conn)
 {
-  /* 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
-  };
-
-  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;
+  struct FTP *ftp = conn->proto.ftp;
+  /* send USER */
+  NEWFTPSENDF(conn, "USER %s", ftp->user?ftp->user:"");
 
-  /* get some initial data into the ftp struct */
-  ftp->bytecountp = &conn->bytecount;
+  ftp->state = FTP_USER;
 
-  /* no need to duplicate them, this connectdata struct won't change */
-  ftp->user = conn->user;
-  ftp->passwd = conn->passwd;
-  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;
-  }
+  return CURLE_OK;
+}
 
-  /* The first thing we do is wait for the "220*" line: */
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
+static CURLcode ftp_state_pwd(struct connectdata *conn)
+{
+  CURLcode result;
+  struct FTP *ftp = conn->proto.ftp;
+  /* send PWD to discover our entry point */
+  NEWFTPSENDF(conn, "PWD", NULL);
 
-  if(ftpcode != 220) {
-    failf(data, "This doesn't seem like a nice ftp-server response");
-    return CURLE_FTP_WEIRD_SERVER_REPLY;
-  }
+  ftp->state = FTP_PWD;
 
-#ifdef HAVE_KRB4
-  /* if not anonymous login, try a secure login */
-  if(data->set.krb4) {
+  return CURLE_OK;
+}
 
-    /* request data protection level (default is 'clear') */
-    Curl_sec_request_prot(conn, "private");
+static CURLcode ftp_stateconnect(struct connectdata *conn)
+{
+  CURLcode result;
+  curl_socket_t sock = conn->sock[FIRSTSOCKET];
+  int rc;
+  bool done = FALSE;
+  struct SessionHandle *data=conn->data;
+  int ftpcode;
+  struct FTP *ftp = conn->proto.ftp;
+  static const char * const ftpauth[]  = {
+    "SSL", "TLS"
+  };
+  char *buf = data->state.buffer; /* this is our buffer */
+  size_t nread;
 
-    /* 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);
+  /* When we connect, we start in the state where we await the 220
+     response */
+  ftp->state = FTP_WAIT220;
+
+  do {
+
+    if(ftp->sendleft) {
+      /* we have a piece of a command still left to send */
+      rc = Curl_select(CURL_SOCKET_BAD, /* no reading */
+                       sock,            /* for writing */
+                       100000           /* FIX */);
+    }
+    else {
+      rc = Curl_select(sock,            /* for reading */
+                       CURL_SOCKET_BAD, /* no writing */
+                       100000           /* FIX */);
+    }
 
-    if(Curl_sec_login(conn) != 0)
-      infof(data, "Logging in with password in cleartext!\n");
-    else
-      infof(data, "Authentication successful\n");
-  }
-#endif
 
-  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 */
+    if(rc == -1) {
+      failf(data, "select error");
+      return CURLE_OUT_OF_MEMORY;
     }
+    else if(rc == 0) {
+      /* timeout FIX */
+    }
+    else {
+      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(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;
+        }
 
-    for (trynum = start; ftpauth[count]; trynum=trynext, count++) {
-
-      FTPSENDF(conn, "AUTH %s", ftpauth[trynum]);
+        continue;
+      }
 
-      result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+      /* we read a piece of response */
+      result = ftp_readresp(sock, conn, &ftpcode, &nread);
 
       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) {
+        /* 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;
+          }
 
-      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;
-      }
-    }
-  }
+          /* We have received a 220 response fine, now we proceed. */
+#ifdef HAVE_KRB4
+          if(data->set.krb4) {
+            /* If not anonymous login, try a secure login. Note that this
+               procedure is still BLOCKING. */
+
+            Curl_sec_request_prot(conn, "private");
+            /* We set private first as default, in case the line below fails to
+               set a valid level */
+            Curl_sec_request_prot(conn, data->set.krb4_level);
+
+            if(Curl_sec_login(conn) != 0)
+              infof(data, "Logging in with password in cleartext!\n");
+            else
+              infof(data, "Authentication successful\n");
+          }
+#endif
 
-  /* send USER */
-  FTPSENDF(conn, "USER %s", ftp->user?ftp->user:"");
+          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 */
+            }
+            NEWFTPSENDF(conn, "AUTH %s", ftpauth[ftp->count1]);
+            ftp->state = FTP_AUTH;
+          }
+          else {
+            ftp_state_user(conn);
+            if(result)
+              return result;
+          }
 
-  /* wait for feedback */
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
+          break;
 
-  if(ftpcode == 530) {
-    /* 530 User ... access denied
-       (the server denies to log the specified user) */
-    failf(data, "Access denied: %s", &buf[4]);
-    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;
+        case FTP_AUTH:
+          /* we have gotten the response to a previous AUTH command */
 
-    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: %s", &buf[4]);
-      return CURLE_FTP_USER_PASSWORD_INCORRECT;
-    }
-    else if(ftpcode/100 == 2) {
-      /* 230 User ... logged in.
-         (user successfully logged in)
+          /* RFC2228 (page 5) says:
+           *
+           * If the server is willing to accept the named security mechanism,
+           * and does not require any security data, it must respond with
+           * reply code 234/334.
+           */
+
+          if((ftpcode == 234) || (ftpcode == 334)) {
+            /* Curl_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(ftp->count3 < 1) {
+            ftp->count3++;
+            ftp->count1 += ftp->count2; /* get next attempt */
+            NEWFTPSENDF(conn, "AUTH %s", ftpauth[ftp->count1]);
+            /* remain in this same state */
+          }
+          else {
+            result = ftp_state_user(conn);
+            if(result)
+              return result;
+          }
+          break;
 
-         Apparently, proftpd with SSL returns 232 here at times. */
+        case FTP_USER:
+        case FTP_PASS:
+          if((ftpcode == 331) && (ftp->state == FTP_USER)) {
+            /* 331 Password required for ...
+               (the server requires to send the user's password too) */
+            NEWFTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:"");
+            ftp->state = FTP_PASS;
+          }
+          else if(ftpcode/100 == 2) {
+            /* 230 User ... logged in.
+               (the user logged in with or without password) */
+
+            infof(data, "We have successfully logged in\n");
 
-      infof(data, "We have successfully logged in\n");
-    }
-    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);
+            /* 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) {
-        result = Curl_krb_kauth(conn);
-        if(result)
-          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) {
+              /* BLOCKING */
+              result = Curl_krb_kauth(conn);
+              if(result)
+                return result;
+            }
 #endif
-    }
-  }
-  else {
-    failf(data, "Odd return code after USER");
-    return CURLE_FTP_WEIRD_USER_REPLY;
-  }
+            if(conn->ssl[FIRSTSOCKET].use) {
+              /* PBSZ = PROTECTION BUFFER SIZE.
 
-  if(conn->ssl[FIRSTSOCKET].use) {
-    /* PBSZ = PROTECTION BUFFER SIZE.
+              The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says:
 
-       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.
+              */
+              NEWFTPSENDF(conn, "PBSZ %d", 0);
+              ftp->state = FTP_PBSZ;
+            }
+            else {
+              result = ftp_state_pwd(conn);
+              if(result)
+                return result;
+            }
+          }
+          else {
+            /* All other response codes, like:
 
-       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)
+            530 User ... access denied
+            (the server denies to log the specified user) */
+            failf(data, "Access denied: %03d", ftpcode);
+            if(ftpcode == 530)
+              return CURLE_FTP_ACCESS_DENIED;
+            if(ftp->state == FTP_USER)
+              return CURLE_FTP_WEIRD_USER_REPLY;
+            else
+              return CURLE_FTP_WEIRD_PASS_REPLY;
+          }
 
-       ... (and on page 8):
+          break;
 
-       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;
+        case FTP_PBSZ:
+          /* FIX: check response code */
 
-    /* For TLS, the data connection can have one of two security levels.
+          /* For TLS, the data connection can have one of two security levels.
 
-       1)Clear (requested by 'PROT C')
+          1) Clear (requested by 'PROT C')
 
-       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;
+          2)Private (requested by 'PROT P')
+          */
+          if(!conn->ssl[SECONDARYSOCKET].use) {
+            NEWFTPSENDF(conn, "PROT %c", 'P');
+            ftp->state = FTP_PROT;
+          }
+          else {
+            result = ftp_state_pwd(conn);
+            if(result)
+              return result;
+          }
 
-      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;
-    }
-  }
+          break;
 
-  /* send PWD to discover our entry point */
-  FTPSENDF(conn, "PWD", NULL);
+        case FTP_PROT:
+          if(ftpcode/100 == 2)
+            /* We have enabled SSL for the data connection! */
+            conn->ssl[SECONDARYSOCKET].use = TRUE;
+          /* FTP servers typically responds with 500 if they decide to reject
+             our 'P' request */
+          else if(data->set.ftp_ssl> CURLFTPSSL_CONTROL)
+            /* we failed and bails out */
+            return CURLE_FTP_SSL_FAILED;
+
+          result = ftp_state_pwd(conn);
+          if(result)
+            return result;
+          break;
+
+        case FTP_PWD:
+          if(ftpcode == 257) {
+            char *dir = (char *)malloc(nread+1);
+            char *store=dir;
+            char *ptr=&buf[4]; /* start on the first letter */
+
+            if(!dir)
+              return CURLE_OUT_OF_MEMORY;
+
+            /* Reply format is like
+               257<space>"<directory-name>"<space><commentary> and the RFC959
+               says
+
+               The directory name can contain any character; embedded
+               double-quotes should be escaped by double-quotes (the
+               "quote-doubling" convention).
+            */
+            if('\"' == *ptr) {
+              /* it started good */
+              ptr++;
+              while(ptr && *ptr) {
+                if('\"' == *ptr) {
+                  if('\"' == ptr[1]) {
+                    /* "quote-doubling" */
+                    *store = ptr[1];
+                    ptr++;
+                  }
+                  else {
+                    /* end of path */
+                    *store = '\0'; /* zero terminate */
+                    break; /* get out of this loop */
+                  }
+                }
+                else
+                  *store = *ptr;
+                store++;
+                ptr++;
+              }
+              ftp->entrypath =dir; /* remember this */
+              infof(data, "Entry path is '%s'\n", ftp->entrypath);
+            }
+            else {
+              /* couldn't get the path */
+              free(dir);
+              infof(data, "Failed to figure out path\n");
+            }
+          }
+          done = TRUE;
+          /* we are done! */
+          break;
+
+        default:
+          /* internal error */
+          break;
+        }
+      } /* if(ftpcode) */
+    } /* data to read/write */
 
-  /* wait for feedback */
-  result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-  if(result)
-    return result;
+  } while(!done);
 
-  if(ftpcode == 257) {
-    char *dir = (char *)malloc(nread+1);
-    char *store=dir;
-    char *ptr=&buf[4]; /* start on the first letter */
+  return CURLE_OK;
+}
 
-    if(!dir)
-      return CURLE_OUT_OF_MEMORY;
+/*
+ * Curl_ftp_connect() should do everything that is to be considered a part of
+ * the connection phase.
+ */
+CURLcode Curl_ftp_connect(struct connectdata *conn)
+{
+  struct FTP *ftp;
+  CURLcode result;
 
-    /* Reply format is like
-       257<space>"<directory-name>"<space><commentary> and the RFC959 says
+  ftp = (struct FTP *)calloc(sizeof(struct FTP), 1);
+  if(!ftp)
+    return CURLE_OUT_OF_MEMORY;
 
-       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);
-    }
-    else {
-      /* couldn't get the path */
-      free(dir);
-      infof(data, "Failed to figure out path\n");
-    }
+  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;
+  ftp->response_time = 3600; /* set default response time-out */
 
+#ifndef CURL_DISABLE_HTTP
+  if (conn->bits.tunnel_proxy) {
+    /* BLOCKING */
+    /* We want "seamless" FTP operations through HTTP proxy tunnel */
+    result = Curl_ConnectHTTPProxyTunnel(conn, FIRSTSOCKET,
+                                         conn->host.name, conn->remote_port);
+    if(CURLE_OK != result)
+      return result;
   }
-  else {
-    /* We couldn't read the PWD response! */
+#endif   /* CURL_DISABLE_HTTP */
+
+  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;
   }
 
+  result = ftp_stateconnect(conn);
+  if(result)
+    return result;
+
   return CURLE_OK;
 }
 
@@ -2136,6 +2397,131 @@
   return CURLE_OK;
 }
 
+static CURLcode ftp_state_quote(struct connectdata *conn,
+                                bool init)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+  struct curl_slist *item = data->set.quote;
+
+  if(init)
+    ftp->count1 = 0;
+  else
+    ftp->count1++;
+  if(data->set.quote) {
+    int i = 0;
+
+    /* Skip count1 items in the linked list */
+    while((i< ftp->count1) && item) {
+      item = item->next;
+      i++;
+    }
+    if(item) {
+      NEWFTPSENDF(conn, "%s", item->data);
+      ftp->state = FTP_QUOTE;
+    }
+  }
+
+  return result;
+}
+
+static CURLcode ftp_statedo(struct connectdata *conn)
+{
+  CURLcode result;
+  struct FTP *ftp = conn->proto.ftp;
+  struct SessionHandle *data = conn->data;
+  int rc;
+  curl_socket_t sock = conn->sock[FIRSTSOCKET];
+  int ftpcode;
+  size_t nread;
+  bool done=FALSE;
+
+  ftp->state = FTP_IDLE;
+
+  result = ftp_state_quote(conn, TRUE);
+  if(result)
+    return result;
+
+  if(ftp->state == FTP_IDLE)
+    return CURLE_OK;
+
+  do {
+
+    if(ftp->sendleft) {
+      /* we have a piece of a command still left to send */
+      rc = Curl_select(CURL_SOCKET_BAD, /* no reading */
+                       sock,            /* for writing */
+                       100000           /* FIX */);
+    }
+    else {
+      rc = Curl_select(sock,            /* for reading */
+                       CURL_SOCKET_BAD, /* no writing */
+                       100000           /* FIX */);
+    }
+
+
+    if(rc == -1) {
+      failf(data, "select error");
+      return CURLE_OUT_OF_MEMORY;
+    }
+    else if(rc == 0) {
+      /* timeout FIX */
+    }
+    else {
+      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(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;
+        }
+
+        continue;
+      }
+
+      /* we read a piece of response */
+      result = ftp_readresp(sock, conn, &ftpcode, &nread);
+
+      if(result)
+        return result;
+
+      if(ftpcode) {
+        /* we have now received a full FTP server response */
+        switch(ftp->state) {
+
+        case FTP_QUOTE:
+          if(ftpcode >= 400) {
+            failf(conn->data, "QUOT command failed with %03d", ftpcode);
+            return CURLE_FTP_QUOTE_ERROR;
+          }
+          ftp->state = FTP_IDLE;
+          result = ftp_state_quote(conn, FALSE);
+          if(result)
+            return result;
+
+          if(ftp->state == FTP_IDLE)
+            done = TRUE;
+          break;
+        default: /* internal error! */
+          break;
+        }
+      } /* if(ftpcode) */
+    } /* data to read/write */
+
+  } while(!done);
+
+  return CURLE_OK;
+}
+
 /***********************************************************************
  *
  * ftp_perform()
@@ -2157,11 +2543,10 @@
   /* the ftp struct is already inited in Curl_ftp_connect() */
   struct FTP *ftp = conn->proto.ftp;
 
-  /* Send any QUOTE strings? */
-  if(data->set.quote) {
-    if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK)
-      return result;
-  }
+  /* statedo() only does the pre-QUOTE strings so far */
+  result = ftp_statedo(conn);
+  if(result)
+    return result;
 
   result = ftp_cwd_and_create_path(conn);
   if (result)
@@ -2322,13 +2707,64 @@
 
 /***********************************************************************
  *
- * Curl_ftpsendf()
+ * Curl_(new)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 "new" version is made to never block.
  */
+CURLcode Curl_newftpsendf(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->host.dispname);
+
+  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;
+    }
+  }
+
+  return res;
+}
+
 CURLcode Curl_ftpsendf(struct connectdata *conn,
                        const char *fmt, ...)
 {
@@ -2356,7 +2792,8 @@
       break;
 
     if(conn->data->set.verbose)
-      Curl_debug(conn->data, CURLINFO_HEADER_OUT, sptr, bytes_written, conn->host.dispname);
+      Curl_debug(conn->data, CURLINFO_HEADER_OUT, sptr, bytes_written,
+                 conn->host.dispname);
 
     if(bytes_written != (ssize_t)write_len) {
       write_len -= bytes_written;
Index: lib/ftp.h
===================================================================
RCS file: /cvsroot/curl/curl/lib/ftp.h,v
retrieving revision 1.21
diff -u -r1.21 ftp.h
--- lib/ftp.h	12 May 2004 12:06:39 -0000	1.21
+++ lib/ftp.h	5 Jan 2005 14:14:02 -0000
@@ -1,10 +1,10 @@
 #ifndef __FTP_H
 #define __FTP_H
 /***************************************************************************
- *                                  _   _ ____  _     
- *  Project                     ___| | | |  _ \| |    
- *                             / __| | | | |_) | |    
- *                            | (__| |_| |  _ <| |___ 
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
  * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
@@ -12,7 +12,7 @@
  * 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.
@@ -29,6 +29,7 @@
 CURLcode Curl_ftp_connect(struct connectdata *conn);
 CURLcode Curl_ftp_disconnect(struct connectdata *conn);
 CURLcode Curl_ftpsendf(struct connectdata *, const char *fmt, ...);
+CURLcode Curl_newftpsendf(struct connectdata *, const char *fmt, ...);
 CURLcode Curl_GetFTPResponse(ssize_t *nread, struct connectdata *conn,
                              int *ftpcode);
 CURLcode Curl_ftp_nextconnect(struct connectdata *conn);
Index: lib/urldata.h
===================================================================
RCS file: /cvsroot/curl/curl/lib/urldata.h,v
retrieving revision 1.252
diff -u -r1.252 urldata.h
--- lib/urldata.h	19 Dec 2004 09:37:32 -0000	1.252
+++ lib/urldata.h	5 Jan 2005 14:14:03 -0000
@@ -277,6 +277,28 @@
   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 */
+  enum {
+    FTP_WAIT220, /* waiting for the inintial 220 response immediately after
+                    a connect */
+    FTP_AUTH,
+    FTP_USER,
+    FTP_PASS,
+    FTP_PBSZ,
+    FTP_PROT,
+    FTP_PWD,
+    FTP_QUOTE, /* waiting for a response to a command sent in a quote list */
+
+    FTP_IDLE,  /* unknown actual state */
+    FTP_LAST /* never used */
+  } state;
 };
 
 /****************************************************************************
Index: src/main.c
===================================================================
RCS file: /cvsroot/curl/curl/src/main.c,v
retrieving revision 1.302
diff -u -r1.302 main.c
--- src/main.c	20 Dec 2004 21:14:45 -0000	1.302
+++ src/main.c	5 Jan 2005 14:14:04 -0000
@@ -3651,7 +3651,8 @@
                 }
               }
             } /* if CURLE_OK */
-            else if(CURLE_FTP_USER_PASSWORD_INCORRECT == res) {
+            else if((CURLE_FTP_USER_PASSWORD_INCORRECT == res) ||
+                    (CURLE_FTP_ACCESS_DENIED == res)) {
               curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
 
               if(response/100 == 5)
Index: tests/data/test195
===================================================================
RCS file: /cvsroot/curl/curl/tests/data/test195,v
retrieving revision 1.1
diff -u -r1.1 test195
--- tests/data/test195	27 Oct 2004 21:29:55 -0000	1.1
+++ tests/data/test195	5 Jan 2005 14:14:04 -0000
@@ -21,7 +21,7 @@
 # Verify data after the test has been "shot"
 <verify>
 <errorcode>
-10
+9
 </errorcode>
 <protocol>
 USER anonymous
Index: tests/data/test196
===================================================================
RCS file: /cvsroot/curl/curl/tests/data/test196,v
retrieving revision 1.1
diff -u -r1.1 test196
--- tests/data/test196	27 Oct 2004 21:29:55 -0000	1.1
+++ tests/data/test196	5 Jan 2005 14:14:04 -0000
@@ -20,8 +20,9 @@
 
 # Verify data after the test has been "shot"
 <verify>
+# 9 is CURLE_FTP_ACCESS_DENIED
 <errorcode>
-10
+9
 </errorcode>
 <protocol>
 USER anonymous
