introducing IMAP, POP3 and SMTP support (still lots of polish left to do)
authorDaniel Stenberg <daniel@haxx.se>
Sat, 12 Dec 2009 21:54:01 +0000 (21:54 +0000)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 12 Dec 2009 21:54:01 +0000 (21:54 +0000)
23 files changed:
include/curl/curl.h
lib/Makefile.am
lib/Makefile.inc
lib/README.pingpong [new file with mode: 0644]
lib/ftp.c
lib/ftp.h
lib/imap.c [new file with mode: 0644]
lib/imap.h [new file with mode: 0644]
lib/pingpong.c [new file with mode: 0644]
lib/pingpong.h [new file with mode: 0644]
lib/pop3.c [new file with mode: 0644]
lib/pop3.h [new file with mode: 0644]
lib/smtp.c [new file with mode: 0644]
lib/smtp.h [new file with mode: 0644]
lib/transfer.c
lib/url.c
lib/urldata.h
lib/version.c
src/main.c
tests/data/Makefile.am
tests/data/test800 [new file with mode: 0644]
tests/ftpserver.pl
tests/runtests.pl

index 279c1b6..f7c3f52 100644 (file)
@@ -613,6 +613,12 @@ typedef enum {
 #define CURLPROTO_DICT   (1<<9)
 #define CURLPROTO_FILE   (1<<10)
 #define CURLPROTO_TFTP   (1<<11)
+#define CURLPROTO_IMAP   (1<<12)
+#define CURLPROTO_IMAPS  (1<<13)
+#define CURLPROTO_POP3   (1<<14)
+#define CURLPROTO_POP3S  (1<<15)
+#define CURLPROTO_SMTP   (1<<16)
+#define CURLPROTO_SMTPS  (1<<17)
 #define CURLPROTO_ALL    (~0) /* enable everything */
 
 /* long may be 32 or 64 bits, but we should never depend on anything else
@@ -1028,6 +1034,7 @@ typedef enum {
      essentially places a demand on the FTP server to acknowledge commands
      in a timely manner. */
   CINIT(FTP_RESPONSE_TIMEOUT, LONG, 112),
+#define CURLOPT_SERVER_RESPONSE_TIMEOUT CURLOPT_FTP_RESPONSE_TIMEOUT
 
   /* Set this option to one of the CURL_IPRESOLVE_* defines (see below) to
      tell libcurl to resolve names to those IP versions only. This only has
@@ -1272,6 +1279,12 @@ typedef enum {
   /* set the SSH host key callback custom pointer */
   CINIT(SSH_KEYDATA, OBJECTPOINT, 185),
 
+  /* set the SMTP mail originator */
+  CINIT(MAIL_FROM, OBJECTPOINT, 186),
+
+  /* set the SMTP mail receiver(s) */
+  CINIT(MAIL_RCPT, OBJECTPOINT, 187),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
index bfc66d1..e75aef4 100644 (file)
@@ -27,7 +27,7 @@ VCPROJ = libcurl.vcproj
 
 DOCS = README.encoding README.memoryleak README.ares README.curlx      \
  README.hostip README.multi_socket README.httpauth README.pipelining    \
- README.curl_off_t README.cmake
+ README.curl_off_t README.cmake README.pingpong
 
 CMAKE_DIST = CMakeLists.txt curl_config.h.cmake
 
index 1b619a3..ff9269e 100644 (file)
@@ -11,7 +11,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c   \
   inet_ntop.c parsedate.c select.c gtls.c sslgen.c tftp.c splay.c      \
   strdup.c socks.c ssh.c nss.c qssl.c rawstr.c curl_addrinfo.c          \
   socks_gssapi.c socks_sspi.c curl_sspi.c slist.c nonblock.c           \
-  curl_memrchr.c
+  curl_memrchr.c imap.c pop3.c smtp.c pingpong.c
 
 HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h      \
   progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h     \
@@ -23,5 +23,4 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h     \
   transfer.h select.h easyif.h multiif.h parsedate.h sslgen.h gtls.h   \
   tftp.h sockaddr.h splay.h strdup.h setup_once.h socks.h ssh.h nssg.h \
   curl_base64.h rawstr.h curl_addrinfo.h curl_sspi.h slist.h nonblock.h        \
-  curl_memrchr.h
-
+  curl_memrchr.h imap.h pop3.h smtp.h pingpong.h
diff --git a/lib/README.pingpong b/lib/README.pingpong
new file mode 100644 (file)
index 0000000..69ba9aa
--- /dev/null
@@ -0,0 +1,30 @@
+Date: December 5, 2009
+
+Pingpong
+========
+
+ Pingpong is just my (Daniel's) jestful collective name on the protocols that
+ share a very similar kind of back-and-forth procedure with command and
+ responses to and from the server. FTP was previously the only protocol in
+ that family that libcurl supported, but when POP3, IMAP and SMTP joined the
+ team I moved some of the internals into a separate pingpong module to be
+ easier to get used by all these protocols to reduce code duplication and ease
+ code re-use between these protocols.
+
+FTP
+
+ In 7.20.0 we converted code to use the new pingpong code from previously
+ having been all "native" FTP code.
+
+POP3
+
+ There's no support in the documented URL format to specify the exact mail to
+ get, but we support that as the path specified in the URL.
+
+IMAP
+
+SMTP
+
+ There's no official URL syntax defined for SMTP, but we use only the generic
+ one and we provide two additional libcurl options to specify receivers and
+ sender of the actual mail.
index 000670f..331d17e 100644 (file)
--- a/lib/ftp.c
+++ b/lib/ftp.c
 #define INET_ADDRSTRLEN 16
 #endif
 
-#ifdef __SYMBIAN32__
-/* Symbian OS panics when given a timeout much greater than 1/2 hour */
-#define RESP_TIMEOUT (1800*1000)
-#else
-/* Default response timeout in milliseconds */
-#define RESP_TIMEOUT (3600*1000)
-#endif
-
 #ifdef CURL_DISABLE_VERBOSE_STRINGS
 #define ftp_pasv_verbose(a,b,c,d)  do { } while(0)
 #endif
@@ -155,7 +147,7 @@ static CURLcode ftp_setup_connection(struct connectdata * conn);
 /* easy-to-use macro: */
 #define FTPSENDF(x,y,z)    if((result = Curl_ftpsendf(x,y,z)) != CURLE_OK) \
                               return result
-#define NBFTPSENDF(x,y,z)  if((result = Curl_nbftpsendf(x,y,z)) != CURLE_OK) \
+#define PPSENDF(x,y,z)  if((result = Curl_pp_sendf(x,y,z)) != CURLE_OK) \
                               return result
 
 
@@ -164,7 +156,7 @@ static CURLcode ftp_setup_connection(struct connectdata * conn);
  */
 
 const struct Curl_handler Curl_handler_ftp = {
-  "FTP",                                /* scheme */
+  "FTP",                           /* scheme */
   ftp_setup_connection,            /* setup_connection */
   ftp_do,                          /* do_it */
   ftp_done,                        /* done */
@@ -187,7 +179,7 @@ const struct Curl_handler Curl_handler_ftp = {
  */
 
 const struct Curl_handler Curl_handler_ftps = {
-  "FTPS",                               /* scheme */
+  "FTPS",                          /* scheme */
   ftp_setup_connection,            /* setup_connection */
   ftp_do,                          /* do_it */
   ftp_done,                        /* done */
@@ -362,217 +354,39 @@ static CURLcode AllowServerConnect(struct connectdata *conn)
   /* never reaches this point */
 }
 
-/* initialize stuff to prepare for reading a fresh new response */
-static void ftp_respinit(struct connectdata *conn)
-{
-  struct ftp_conn *ftpc = &conn->proto.ftpc;
-  ftpc->nread_resp = 0;
-  ftpc->linestart_resp = conn->data->state.buffer;
-  ftpc->pending_resp = TRUE;
-}
-
 /* macro to check for a three-digit ftp status code at the start of the
    given string */
-#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \
-                        ISDIGIT(line[2]))
+#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) &&       \
+                          ISDIGIT(line[2]))
 
 /* macro to check for the last line in an FTP server response */
 #define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3]))
 
+static int ftp_endofresp(struct pingpong *pp,
+                         int *code)
+{
+  char *line = pp->linestart_resp;
+  size_t len = pp->nread_resp;
+
+  if((len > 3) && LASTLINE(line)) {
+    *code = atoi(line);
+    return 1;
+  }
+  return 0;
+}
+
 static CURLcode ftp_readresp(curl_socket_t sockfd,
-                             struct connectdata *conn,
+                             struct pingpong *pp,
                              int *ftpcode, /* return the ftp-code if done */
                              size_t *size) /* size of the response */
 {
-  ssize_t perline; /* count bytes per line */
-  bool keepon=TRUE;
-  ssize_t gotbytes;
-  char *ptr;
+  struct connectdata *conn = pp->conn;
   struct SessionHandle *data = conn->data;
   char * const buf = data->state.buffer;
   CURLcode result = CURLE_OK;
-  struct ftp_conn *ftpc = &conn->proto.ftpc;
-  int code = 0;
-
-  *ftpcode = 0; /* 0 for errors or not done */
-  *size = 0;
-
-  ptr=buf + ftpc->nread_resp;
-
-  /* number of bytes in the current line, so far */
-  perline = (ssize_t)(ptr-ftpc->linestart_resp);
-
-  keepon=TRUE;
-
-  while((ftpc->nread_resp<BUFSIZE) && (keepon && !result)) {
-
-    if(ftpc->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.
-       */
-      DEBUGASSERT((ptr+ftpc->cache_size) <= (buf+BUFSIZE+1));
-      memcpy(ptr, ftpc->cache, (int)ftpc->cache_size);
-      gotbytes = (int)ftpc->cache_size;
-      free(ftpc->cache);    /* free the cache */
-      ftpc->cache = NULL;   /* clear the pointer */
-      ftpc->cache_size = 0; /* zero the size just in case */
-    }
-    else {
-      int res;
-#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
-      enum protection_level prot = conn->data_prot;
-
-      conn->data_prot = 0;
-#endif
-      DEBUGASSERT((ptr+BUFSIZE-ftpc->nread_resp) <= (buf+BUFSIZE+1));
-      res = Curl_read(conn, sockfd, ptr, BUFSIZE-ftpc->nread_resp,
-                      &gotbytes);
-#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
-      conn->data_prot = prot;
-#endif
-      if(res < 0)
-        /* EWOULDBLOCK */
-        return CURLE_OK; /* return */
-
-#ifdef CURL_DOES_CONVERSIONS
-      if((res == CURLE_OK) && (gotbytes > 0)) {
-        /* convert from the network encoding */
-        res = Curl_convert_from_network(data, ptr, gotbytes);
-        /* Curl_convert_from_network calls failf if unsuccessful */
-      }
-#endif /* CURL_DOES_CONVERSIONS */
-
-      if(CURLE_OK != res) {
-        result = (CURLcode)res; /* Set outer result variable to this error. */
-        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 */
-      ssize_t i;
-      ssize_t clipamount = 0;
-      bool restart = FALSE;
-
-      data->req.headerbytecount += gotbytes;
-
-      ftpc->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 defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
-          if(!conn->sec_complete)
-#endif
-          if(data->set.verbose)
-            Curl_debug(data, CURLINFO_HEADER_IN,
-                       ftpc->linestart_resp, (size_t)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(conn, CLIENTWRITE_HEADER,
-                                     ftpc->linestart_resp, perline);
-          if(result)
-            return result;
+  int code;
 
-          if(perline>3 && LASTLINE(ftpc->linestart_resp)) {
-            /* 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=ftpc->linestart_resp, n=0; meow<ptr; meow++, n++)
-              buf[n] = *meow;
-            *meow=0; /* zero terminate */
-            keepon=FALSE;
-            ftpc->linestart_resp = ptr+1; /* advance pointer */
-            i++; /* skip this before getting out */
-
-            *size = ftpc->nread_resp; /* size of the response */
-            ftpc->nread_resp = 0; /* restart */
-            break;
-          }
-          perline=0; /* line starts over here */
-          ftpc->linestart_resp = 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! */
-        clipamount = gotbytes - i;
-        restart = TRUE;
-      }
-      else if(keepon) {
-
-        if((perline == gotbytes) && (gotbytes > BUFSIZE/2)) {
-          /* We got an excessive line without newlines and we need to deal
-             with it. First, check if it seems to start with a valid status
-             code and then we keep just that in the line cache. Then throw
-             away the rest. */
-          infof(data, "Excessive FTP response line length received, %zd bytes."
-                " Stripping\n", gotbytes);
-          restart = TRUE;
-          if(STATUSCODE(ftpc->linestart_resp))
-            /* we copy 4 bytes since after the three-digit number there is a
-               dash or a space and it is significant */
-            clipamount = 4;
-        }
-        else if(ftpc->nread_resp > BUFSIZE/2) {
-          /* We got a large chunk of data and there's potentially still trailing
-             data to take care of, so we put any such part in the "cache", clear
-             the buffer to make space and restart. */
-          clipamount = perline;
-          restart = TRUE;
-        }
-      }
-      else if(i == gotbytes)
-        restart = TRUE;
-
-      if(clipamount) {
-        ftpc->cache_size = clipamount;
-        ftpc->cache = malloc((int)ftpc->cache_size);
-        if(ftpc->cache)
-          memcpy(ftpc->cache, ftpc->linestart_resp, (int)ftpc->cache_size);
-        else
-          return CURLE_OUT_OF_MEMORY;
-      }
-      if(restart) {
-        /* now reset a few variables to start over nicely from the start of
-           the big buffer */
-        ftpc->nread_resp = 0; /* start over from scratch in the buffer */
-        ptr = ftpc->linestart_resp = buf;
-        perline = 0;
-      }
-
-    } /* there was data */
-
-  } /* while there's buffer left and loop is requested */
-
-  if(!result)
-    code = atoi(buf);
+  result = Curl_pp_readresp(sockfd, pp, &code, size);
 
 #if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
   /* handle the security-oriented responses 6xx ***/
@@ -593,12 +407,11 @@ static CURLcode ftp_readresp(curl_socket_t sockfd,
   }
 #endif
 
-  *ftpcode=code; /* return the initial number like this */
-
   /* store the latest code for later retrieval */
   conn->data->info.httpcode=code;
 
-  ftpc->pending_resp = FALSE;
+  if(ftpcode)
+    *ftpcode = code;
 
   return result;
 }
@@ -628,7 +441,7 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
   struct SessionHandle *data = conn->data;
   CURLcode result = CURLE_OK;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
-  struct timeval now = Curl_tvnow();
+  struct pingpong *pp = &ftpc->pp;
   size_t nread;
   int cache_skip=0;
   int value_to_be_ignored=0;
@@ -643,23 +456,7 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
 
   while(!*ftpcode && !result) {
     /* check and reset timeout value every lap */
-    if(data->set.ftp_response_timeout )
-      /* if CURLOPT_FTP_RESPONSE_TIMEOUT is set, use that to determine
-         remaining time.  Also, use "now" as opposed to "conn->now"
-         because ftp_response_timeout is only supposed to govern
-         the response for any given ftp response, not for the time
-         from connect to the given ftp response. */
-      timeout = data->set.ftp_response_timeout - /* timeout time */
-        Curl_tvdiff(Curl_tvnow(), now); /* spent time */
-    else if(data->set.timeout)
-      /* if timeout is requested, find out how much remaining time we have */
-      timeout = data->set.timeout - /* timeout time */
-        Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
-    else
-      /* Even without a requested timeout, we only wait response_time
-         seconds for the full response to arrive before we bail out */
-      timeout = ftpc->response_time -
-        Curl_tvdiff(Curl_tvnow(), now); /* spent time */
+    timeout = Curl_pp_state_timeout(pp);
 
     if(timeout <=0 ) {
       failf(data, "FTP response timeout");
@@ -684,7 +481,7 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
      *
      */
 
-    if(ftpc->cache && (cache_skip < 2)) {
+    if(pp->cache && (cache_skip < 2)) {
       /*
        * There's a cache left since before. We then skipping the wait for
        * socket action, unless this is the same cache like the previous round
@@ -708,11 +505,11 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
         break;
       }
     }
-    result = ftp_readresp(sockfd, conn, ftpcode, &nread);
+    result = ftp_readresp(sockfd, pp, ftpcode, &nread);
     if(result)
       break;
 
-    if(!nread && ftpc->cache)
+    if(!nread && pp->cache)
       /* bump cache skip counter as on repeated skips we must wait for more
          data */
       cache_skip++;
@@ -725,7 +522,7 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
 
   } /* while there's buffer left and loop is requested */
 
-  ftpc->pending_resp = FALSE;
+  pp->pending_resp = FALSE;
 
   return result;
 }
@@ -787,7 +584,7 @@ static CURLcode ftp_state_user(struct connectdata *conn)
   CURLcode result;
   struct FTP *ftp = conn->data->state.proto.ftp;
   /* send USER */
-  NBFTPSENDF(conn, "USER %s", ftp->user?ftp->user:"");
+  PPSENDF(&conn->proto.ftpc.pp, "USER %s", ftp->user?ftp->user:"");
 
   state(conn, FTP_USER);
   conn->data->state.ftp_trying_alternative = FALSE;
@@ -800,7 +597,7 @@ static CURLcode ftp_state_pwd(struct connectdata *conn)
   CURLcode result;
 
   /* send PWD to discover our entry point */
-  NBFTPSENDF(conn, "PWD", NULL);
+  PPSENDF(&conn->proto.ftpc.pp, "PWD", NULL);
   state(conn, FTP_PWD);
 
   return CURLE_OK;
@@ -808,23 +605,10 @@ static CURLcode ftp_state_pwd(struct connectdata *conn)
 
 /* For the FTP "protocol connect" and "doing" phases only */
 static int ftp_getsock(struct connectdata *conn,
-                            curl_socket_t *socks,
-                            int numsocks)
+                       curl_socket_t *socks,
+                       int numsocks)
 {
-  struct ftp_conn *ftpc = &conn->proto.ftpc;
-
-  if(!numsocks)
-    return GETSOCK_BLANK;
-
-  socks[0] = conn->sock[FIRSTSOCKET];
-
-  if(ftpc->sendleft) {
-    /* write mode */
-    return GETSOCK_WRITESOCK(0);
-  }
-
-  /* read mode */
-  return GETSOCK_READSOCK(0);
+  return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks);
 }
 
 /* This is called after the FTP_QUOTE state is passed.
@@ -855,7 +639,7 @@ static CURLcode ftp_state_cwd(struct connectdata *conn)
          where we ended up after login: */
       ftpc->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", ftpc->entrypath);
+      PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->entrypath);
       state(conn, FTP_CWD);
     }
     else {
@@ -863,7 +647,7 @@ static CURLcode ftp_state_cwd(struct connectdata *conn)
         ftpc->count1 = 1;
         /* issue the first CWD, the rest is sent when the CWD responses are
            received... */
-        NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 -1]);
+        PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->dirs[ftpc->count1 -1]);
         state(conn, FTP_CWD);
       }
       else {
@@ -1190,9 +974,9 @@ static CURLcode ftp_state_use_port(struct connectdata *conn,
        * EPRT |2|1080::8:800:200C:417A|5282|
        */
 
-      result = Curl_nbftpsendf(conn, "%s |%d|%s|%d|", mode[fcmd],
-                               sa->sa_family == AF_INET?1:2,
-                               myhost, port);
+      result = Curl_pp_sendf(&ftpc->pp, "%s |%d|%s|%d|", mode[fcmd],
+                             sa->sa_family == AF_INET?1:2,
+                             myhost, port);
       if(result)
         return result;
       break;
@@ -1213,7 +997,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn,
       *dest = 0;
       snprintf(dest, 20, ",%d,%d", port>>8, port&0xff);
 
-      result = Curl_nbftpsendf(conn, "%s %s", mode[fcmd], tmp);
+      result = Curl_pp_sendf(&ftpc->pp, "%s %s", mode[fcmd], tmp);
       if(result)
         return result;
       break;
@@ -1273,9 +1057,7 @@ static CURLcode ftp_state_use_pasv(struct connectdata *conn)
 
   modeoff = conn->bits.ftp_use_epsv?0:1;
 
-  result = Curl_nbftpsendf(conn, "%s", mode[modeoff]);
-  if(result)
-    return result;
+  PPSENDF(&ftpc->pp, "%s", mode[modeoff]);
 
   ftpc->count1 = modeoff;
   state(conn, FTP_PASV);
@@ -1322,7 +1104,7 @@ static CURLcode ftp_state_post_size(struct connectdata *conn)
 
     /* Determine if server can respond to REST command and therefore
        whether it supports range */
-    NBFTPSENDF(conn, "REST %d", 0);
+    PPSENDF(&conn->proto.ftpc.pp, "REST %d", 0);
 
     state(conn, FTP_REST);
   }
@@ -1342,7 +1124,7 @@ static CURLcode ftp_state_post_type(struct connectdata *conn)
     /* if a "head"-like request is being made (on a file) */
 
     /* we know ftpc->file is a valid pointer to a file name */
-    NBFTPSENDF(conn, "SIZE %s", ftpc->file);
+    PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file);
 
     state(conn, FTP_SIZE);
   }
@@ -1406,7 +1188,7 @@ static CURLcode ftp_state_post_listtype(struct connectdata *conn)
     return CURLE_OUT_OF_MEMORY;
   }
 
-  NBFTPSENDF(conn, "%s",cmd);
+  PPSENDF(&conn->proto.ftpc.pp, "%s",cmd);
 
   if(lstArg)
     free(lstArg);
@@ -1484,7 +1266,7 @@ static CURLcode ftp_state_post_cwd(struct connectdata *conn)
 
     /* 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", ftpc->file);
+    PPSENDF(&ftpc->pp, "MDTM %s", ftpc->file);
 
     state(conn, FTP_MDTM);
   }
@@ -1522,7 +1304,7 @@ static CURLcode ftp_state_ul_setup(struct connectdata *conn,
 
     if(data->state.resume_from < 0 ) {
       /* Got no given size to start from, figure it out */
-      NBFTPSENDF(conn, "SIZE %s", ftpc->file);
+      PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file);
       state(conn, FTP_STOR_SIZE);
       return result;
     }
@@ -1586,8 +1368,8 @@ static CURLcode ftp_state_ul_setup(struct connectdata *conn,
     /* we've passed, proceed as normal */
   } /* resume_from */
 
-  NBFTPSENDF(conn, data->set.ftp_append?"APPE %s":"STOR %s",
-             ftpc->file);
+  PPSENDF(&ftpc->pp, data->set.ftp_append?"APPE %s":"STOR %s",
+          ftpc->file);
 
   state(conn, FTP_STOR);
 
@@ -1633,7 +1415,7 @@ static CURLcode ftp_state_quote(struct connectdata *conn,
       i++;
     }
     if(item) {
-      NBFTPSENDF(conn, "%s", item->data);
+      PPSENDF(&ftpc->pp, "%s", item->data);
       state(conn, instate);
       quote = TRUE;
     }
@@ -1650,7 +1432,7 @@ static CURLcode ftp_state_quote(struct connectdata *conn,
       if(ftp->transfer != FTPTRANSFER_BODY)
         state(conn, FTP_STOP);
       else {
-        NBFTPSENDF(conn, "SIZE %s", ftpc->file);
+        PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file);
         state(conn, FTP_RETR_SIZE);
       }
       break;
@@ -1791,7 +1573,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
     conn->bits.ftp_use_epsv = FALSE;
     infof(data, "disabling EPSV usage\n");
 
-    NBFTPSENDF(conn, "PASV", NULL);
+    PPSENDF(&ftpc->pp, "PASV", NULL);
     ftpc->count1++;
     /* remain in the FTP_PASV state */
     return result;
@@ -1852,7 +1634,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
     /* disable it for next transfer */
     conn->bits.ftp_use_epsv = FALSE;
     data->state.errorbuf = FALSE; /* allow error message to get rewritten */
-    NBFTPSENDF(conn, "PASV", NULL);
+    PPSENDF(&ftpc->pp, "PASV", NULL);
     ftpc->count1++;
     /* remain in the FTP_PASV state */
     return result;
@@ -2168,14 +1950,14 @@ static CURLcode ftp_state_post_retr_size(struct connectdata *conn,
     infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T
           "\n", data->state.resume_from);
 
-    NBFTPSENDF(conn, "REST %" FORMAT_OFF_T, data->state.resume_from);
+    PPSENDF(&ftpc->pp, "REST %" FORMAT_OFF_T, data->state.resume_from);
 
     state(conn, FTP_RETR_REST);
 
   }
   else {
     /* no resume */
-    NBFTPSENDF(conn, "RETR %s", ftpc->file);
+    PPSENDF(&ftpc->pp, "RETR %s", ftpc->file);
     state(conn, FTP_RETR);
   }
 
@@ -2246,7 +2028,7 @@ static CURLcode ftp_state_rest_resp(struct connectdata *conn,
       result = CURLE_FTP_COULDNT_USE_REST;
     }
     else {
-      NBFTPSENDF(conn, "RETR %s", ftpc->file);
+      PPSENDF(&ftpc->pp, "RETR %s", ftpc->file);
       state(conn, FTP_RETR);
     }
     break;
@@ -2300,7 +2082,7 @@ static CURLcode ftp_state_stor_resp(struct connectdata *conn,
                                SECONDARYSOCKET, ftp->bytecountp);
   state(conn, FTP_STOP);
 
-  conn->proto.ftpc.pending_resp = TRUE; /* we expect a server response more */
+  conn->proto.ftpc.pp.pending_resp = TRUE; /* expect a server response */
 
   return result;
 }
@@ -2414,7 +2196,7 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn,
     if(result)
       return result;
 
-    conn->proto.ftpc.pending_resp = TRUE; /* we expect a server response more */
+    conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */
     state(conn, FTP_STOP);
   }
   else {
@@ -2466,7 +2248,7 @@ static CURLcode ftp_state_loggedin(struct connectdata *conn)
     parameter of '0' to indicate that no buffering is taking place
     and the data connection should not be encapsulated.
     */
-    NBFTPSENDF(conn, "PBSZ %d", 0);
+    PPSENDF(&conn->proto.ftpc.pp, "PBSZ %d", 0);
     state(conn, FTP_PBSZ);
   }
   else {
@@ -2490,7 +2272,7 @@ static CURLcode ftp_state_user_resp(struct connectdata *conn,
   if((ftpcode == 331) && (ftpc->state == FTP_USER)) {
     /* 331 Password required for ...
        (the server requires to send the user's password too) */
-    NBFTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:"");
+    PPSENDF(&ftpc->pp, "PASS %s", ftp->passwd?ftp->passwd:"");
     state(conn, FTP_PASS);
   }
   else if(ftpcode/100 == 2) {
@@ -2500,7 +2282,7 @@ static CURLcode ftp_state_user_resp(struct connectdata *conn,
   }
   else if(ftpcode == 332) {
     if(data->set.str[STRING_FTP_ACCOUNT]) {
-      NBFTPSENDF(conn, "ACCT %s", data->set.str[STRING_FTP_ACCOUNT]);
+      PPSENDF(&ftpc->pp, "ACCT %s", data->set.str[STRING_FTP_ACCOUNT]);
       state(conn, FTP_ACCT);
     }
     else {
@@ -2517,8 +2299,8 @@ static CURLcode ftp_state_user_resp(struct connectdata *conn,
     if(conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] &&
         !conn->data->state.ftp_trying_alternative) {
       /* Ok, USER failed.  Let's try the supplied command. */
-      NBFTPSENDF(conn, "%s",
-                 conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]);
+      PPSENDF(&conn->proto.ftpc.pp, "%s",
+              conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]);
       conn->data->state.ftp_trying_alternative = TRUE;
       state(conn, FTP_USER);
       result = CURLE_OK;
@@ -2555,32 +2337,15 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
   struct SessionHandle *data=conn->data;
   int ftpcode;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
+  struct pingpong *pp = &ftpc->pp;
   static const char ftpauth[][4]  = { "SSL", "TLS" };
   size_t nread = 0;
 
-  if(ftpc->sendleft) {
-    /* we have a piece of a command still left to send */
-    ssize_t written;
-    result = Curl_write(conn, sock, ftpc->sendthis + ftpc->sendsize -
-                        ftpc->sendleft, ftpc->sendleft, &written);
-    if(result)
-      return result;
-
-    if(written != (ssize_t)ftpc->sendleft) {
-      /* only a fraction was sent */
-      ftpc->sendleft -= written;
-    }
-    else {
-      free(ftpc->sendthis);
-      ftpc->sendthis=NULL;
-      ftpc->sendleft = ftpc->sendsize = 0;
-      ftpc->response = Curl_tvnow();
-    }
-    return CURLE_OK;
-  }
+  if(pp->sendleft)
+    return Curl_pp_flushsend(pp);
 
   /* we read a piece of response */
-  result = ftp_readresp(sock, conn, &ftpcode, &nread);
+  result = ftp_readresp(sock, pp, &ftpcode, &nread);
   if(result)
     return result;
 
@@ -2632,7 +2397,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
                 data->set.ftpsslauth);
           return CURLE_FAILED_INIT; /* we don't know what to do */
         }
-        NBFTPSENDF(conn, "AUTH %s", ftpauth[ftpc->count1]);
+        PPSENDF(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]);
         state(conn, FTP_AUTH);
       }
       else {
@@ -2665,7 +2430,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
       else if(ftpc->count3 < 1) {
         ftpc->count3++;
         ftpc->count1 += ftpc->count2; /* get next attempt */
-        result = Curl_nbftpsendf(conn, "AUTH %s", ftpauth[ftpc->count1]);
+        result = Curl_pp_sendf(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]);
         /* remain in this same state */
       }
       else {
@@ -2691,8 +2456,8 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
       break;
 
     case FTP_PBSZ:
-      NBFTPSENDF(conn, "PROT %c",
-                 data->set.ftp_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
+      PPSENDF(&ftpc->pp, "PROT %c",
+              data->set.ftp_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
       state(conn, FTP_PROT);
 
       break;
@@ -2711,7 +2476,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
       if(data->set.ftp_ccc) {
         /* CCC - Clear Command Channel
          */
-        NBFTPSENDF(conn, "CCC", NULL);
+        PPSENDF(&ftpc->pp, "CCC", NULL);
         state(conn, FTP_CCC);
       }
       else {
@@ -2797,7 +2562,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
              systems. */
 
           if(!ftpc->server_os && ftpc->entrypath[0] != '/') {
-            NBFTPSENDF(conn, "SYST", NULL);
+            PPSENDF(&ftpc->pp, "SYST", NULL);
             state(conn, FTP_SYST);
             break;
           }
@@ -2836,7 +2601,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
 
         if(strequal(ftpc->server_os, "OS/400")) {
           /* Force OS400 name format 1. */
-          NBFTPSENDF(conn, "SITE NAMEFMT 1", NULL);
+          PPSENDF(&ftpc->pp, "SITE NAMEFMT 1", NULL);
           state(conn, FTP_NAMEFMT);
           break;
         }
@@ -2884,7 +2649,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
            ftpc->count1 && !ftpc->count2) {
           /* try making it */
           ftpc->count2++; /* counter to prevent CWD-MKD loops */
-          NBFTPSENDF(conn, "MKD %s", ftpc->dirs[ftpc->count1 - 1]);
+          PPSENDF(&ftpc->pp, "MKD %s", ftpc->dirs[ftpc->count1 - 1]);
           state(conn, FTP_MKD);
         }
         else {
@@ -2900,7 +2665,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
         ftpc->count2=0;
         if(++ftpc->count1 <= ftpc->dirdepth) {
           /* send next CWD */
-          NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
+          PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
         }
         else {
           result = ftp_state_post_cwd(conn);
@@ -2918,7 +2683,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
       }
       state(conn, FTP_CWD);
       /* send CWD */
-      NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
+      PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
       break;
 
     case FTP_MDTM:
@@ -2972,65 +2737,13 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
   return result;
 }
 
-/* Returns timeout in ms. 0 or negative number means the timeout has already
-   triggered */
-static long ftp_state_timeout(struct connectdata *conn)
-{
-  struct SessionHandle *data=conn->data;
-  struct ftp_conn *ftpc = &conn->proto.ftpc;
-  long timeout_ms=360000; /* in milliseconds */
-
-  if(data->set.ftp_response_timeout )
-    /* if CURLOPT_FTP_RESPONSE_TIMEOUT is set, use that to determine remaining
-       time.  Also, use ftp->response because FTP_RESPONSE_TIMEOUT is supposed
-       to govern the response for any given ftp response, not for the time
-       from connect to the given ftp response. */
-    timeout_ms = data->set.ftp_response_timeout - /* timeout time */
-      Curl_tvdiff(Curl_tvnow(), ftpc->response); /* spent time */
-  else if(data->set.timeout)
-    /* if timeout is requested, find out how much remaining time we have */
-    timeout_ms = data->set.timeout - /* timeout time */
-      Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
-  else
-    /* Without a requested timeout, we only wait 'response_time' seconds for
-       the full response to arrive before we bail out */
-    timeout_ms = ftpc->response_time -
-      Curl_tvdiff(Curl_tvnow(), ftpc->response); /* spent time */
-
-  return timeout_ms;
-}
-
 
 /* called repeatedly until done from multi.c */
 static CURLcode ftp_multi_statemach(struct connectdata *conn,
-                                         bool *done)
+                                    bool *done)
 {
-  curl_socket_t sock = conn->sock[FIRSTSOCKET];
-  int rc;
-  struct SessionHandle *data=conn->data;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
-  CURLcode result = CURLE_OK;
-  long timeout_ms = ftp_state_timeout(conn);
-
-  *done = FALSE; /* default to not done yet */
-
-  if(timeout_ms <= 0) {
-    failf(data, "FTP response timeout");
-    return CURLE_OPERATION_TIMEDOUT;
-  }
-
-  rc = Curl_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
-                         ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
-                         0);
-
-  if(rc == -1) {
-    failf(data, "select/poll error");
-    return CURLE_OUT_OF_MEMORY;
-  }
-  else if(rc != 0) {
-    result = ftp_statemach_act(conn);
-  }
-  /* if rc == 0, then select() timed out */
+  CURLcode result = Curl_pp_multi_statemach(&ftpc->pp);
 
   /* Check for the state outside of the Curl_socket_ready() return code checks
      since at times we are in fact already in this state when this function
@@ -3042,44 +2755,12 @@ static CURLcode ftp_multi_statemach(struct connectdata *conn,
 
 static CURLcode ftp_easy_statemach(struct connectdata *conn)
 {
-  curl_socket_t sock = conn->sock[FIRSTSOCKET];
-  int rc;
-  struct SessionHandle *data=conn->data;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
+  struct pingpong *pp = &ftpc->pp;
   CURLcode result = CURLE_OK;
 
   while(ftpc->state != FTP_STOP) {
-    long interval_ms;
-    long timeout_ms = ftp_state_timeout(conn);
-
-    if(timeout_ms <=0 ) {
-      failf(data, "FTP response timeout");
-      return CURLE_OPERATION_TIMEDOUT; /* already too little time */
-    }
-
-    interval_ms = 1000;  /* use 1 second timeout intervals */
-    if(timeout_ms < interval_ms)
-      interval_ms = timeout_ms;
-
-    rc = Curl_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
-                           ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
-                           (int)interval_ms);
-
-    if(Curl_pgrsUpdate(conn))
-      result = CURLE_ABORTED_BY_CALLBACK;
-    else
-      result = Curl_speedcheck(data, Curl_tvnow());
-
-    if(result)
-      break;
-
-    if(rc == -1) {
-      failf(data, "select/poll error");
-      result = CURLE_OUT_OF_MEMORY;
-    }
-    else if(rc)
-      result = ftp_statemach_act(conn);
-
+    result = Curl_pp_easy_statemach(pp);
     if(result)
       break;
   }
@@ -3136,6 +2817,7 @@ static CURLcode ftp_connect(struct connectdata *conn,
   CURLcode result;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
   struct SessionHandle *data=conn->data;
+  struct pingpong *pp = &ftpc->pp;
 
   *done = FALSE; /* default to not done yet */
 
@@ -3150,7 +2832,10 @@ static CURLcode ftp_connect(struct connectdata *conn,
   /* We always support persistant connections on ftp */
   conn->bits.close = FALSE;
 
-  ftpc->response_time = RESP_TIMEOUT; /* set default response time-out */
+  pp->response_time = RESP_TIMEOUT; /* set default response time-out */
+  pp->statemach_act = ftp_statemach_act;
+  pp->endofresp = ftp_endofresp;
+  pp->conn = conn;
 
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
   if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
@@ -3190,11 +2875,11 @@ static CURLcode ftp_connect(struct connectdata *conn,
       return result;
   }
 
+  Curl_pp_init(pp); /* init the generic pingpong data */
+
   /* When we connect, we start in the state where we await the 220
      response */
-  ftp_respinit(conn); /* init the response reader stuff */
   state(conn, FTP_WAIT220);
-  ftpc->response = Curl_tvnow(); /* start response time-out now! */
 
   if(data->state.used_interface == Curl_if_multi)
     result = ftp_multi_statemach(conn, done);
@@ -3222,6 +2907,7 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status,
   struct SessionHandle *data = conn->data;
   struct FTP *ftp = data->state.proto.ftp;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
+  struct pingpong *pp = &ftpc->pp;
   ssize_t nread;
   int ftpcode;
   CURLcode result=CURLE_OK;
@@ -3330,20 +3016,20 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status,
   }
 
   if((ftp->transfer == FTPTRANSFER_BODY) && ftpc->ctl_valid &&
-     ftpc->pending_resp && !premature) {
+     pp->pending_resp && !premature) {
     /*
      * 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.
      */
-    long old_time = ftpc->response_time;
+    long old_time = pp->response_time;
 
-    ftpc->response_time = 60*1000; /* give it only a minute for now */
+    pp->response_time = 60*1000; /* give it only a minute for now */
 
     result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
 
-    ftpc->response_time = old_time; /* set this back to previous value */
+    pp->response_time = old_time; /* set this back to previous value */
 
     if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) {
       failf(data, "control connection looks dead");
@@ -3497,7 +3183,7 @@ static CURLcode ftp_nb_type(struct connectdata *conn,
     return ftp_state_type_resp(conn, 200, newstate);
   }
 
-  NBFTPSENDF(conn, "TYPE %c", want);
+  PPSENDF(&ftpc->pp, "TYPE %c", want);
   state(conn, newstate);
 
   /* keep track of our current transfer type */
@@ -3735,93 +3421,12 @@ static CURLcode ftp_do(struct connectdata *conn, bool *done)
   return retcode;
 }
 
-/***********************************************************************
- *
- * 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;
-/* may still not be big enough for some krb5 tokens */
-#define SBUF_SIZE 1024
-  char s[SBUF_SIZE];
-  size_t write_len;
-  char *sptr=s;
-  CURLcode res = CURLE_OK;
-  struct SessionHandle *data = conn->data;
-  struct ftp_conn *ftpc = &conn->proto.ftpc;
-#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
-  enum protection_level data_sec = conn->data_prot;
-#endif
-
-  va_list ap;
-  va_start(ap, fmt);
-  vsnprintf(s, SBUF_SIZE-3, fmt, ap);
-  va_end(ap);
-
-  strcat(s, "\r\n"); /* append a trailing CRLF */
-
-  bytes_written=0;
-  write_len = strlen(s);
-
-  ftp_respinit(conn);
-
-#ifdef CURL_DOES_CONVERSIONS
-  res = Curl_convert_to_network(data, s, write_len);
-  /* Curl_convert_to_network calls failf if unsuccessful */
-  if(res != CURLE_OK) {
-    return res;
-  }
-#endif /* CURL_DOES_CONVERSIONS */
-
-#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
-  conn->data_prot = prot_cmd;
-#endif
-  res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len,
-                   &bytes_written);
-#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
-  conn->data_prot = data_sec;
-#endif
-
-  if(CURLE_OK != res)
-    return res;
-
-  if(conn->data->set.verbose)
-    Curl_debug(conn->data, CURLINFO_HEADER_OUT,
-               sptr, (size_t)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;
-    ftpc->sendthis = malloc(write_len);
-    if(ftpc->sendthis) {
-      memcpy(ftpc->sendthis, sptr, write_len);
-      ftpc->sendsize = ftpc->sendleft = write_len;
-    }
-    else {
-      failf(data, "out of memory");
-      res = CURLE_OUT_OF_MEMORY;
-    }
-  }
-  else
-    ftpc->response = Curl_tvnow();
-
-  return res;
-}
 
 CURLcode Curl_ftpsendf(struct connectdata *conn,
                        const char *fmt, ...)
 {
   ssize_t bytes_written;
+#define SBUF_SIZE 1024
   char s[SBUF_SIZE];
   size_t write_len;
   char *sptr=s;
@@ -3891,7 +3496,7 @@ static CURLcode ftp_quit(struct connectdata *conn)
   CURLcode result = CURLE_OK;
 
   if(conn->proto.ftpc.ctl_valid) {
-    NBFTPSENDF(conn, "QUIT", NULL);
+    PPSENDF(&conn->proto.ftpc.pp, "QUIT", NULL);
     state(conn, FTP_QUIT);
 
     result = ftp_easy_statemach(conn);
@@ -3910,6 +3515,7 @@ static CURLcode ftp_quit(struct connectdata *conn)
 static CURLcode ftp_disconnect(struct connectdata *conn)
 {
   struct ftp_conn *ftpc= &conn->proto.ftpc;
+  struct pingpong *pp = &ftpc->pp;
 
   /* We cannot send quit unconditionally. If this connection is stale or
      bad in any way, sending quit and waiting around here will make the
@@ -3930,10 +3536,7 @@ static CURLcode ftp_disconnect(struct connectdata *conn)
     free(ftpc->entrypath);
     ftpc->entrypath = NULL;
   }
-  if(ftpc->cache) {
-    free(ftpc->cache);
-    ftpc->cache = NULL;
-  }
+
   freedirs(ftpc);
   if(ftpc->prevpath) {
     free(ftpc->prevpath);
@@ -3944,6 +3547,8 @@ static CURLcode ftp_disconnect(struct connectdata *conn)
     ftpc->server_os = NULL;
   }
 
+  Curl_pp_disconnect(pp);
+
   return CURLE_OK;
 }
 
index 13652ac..93581bf 100644 (file)
--- a/lib/ftp.h
+++ b/lib/ftp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2007, 2009, 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
@@ -23,6 +23,8 @@
  * $Id$
  ***************************************************************************/
 
+#include "pingpong.h"
+
 #ifndef CURL_DISABLE_FTP
 extern const struct Curl_handler Curl_handler_ftp;
 
@@ -39,8 +41,108 @@ extern const struct Curl_handler Curl_handler_ftps_proxy;
 #endif
 
 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);
 #endif /* CURL_DISABLE_FTP */
+
+/****************************************************************************
+ * FTP unique setup
+ ***************************************************************************/
+typedef enum {
+  FTP_STOP,    /* do nothing state, stops the state machine */
+  FTP_WAIT220, /* waiting for the initial 220 response immediately after
+                  a connect */
+  FTP_AUTH,
+  FTP_USER,
+  FTP_PASS,
+  FTP_ACCT,
+  FTP_PBSZ,
+  FTP_PROT,
+  FTP_CCC,
+  FTP_PWD,
+  FTP_SYST,
+  FTP_NAMEFMT,
+  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;
+
+typedef enum {
+  FTPFILE_MULTICWD  = 1, /* as defined by RFC1738 */
+  FTPFILE_NOCWD     = 2, /* use SIZE / RETR / STOR on the full path */
+  FTPFILE_SINGLECWD = 3  /* make one CWD, then SIZE / RETR / STOR on the file */
+} curl_ftpfile;
+
+typedef enum {
+  FTPTRANSFER_BODY, /* yes do transfer a body */
+  FTPTRANSFER_INFO, /* do still go through to get info/headers */
+  FTPTRANSFER_NONE, /* don't get anything and don't get info */
+  FTPTRANSFER_LAST  /* end of list marker, never used */
+} curl_ftptransfer;
+
+/* This FTP struct is used in the SessionHandle. All FTP data that is
+   connection-oriented must be in FTP_conn to properly deal with the fact that
+   perhaps the SessionHandle is changed between the times the connection is
+   used. */
+struct FTP {
+  curl_off_t *bytecountp;
+  char *user;    /* user name string */
+  char *passwd;  /* password string */
+
+  /* transfer a file/body or not, done as a typedefed enum just to make
+     debuggers display the full symbol and not just the numerical value */
+  curl_ftptransfer transfer;
+  curl_off_t downloadsize;
+};
+
+
+/* ftp_conn is used for struct connection-oriented data in the connectdata
+   struct */
+struct ftp_conn {
+  struct pingpong pp;
+  char *entrypath; /* the PWD reply when we logged on */
+  char **dirs;   /* realloc()ed array for path components */
+  int dirdepth;  /* number of entries used in the 'dirs' array */
+  int diralloc;  /* number of entries allocated for the 'dirs' array */
+  char *file;    /* decoded file */
+  bool dont_check;  /* Set to TRUE to prevent the final (post-transfer)
+                       file size and 226/250 status check. It should still
+                       read the line, just ignore the result. */
+  bool ctl_valid;   /* Tells Curl_ftp_quit() whether or not to do anything. If
+                       the connection has timed out or been closed, this
+                       should be FALSE when it gets to Curl_ftp_quit() */
+  bool cwddone;     /* if it has been determined that the proper CWD combo
+                       already has been done */
+  bool cwdfail;     /* set TRUE if a CWD command fails, as then we must prevent
+                       caching the current directory */
+  char *prevpath;   /* conn->path from the previous transfer */
+  char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a
+                        and others (A/I or zero) */
+  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 */
+  ftpstate state; /* always use ftp.c:state() to change state! */
+  char * server_os;     /* The target server operating system. */
+};
+
 #endif /* __FTP_H */
diff --git a/lib/imap.c b/lib/imap.c
new file mode 100644 (file)
index 0000000..fecba3c
--- /dev/null
@@ -0,0 +1,1026 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2009, 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * RFC3501 IMAPv4 protocol
+ * RFC5092 IMAP URL Scheme
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "setup.h"
+
+#ifndef CURL_DISABLE_IMAP
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef  VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
+#undef in_addr_t
+#define in_addr_t unsigned long
+#endif
+
+#include <curl/curl.h>
+#include "urldata.h"
+#include "sendf.h"
+#include "easyif.h" /* for Curl_convert_... prototypes */
+
+#include "if2ip.h"
+#include "hostip.h"
+#include "progress.h"
+#include "transfer.h"
+#include "escape.h"
+#include "http.h" /* for HTTP proxy tunnel stuff */
+#include "socks.h"
+#include "imap.h"
+
+#include "strtoofft.h"
+#include "strequal.h"
+#include "sslgen.h"
+#include "connect.h"
+#include "strerror.h"
+#include "select.h"
+#include "multiif.h"
+#include "url.h"
+#include "rawstr.h"
+#include "strtoofft.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+/* Local API functions */
+static CURLcode imap_parse_url_path(struct connectdata *conn);
+static CURLcode imap_regular_transfer(struct connectdata *conn, bool *done);
+static CURLcode imap_do(struct connectdata *conn, bool *done);
+static CURLcode imap_done(struct connectdata *conn,
+                          CURLcode, bool premature);
+static CURLcode imap_connect(struct connectdata *conn, bool *done);
+static CURLcode imap_disconnect(struct connectdata *conn);
+static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done);
+static int imap_getsock(struct connectdata *conn,
+                        curl_socket_t *socks,
+                        int numsocks);
+static CURLcode imap_doing(struct connectdata *conn,
+                           bool *dophase_done);
+static CURLcode imap_setup_connection(struct connectdata * conn);
+
+/*
+ * IMAP protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_imap = {
+  "IMAP",                           /* scheme */
+  imap_setup_connection,            /* setup_connection */
+  imap_do,                          /* do_it */
+  imap_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  imap_connect,                     /* connect_it */
+  imap_multi_statemach,             /* connecting */
+  imap_doing,                       /* doing */
+  imap_getsock,                     /* proto_getsock */
+  imap_getsock,                     /* doing_getsock */
+  ZERO_NULL,                        /* perform_getsock */
+  imap_disconnect,                  /* disconnect */
+  PORT_IMAP,                        /* defport */
+  PROT_IMAP                         /* protocol */
+};
+
+
+#ifdef USE_SSL
+/*
+ * IMAPS protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_imaps = {
+  "IMAPS",                          /* scheme */
+  imap_setup_connection,            /* setup_connection */
+  imap_do,                          /* do_it */
+  imap_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  imap_connect,                     /* connect_it */
+  imap_multi_statemach,             /* connecting */
+  imap_doing,                       /* doing */
+  imap_getsock,                     /* proto_getsock */
+  imap_getsock,                     /* doing_getsock */
+  ZERO_NULL,                        /* perform_getsock */
+  imap_disconnect,                  /* disconnect */
+  PORT_IMAPS,                       /* defport */
+  PROT_IMAP | PROT_IMAPS | PROT_SSL  /* protocol */
+};
+#endif
+
+#ifndef CURL_DISABLE_HTTP
+/*
+ * HTTP-proxyed IMAP protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_imap_proxy = {
+  "IMAP",                               /* scheme */
+  ZERO_NULL,                            /* setup_connection */
+  Curl_http,                            /* do_it */
+  Curl_http_done,                       /* done */
+  ZERO_NULL,                            /* do_more */
+  ZERO_NULL,                            /* connect_it */
+  ZERO_NULL,                            /* connecting */
+  ZERO_NULL,                            /* doing */
+  ZERO_NULL,                            /* proto_getsock */
+  ZERO_NULL,                            /* doing_getsock */
+  ZERO_NULL,                            /* perform_getsock */
+  ZERO_NULL,                            /* disconnect */
+  PORT_IMAP,                            /* defport */
+  PROT_HTTP                             /* protocol */
+};
+
+
+#ifdef USE_SSL
+/*
+ * HTTP-proxyed IMAPS protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_imaps_proxy = {
+  "IMAPS",                              /* scheme */
+  ZERO_NULL,                            /* setup_connection */
+  Curl_http,                            /* do_it */
+  Curl_http_done,                       /* done */
+  ZERO_NULL,                            /* do_more */
+  ZERO_NULL,                            /* connect_it */
+  ZERO_NULL,                            /* connecting */
+  ZERO_NULL,                            /* doing */
+  ZERO_NULL,                            /* proto_getsock */
+  ZERO_NULL,                            /* doing_getsock */
+  ZERO_NULL,                            /* perform_getsock */
+  ZERO_NULL,                            /* disconnect */
+  PORT_IMAPS,                           /* defport */
+  PROT_HTTP                             /* protocol */
+};
+#endif
+#endif
+
+/***********************************************************************
+ *
+ * imapsendf()
+ *
+ * Sends the formated string as an IMAP command to a server
+ *
+ * NOTE: we build the command in a fixed-length buffer, which sets length
+ * restrictions on the command!
+ *
+ * Designed to never block.
+ */
+static CURLcode imapsendf(struct connectdata *conn,
+                          const char *idstr, /* id to wait for at the
+                                                completion of this command */
+                          const char *fmt, ...)
+{
+  CURLcode res;
+  struct imap_conn *imapc = &conn->proto.imapc;
+  va_list ap;
+  va_start(ap, fmt);
+
+  imapc->idstr = idstr; /* this is the thing */
+
+  res = Curl_pp_vsendf(&imapc->pp, fmt, ap);
+
+  va_end(ap);
+
+  return res;
+}
+
+static const char *getcmdid(struct connectdata *conn)
+{
+  static const char * const ids[]= {
+    "A",
+    "B",
+    "C",
+    "D"
+  };
+
+  struct imap_conn *imapc = &conn->proto.imapc;
+
+  /* get the next id, but wrap at end of table */
+  imapc->cmdid = (imapc->cmdid+1)% (sizeof(ids)/sizeof(ids[0]));
+
+  return ids[imapc->cmdid];
+}
+
+/* For the IMAP "protocol connect" and "doing" phases only */
+static int imap_getsock(struct connectdata *conn,
+                        curl_socket_t *socks,
+                        int numsocks)
+{
+  return Curl_pp_getsock(&conn->proto.imapc.pp, socks, numsocks);
+}
+
+/* fucntion that checks for an imap status code at the start of the
+   given string */
+static int imap_endofresp(struct pingpong *pp, int *resp)
+{
+  char *line = pp->linestart_resp;
+  size_t len = pp->nread_resp;
+  struct imap_conn *imapc = &pp->conn->proto.imapc;
+  const char *id = imapc->idstr;
+  size_t id_len = strlen(id);
+
+  if(len >= id_len + 3) {
+    if(!memcmp(id, line, id_len) && (line[id_len] == ' ') ) {
+      /* end of response */
+      *resp = line[id_len+1]; /* O, N or B */
+      return TRUE;
+    }
+    else if((imapc->state == IMAP_FETCH) &&
+            !memcmp("* ", line, 2) ) {
+      /* FETCH response we're interested in */
+      *resp = '*';
+      return TRUE;
+    }
+  }
+  return FALSE; /* nothing for us */
+}
+
+/* This is the ONLY way to change IMAP state! */
+static void state(struct connectdata *conn,
+                  imapstate newstate)
+{
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  /* for debug purposes */
+  static const char * const names[]={
+    "STOP",
+    "SERVERGREET",
+    "LOGIN",
+    "STARTTLS",
+    "SELECT",
+    "FETCH",
+    "LOGOUT",
+    /* LAST */
+  };
+#endif
+  struct imap_conn *imapc = &conn->proto.imapc;
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  if(imapc->state != newstate)
+    infof(conn->data, "IMAP %p state change from %s to %s\n",
+          imapc, names[imapc->state], names[newstate]);
+#endif
+  imapc->state = newstate;
+}
+
+static CURLcode imap_state_login(struct connectdata *conn)
+{
+  CURLcode result;
+  struct FTP *imap = conn->data->state.proto.imap;
+  const char *str;
+
+  str = getcmdid(conn);
+
+  /* send USER and password */
+  result = imapsendf(conn, str, "%s LOGIN %s %s", str,
+                     imap->user?imap->user:"",
+                     imap->passwd?imap->passwd:"");
+  if(result)
+    return result;
+
+  state(conn, IMAP_LOGIN);
+
+  return CURLE_OK;
+}
+
+/* for STARTTLS responses */
+static CURLcode imap_state_starttls_resp(struct connectdata *conn,
+                                         int imapcode,
+                                         imapstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  (void)instate; /* no use for this yet */
+
+  if(imapcode != 'O') {
+    failf(data, "STARTTLS denied. %c", imapcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else {
+    /* Curl_ssl_connect is BLOCKING */
+    result = Curl_ssl_connect(conn, FIRSTSOCKET);
+    if(CURLE_OK == result) {
+      conn->protocol |= PROT_IMAPS;
+      result = imap_state_login(conn);
+    }
+  }
+  state(conn, IMAP_STOP);
+  return result;
+}
+
+/* for LOGIN responses */
+static CURLcode imap_state_login_resp(struct connectdata *conn,
+                                      int imapcode,
+                                      imapstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  (void)instate; /* no use for this yet */
+
+  if(imapcode != 'O') {
+    failf(data, "Access denied. %c", imapcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+
+  state(conn, IMAP_STOP);
+  return result;
+}
+
+/* for the (first line of) FETCH BODY[TEXT] response */
+static CURLcode imap_state_fetch_resp(struct connectdata *conn,
+                                      int imapcode,
+                                      imapstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  struct imap_conn *imapc = &conn->proto.imapc;
+  struct FTP *imap = data->state.proto.imap;
+  struct pingpong *pp = &imapc->pp;
+  const char *ptr = data->state.buffer;
+  (void)instate; /* no use for this yet */
+
+  if('*' != imapcode) {
+    Curl_pgrsSetDownloadSize(data, 0);
+    state(conn, IMAP_STOP);
+    return CURLE_OK;
+  }
+
+  /* Something like this comes "* 1 FETCH (BODY[TEXT] {2021}\r" */
+  while(*ptr && (*ptr != '{'))
+    ptr++;
+
+  if(*ptr == '{') {
+    curl_off_t filesize = curlx_strtoofft(ptr+1, NULL, 10);
+    if(filesize)
+      Curl_pgrsSetDownloadSize(data, filesize);
+
+    infof(data, "Found %" FORMAT_OFF_TU " bytes to download\n", filesize);
+
+    if(pp->cache) {
+      /* At this point there is a bunch of data in the header "cache" that is
+         actually body content, send it as body and then skip it. Do note
+         that there may even be additional "headers" after the body. */
+      size_t chunk = pp->cache_size;
+
+      if(chunk > filesize)
+        /* the conversion from curl_off_t to size_t is always fine here */
+        chunk = (size_t)filesize;
+
+      result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk);
+      if(result)
+        return result;
+
+      filesize -= chunk;
+
+      /* we've now used parts of or the entire cache */
+      if(pp->cache_size > chunk) {
+        /* part of, move the trailing data to the start and reduce the size */
+        memmove(pp->cache, pp->cache+chunk,
+                pp->cache_size - chunk);
+        pp->cache_size -= chunk;
+      }
+      else {
+        /* cache is drained */
+        free(pp->cache);
+        pp->cache = NULL;
+        pp->cache_size = 0;
+      }
+    }
+
+    infof(data, "Filesize left: %" FORMAT_OFF_T "\n", filesize);
+
+    if(!filesize)
+      /* the entire data is already transfered! */
+      result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+    else
+      /* IMAP download */
+      result=Curl_setup_transfer(conn, FIRSTSOCKET, filesize, FALSE,
+                                 imap->bytecountp,
+                                 -1, NULL); /* no upload here */
+
+    data->req.maxdownload = filesize;
+  }
+  else
+    /* We don't know how to parse this line */
+    result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */
+
+  state(conn, IMAP_STOP);
+  return result;
+}
+
+/* start the DO phase */
+static CURLcode imap_select(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct imap_conn *imapc = &conn->proto.imapc;
+  const char *str;
+
+  str = getcmdid(conn);
+
+  result = imapsendf(conn, str, "%s SELECT %s", str,
+                     imapc->mailbox?imapc->mailbox:"");
+  if(result)
+    return result;
+
+  state(conn, IMAP_SELECT);
+  return result;
+}
+
+static CURLcode imap_fetch(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  const char *str;
+
+  str = getcmdid(conn);
+
+  /* TODO: make this select the correct mail
+   * Use "1 body[text]" to get the full mail body of mail 1
+   */
+  result = imapsendf(conn, str, "%s FETCH 1 BODY[TEXT]", str);
+  if(result)
+    return result;
+
+  /*
+   * When issued, the server will respond with a single line similar to
+   * '* 1 FETCH (BODY[TEXT] {2021}'
+   *
+   * Identifying the fetch and how many bytes of contents we can expect. We
+   * must extract that number before continuing to "download as usual".
+   */
+
+  state(conn, IMAP_FETCH);
+  return result;
+}
+
+/* for SELECT responses */
+static CURLcode imap_state_select_resp(struct connectdata *conn,
+                                       int imapcode,
+                                       imapstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  (void)instate; /* no use for this yet */
+
+  if(imapcode != 'O') {
+    failf(data, "Select failed");
+    result = CURLE_LOGIN_DENIED;
+  }
+  else
+    result = imap_fetch(conn);
+  return result;
+}
+
+static CURLcode imap_statemach_act(struct connectdata *conn)
+{
+  CURLcode result;
+  curl_socket_t sock = conn->sock[FIRSTSOCKET];
+  struct SessionHandle *data=conn->data;
+  int imapcode;
+  struct imap_conn *imapc = &conn->proto.imapc;
+  struct pingpong *pp = &imapc->pp;
+  size_t nread = 0;
+
+  if(pp->sendleft)
+    return Curl_pp_flushsend(pp);
+
+  /* we read a piece of response */
+  result = Curl_pp_readresp(sock, pp, &imapcode, &nread);
+  if(result)
+    return result;
+
+  if(imapcode)
+  /* we have now received a full IMAP server response */
+  switch(imapc->state) {
+  case IMAP_SERVERGREET:
+    if(imapcode != 'O') {
+      failf(data, "Got unexpected imap-server response");
+      return CURLE_FTP_WEIRD_SERVER_REPLY;
+    }
+
+    if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
+      /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch
+         to TLS connection now */
+      const char *str;
+
+      str = getcmdid(conn);
+      result = imapsendf(conn, str, "%s STARTTLS", str);
+      state(conn, IMAP_STARTTLS);
+    }
+    else
+      result = imap_state_login(conn);
+    if(result)
+      return result;
+    break;
+
+  case IMAP_LOGIN:
+    result = imap_state_login_resp(conn, imapcode, imapc->state);
+    break;
+
+  case IMAP_STARTTLS:
+    result = imap_state_starttls_resp(conn, imapcode, imapc->state);
+    break;
+
+  case IMAP_FETCH:
+    result = imap_state_fetch_resp(conn, imapcode, imapc->state);
+    break;
+
+  case IMAP_SELECT:
+    result = imap_state_select_resp(conn, imapcode, imapc->state);
+    break;
+
+  case IMAP_LOGOUT:
+    /* fallthrough, just stop! */
+  default:
+    /* internal error */
+    state(conn, IMAP_STOP);
+    break;
+  }
+
+  return result;
+}
+
+/* called repeatedly until done from multi.c */
+static CURLcode imap_multi_statemach(struct connectdata *conn,
+                                         bool *done)
+{
+  struct imap_conn *imapc = &conn->proto.imapc;
+  CURLcode result = Curl_pp_multi_statemach(&imapc->pp);
+
+  *done = (bool)(imapc->state == IMAP_STOP);
+
+  return result;
+}
+
+static CURLcode imap_easy_statemach(struct connectdata *conn)
+{
+  struct imap_conn *imapc = &conn->proto.imapc;
+  struct pingpong *pp = &imapc->pp;
+  CURLcode result = CURLE_OK;
+
+  while(imapc->state != IMAP_STOP) {
+    result = Curl_pp_easy_statemach(pp);
+    if(result)
+      break;
+  }
+
+  return result;
+}
+
+/*
+ * Allocate and initialize the struct IMAP for the current SessionHandle.  If
+ * need be.
+ */
+static CURLcode imap_init(struct connectdata *conn)
+{
+  struct SessionHandle *data = conn->data;
+  struct FTP *imap = data->state.proto.imap;
+  if(!imap) {
+    imap = data->state.proto.imap = calloc(sizeof(struct FTP), 1);
+    if(!imap)
+      return CURLE_OUT_OF_MEMORY;
+  }
+
+  /* get some initial data into the imap struct */
+  imap->bytecountp = &data->req.bytecount;
+
+  /* No need to duplicate user+password, the connectdata struct won't change
+     during a session, but we re-init them here since on subsequent inits
+     since the conn struct may have changed or been replaced.
+  */
+  imap->user = conn->user;
+  imap->passwd = conn->passwd;
+
+  return CURLE_OK;
+}
+
+/*
+ * imap_connect() should do everything that is to be considered a part of
+ * the connection phase.
+ *
+ * The variable 'done' points to will be TRUE if the protocol-layer connect
+ * phase is done when this function returns, or FALSE is not. When called as
+ * a part of the easy interface, it will always be TRUE.
+ */
+static CURLcode imap_connect(struct connectdata *conn,
+                                 bool *done) /* see description above */
+{
+  CURLcode result;
+  struct imap_conn *imapc = &conn->proto.imapc;
+  struct SessionHandle *data=conn->data;
+  struct pingpong *pp = &imapc->pp;
+
+  *done = FALSE; /* default to not done yet */
+
+  /* If there already is a protocol-specific struct allocated for this
+     sessionhandle, deal with it */
+  Curl_reset_reqproto(conn);
+
+  result = imap_init(conn);
+  if(CURLE_OK != result)
+    return result;
+
+  /* We always support persistant connections on imap */
+  conn->bits.close = FALSE;
+
+  pp->response_time = RESP_TIMEOUT; /* set default response time-out */
+  pp->statemach_act = imap_statemach_act;
+  pp->endofresp = imap_endofresp;
+  pp->conn = conn;
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
+  if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
+    /* for IMAP over HTTP proxy */
+    struct HTTP http_proxy;
+    struct FTP *imap_save;
+
+    /* BLOCKING */
+    /* We want "seamless" IMAP operations through HTTP proxy tunnel */
+
+    /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member
+     * conn->proto.http; we want IMAP through HTTP and we have to change the
+     * member temporarily for connecting to the HTTP proxy. After
+     * Curl_proxyCONNECT we have to set back the member to the original struct
+     * IMAP pointer
+     */
+    imap_save = data->state.proto.imap;
+    memset(&http_proxy, 0, sizeof(http_proxy));
+    data->state.proto.http = &http_proxy;
+
+    result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
+                               conn->host.name, conn->remote_port);
+
+    data->state.proto.imap = imap_save;
+
+    if(CURLE_OK != result)
+      return result;
+  }
+#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
+
+  if(conn->protocol & PROT_IMAPS) {
+    /* BLOCKING */
+    /* IMAPS is simply imap with SSL for the control channel */
+    /* now, perform the SSL initialization for this socket */
+    result = Curl_ssl_connect(conn, FIRSTSOCKET);
+    if(result)
+      return result;
+  }
+
+  Curl_pp_init(pp); /* init generic pingpong data */
+
+  /* When we connect, we start in the state where we await the server greeting
+     response */
+  state(conn, IMAP_SERVERGREET);
+  imapc->idstr = "*"; /* we start off waiting for a '*' response */
+
+  if(data->state.used_interface == Curl_if_multi)
+    result = imap_multi_statemach(conn, done);
+  else {
+    result = imap_easy_statemach(conn);
+    if(!result)
+      *done = TRUE;
+  }
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * imap_done()
+ *
+ * The DONE function. This does what needs to be done after a single DO has
+ * performed.
+ *
+ * Input argument is already checked for validity.
+ */
+static CURLcode imap_done(struct connectdata *conn, CURLcode status,
+                          bool premature)
+{
+  struct SessionHandle *data = conn->data;
+  struct FTP *imap = data->state.proto.imap;
+  CURLcode result=CURLE_OK;
+  (void)premature;
+
+  if(!imap)
+    /* When the easy handle is removed from the multi while libcurl is still
+     * trying to resolve the host name, it seems that the imap struct is not
+     * yet initialized, but the removal action calls Curl_done() which calls
+     * this function. So we simply return success if no imap pointer is set.
+     */
+    return CURLE_OK;
+
+  if(status) {
+    conn->bits.close = TRUE; /* marked for closure */
+    result = status;      /* use the already set error code */
+  }
+
+  /* clear these for next connection */
+  imap->transfer = FTPTRANSFER_BODY;
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * imap_perform()
+ *
+ * This is the actual DO function for IMAP. Get a file/directory according to
+ * the options previously setup.
+ */
+
+static
+CURLcode imap_perform(struct connectdata *conn,
+                     bool *connected,  /* connect status after PASV / PORT */
+                     bool *dophase_done)
+{
+  /* this is IMAP and no proxy */
+  CURLcode result=CURLE_OK;
+
+  DEBUGF(infof(conn->data, "DO phase starts\n"));
+
+  if(conn->data->set.opt_no_body) {
+    /* requested no body means no transfer... */
+    struct FTP *imap = conn->data->state.proto.imap;
+    imap->transfer = FTPTRANSFER_INFO;
+  }
+
+  *dophase_done = FALSE; /* not done yet */
+
+  /* start the first command in the DO phase */
+  result = imap_select(conn);
+  if(result)
+    return result;
+
+  /* run the state-machine */
+  if(conn->data->state.used_interface == Curl_if_multi)
+    result = imap_multi_statemach(conn, dophase_done);
+  else {
+    result = imap_easy_statemach(conn);
+    *dophase_done = TRUE; /* with the easy interface we are done here */
+  }
+  *connected = conn->bits.tcpconnect;
+
+  if(*dophase_done)
+    DEBUGF(infof(conn->data, "DO phase is complete\n"));
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * imap_do()
+ *
+ * This function is registered as 'curl_do' function. It decodes the path
+ * parts etc as a wrapper to the actual DO function (imap_perform).
+ *
+ * The input argument is already checked for validity.
+ */
+static CURLcode imap_do(struct connectdata *conn, bool *done)
+{
+  CURLcode retcode = CURLE_OK;
+
+  *done = FALSE; /* default to false */
+
+  /*
+    Since connections can be re-used between SessionHandles, this might be a
+    connection already existing but on a fresh SessionHandle struct so we must
+    make sure we have a good 'struct IMAP' to play with. For new connections,
+    the struct IMAP is allocated and setup in the imap_connect() function.
+  */
+  Curl_reset_reqproto(conn);
+  retcode = imap_init(conn);
+  if(retcode)
+    return retcode;
+
+  retcode = imap_parse_url_path(conn);
+  if(retcode)
+    return retcode;
+
+  retcode = imap_regular_transfer(conn, done);
+
+  return retcode;
+}
+
+/***********************************************************************
+ *
+ * imap_logout()
+ *
+ * This should be called before calling sclose().  We should then wait for the
+ * response from the server before returning. The calling code should then try
+ * to close the connection.
+ *
+ */
+static CURLcode imap_logout(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  const char *str;
+
+  str = getcmdid(conn);
+
+  result = imapsendf(conn, str, "%s LOGOUT", str, NULL);
+  if(result)
+    return result;
+  state(conn, IMAP_LOGOUT);
+
+  result = imap_easy_statemach(conn);
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * imap_disconnect()
+ *
+ * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
+ * resources. BLOCKING.
+ */
+static CURLcode imap_disconnect(struct connectdata *conn)
+{
+  struct imap_conn *imapc= &conn->proto.imapc;
+
+  /* The IMAP session may or may not have been allocated/setup at this
+     point! */
+  (void)imap_logout(conn); /* ignore errors on the LOGOUT */
+
+  Curl_pp_disconnect(&imapc->pp);
+
+  return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * imap_parse_url_path()
+ *
+ * Parse the URL path into separate path components.
+ *
+ */
+static CURLcode imap_parse_url_path(struct connectdata *conn)
+{
+  /* the imap struct is already inited in imap_connect() */
+  struct imap_conn *imapc = &conn->proto.imapc;
+  struct SessionHandle *data = conn->data;
+  const char *path = data->state.path;
+  int len;
+
+  if(!*path)
+    path = "INBOX";
+
+  /* url decode the path and use this mailbox */
+  imapc->mailbox = curl_easy_unescape(data, path, 0, &len);
+
+  return CURLE_OK;
+}
+
+/* call this when the DO phase has completed */
+static CURLcode imap_dophase_done(struct connectdata *conn,
+                                  bool connected)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *imap = conn->data->state.proto.imap;
+  (void)connected;
+
+  if(imap->transfer != FTPTRANSFER_BODY)
+    /* no data to transfer */
+    result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+
+  return result;
+}
+
+/* called from multi.c while DOing */
+static CURLcode imap_doing(struct connectdata *conn,
+                               bool *dophase_done)
+{
+  CURLcode result;
+  result = imap_multi_statemach(conn, dophase_done);
+
+  if(*dophase_done) {
+    result = imap_dophase_done(conn, FALSE /* not connected */);
+
+    DEBUGF(infof(conn->data, "DO phase is complete\n"));
+  }
+  return result;
+}
+
+/***********************************************************************
+ *
+ * imap_regular_transfer()
+ *
+ * The input argument is already checked for validity.
+ *
+ * Performs all commands done before a regular transfer between a local and a
+ * remote host.
+ *
+ */
+static
+CURLcode imap_regular_transfer(struct connectdata *conn,
+                              bool *dophase_done)
+{
+  CURLcode result=CURLE_OK;
+  bool connected=FALSE;
+  struct SessionHandle *data = conn->data;
+  data->req.size = -1; /* make sure this is unknown at this point */
+
+  Curl_pgrsSetUploadCounter(data, 0);
+  Curl_pgrsSetDownloadCounter(data, 0);
+  Curl_pgrsSetUploadSize(data, 0);
+  Curl_pgrsSetDownloadSize(data, 0);
+
+  result = imap_perform(conn,
+                        &connected, /* have we connected after PASV/PORT */
+                        dophase_done); /* all commands in the DO-phase done? */
+
+  if(CURLE_OK == result) {
+
+    if(!*dophase_done)
+      /* the DO phase has not completed yet */
+      return CURLE_OK;
+
+    result = imap_dophase_done(conn, connected);
+    if(result)
+      return result;
+  }
+
+  return result;
+}
+
+static CURLcode imap_setup_connection(struct connectdata * conn)
+{
+  struct SessionHandle *data = conn->data;
+
+  if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) {
+    /* Unless we have asked to tunnel imap operations through the proxy, we
+       switch and use HTTP operations only */
+#ifndef CURL_DISABLE_HTTP
+    if(conn->handler == &Curl_handler_imap)
+      conn->handler = &Curl_handler_imap_proxy;
+    else {
+#ifdef USE_SSL
+      conn->handler = &Curl_handler_imaps_proxy;
+#else
+      failf(data, "IMAPS not supported!");
+      return CURLE_UNSUPPORTED_PROTOCOL;
+#endif
+    }
+    /*
+     * We explicitly mark this connection as persistent here as we're doing
+     * IMAP over HTTP and thus we accidentally avoid setting this value
+     * otherwise.
+     */
+    conn->bits.close = FALSE;
+#else
+    failf(data, "IMAP over http proxy requires HTTP support built-in!");
+    return CURLE_UNSUPPORTED_PROTOCOL;
+#endif
+  }
+
+  data->state.path++;   /* don't include the initial slash */
+
+  return CURLE_OK;
+}
+
+#endif /* CURL_DISABLE_IMAP */
diff --git a/lib/imap.h b/lib/imap.h
new file mode 100644 (file)
index 0000000..75109d1
--- /dev/null
@@ -0,0 +1,56 @@
+#ifndef __IMAP_H
+#define __IMAP_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2009, 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "pingpong.h"
+
+/****************************************************************************
+ * IMAP unique setup
+ ***************************************************************************/
+typedef enum {
+  IMAP_STOP,    /* do nothing state, stops the state machine */
+  IMAP_SERVERGREET, /* waiting for the initial greeting immediately after
+                       a connect */
+  IMAP_LOGIN,
+  IMAP_STARTTLS,
+  IMAP_SELECT,
+  IMAP_FETCH,
+  IMAP_LOGOUT,
+  IMAP_LAST  /* never used */
+} imapstate;
+
+/* imap_conn is used for struct connection-oriented data in the connectdata
+   struct */
+struct imap_conn {
+  struct pingpong pp;
+  char *mailbox;     /* what to FETCH */
+  imapstate state; /* always use imap.c:state() to change state! */
+  int cmdid;       /* id number/index */
+  const char *idstr; /* pointer to a string for which to wait for as id */
+};
+
+extern const struct Curl_handler Curl_handler_imap;
+extern const struct Curl_handler Curl_handler_imaps;
+
+#endif /* __IMAP_H */
diff --git a/lib/pingpong.c b/lib/pingpong.c
new file mode 100644 (file)
index 0000000..9de6e6f
--- /dev/null
@@ -0,0 +1,536 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2009, 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ *   'pingpong' is for generic back-and-forth support functions used by FTP,
+ *   IMAP, POP3, SMTP and whatever more that likes them.
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "setup.h"
+
+#include "urldata.h"
+#include "sendf.h"
+#include "select.h"
+#include "progress.h"
+#include "speedcheck.h"
+#include "pingpong.h"
+#include "multiif.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+#ifdef USE_PINGPONG
+
+/* Returns timeout in ms. 0 or negative number means the timeout has already
+   triggered */
+long Curl_pp_state_timeout(struct pingpong *pp)
+{
+  struct connectdata *conn = pp->conn;
+  struct SessionHandle *data=conn->data;
+  long timeout_ms=360000; /* in milliseconds */
+
+  if(data->set.server_response_timeout )
+    /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine
+       remaining time.  Also, use pp->response because SERVER_RESPONSE_TIMEOUT
+       is supposed to govern the response for any given server response, not
+       for the time from connect to the given server response. */
+    timeout_ms = data->set.server_response_timeout - /* timeout time */
+      Curl_tvdiff(Curl_tvnow(), pp->response); /* spent time */
+  else if(data->set.timeout)
+    /* if timeout is requested, find out how much remaining time we have */
+    timeout_ms = data->set.timeout - /* timeout time */
+      Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
+  else
+    /* Without a requested timeout, we only wait 'response_time' seconds for
+       the full response to arrive before we bail out */
+    timeout_ms = pp->response_time -
+      Curl_tvdiff(Curl_tvnow(), pp->response); /* spent time */
+
+  return timeout_ms;
+}
+
+
+/*
+ * Curl_pp_multi_statemach()
+ *
+ * called repeatedly until done when the multi interface is used.
+ */
+CURLcode Curl_pp_multi_statemach(struct pingpong *pp)
+{
+  struct connectdata *conn = pp->conn;
+  curl_socket_t sock = conn->sock[FIRSTSOCKET];
+  int rc;
+  struct SessionHandle *data=conn->data;
+  CURLcode result = CURLE_OK;
+  long timeout_ms = Curl_pp_state_timeout(pp);
+
+  if(timeout_ms <= 0) {
+    failf(data, "server response timeout");
+    return CURLE_OPERATION_TIMEDOUT;
+  }
+
+  rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
+                         pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
+                         0);
+
+  if(rc == -1) {
+    failf(data, "select/poll error");
+    return CURLE_OUT_OF_MEMORY;
+  }
+  else if(rc != 0)
+    result = pp->statemach_act(conn);
+
+  /* if rc == 0, then select() timed out */
+
+  return result;
+}
+
+/*
+ * Curl_pp_easy_statemach()
+ *
+ * called repeatedly until done when the easy interface is used.
+ */
+CURLcode Curl_pp_easy_statemach(struct pingpong *pp)
+{
+  struct connectdata *conn = pp->conn;
+  curl_socket_t sock = conn->sock[FIRSTSOCKET];
+  int rc;
+  long interval_ms;
+  long timeout_ms = Curl_pp_state_timeout(pp);
+  struct SessionHandle *data=conn->data;
+  CURLcode result;
+
+  if(timeout_ms <=0 ) {
+    failf(data, "server response timeout");
+    return CURLE_OPERATION_TIMEDOUT; /* already too little time */
+  }
+
+  interval_ms = 1000;  /* use 1 second timeout intervals */
+  if(timeout_ms < interval_ms)
+    interval_ms = timeout_ms;
+
+  rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
+                         pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
+                         (int)interval_ms);
+
+  if(Curl_pgrsUpdate(conn))
+    result = CURLE_ABORTED_BY_CALLBACK;
+  else
+    result = Curl_speedcheck(data, Curl_tvnow());
+
+  if(result)
+    ;
+  else if(rc == -1) {
+    failf(data, "select/poll error");
+    result = CURLE_OUT_OF_MEMORY;
+  }
+  else if(rc)
+    result = pp->statemach_act(conn);
+
+  return result;
+}
+
+/* initialize stuff to prepare for reading a fresh new response */
+void Curl_pp_init(struct pingpong *pp)
+{
+  struct connectdata *conn = pp->conn;
+  pp->nread_resp = 0;
+  pp->linestart_resp = conn->data->state.buffer;
+  pp->pending_resp = TRUE;
+  pp->response = Curl_tvnow(); /* start response time-out now! */
+}
+
+
+
+/***********************************************************************
+ *
+ * Curl_pp_sendfv()
+ *
+ * Send the formated string as a command to a pingpong server. Note that
+ * the string should not have any CRLF appended, as this function will
+ * append the necessary things itself.
+ *
+ * NOTE: we build the command in a fixed-length buffer, which sets length
+ * restrictions on the command!
+ *
+ * made to never block
+ */
+CURLcode Curl_pp_vsendf(struct pingpong *pp,
+                        const char *fmt,
+                        va_list args)
+{
+  ssize_t bytes_written;
+/* may still not be big enough for some krb5 tokens */
+#define SBUF_SIZE 1024
+  char s[SBUF_SIZE];
+  size_t write_len;
+  char *sptr=s;
+  CURLcode res = CURLE_OK;
+  struct connectdata *conn = pp->conn;
+  struct SessionHandle *data = conn->data;
+
+#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
+  enum protection_level data_sec = conn->data_prot;
+#endif
+
+  vsnprintf(s, SBUF_SIZE-3, fmt, args);
+
+  strcat(s, "\r\n"); /* append a trailing CRLF */
+
+  bytes_written=0;
+  write_len = strlen(s);
+
+  Curl_pp_init(pp);
+
+#ifdef CURL_DOES_CONVERSIONS
+  res = Curl_convert_to_network(data, s, write_len);
+  /* Curl_convert_to_network calls failf if unsuccessful */
+  if(res != CURLE_OK) {
+    return res;
+  }
+#endif /* CURL_DOES_CONVERSIONS */
+
+#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
+  conn->data_prot = prot_cmd;
+#endif
+  res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len,
+                   &bytes_written);
+#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
+  conn->data_prot = data_sec;
+#endif
+
+  if(CURLE_OK != res)
+    return res;
+
+  if(conn->data->set.verbose)
+    Curl_debug(conn->data, CURLINFO_HEADER_OUT,
+               sptr, (size_t)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;
+    pp->sendthis = malloc(write_len);
+    if(pp->sendthis) {
+      memcpy(pp->sendthis, sptr, write_len);
+      pp->sendsize = pp->sendleft = write_len;
+    }
+    else {
+      failf(data, "out of memory");
+      res = CURLE_OUT_OF_MEMORY;
+    }
+  }
+  else
+    pp->response = Curl_tvnow();
+
+  return res;
+}
+
+
+/***********************************************************************
+ *
+ * Curl_pp_sendf()
+ *
+ * Send the formated string as a command to a pingpong server. Note that
+ * the string should not have any CRLF appended, as this function will
+ * append the necessary things itself.
+ *
+ * NOTE: we build the command in a fixed-length buffer, which sets length
+ * restrictions on the command!
+ *
+ * made to never block
+ */
+CURLcode Curl_pp_sendf(struct pingpong *pp,
+                       const char *fmt, ...)
+{
+  CURLcode res;
+  va_list ap;
+  va_start(ap, fmt);
+
+  res = Curl_pp_vsendf(pp, fmt, ap);
+
+  va_end(ap);
+
+  return res;
+}
+
+/*
+ * Curl_pp_readresp()
+ *
+ * Reads a piece of a server response.
+ */
+CURLcode Curl_pp_readresp(curl_socket_t sockfd,
+                          struct pingpong *pp,
+                          int *code, /* return the server code if done */
+                          size_t *size) /* size of the response */
+{
+  ssize_t perline; /* count bytes per line */
+  bool keepon=TRUE;
+  ssize_t gotbytes;
+  char *ptr;
+  struct connectdata *conn = pp->conn;
+  struct SessionHandle *data = conn->data;
+  char * const buf = data->state.buffer;
+  CURLcode result = CURLE_OK;
+
+  *code = 0; /* 0 for errors or not done */
+  *size = 0;
+
+  ptr=buf + pp->nread_resp;
+
+  /* number of bytes in the current line, so far */
+  perline = (ssize_t)(ptr-pp->linestart_resp);
+
+  keepon=TRUE;
+
+  while((pp->nread_resp<BUFSIZE) && (keepon && !result)) {
+
+    if(pp->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.
+       */
+      DEBUGASSERT((ptr+pp->cache_size) <= (buf+BUFSIZE+1));
+      memcpy(ptr, pp->cache, pp->cache_size);
+      gotbytes = pp->cache_size;
+      free(pp->cache);    /* free the cache */
+      pp->cache = NULL;   /* clear the pointer */
+      pp->cache_size = 0; /* zero the size just in case */
+    }
+    else {
+      int res;
+#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
+      enum protection_level prot = conn->data_prot;
+
+      conn->data_prot = 0;
+#endif
+      DEBUGASSERT((ptr+BUFSIZE-pp->nread_resp) <= (buf+BUFSIZE+1));
+      res = Curl_read(conn, sockfd, ptr, BUFSIZE-pp->nread_resp,
+                      &gotbytes);
+#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
+      conn->data_prot = prot;
+#endif
+      if(res < 0)
+        /* EWOULDBLOCK */
+        return CURLE_OK; /* return */
+
+#ifdef CURL_DOES_CONVERSIONS
+      if((res == CURLE_OK) && (gotbytes > 0)) {
+        /* convert from the network encoding */
+        res = Curl_convert_from_network(data, ptr, gotbytes);
+        /* Curl_convert_from_network calls failf if unsuccessful */
+      }
+#endif /* CURL_DOES_CONVERSIONS */
+
+      if(CURLE_OK != res) {
+        result = (CURLcode)res; /* Set outer result variable to this error. */
+        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 */
+      ssize_t i;
+      ssize_t clipamount = 0;
+      bool restart = FALSE;
+
+      data->req.headerbytecount += gotbytes;
+
+      pp->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 defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
+          if(!conn->sec_complete)
+#endif
+            if(data->set.verbose)
+            Curl_debug(data, CURLINFO_HEADER_IN,
+                       pp->linestart_resp, (size_t)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(conn, CLIENTWRITE_HEADER,
+                                     pp->linestart_resp, perline);
+          if(result)
+            return result;
+
+          if(pp->endofresp(pp, code)) {
+            /* 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=pp->linestart_resp, n=0; meow<ptr; meow++, n++)
+              buf[n] = *meow;
+            *meow=0; /* zero terminate */
+            keepon=FALSE;
+            pp->linestart_resp = ptr+1; /* advance pointer */
+            i++; /* skip this before getting out */
+
+            *size = pp->nread_resp; /* size of the response */
+            pp->nread_resp = 0; /* restart */
+            break;
+          }
+          perline=0; /* line starts over here */
+          pp->linestart_resp = 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! */
+        clipamount = gotbytes - i;
+        restart = TRUE;
+      }
+      else if(keepon) {
+
+        if((perline == gotbytes) && (gotbytes > BUFSIZE/2)) {
+          /* We got an excessive line without newlines and we need to deal
+             with it. We keep the first bytes of the line then we throw
+             away the rest. */
+          infof(data, "Excessive server response line length received, %zd bytes."
+                " Stripping\n", gotbytes);
+          restart = TRUE;
+
+          /* we keep 40 bytes since all our pingpong protocols are only
+             interested in the first piece */
+          clipamount = 40;
+        }
+        else if(pp->nread_resp > BUFSIZE/2) {
+          /* We got a large chunk of data and there's potentially still trailing
+             data to take care of, so we put any such part in the "cache", clear
+             the buffer to make space and restart. */
+          clipamount = perline;
+          restart = TRUE;
+        }
+      }
+      else if(i == gotbytes)
+        restart = TRUE;
+
+      if(clipamount) {
+        pp->cache_size = clipamount;
+        pp->cache = malloc(pp->cache_size);
+        if(pp->cache)
+          memcpy(pp->cache, pp->linestart_resp, pp->cache_size);
+        else
+          return CURLE_OUT_OF_MEMORY;
+      }
+      if(restart) {
+        /* now reset a few variables to start over nicely from the start of
+           the big buffer */
+        pp->nread_resp = 0; /* start over from scratch in the buffer */
+        ptr = pp->linestart_resp = buf;
+        perline = 0;
+      }
+
+    } /* there was data */
+
+  } /* while there's buffer left and loop is requested */
+
+  pp->pending_resp = FALSE;
+
+  return result;
+}
+
+int Curl_pp_getsock(struct pingpong *pp,
+                    curl_socket_t *socks,
+                    int numsocks)
+{
+  struct connectdata *conn = pp->conn;
+
+  if(!numsocks)
+    return GETSOCK_BLANK;
+
+  socks[0] = conn->sock[FIRSTSOCKET];
+
+  if(pp->sendleft) {
+    /* write mode */
+    return GETSOCK_WRITESOCK(0);
+  }
+
+  /* read mode */
+  return GETSOCK_READSOCK(0);
+}
+
+CURLcode Curl_pp_flushsend(struct pingpong *pp)
+{
+  /* we have a piece of a command still left to send */
+  struct connectdata *conn = pp->conn;
+  ssize_t written;
+  CURLcode result = CURLE_OK;
+  curl_socket_t sock = conn->sock[FIRSTSOCKET];
+
+  result = Curl_write(conn, sock, pp->sendthis + pp->sendsize -
+                      pp->sendleft, pp->sendleft, &written);
+  if(result)
+    return result;
+
+  if(written != (ssize_t)pp->sendleft) {
+    /* only a fraction was sent */
+    pp->sendleft -= written;
+  }
+  else {
+    free(pp->sendthis);
+    pp->sendthis=NULL;
+    pp->sendleft = pp->sendsize = 0;
+    pp->response = Curl_tvnow();
+  }
+  return CURLE_OK;
+}
+
+CURLcode Curl_pp_disconnect(struct pingpong *pp)
+{
+  if(pp->cache) {
+    free(pp->cache);
+    pp->cache = NULL;
+  }
+  return CURLE_OK;
+}
+
+
+
+#endif
diff --git a/lib/pingpong.h b/lib/pingpong.h
new file mode 100644 (file)
index 0000000..830aa89
--- /dev/null
@@ -0,0 +1,148 @@
+#ifndef __PINGPONG_H
+#define __PINGPONG_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2007, 2009, 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include <stdarg.h>
+
+#include "setup.h"
+
+#if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_FTP) || \
+  !defined(CURL_DISABLE_POP3) || !defined(CURL_DISABLE_SMTP)
+#define USE_PINGPONG
+#endif
+
+/* forward-declaration, this is defined in urldata.h */
+struct connectdata;
+
+/*
+ * 'pingpong' is the generic struct used for protocols doing server<->client
+ * conversations in a back-and-forth style such as FTP, IMAP, POP3, SMTP etc.
+ *
+ * It holds response cache and non-blocking sending data.
+ */
+struct pingpong {
+  char *cache;     /* data cache between getresponse()-calls */
+  size_t cache_size;  /* size of cache in bytes */
+  size_t nread_resp;  /* number of bytes currently read of a server response */
+  char *linestart_resp; /* line start pointer for the server response
+                           reader function */
+  bool pending_resp;  /* set TRUE when a server response is pending or in
+                         progress, and is cleared once the last response is
+                         read */
+  char *sendthis; /* allocated pointer to a buffer that is to be sent to the
+                     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 */
+  long response_time; /* When no timeout is given, this is the amount of
+                         seconds we await for a server response. */
+
+  struct connectdata *conn; /* points to the connectdata struct that this
+                               belongs to */
+
+  /* Function pointers the protocols MUST implement and provide for the
+     pingpong layer to function */
+
+  CURLcode (*statemach_act)(struct connectdata *conn);
+
+  int (*endofresp)(struct pingpong *pp, int *code);
+};
+
+/*
+ * Curl_pp_multi_statemach()
+ *
+ * called repeatedly until done when the multi interface is used.
+ */
+CURLcode Curl_pp_multi_statemach(struct pingpong *pp);
+
+/*
+ * Curl_pp_easy_statemach()
+ *
+ * called repeatedly until done when the easy interface is used.
+ */
+CURLcode Curl_pp_easy_statemach(struct pingpong *pp);
+
+
+/* initialize stuff to prepare for reading a fresh new response */
+void Curl_pp_init(struct pingpong *pp);
+
+/* Returns timeout in ms. 0 or negative number means the timeout has already
+   triggered */
+long Curl_pp_state_timeout(struct pingpong *pp);
+
+
+/***********************************************************************
+ *
+ * Curl_pp_sendf()
+ *
+ * Send the formated string as a command to a pingpong server. Note that
+ * the string should not have any CRLF appended, as this function will
+ * append the necessary things itself.
+ *
+ * NOTE: we build the command in a fixed-length buffer, which sets length
+ * restrictions on the command!
+ *
+ * made to never block
+ */
+CURLcode Curl_pp_sendf(struct pingpong *pp,
+                       const char *fmt, ...);
+
+/***********************************************************************
+ *
+ * Curl_pp_vsendf()
+ *
+ * Send the formated string as a command to a pingpong server. Note that
+ * the string should not have any CRLF appended, as this function will
+ * append the necessary things itself.
+ *
+ * NOTE: we build the command in a fixed-length buffer, which sets length
+ * restrictions on the command!
+ *
+ * made to never block
+ */
+CURLcode Curl_pp_vsendf(struct pingpong *pp,
+                        const char *fmt,
+                        va_list args);
+
+/*
+ * Curl_pp_readresp()
+ *
+ * Reads a piece of a server response.
+ */
+CURLcode Curl_pp_readresp(curl_socket_t sockfd,
+                          struct pingpong *pp,
+                          int *code, /* return the server code if done */
+                          size_t *size); /* size of the response */
+
+
+CURLcode Curl_pp_flushsend(struct pingpong *pp);
+
+/* call this when a pingpong connection is disconnected */
+CURLcode Curl_pp_disconnect(struct pingpong *pp);
+
+int Curl_pp_getsock(struct pingpong *pp, curl_socket_t *socks,
+                    int numsocks);
+
+#endif /* __PINGPONG_H */
diff --git a/lib/pop3.c b/lib/pop3.c
new file mode 100644 (file)
index 0000000..cf4fd1c
--- /dev/null
@@ -0,0 +1,961 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2009, 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * RFC1939 POP3 protocol
+ * RFC2384 POP URL Scheme
+ * RFC2595 Using TLS with IMAP, POP3 and ACAP
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "setup.h"
+
+#ifndef CURL_DISABLE_POP3
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef  VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
+#undef in_addr_t
+#define in_addr_t unsigned long
+#endif
+
+#include <curl/curl.h>
+#include "urldata.h"
+#include "sendf.h"
+#include "easyif.h" /* for Curl_convert_... prototypes */
+
+#include "if2ip.h"
+#include "hostip.h"
+#include "progress.h"
+#include "transfer.h"
+#include "escape.h"
+#include "http.h" /* for HTTP proxy tunnel stuff */
+#include "socks.h"
+#include "pop3.h"
+
+#include "strtoofft.h"
+#include "strequal.h"
+#include "sslgen.h"
+#include "connect.h"
+#include "strerror.h"
+#include "select.h"
+#include "multiif.h"
+#include "url.h"
+#include "rawstr.h"
+#include "strtoofft.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+/* Local API functions */
+static CURLcode pop3_parse_url_path(struct connectdata *conn);
+static CURLcode pop3_regular_transfer(struct connectdata *conn, bool *done);
+static CURLcode pop3_do(struct connectdata *conn, bool *done);
+static CURLcode pop3_done(struct connectdata *conn,
+                          CURLcode, bool premature);
+static CURLcode pop3_connect(struct connectdata *conn, bool *done);
+static CURLcode pop3_disconnect(struct connectdata *conn);
+static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done);
+static int pop3_getsock(struct connectdata *conn,
+                        curl_socket_t *socks,
+                        int numsocks);
+static CURLcode pop3_doing(struct connectdata *conn,
+                           bool *dophase_done);
+static CURLcode pop3_setup_connection(struct connectdata * conn);
+
+/*
+ * POP3 protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_pop3 = {
+  "POP3",                           /* scheme */
+  pop3_setup_connection,            /* setup_connection */
+  pop3_do,                          /* do_it */
+  pop3_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  pop3_connect,                     /* connect_it */
+  pop3_multi_statemach,             /* connecting */
+  pop3_doing,                       /* doing */
+  pop3_getsock,                     /* proto_getsock */
+  pop3_getsock,                     /* doing_getsock */
+  ZERO_NULL,                        /* perform_getsock */
+  pop3_disconnect,                  /* disconnect */
+  PORT_POP3,                        /* defport */
+  PROT_POP3                         /* protocol */
+};
+
+
+#ifdef USE_SSL
+/*
+ * POP3S protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_pop3s = {
+  "POP3S",                          /* scheme */
+  pop3_setup_connection,            /* setup_connection */
+  pop3_do,                          /* do_it */
+  pop3_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  pop3_connect,                     /* connect_it */
+  pop3_multi_statemach,             /* connecting */
+  pop3_doing,                       /* doing */
+  pop3_getsock,                     /* proto_getsock */
+  pop3_getsock,                     /* doing_getsock */
+  ZERO_NULL,                        /* perform_getsock */
+  pop3_disconnect,                  /* disconnect */
+  PORT_POP3S,                       /* defport */
+  PROT_POP3 | PROT_POP3S | PROT_SSL  /* protocol */
+};
+#endif
+
+#ifndef CURL_DISABLE_HTTP
+/*
+ * HTTP-proxyed POP3 protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_pop3_proxy = {
+  "POP3",                               /* scheme */
+  ZERO_NULL,                            /* setup_connection */
+  Curl_http,                            /* do_it */
+  Curl_http_done,                       /* done */
+  ZERO_NULL,                            /* do_more */
+  ZERO_NULL,                            /* connect_it */
+  ZERO_NULL,                            /* connecting */
+  ZERO_NULL,                            /* doing */
+  ZERO_NULL,                            /* proto_getsock */
+  ZERO_NULL,                            /* doing_getsock */
+  ZERO_NULL,                            /* perform_getsock */
+  ZERO_NULL,                            /* disconnect */
+  PORT_POP3,                            /* defport */
+  PROT_HTTP                             /* protocol */
+};
+
+
+#ifdef USE_SSL
+/*
+ * HTTP-proxyed POP3S protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_pop3s_proxy = {
+  "POP3S",                              /* scheme */
+  ZERO_NULL,                            /* setup_connection */
+  Curl_http,                            /* do_it */
+  Curl_http_done,                       /* done */
+  ZERO_NULL,                            /* do_more */
+  ZERO_NULL,                            /* connect_it */
+  ZERO_NULL,                            /* connecting */
+  ZERO_NULL,                            /* doing */
+  ZERO_NULL,                            /* proto_getsock */
+  ZERO_NULL,                            /* doing_getsock */
+  ZERO_NULL,                            /* perform_getsock */
+  ZERO_NULL,                            /* disconnect */
+  PORT_POP3S,                           /* defport */
+  PROT_HTTP                             /* protocol */
+};
+#endif
+#endif
+
+
+/* function that checks for a pop3 status code at the start of the given
+   string */
+static int pop3_endofresp(struct pingpong *pp,
+                          int *resp)
+{
+  char *line = pp->linestart_resp;
+  size_t len = pp->nread_resp;
+
+  if( ((len >= 3) && !memcmp("+OK", line, 3)) ||
+      ((len >= 4) && !memcmp("-ERR", line, 4)) ) {
+    *resp=line[1]; /* O or E */
+    return TRUE;
+  }
+
+  return FALSE; /* nothing for us */
+}
+
+/* This is the ONLY way to change POP3 state! */
+static void state(struct connectdata *conn,
+                  pop3state newstate)
+{
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  /* for debug purposes */
+  static const char * const names[]={
+    "STOP",
+    "SERVERGREET",
+    "USER",
+    "PASS",
+    "STARTTLS",
+    "RETR",
+    "QUIT",
+    /* LAST */
+  };
+#endif
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  if(pop3c->state != newstate)
+    infof(conn->data, "POP3 %p state change from %s to %s\n",
+          pop3c, names[pop3c->state], names[newstate]);
+#endif
+  pop3c->state = newstate;
+}
+
+static CURLcode pop3_state_user(struct connectdata *conn)
+{
+  CURLcode result;
+  struct FTP *pop3 = conn->data->state.proto.pop3;
+
+  /* send USER */
+  result = Curl_pp_sendf(&conn->proto.pop3c.pp, "USER %s",
+                         pop3->user?pop3->user:"");
+  if(result)
+    return result;
+
+  state(conn, POP3_USER);
+
+  return CURLE_OK;
+}
+
+/* For the POP3 "protocol connect" and "doing" phases only */
+static int pop3_getsock(struct connectdata *conn,
+                        curl_socket_t *socks,
+                        int numsocks)
+{
+  return Curl_pp_getsock(&conn->proto.pop3c.pp, socks, numsocks);
+}
+
+/* for STARTTLS responses */
+static CURLcode pop3_state_starttls_resp(struct connectdata *conn,
+                                         int pop3code,
+                                         pop3state instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  (void)instate; /* no use for this yet */
+
+  if(pop3code != 'O') {
+    failf(data, "STARTTLS denied. %c", pop3code);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else {
+    /* Curl_ssl_connect is BLOCKING */
+    result = Curl_ssl_connect(conn, FIRSTSOCKET);
+    if(CURLE_OK == result) {
+      conn->protocol |= PROT_POP3S;
+      result = pop3_state_user(conn);
+    }
+  }
+  state(conn, POP3_STOP);
+  return result;
+}
+
+/* for USER responses */
+static CURLcode pop3_state_user_resp(struct connectdata *conn,
+                                     int pop3code,
+                                     pop3state instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  struct FTP *pop3 = data->state.proto.pop3;
+
+  (void)instate; /* no use for this yet */
+
+  if(pop3code != 'O') {
+    failf(data, "Access denied. %c", pop3code);
+    result = CURLE_LOGIN_DENIED;
+  }
+
+  /* send PASS */
+  result = Curl_pp_sendf(&conn->proto.pop3c.pp, "PASS %s",
+                         pop3->passwd?pop3->passwd:"");
+  if(result)
+    return result;
+
+  state(conn, POP3_PASS);
+  return result;
+}
+
+/* for PASS responses */
+static CURLcode pop3_state_pass_resp(struct connectdata *conn,
+                                     int pop3code,
+                                     pop3state instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  (void)instate; /* no use for this yet */
+
+  if(pop3code != 'O') {
+    failf(data, "Access denied. %c", pop3code);
+    result = CURLE_LOGIN_DENIED;
+  }
+
+  state(conn, POP3_STOP);
+  return result;
+}
+
+/* for the retr response */
+static CURLcode pop3_state_retr_resp(struct connectdata *conn,
+                                     int pop3code,
+                                     pop3state instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  struct FTP *pop3 = data->state.proto.pop3;
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+  struct pingpong *pp = &pop3c->pp;
+
+  (void)instate; /* no use for this yet */
+
+  if('O' != pop3code) {
+    state(conn, POP3_STOP);
+    return CURLE_RECV_ERROR;
+  }
+
+  /* POP3 download */
+  result=Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE,
+                             pop3->bytecountp,
+                             -1, NULL); /* no upload here */
+
+  if(pp->cache) {
+    /* At this point there is a bunch of data in the header "cache" that is
+       actually body content, send it as body and then skip it. Do note
+       that there may even be additional "headers" after the body. */
+
+    /* we may get the EOB already here! */
+    result = Curl_pop3_write(conn, pp->cache, pp->cache_size);
+    if(result)
+      return result;
+
+    /* cache is drained */
+    free(pp->cache);
+    pp->cache = NULL;
+    pp->cache_size = 0;
+  }
+
+  state(conn, POP3_STOP);
+  return result;
+}
+
+/* start the DO phase */
+static CURLcode pop3_retr(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+
+  result = Curl_pp_sendf(&conn->proto.pop3c.pp, "RETR %s", pop3c->mailbox);
+  if(result)
+    return result;
+
+  state(conn, POP3_RETR);
+  return result;
+}
+
+static CURLcode pop3_statemach_act(struct connectdata *conn)
+{
+  CURLcode result;
+  curl_socket_t sock = conn->sock[FIRSTSOCKET];
+  struct SessionHandle *data=conn->data;
+  int pop3code;
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+  struct pingpong *pp = &pop3c->pp;
+  size_t nread = 0;
+
+  if(pp->sendleft)
+    return Curl_pp_flushsend(pp);
+
+  /* we read a piece of response */
+  result = Curl_pp_readresp(sock, pp, &pop3code, &nread);
+  if(result)
+    return result;
+
+  if(pop3code) {
+    /* we have now received a full POP3 server response */
+    switch(pop3c->state) {
+    case POP3_SERVERGREET:
+      if(pop3code != 'O') {
+        failf(data, "Got unexpected pop3-server response");
+        return CURLE_FTP_WEIRD_SERVER_REPLY;
+      }
+
+      if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
+        /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch
+           to TLS connection now */
+        const char *str;
+
+        result = Curl_pp_sendf(&pop3c->pp, "STARTTLS", str);
+        state(conn, POP3_STARTTLS);
+      }
+      else
+        result = pop3_state_user(conn);
+      if(result)
+        return result;
+      break;
+
+    case POP3_USER:
+      result = pop3_state_user_resp(conn, pop3code, pop3c->state);
+      break;
+
+    case POP3_PASS:
+      result = pop3_state_pass_resp(conn, pop3code, pop3c->state);
+      break;
+
+    case POP3_STARTTLS:
+      result = pop3_state_starttls_resp(conn, pop3code, pop3c->state);
+      break;
+
+    case POP3_RETR:
+      result = pop3_state_retr_resp(conn, pop3code, pop3c->state);
+      break;
+
+    case POP3_QUIT:
+      /* fallthrough, just stop! */
+    default:
+      /* internal error */
+      state(conn, POP3_STOP);
+      break;
+    }
+  }
+  return result;
+}
+
+/* called repeatedly until done from multi.c */
+static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done)
+{
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+  CURLcode result = Curl_pp_multi_statemach(&pop3c->pp);
+
+  *done = (bool)(pop3c->state == POP3_STOP);
+
+  return result;
+}
+
+static CURLcode pop3_easy_statemach(struct connectdata *conn)
+{
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+  struct pingpong *pp = &pop3c->pp;
+  CURLcode result = CURLE_OK;
+
+  while(pop3c->state != POP3_STOP) {
+    result = Curl_pp_easy_statemach(pp);
+    if(result)
+      break;
+  }
+
+  return result;
+}
+
+/*
+ * Allocate and initialize the struct POP3 for the current SessionHandle.  If
+ * need be.
+ */
+static CURLcode pop3_init(struct connectdata *conn)
+{
+  struct SessionHandle *data = conn->data;
+  struct FTP *pop3 = data->state.proto.pop3;
+  if(!pop3) {
+    pop3 = data->state.proto.pop3 = calloc(sizeof(struct FTP), 1);
+    if(!pop3)
+      return CURLE_OUT_OF_MEMORY;
+  }
+
+  /* get some initial data into the pop3 struct */
+  pop3->bytecountp = &data->req.bytecount;
+
+  /* No need to duplicate user+password, the connectdata struct won't change
+     during a session, but we re-init them here since on subsequent inits
+     since the conn struct may have changed or been replaced.
+  */
+  pop3->user = conn->user;
+  pop3->passwd = conn->passwd;
+
+  return CURLE_OK;
+}
+
+/*
+ * pop3_connect() should do everything that is to be considered a part of
+ * the connection phase.
+ *
+ * The variable 'done' points to will be TRUE if the protocol-layer connect
+ * phase is done when this function returns, or FALSE is not. When called as
+ * a part of the easy interface, it will always be TRUE.
+ */
+static CURLcode pop3_connect(struct connectdata *conn,
+                                 bool *done) /* see description above */
+{
+  CURLcode result;
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+  struct SessionHandle *data=conn->data;
+  struct pingpong *pp = &pop3c->pp;
+
+  *done = FALSE; /* default to not done yet */
+
+  /* If there already is a protocol-specific struct allocated for this
+     sessionhandle, deal with it */
+  Curl_reset_reqproto(conn);
+
+  result = pop3_init(conn);
+  if(CURLE_OK != result)
+    return result;
+
+  /* We always support persistant connections on pop3 */
+  conn->bits.close = FALSE;
+
+  pp->response_time = RESP_TIMEOUT; /* set default response time-out */
+  pp->statemach_act = pop3_statemach_act;
+  pp->endofresp = pop3_endofresp;
+  pp->conn = conn;
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
+  if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
+    /* for POP3 over HTTP proxy */
+    struct HTTP http_proxy;
+    struct FTP *pop3_save;
+
+    /* BLOCKING */
+    /* We want "seamless" POP3 operations through HTTP proxy tunnel */
+
+    /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member
+     * conn->proto.http; we want POP3 through HTTP and we have to change the
+     * member temporarily for connecting to the HTTP proxy. After
+     * Curl_proxyCONNECT we have to set back the member to the original struct
+     * POP3 pointer
+     */
+    pop3_save = data->state.proto.pop3;
+    memset(&http_proxy, 0, sizeof(http_proxy));
+    data->state.proto.http = &http_proxy;
+
+    result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
+                               conn->host.name, conn->remote_port);
+
+    data->state.proto.pop3 = pop3_save;
+
+    if(CURLE_OK != result)
+      return result;
+  }
+#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
+
+  if(conn->protocol & PROT_POP3S) {
+    /* BLOCKING */
+    /* POP3S is simply pop3 with SSL for the control channel */
+    /* now, perform the SSL initialization for this socket */
+    result = Curl_ssl_connect(conn, FIRSTSOCKET);
+    if(result)
+      return result;
+  }
+
+  Curl_pp_init(pp); /* init the response reader stuff */
+
+  /* When we connect, we start in the state where we await the server greet
+     response */
+  state(conn, POP3_SERVERGREET);
+
+  if(data->state.used_interface == Curl_if_multi)
+    result = pop3_multi_statemach(conn, done);
+  else {
+    result = pop3_easy_statemach(conn);
+    if(!result)
+      *done = TRUE;
+  }
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * pop3_done()
+ *
+ * The DONE function. This does what needs to be done after a single DO has
+ * performed.
+ *
+ * Input argument is already checked for validity.
+ */
+static CURLcode pop3_done(struct connectdata *conn, CURLcode status,
+                          bool premature)
+{
+  struct SessionHandle *data = conn->data;
+  struct FTP *pop3 = data->state.proto.pop3;
+  CURLcode result=CURLE_OK;
+  (void)premature;
+
+  if(!pop3)
+    /* When the easy handle is removed from the multi while libcurl is still
+     * trying to resolve the host name, it seems that the pop3 struct is not
+     * yet initialized, but the removal action calls Curl_done() which calls
+     * this function. So we simply return success if no pop3 pointer is set.
+     */
+    return CURLE_OK;
+
+  if(status) {
+    conn->bits.close = TRUE; /* marked for closure */
+    result = status;      /* use the already set error code */
+  }
+
+  /* clear these for next connection */
+  pop3->transfer = FTPTRANSFER_BODY;
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * pop3_perform()
+ *
+ * This is the actual DO function for POP3. Get a file/directory according to
+ * the options previously setup.
+ */
+
+static
+CURLcode pop3_perform(struct connectdata *conn,
+                     bool *connected,  /* connect status after PASV / PORT */
+                     bool *dophase_done)
+{
+  /* this is POP3 and no proxy */
+  CURLcode result=CURLE_OK;
+
+  DEBUGF(infof(conn->data, "DO phase starts\n"));
+
+  if(conn->data->set.opt_no_body) {
+    /* requested no body means no transfer... */
+    struct FTP *pop3 = conn->data->state.proto.pop3;
+    pop3->transfer = FTPTRANSFER_INFO;
+  }
+
+  *dophase_done = FALSE; /* not done yet */
+
+  /* start the first command in the DO phase */
+  result = pop3_retr(conn);
+  if(result)
+    return result;
+
+  /* run the state-machine */
+  if(conn->data->state.used_interface == Curl_if_multi)
+    result = pop3_multi_statemach(conn, dophase_done);
+  else {
+    result = pop3_easy_statemach(conn);
+    *dophase_done = TRUE; /* with the easy interface we are done here */
+  }
+  *connected = conn->bits.tcpconnect;
+
+  if(*dophase_done)
+    DEBUGF(infof(conn->data, "DO phase is complete\n"));
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * pop3_do()
+ *
+ * This function is registered as 'curl_do' function. It decodes the path
+ * parts etc as a wrapper to the actual DO function (pop3_perform).
+ *
+ * The input argument is already checked for validity.
+ */
+static CURLcode pop3_do(struct connectdata *conn, bool *done)
+{
+  CURLcode retcode = CURLE_OK;
+
+  *done = FALSE; /* default to false */
+
+  /*
+    Since connections can be re-used between SessionHandles, this might be a
+    connection already existing but on a fresh SessionHandle struct so we must
+    make sure we have a good 'struct POP3' to play with. For new connections,
+    the struct POP3 is allocated and setup in the pop3_connect() function.
+  */
+  Curl_reset_reqproto(conn);
+  retcode = pop3_init(conn);
+  if(retcode)
+    return retcode;
+
+  retcode = pop3_parse_url_path(conn);
+  if(retcode)
+    return retcode;
+
+  retcode = pop3_regular_transfer(conn, done);
+
+  return retcode;
+}
+
+/***********************************************************************
+ *
+ * pop3_quit()
+ *
+ * This should be called before calling sclose().  We should then wait for the
+ * response from the server before returning. The calling code should then try
+ * to close the connection.
+ *
+ */
+static CURLcode pop3_quit(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+
+  result = Curl_pp_sendf(&conn->proto.pop3c.pp, "QUIT", NULL);
+  if(result)
+    return result;
+  state(conn, POP3_QUIT);
+
+  result = pop3_easy_statemach(conn);
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * pop3_disconnect()
+ *
+ * Disconnect from an POP3 server. Cleanup protocol-specific per-connection
+ * resources. BLOCKING.
+ */
+static CURLcode pop3_disconnect(struct connectdata *conn)
+{
+  struct pop3_conn *pop3c= &conn->proto.pop3c;
+
+  /* We cannot send quit unconditionally. If this connection is stale or
+     bad in any way, sending quit and waiting around here will make the
+     disconnect wait in vain and cause more problems than we need to.
+  */
+
+  /* The POP3 session may or may not have been allocated/setup at this
+     point! */
+  (void)pop3_quit(conn); /* ignore errors on the LOGOUT */
+
+
+  Curl_pp_disconnect(&pop3c->pp);
+
+  return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * pop3_parse_url_path()
+ *
+ * Parse the URL path into separate path components.
+ *
+ */
+static CURLcode pop3_parse_url_path(struct connectdata *conn)
+{
+  /* the pop3 struct is already inited in pop3_connect() */
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+  struct SessionHandle *data = conn->data;
+  const char *path = data->state.path;
+  int len;
+
+  if(!*path)
+    path = "INBOX";
+
+  /* url decode the path and use this mailbox */
+  pop3c->mailbox = curl_easy_unescape(data, path, 0, &len);
+
+  return CURLE_OK;
+}
+
+/* call this when the DO phase has completed */
+static CURLcode pop3_dophase_done(struct connectdata *conn,
+                                  bool connected)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *pop3 = conn->data->state.proto.pop3;
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+ (void)connected;
+
+  if(pop3->transfer != FTPTRANSFER_BODY)
+    /* no data to transfer */
+    result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+
+  free(pop3c->mailbox);
+
+  return result;
+}
+
+/* called from multi.c while DOing */
+static CURLcode pop3_doing(struct connectdata *conn,
+                               bool *dophase_done)
+{
+  CURLcode result;
+  result = pop3_multi_statemach(conn, dophase_done);
+
+  if(*dophase_done) {
+    result = pop3_dophase_done(conn, FALSE /* not connected */);
+
+    DEBUGF(infof(conn->data, "DO phase is complete\n"));
+  }
+  return result;
+}
+
+/***********************************************************************
+ *
+ * pop3_regular_transfer()
+ *
+ * The input argument is already checked for validity.
+ *
+ * Performs all commands done before a regular transfer between a local and a
+ * remote host.
+ *
+ */
+static
+CURLcode pop3_regular_transfer(struct connectdata *conn,
+                              bool *dophase_done)
+{
+  CURLcode result=CURLE_OK;
+  bool connected=FALSE;
+  struct SessionHandle *data = conn->data;
+  data->req.size = -1; /* make sure this is unknown at this point */
+
+  Curl_pgrsSetUploadCounter(data, 0);
+  Curl_pgrsSetDownloadCounter(data, 0);
+  Curl_pgrsSetUploadSize(data, 0);
+  Curl_pgrsSetDownloadSize(data, 0);
+
+  result = pop3_perform(conn,
+                        &connected, /* have we connected after PASV/PORT */
+                        dophase_done); /* all commands in the DO-phase done? */
+
+  if(CURLE_OK == result) {
+
+    if(!*dophase_done)
+      /* the DO phase has not completed yet */
+      return CURLE_OK;
+
+    result = pop3_dophase_done(conn, connected);
+    if(result)
+      return result;
+  }
+
+  return result;
+}
+
+static CURLcode pop3_setup_connection(struct connectdata * conn)
+{
+  struct SessionHandle *data = conn->data;
+
+  if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) {
+    /* Unless we have asked to tunnel pop3 operations through the proxy, we
+       switch and use HTTP operations only */
+#ifndef CURL_DISABLE_HTTP
+    if(conn->handler == &Curl_handler_pop3)
+      conn->handler = &Curl_handler_pop3_proxy;
+    else {
+#ifdef USE_SSL
+      conn->handler = &Curl_handler_pop3s_proxy;
+#else
+      failf(data, "POP3S not supported!");
+      return CURLE_UNSUPPORTED_PROTOCOL;
+#endif
+    }
+    /*
+     * We explicitly mark this connection as persistent here as we're doing
+     * POP3 over HTTP and thus we accidentally avoid setting this value
+     * otherwise.
+     */
+    conn->bits.close = FALSE;
+#else
+    failf(data, "POP3 over http proxy requires HTTP support built-in!");
+    return CURLE_UNSUPPORTED_PROTOCOL;
+#endif
+  }
+
+  data->state.path++;   /* don't include the initial slash */
+
+  return CURLE_OK;
+}
+
+/* this is the 5-bytes End-Of-Body marker for POP3 */
+#define POP3_EOB "\x0d\x0a\x2e\x0d\x0a"
+#define POP3_EOB_LEN 5
+
+/*
+ * This function scans the body after the end-of-body and writes everything
+ * until the end is found.
+ */
+CURLcode Curl_pop3_write(struct connectdata *conn,
+                         char *str,
+                         size_t nread)
+{
+  /* This code could be made into a special function in the handler struct. */
+  CURLcode result;
+  struct SessionHandle *data = conn->data;
+  struct SingleRequest *k = &data->req;
+
+  /* Detect the end-of-body marker, which is 5 bytes:
+     0d 0a 2e 0d 0a. This marker can of course be spread out
+     over up to 5 different data chunks. Deal with it! */
+  struct pop3_conn *pop3c = &conn->proto.pop3c;
+  int checkmax = (nread >= POP3_EOB_LEN?POP3_EOB_LEN:nread);
+  int checkleft = POP3_EOB_LEN-pop3c->eob;
+  int check = checkmax>= checkleft?checkleft:checkmax;
+
+  if(!memcmp(POP3_EOB, &str[nread - check], check)) {
+    /* substring match */
+    pop3c->eob += check;
+    if(pop3c->eob == POP3_EOB_LEN) {
+      /* full match, the transfer is done! */
+      nread -= check;
+      k->keepon &= ~KEEP_RECV;
+      pop3c->eob = 0;
+    }
+  }
+  else if(pop3c->eob) {
+    /* not a match, but we matched a piece before so we must now
+       send that part as body first, before we move on and send
+       this buffer */
+    result = Curl_client_write(conn, CLIENTWRITE_BODY,
+                               (char *)POP3_EOB, pop3c->eob);
+    if(result)
+      return result;
+    pop3c->eob = 0;
+  }
+
+  result = Curl_client_write(conn, CLIENTWRITE_BODY, str, nread);
+
+  return result;
+}
+
+#endif /* CURL_DISABLE_POP3 */
diff --git a/lib/pop3.h b/lib/pop3.h
new file mode 100644 (file)
index 0000000..f47aa4e
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef __POP3_H
+#define __POP3_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2009, 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+/****************************************************************************
+ * POP3 unique setup
+ ***************************************************************************/
+typedef enum {
+  POP3_STOP,        /* do nothing state, stops the state machine */
+  POP3_SERVERGREET, /* waiting for the initial greeting immediately after
+                       a connect */
+  POP3_USER,
+  POP3_PASS,
+  POP3_STARTTLS,
+  POP3_RETR,
+  POP3_QUIT,
+  POP3_LAST  /* never used */
+} pop3state;
+
+/* pop3_conn is used for struct connection-oriented data in the connectdata
+   struct */
+struct pop3_conn {
+  struct pingpong pp;
+  char *mailbox;     /* what to RETR */
+  int eob;        /* number of bytes of the EOB (End Of Body) that has been
+                     received thus far */
+  pop3state state; /* always use pop3.c:state() to change state! */
+};
+
+extern const struct Curl_handler Curl_handler_pop3;
+extern const struct Curl_handler Curl_handler_pop3s;
+
+/*
+ * This function scans the body after the end-of-body and writes everything
+ * until the end is found.
+ */
+CURLcode Curl_pop3_write(struct connectdata *conn,
+                         char *str,
+                         size_t nread);
+
+#endif /* __POP3_H */
diff --git a/lib/smtp.c b/lib/smtp.c
new file mode 100644 (file)
index 0000000..7f1b6fc
--- /dev/null
@@ -0,0 +1,921 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2009, 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * RFC2821 SMTP protocol
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "setup.h"
+
+#ifndef CURL_DISABLE_SMTP
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef  VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
+#undef in_addr_t
+#define in_addr_t unsigned long
+#endif
+
+#include <curl/curl.h>
+#include "urldata.h"
+#include "sendf.h"
+#include "easyif.h" /* for Curl_convert_... prototypes */
+
+#include "if2ip.h"
+#include "hostip.h"
+#include "progress.h"
+#include "transfer.h"
+#include "escape.h"
+#include "http.h" /* for HTTP proxy tunnel stuff */
+#include "socks.h"
+#include "smtp.h"
+
+#include "strtoofft.h"
+#include "strequal.h"
+#include "sslgen.h"
+#include "connect.h"
+#include "strerror.h"
+#include "select.h"
+#include "multiif.h"
+#include "url.h"
+#include "rawstr.h"
+#include "strtoofft.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+/* Local API functions */
+static CURLcode smtp_parse_url_path(struct connectdata *conn);
+static CURLcode smtp_regular_transfer(struct connectdata *conn, bool *done);
+static CURLcode smtp_do(struct connectdata *conn, bool *done);
+static CURLcode smtp_done(struct connectdata *conn,
+                          CURLcode, bool premature);
+static CURLcode smtp_connect(struct connectdata *conn, bool *done);
+static CURLcode smtp_disconnect(struct connectdata *conn);
+static CURLcode smtp_multi_statemach(struct connectdata *conn, bool *done);
+static int smtp_getsock(struct connectdata *conn,
+                        curl_socket_t *socks,
+                        int numsocks);
+static CURLcode smtp_doing(struct connectdata *conn,
+                           bool *dophase_done);
+static CURLcode smtp_setup_connection(struct connectdata * conn);
+static void smtp_respinit(struct connectdata *conn);
+
+/*
+ * SMTP protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_smtp = {
+  "SMTP",                           /* scheme */
+  smtp_setup_connection,            /* setup_connection */
+  smtp_do,                          /* do_it */
+  smtp_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  smtp_connect,                     /* connect_it */
+  smtp_multi_statemach,             /* connecting */
+  smtp_doing,                       /* doing */
+  smtp_getsock,                     /* proto_getsock */
+  smtp_getsock,                     /* doing_getsock */
+  ZERO_NULL,                        /* perform_getsock */
+  smtp_disconnect,                  /* disconnect */
+  PORT_SMTP,                        /* defport */
+  PROT_SMTP                         /* protocol */
+};
+
+
+#ifdef USE_SSL
+/*
+ * SMTPS protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_smtps = {
+  "SMTPS",                          /* scheme */
+  smtp_setup_connection,            /* setup_connection */
+  smtp_do,                          /* do_it */
+  smtp_done,                        /* done */
+  ZERO_NULL,                        /* do_more */
+  smtp_connect,                     /* connect_it */
+  smtp_multi_statemach,             /* connecting */
+  smtp_doing,                       /* doing */
+  smtp_getsock,                     /* proto_getsock */
+  smtp_getsock,                     /* doing_getsock */
+  ZERO_NULL,                        /* perform_getsock */
+  smtp_disconnect,                  /* disconnect */
+  PORT_SMTPS,                       /* defport */
+  PROT_SMTP | PROT_SMTPS | PROT_SSL  /* protocol */
+};
+#endif
+
+#ifndef CURL_DISABLE_HTTP
+/*
+ * HTTP-proxyed SMTP protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_smtp_proxy = {
+  "SMTP",                               /* scheme */
+  ZERO_NULL,                            /* setup_connection */
+  Curl_http,                            /* do_it */
+  Curl_http_done,                       /* done */
+  ZERO_NULL,                            /* do_more */
+  ZERO_NULL,                            /* connect_it */
+  ZERO_NULL,                            /* connecting */
+  ZERO_NULL,                            /* doing */
+  ZERO_NULL,                            /* proto_getsock */
+  ZERO_NULL,                            /* doing_getsock */
+  ZERO_NULL,                            /* perform_getsock */
+  ZERO_NULL,                            /* disconnect */
+  PORT_SMTP,                            /* defport */
+  PROT_HTTP                             /* protocol */
+};
+
+
+#ifdef USE_SSL
+/*
+ * HTTP-proxyed SMTPS protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_smtps_proxy = {
+  "SMTPS",                              /* scheme */
+  ZERO_NULL,                            /* setup_connection */
+  Curl_http,                            /* do_it */
+  Curl_http_done,                       /* done */
+  ZERO_NULL,                            /* do_more */
+  ZERO_NULL,                            /* connect_it */
+  ZERO_NULL,                            /* connecting */
+  ZERO_NULL,                            /* doing */
+  ZERO_NULL,                            /* proto_getsock */
+  ZERO_NULL,                            /* doing_getsock */
+  ZERO_NULL,                            /* perform_getsock */
+  ZERO_NULL,                            /* disconnect */
+  PORT_SMTPS,                           /* defport */
+  PROT_HTTP                             /* protocol */
+};
+#endif
+#endif
+
+
+/* fucntion that checks for an ending smtp status code at the start of the
+   given string */
+static int smtp_endofresp(struct pingpong *pp, int *resp)
+{
+  char *line = pp->linestart_resp;
+  size_t len = pp->nread_resp;
+
+  if( (len >= 4) && (' ' == line[3]) &&
+      ISDIGIT(line[0]) && ISDIGIT(line[1]) && ISDIGIT(line[2])) {
+    *resp=atoi(line);
+    return TRUE;
+  }
+
+  return FALSE; /* nothing for us */
+}
+
+/* This is the ONLY way to change SMTP state! */
+static void state(struct connectdata *conn,
+                  smtpstate newstate)
+{
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  /* for debug purposes */
+  static const char * const names[]={
+    "STOP",
+    "SERVERGREET",
+    "EHLO",
+    "STARTTLS",
+    "MAIL",
+    "RCPT",
+    "DATA",
+    "QUIT",
+    /* LAST */
+  };
+#endif
+  struct smtp_conn *smtpc = &conn->proto.smtpc;
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  if(smtpc->state != newstate)
+    infof(conn->data, "SMTP %p state change from %s to %s\n",
+          smtpc, names[smtpc->state], names[newstate]);
+#endif
+  smtpc->state = newstate;
+}
+
+static CURLcode smtp_state_ehlo(struct connectdata *conn)
+{
+  CURLcode result;
+  struct FTP *smtp = conn->data->state.proto.smtp;
+
+  /* send EHLO */
+  result = Curl_pp_sendf(&conn->proto.smtpc.pp, "EHLO %s",
+                         smtp->user?smtp->user:"");
+  if(result)
+    return result;
+
+  state(conn, SMTP_EHLO);
+
+  return CURLE_OK;
+}
+
+/* For the SMTP "protocol connect" and "doing" phases only */
+static int smtp_getsock(struct connectdata *conn,
+                        curl_socket_t *socks,
+                        int numsocks)
+{
+  return Curl_pp_getsock(&conn->proto.smtpc.pp, socks, numsocks);
+}
+
+/* for STARTTLS responses */
+static CURLcode smtp_state_starttls_resp(struct connectdata *conn,
+                                         int smtpcode,
+                                         smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode != 'O') {
+    failf(data, "STARTTLS denied. %c", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+  else {
+    /* Curl_ssl_connect is BLOCKING */
+    result = Curl_ssl_connect(conn, FIRSTSOCKET);
+    if(CURLE_OK == result) {
+      conn->protocol |= PROT_SMTPS;
+      result = smtp_state_ehlo(conn);
+    }
+  }
+  state(conn, SMTP_STOP);
+  return result;
+}
+
+/* for EHLO responses */
+static CURLcode smtp_state_ehlo_resp(struct connectdata *conn,
+                                     int smtpcode,
+                                     smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode/100 != 2) {
+    failf(data, "Access denied: %d", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+  }
+
+  /* end the connect phase */
+  state(conn, SMTP_STOP);
+  return result;
+}
+
+/* start the DO phase */
+static CURLcode smtp_mail(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  struct FTP *smtp = data->state.proto.smtp;
+
+  /* send MAIL */
+  result = Curl_pp_sendf(&conn->proto.smtpc.pp, "MAIL FROM:<%s>",
+                         data->set.str[STRING_MAIL_FROM]);
+  if(result)
+    return result;
+
+  state(conn, SMTP_MAIL);
+  return result;
+}
+
+/* for MAIL responses */
+static CURLcode smtp_state_mail_resp(struct connectdata *conn,
+                                     int smtpcode,
+                                     smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode/100 != 2) {
+    failf(data, "Access denied: %d", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+    state(conn, SMTP_STOP);
+  }
+  else {
+    /* send RCPT TO */
+    result = Curl_pp_sendf(&conn->proto.smtpc.pp, "RCPT TO:<%s>",
+                           data->set.str[STRING_MAIL_RCPT]);
+    if(result)
+      return result;
+
+    state(conn, SMTP_RCPT);
+  }
+  return result;
+}
+
+/* for RCPT responses */
+static CURLcode smtp_state_rcpt_resp(struct connectdata *conn,
+                                     int smtpcode,
+                                     smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode/100 != 2) {
+    failf(data, "Access denied: %d", smtpcode);
+    result = CURLE_LOGIN_DENIED;
+    state(conn, SMTP_STOP);
+  }
+  else {
+    /* send DATA */
+    result = Curl_pp_sendf(&conn->proto.smtpc.pp, "DATA", "");
+    if(result)
+      return result;
+
+    state(conn, SMTP_DATA);
+  }
+  return result;
+}
+
+/* for the DATA response */
+static CURLcode smtp_state_data_resp(struct connectdata *conn,
+                                     int smtpcode,
+                                     smtpstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  struct FTP *smtp = data->state.proto.smtp;
+
+  (void)instate; /* no use for this yet */
+
+  if(smtpcode/100 != 2) {
+    state(conn, SMTP_STOP);
+    return CURLE_RECV_ERROR;
+  }
+
+  /* SMTP upload */
+  result=Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE,
+                             smtp->bytecountp,
+                             -1, NULL); /* no upload here */
+
+  state(conn, SMTP_STOP);
+  return result;
+}
+
+static CURLcode smtp_statemach_act(struct connectdata *conn)
+{
+  CURLcode result;
+  curl_socket_t sock = conn->sock[FIRSTSOCKET];
+  struct SessionHandle *data=conn->data;
+  int smtpcode;
+  struct smtp_conn *smtpc = &conn->proto.smtpc;
+  struct pingpong *pp = &smtpc->pp;
+  size_t nread = 0;
+
+  if(pp->sendleft)
+    /* we have a piece of a command still left to send */
+    return Curl_pp_flushsend(pp);
+
+  /* we read a piece of response */
+  result = Curl_pp_readresp(sock, pp, &smtpcode, &nread);
+  if(result)
+    return result;
+
+  if(smtpcode) {
+    /* we have now received a full SMTP server response */
+    switch(smtpc->state) {
+    case SMTP_SERVERGREET:
+      if(smtpcode/100 != 2) {
+        failf(data, "Got unexpected smtp-server response: %d", smtpcode);
+        return CURLE_FTP_WEIRD_SERVER_REPLY;
+      }
+
+      if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
+        /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch
+           to TLS connection now */
+        const char *str;
+
+        result = Curl_pp_sendf(&smtpc->pp, "STARTTLS", str);
+        state(conn, SMTP_STARTTLS);
+      }
+      else
+        result = smtp_state_ehlo(conn);
+      if(result)
+        return result;
+      break;
+
+    case SMTP_EHLO:
+      result = smtp_state_ehlo_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_MAIL:
+      result = smtp_state_mail_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_RCPT:
+      result = smtp_state_rcpt_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_STARTTLS:
+      result = smtp_state_starttls_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_DATA:
+      result = smtp_state_data_resp(conn, smtpcode, smtpc->state);
+      break;
+
+    case SMTP_QUIT:
+      /* fallthrough, just stop! */
+    default:
+      /* internal error */
+      state(conn, SMTP_STOP);
+      break;
+    }
+  }
+  return result;
+}
+
+/* called repeatedly until done from multi.c */
+static CURLcode smtp_multi_statemach(struct connectdata *conn,
+                                     bool *done)
+{
+  struct smtp_conn *smtpc = &conn->proto.smtpc;
+  CURLcode result = Curl_pp_multi_statemach(&smtpc->pp);
+
+  *done = (bool)(smtpc->state == SMTP_STOP);
+
+  return result;
+}
+
+static CURLcode smtp_easy_statemach(struct connectdata *conn)
+{
+  struct smtp_conn *smtpc = &conn->proto.smtpc;
+  struct pingpong *pp = &smtpc->pp;
+  CURLcode result = CURLE_OK;
+
+  while(smtpc->state != SMTP_STOP) {
+    result = Curl_pp_easy_statemach(pp);
+    if(result)
+      break;
+  }
+
+  return result;
+}
+
+/*
+ * Allocate and initialize the struct SMTP for the current SessionHandle.  If
+ * need be.
+ */
+static CURLcode smtp_init(struct connectdata *conn)
+{
+  struct SessionHandle *data = conn->data;
+  struct FTP *smtp = data->state.proto.smtp;
+  if(!smtp) {
+    smtp = data->state.proto.smtp = calloc(sizeof(struct FTP), 1);
+    if(!smtp)
+      return CURLE_OUT_OF_MEMORY;
+  }
+
+  /* get some initial data into the smtp struct */
+  smtp->bytecountp = &data->req.bytecount;
+
+  /* No need to duplicate user+password, the connectdata struct won't change
+     during a session, but we re-init them here since on subsequent inits
+     since the conn struct may have changed or been replaced.
+  */
+  smtp->user = conn->user;
+  smtp->passwd = conn->passwd;
+
+  return CURLE_OK;
+}
+
+/*
+ * smtp_connect() should do everything that is to be considered a part of
+ * the connection phase.
+ *
+ * The variable 'done' points to will be TRUE if the protocol-layer connect
+ * phase is done when this function returns, or FALSE is not. When called as
+ * a part of the easy interface, it will always be TRUE.
+ */
+static CURLcode smtp_connect(struct connectdata *conn,
+                                 bool *done) /* see description above */
+{
+  CURLcode result;
+  struct smtp_conn *smtpc = &conn->proto.smtpc;
+  struct SessionHandle *data=conn->data;
+  struct pingpong *pp=&smtpc->pp;
+
+  *done = FALSE; /* default to not done yet */
+
+  /* If there already is a protocol-specific struct allocated for this
+     sessionhandle, deal with it */
+  Curl_reset_reqproto(conn);
+
+  result = smtp_init(conn);
+  if(CURLE_OK != result)
+    return result;
+
+  /* We always support persistant connections on smtp */
+  conn->bits.close = FALSE;
+
+  pp->response_time = RESP_TIMEOUT; /* set default response time-out */
+  pp->statemach_act = smtp_statemach_act;
+  pp->endofresp = smtp_endofresp;
+  pp->conn = conn;
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
+  if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
+    /* for SMTP over HTTP proxy */
+    struct HTTP http_proxy;
+    struct FTP *smtp_save;
+
+    /* BLOCKING */
+    /* We want "seamless" SMTP operations through HTTP proxy tunnel */
+
+    /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member
+     * conn->proto.http; we want SMTP through HTTP and we have to change the
+     * member temporarily for connecting to the HTTP proxy. After
+     * Curl_proxyCONNECT we have to set back the member to the original struct
+     * SMTP pointer
+     */
+    smtp_save = data->state.proto.smtp;
+    memset(&http_proxy, 0, sizeof(http_proxy));
+    data->state.proto.http = &http_proxy;
+
+    result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
+                               conn->host.name, conn->remote_port);
+
+    data->state.proto.smtp = smtp_save;
+
+    if(CURLE_OK != result)
+      return result;
+  }
+#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
+
+  if(conn->protocol & PROT_SMTPS) {
+    /* BLOCKING */
+    /* SMTPS is simply smtp with SSL for the control channel */
+    /* now, perform the SSL initialization for this socket */
+    result = Curl_ssl_connect(conn, FIRSTSOCKET);
+    if(result)
+      return result;
+  }
+
+  Curl_pp_init(pp); /* init the response reader stuff */
+
+  pp->response_time = RESP_TIMEOUT; /* set default response time-out */
+  pp->statemach_act = smtp_statemach_act;
+  pp->endofresp = smtp_endofresp;
+  pp->conn = conn;
+
+  /* When we connect, we start in the state where we await the server greeting
+   */
+  state(conn, SMTP_SERVERGREET);
+
+  if(data->state.used_interface == Curl_if_multi)
+    result = smtp_multi_statemach(conn, done);
+  else {
+    result = smtp_easy_statemach(conn);
+    if(!result)
+      *done = TRUE;
+  }
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * smtp_done()
+ *
+ * The DONE function. This does what needs to be done after a single DO has
+ * performed.
+ *
+ * Input argument is already checked for validity.
+ */
+static CURLcode smtp_done(struct connectdata *conn, CURLcode status,
+                          bool premature)
+{
+  struct SessionHandle *data = conn->data;
+  struct FTP *smtp = data->state.proto.smtp;
+  CURLcode result=CURLE_OK;
+  (void)premature;
+
+  if(!smtp)
+    /* When the easy handle is removed from the multi while libcurl is still
+     * trying to resolve the host name, it seems that the smtp struct is not
+     * yet initialized, but the removal action calls Curl_done() which calls
+     * this function. So we simply return success if no smtp pointer is set.
+     */
+    return CURLE_OK;
+
+  if(status) {
+    conn->bits.close = TRUE; /* marked for closure */
+    result = status;      /* use the already set error code */
+  }
+
+  /* clear these for next connection */
+  smtp->transfer = FTPTRANSFER_BODY;
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * smtp_perform()
+ *
+ * This is the actual DO function for SMTP. Get a file/directory according to
+ * the options previously setup.
+ */
+
+static
+CURLcode smtp_perform(struct connectdata *conn,
+                     bool *connected,  /* connect status after PASV / PORT */
+                     bool *dophase_done)
+{
+  /* this is SMTP and no proxy */
+  CURLcode result=CURLE_OK;
+
+  DEBUGF(infof(conn->data, "DO phase starts\n"));
+
+  if(conn->data->set.opt_no_body) {
+    /* requested no body means no transfer... */
+    struct FTP *smtp = conn->data->state.proto.smtp;
+    smtp->transfer = FTPTRANSFER_INFO;
+  }
+
+  *dophase_done = FALSE; /* not done yet */
+
+  /* start the first command in the DO phase */
+  result = smtp_mail(conn);
+  if(result)
+    return result;
+
+  /* run the state-machine */
+  if(conn->data->state.used_interface == Curl_if_multi)
+    result = smtp_multi_statemach(conn, dophase_done);
+  else {
+    result = smtp_easy_statemach(conn);
+    *dophase_done = TRUE; /* with the easy interface we are done here */
+  }
+  *connected = conn->bits.tcpconnect;
+
+  if(*dophase_done)
+    DEBUGF(infof(conn->data, "DO phase is complete\n"));
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * smtp_do()
+ *
+ * This function is registered as 'curl_do' function. It decodes the path
+ * parts etc as a wrapper to the actual DO function (smtp_perform).
+ *
+ * The input argument is already checked for validity.
+ */
+static CURLcode smtp_do(struct connectdata *conn, bool *done)
+{
+  CURLcode retcode = CURLE_OK;
+
+  *done = FALSE; /* default to false */
+
+  /*
+    Since connections can be re-used between SessionHandles, this might be a
+    connection already existing but on a fresh SessionHandle struct so we must
+    make sure we have a good 'struct SMTP' to play with. For new connections,
+    the struct SMTP is allocated and setup in the smtp_connect() function.
+  */
+  Curl_reset_reqproto(conn);
+  retcode = smtp_init(conn);
+  if(retcode)
+    return retcode;
+
+  retcode = smtp_parse_url_path(conn);
+  if(retcode)
+    return retcode;
+
+  retcode = smtp_regular_transfer(conn, done);
+
+  return retcode;
+}
+
+/***********************************************************************
+ *
+ * smtp_quit()
+ *
+ * This should be called before calling sclose().  We should then wait for the
+ * response from the server before returning. The calling code should then try
+ * to close the connection.
+ *
+ */
+static CURLcode smtp_quit(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+
+  result = Curl_pp_sendf(&conn->proto.smtpc.pp, "QUIT", NULL);
+  if(result)
+    return result;
+  state(conn, SMTP_QUIT);
+
+  result = smtp_easy_statemach(conn);
+
+  return result;
+}
+
+/***********************************************************************
+ *
+ * smtp_disconnect()
+ *
+ * Disconnect from an SMTP server. Cleanup protocol-specific per-connection
+ * resources. BLOCKING.
+ */
+static CURLcode smtp_disconnect(struct connectdata *conn)
+{
+  struct smtp_conn *smtpc= &conn->proto.smtpc;
+
+  /* We cannot send quit unconditionally. If this connection is stale or
+     bad in any way, sending quit and waiting around here will make the
+     disconnect wait in vain and cause more problems than we need to.
+  */
+
+  /* The SMTP session may or may not have been allocated/setup at this
+     point! */
+  (void)smtp_quit(conn); /* ignore errors on the LOGOUT */
+
+  Curl_pp_disconnect(&smtpc->pp);
+
+  return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * smtp_parse_url_path()
+ *
+ * Parse the URL path into separate path components.
+ *
+ */
+static CURLcode smtp_parse_url_path(struct connectdata *conn)
+{
+  /* the smtp struct is already inited in smtp_connect() */
+  struct smtp_conn *smtpc = &conn->proto.smtpc;
+  struct SessionHandle *data = conn->data;
+
+  /* url decode... */
+
+  return CURLE_OK;
+}
+
+/* call this when the DO phase has completed */
+static CURLcode smtp_dophase_done(struct connectdata *conn,
+                                  bool connected)
+{
+  CURLcode result = CURLE_OK;
+  struct FTP *smtp = conn->data->state.proto.smtp;
+  (void)connected;
+
+  if(smtp->transfer != FTPTRANSFER_BODY)
+    /* no data to transfer */
+    result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+
+  return result;
+}
+
+/* called from multi.c while DOing */
+static CURLcode smtp_doing(struct connectdata *conn,
+                               bool *dophase_done)
+{
+  CURLcode result;
+  result = smtp_multi_statemach(conn, dophase_done);
+
+  if(*dophase_done) {
+    result = smtp_dophase_done(conn, FALSE /* not connected */);
+
+    DEBUGF(infof(conn->data, "DO phase is complete\n"));
+  }
+  return result;
+}
+
+/***********************************************************************
+ *
+ * smtp_regular_transfer()
+ *
+ * The input argument is already checked for validity.
+ *
+ * Performs all commands done before a regular transfer between a local and a
+ * remote host.
+ */
+static
+CURLcode smtp_regular_transfer(struct connectdata *conn,
+                              bool *dophase_done)
+{
+  CURLcode result=CURLE_OK;
+  bool connected=FALSE;
+  struct SessionHandle *data = conn->data;
+  data->req.size = -1; /* make sure this is unknown at this point */
+
+  Curl_pgrsSetUploadCounter(data, 0);
+  Curl_pgrsSetDownloadCounter(data, 0);
+  Curl_pgrsSetUploadSize(data, 0);
+  Curl_pgrsSetDownloadSize(data, 0);
+
+  result = smtp_perform(conn,
+                        &connected, /* have we connected after PASV/PORT */
+                        dophase_done); /* all commands in the DO-phase done? */
+
+  if(CURLE_OK == result) {
+
+    if(!*dophase_done)
+      /* the DO phase has not completed yet */
+      return CURLE_OK;
+
+    result = smtp_dophase_done(conn, connected);
+    if(result)
+      return result;
+  }
+
+  return result;
+}
+
+static CURLcode smtp_setup_connection(struct connectdata * conn)
+{
+  struct SessionHandle *data = conn->data;
+
+  if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) {
+    /* Unless we have asked to tunnel smtp operations through the proxy, we
+       switch and use HTTP operations only */
+#ifndef CURL_DISABLE_HTTP
+    if(conn->handler == &Curl_handler_smtp)
+      conn->handler = &Curl_handler_smtp_proxy;
+    else {
+#ifdef USE_SSL
+      conn->handler = &Curl_handler_smtps_proxy;
+#else
+      failf(data, "SMTPS not supported!");
+      return CURLE_UNSUPPORTED_PROTOCOL;
+#endif
+    }
+    /*
+     * We explicitly mark this connection as persistent here as we're doing
+     * SMTP over HTTP and thus we accidentally avoid setting this value
+     * otherwise.
+     */
+    conn->bits.close = FALSE;
+#else
+    failf(data, "SMTP over http proxy requires HTTP support built-in!");
+    return CURLE_UNSUPPORTED_PROTOCOL;
+#endif
+  }
+
+  data->state.path++;   /* don't include the initial slash */
+
+  return CURLE_OK;
+}
+
+#endif /* CURL_DISABLE_SMTP */
diff --git a/lib/smtp.h b/lib/smtp.h
new file mode 100644 (file)
index 0000000..199481a
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef __SMTP_H
+#define __SMTP_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2009, 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.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "pingpong.h"
+
+/****************************************************************************
+ * SMTP unique setup
+ ***************************************************************************/
+typedef enum {
+  SMTP_STOP,        /* do nothing state, stops the state machine */
+  SMTP_SERVERGREET, /* waiting for the initial greeting immediately after
+                       a connect */
+  SMTP_EHLO,
+  SMTP_STARTTLS,
+  SMTP_MAIL, /* MAIL FROM */
+  SMTP_RCPT, /* RCPT TO */
+  SMTP_DATA,
+  SMTP_QUIT,
+  SMTP_LAST  /* never used */
+} smtpstate;
+
+/* smtp_conn is used for struct connection-oriented data in the connectdata
+   struct */
+struct smtp_conn {
+  struct pingpong pp;
+  char *domain;      /* what to send in the EHLO */
+  int eob;        /* number of bytes of the EOB (End Of Body) that has been
+                     received thus far */
+  smtpstate state; /* always use smtp.c:state() to change state! */
+};
+
+extern const struct Curl_handler Curl_handler_smtp;
+extern const struct Curl_handler Curl_handler_smtps;
+
+/* this is the 5-bytes End-Of-Body marker for SMTP */
+#define SMTP_EOB "\x0d\x0a\x2e\x0d\x0a"
+#define SMTP_EOB_LEN 5
+
+#endif /* __SMTP_H */
index a18ad87..820d681 100644 (file)
@@ -690,9 +690,17 @@ static CURLcode readwrite_data(struct SessionHandle *data,
             /* This is the default when the server sends no
                Content-Encoding header. See Curl_readwrite_init; the
                memset() call initializes k->content_encoding to zero. */
-            if(!k->ignorebody)
+            if(!k->ignorebody) {
+
+#ifndef CURL_DISABLE_POP3
+              if(conn->protocol&PROT_POP3)
+                result = Curl_pop3_write(conn, k->str, nread);
+              else
+#endif /* CURL_DISABLE_POP3 */
+
               result = Curl_client_write(conn, CLIENTWRITE_BODY, k->str,
                                          nread);
+            }
 #ifdef HAVE_LIBZ
             break;
 
index edfa3ed..95ca316 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -130,6 +130,7 @@ void idn_free (void *ptr); /* prototype from idn-free.h, not provided by
 #include "file.h"
 #include "curl_ldap.h"
 #include "ssh.h"
+#include "imap.h"
 #include "url.h"
 #include "connect.h"
 #include "inet_ntop.h"
@@ -204,6 +205,27 @@ static const struct Curl_handler * const protocols[] = {
   &Curl_handler_sftp,
 #endif
 
+#ifndef CURL_DISABLE_IMAP
+  &Curl_handler_imap,
+#ifdef USE_SSL
+  &Curl_handler_imaps,
+#endif
+#endif
+
+#ifndef CURL_DISABLE_POP3
+  &Curl_handler_pop3,
+#ifdef USE_SSL
+  &Curl_handler_pop3s,
+#endif
+#endif
+
+#ifndef CURL_DISABLE_SMTP
+  &Curl_handler_smtp,
+#ifdef USE_SSL
+  &Curl_handler_smtps,
+#endif
+#endif
+
   (struct Curl_handler *) NULL
 };
 
@@ -950,12 +972,12 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
      */
     data->set.ftp_create_missing_dirs = (int)va_arg(param, long);
     break;
-  case CURLOPT_FTP_RESPONSE_TIMEOUT:
+  case CURLOPT_SERVER_RESPONSE_TIMEOUT:
     /*
-     * An FTP option that specifies how quickly an FTP response must be
-     * obtained before it is considered failure.
+     * Option that specifies how quickly an server response must be obtained
+     * before it is considered failure. For pingpong protocols.
      */
-    data->set.ftp_response_timeout = va_arg( param , long ) * 1000;
+    data->set.server_response_timeout = va_arg( param , long ) * 1000;
     break;
   case CURLOPT_TFTP_BLKSIZE:
     /*
@@ -2286,6 +2308,16 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
     data->set.redir_protocols = va_arg(param, long) & PROT_EXTMASK;
     break;
 
+  case CURLOPT_MAIL_FROM:
+    result = setstropt(&data->set.str[STRING_MAIL_FROM],
+                       va_arg(param, char *));
+    break;
+
+  case CURLOPT_MAIL_RCPT:
+    result = setstropt(&data->set.str[STRING_MAIL_RCPT],
+                       va_arg(param, char *));
+    break;
+
   default:
     /* unknown tag and its companion, just ignore: */
     result = CURLE_FAILED_INIT; /* correct this */
@@ -3334,6 +3366,8 @@ static CURLcode ParseURLAndFillConnection(struct SessionHandle *data,
         strcpy(conn->protostr, "DICT");
       else if(checkprefix("LDAP.", conn->host.name))
         strcpy(conn->protostr, "LDAP");
+      else if(checkprefix("IMAP.", conn->host.name))
+        strcpy(conn->protostr, "IMAP");
       else {
         strcpy(conn->protostr, "http");
       }
@@ -4069,7 +4103,7 @@ static CURLcode set_userpass(struct connectdata *conn,
                              const char *user, const char *passwd)
 {
   /* If our protocol needs a password and we have none, use the defaults */
-  if( (conn->protocol & PROT_FTP) &&
+  if( (conn->protocol & (PROT_FTP|PROT_IMAP)) &&
        !conn->bits.user_passwd) {
 
     conn->user = strdup(CURL_DEFAULT_USER);
index daf6d0e..4da4ed9 100644 (file)
 #define PORT_LDAPS 636
 #define PORT_TFTP 69
 #define PORT_SSH 22
+#define PORT_IMAP 143
+#define PORT_IMAPS 993
+#define PORT_POP3 110
+#define PORT_POP3S 995
+#define PORT_SMTP 25
+#define PORT_SMTPS 465 /* sometimes called SSMTP */
 
 #define DICT_MATCH "/MATCH:"
 #define DICT_MATCH2 "/M:"
 /* length of longest IPv6 address string including the trailing null */
 #define MAX_IPADR_LEN sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")
 
+/* Default FTP/IMAP etc response timeout in milliseconds.
+   Symbian OS panics when given a timeout much greater than 1/2 hour.
+*/
+#define RESP_TIMEOUT (1800*1000)
+
 #include "cookie.h"
 #include "formdata.h"
 
 #include "hash.h"
 #include "splay.h"
 
+#include "imap.h"
+#include "pop3.h"
+#include "smtp.h"
+#include "ftp.h"
+
 #ifdef HAVE_GSSAPI
 # ifdef HAVE_GSSGNU
 #  include <gss.h>
@@ -351,121 +367,6 @@ struct HTTP {
                         points to an allocated send_buffer struct */
 };
 
-/****************************************************************************
- * FTP unique setup
- ***************************************************************************/
-typedef enum {
-  FTP_STOP,    /* do nothing state, stops the state machine */
-  FTP_WAIT220, /* waiting for the initial 220 response immediately after
-                  a connect */
-  FTP_AUTH,
-  FTP_USER,
-  FTP_PASS,
-  FTP_ACCT,
-  FTP_PBSZ,
-  FTP_PROT,
-  FTP_CCC,
-  FTP_PWD,
-  FTP_SYST,
-  FTP_NAMEFMT,
-  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;
-
-typedef enum {
-  FTPFILE_MULTICWD  = 1, /* as defined by RFC1738 */
-  FTPFILE_NOCWD     = 2, /* use SIZE / RETR / STOR on the full path */
-  FTPFILE_SINGLECWD = 3  /* make one CWD, then SIZE / RETR / STOR on the file */
-} curl_ftpfile;
-
-typedef enum {
-  FTPTRANSFER_BODY, /* yes do transfer a body */
-  FTPTRANSFER_INFO, /* do still go through to get info/headers */
-  FTPTRANSFER_NONE, /* don't get anything and don't get info */
-  FTPTRANSFER_LAST  /* end of list marker, never used */
-} curl_ftptransfer;
-
-/* This FTP struct is used in the SessionHandle. All FTP data that is
-   connection-oriented must be in FTP_conn to properly deal with the fact that
-   perhaps the SessionHandle is changed between the times the connection is
-   used. */
-struct FTP {
-  curl_off_t *bytecountp;
-  char *user;    /* user name string */
-  char *passwd;  /* password string */
-
-  /* transfer a file/body or not, done as a typedefed enum just to make
-     debuggers display the full symbol and not just the numerical value */
-  curl_ftptransfer transfer;
-  curl_off_t downloadsize;
-};
-
-/* ftp_conn is used for struct connection-oriented data in the connectdata
-   struct */
-struct ftp_conn {
-  char *entrypath; /* the PWD reply when we logged on */
-  char **dirs;   /* realloc()ed array for path components */
-  int dirdepth;  /* number of entries used in the 'dirs' array */
-  int diralloc;  /* number of entries allocated for the 'dirs' array */
-  char *file;    /* decoded file */
-  char *cache;       /* data cache between getresponse()-calls */
-  curl_off_t cache_size; /* size of cache in bytes */
-  bool dont_check;  /* Set to TRUE to prevent the final (post-transfer)
-                       file size and 226/250 status check. It should still
-                       read the line, just ignore the result. */
-  long response_time; /* When no timeout is given, this is the amount of
-                         seconds we await for an FTP response. Initialized
-                         in Curl_ftp_connect() */
-  bool ctl_valid;   /* Tells Curl_ftp_quit() whether or not to do anything. If
-                       the connection has timed out or been closed, this
-                       should be FALSE when it gets to Curl_ftp_quit() */
-  bool cwddone;     /* if it has been determined that the proper CWD combo
-                       already has been done */
-  bool cwdfail;     /* set TRUE if a CWD command fails, as then we must prevent
-                       caching the current directory */
-  char *prevpath;   /* conn->path from the previous transfer */
-  char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a
-                        and others (A/I or zero) */
-  size_t nread_resp; /* number of bytes currently read of a server response */
-  char *linestart_resp; /* line start pointer for the FTP server response
-                           reader function */
-  bool pending_resp;  /* set TRUE when a server response is pending or in
-                         progress, and is cleared once the last response is
-                         read */
-
-  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! */
-  char * server_os;     /* The target server operating system. */
-};
 
 /****************************************************************************
  * SSH unique setup
@@ -922,18 +823,23 @@ struct connectdata {
 #define PROT_TFTP    CURLPROTO_TFTP
 #define PROT_SCP     CURLPROTO_SCP
 #define PROT_SFTP    CURLPROTO_SFTP
+#define PROT_IMAP    CURLPROTO_IMAP
+#define PROT_IMAPS   CURLPROTO_IMAPS
+#define PROT_POP3    CURLPROTO_POP3
+#define PROT_POP3S   CURLPROTO_POP3S
+#define PROT_SMTP    CURLPROTO_SMTP
+#define PROT_SMTPS   CURLPROTO_SMTPS
 
-/* CURLPROTO_TFTP (1<<11) is currently the highest used bit in the public
-   bitmask. We make sure we use "private bits" above the first 16 to make
-   things easier. */
+/* (1<<17) is currently the highest used bit in the public bitmask. We make
+   sure we use "private bits" above the public ones to make things easier. */
 
-#define PROT_EXTMASK 0xffff
+#define PROT_EXTMASK 0xfffff
 
-#define PROT_SSL     (1<<22) /* protocol requires SSL */
-#define PROT_MISSING (1<<23)
+#define PROT_SSL     (1<<25) /* protocol requires SSL */
+#define PROT_MISSING (1<<26)
 
 /* these ones need action before socket close */
-#define PROT_CLOSEACTION (PROT_FTP | PROT_TFTP
+#define PROT_CLOSEACTION (PROT_FTP | PROT_TFTP | PROT_IMAP | PROT_POP3)
 #define PROT_DUALCHANNEL PROT_FTP /* these protocols use two connections */
 
   /* 'dns_entry' is the particular host we use. This points to an entry in the
@@ -1035,7 +941,7 @@ struct connectdata {
   struct curl_llist *pend_pipe; /* List of pending handles on
                                    this pipeline */
   struct curl_llist *done_pipe; /* Handles that are finished, but
-                                  still reference this connectdata */
+                                   still reference this connectdata */
 #define MAX_PIPELINE_LENGTH 5
 
   char* master_buffer; /* The master buffer allocated on-demand;
@@ -1075,6 +981,9 @@ struct connectdata {
     struct ftp_conn ftpc;
     struct ssh_conn sshc;
     struct tftp_state_data *tftpc;
+    struct imap_conn imapc;
+    struct pop3_conn pop3c;
+    struct smtp_conn smtpc;
   } proto;
 
   int cselect_bits; /* bitmask of socket events */
@@ -1331,6 +1240,9 @@ struct UrlState {
     void *telnet;        /* private for telnet.c-eyes only */
     void *generic;
     struct SSHPROTO *ssh;
+    struct FTP *imap;
+    struct FTP *pop3;
+    struct FTP *smtp;
   } proto;
   /* current user of this SessionHandle instance, or NULL */
   struct connectdata *current_conn;
@@ -1412,6 +1324,8 @@ enum dupstring {
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
   STRING_SOCKS5_GSSAPI_SERVICE,  /* GSSAPI service name */
 #endif
+  STRING_MAIL_FROM,
+  STRING_MAIL_RCPT,
 
   /* -- end of strings -- */
   STRING_LAST /* not used, just an end-of-list marker */
@@ -1471,7 +1385,7 @@ struct UserDefined {
   void *ioctl_client;   /* pointer to pass to the ioctl callback */
   long timeout;         /* in milliseconds, 0 means no timeout */
   long connecttimeout;  /* in milliseconds, 0 means no timeout */
-  long ftp_response_timeout; /* in milliseconds, 0 means no timeout */
+  long server_response_timeout; /* in milliseconds, 0 means no timeout */
   long tftp_blksize ; /* in bytes, 0 means use default */
   curl_off_t infilesize;      /* size of file to upload, -1 means unknown */
   long low_speed_limit; /* bytes/second */
@@ -1555,7 +1469,8 @@ struct UserDefined {
   bool ftp_use_epsv;     /* if EPSV is to be attempted or not */
   bool ftp_use_eprt;     /* if EPRT is to be attempted or not */
 
-  curl_usessl ftp_ssl;   /* if AUTH TLS is to be attempted etc */
+  curl_usessl ftp_ssl;   /* if AUTH TLS is to be attempted etc, for FTP or
+                            IMAP or POP3 or others! */
   curl_ftpauth ftpsslauth; /* what AUTH XXX to be attempted */
   curl_ftpccc ftp_ccc;   /* FTP CCC options */
   bool no_signal;        /* do not use any signal/alarm handler */
index 2bcec00..19eb3d4 100644 (file)
@@ -158,6 +158,27 @@ static const char * const protocols[] = {
   "sftp",
 #endif
 
+#ifndef CURL_DISABLE_IMAP
+  "imap",
+#ifdef USE_SSL
+  "imaps",
+#endif
+#endif
+
+#ifndef CURL_DISABLE_POP3
+  "pop3",
+#ifdef USE_SSL
+  "pop3s",
+#endif
+#endif
+
+#ifndef CURL_DISABLE_SMTP
+  "smtp",
+#ifdef USE_SSL
+  "smtps",
+#endif
+#endif
+
   NULL
 };
 
index 47e25fb..f7c2eb4 100644 (file)
@@ -500,6 +500,8 @@ struct Configurable {
   char *proxy;
   int proxyver;     /* set to CURLPROXY_HTTP* define */
   char *noproxy;
+  char *mail_from;
+  char *mail_rcpt;
   bool proxytunnel;
   bool ftp_append;         /* APPE on ftp */
   bool mute;               /* shutup */
@@ -822,6 +824,8 @@ static void help(void)
     " -L/--location      Follow Location: hints (H)",
     "    --location-trusted Follow Location: and send auth to other hosts (H)",
     " -M/--manual        Display the full manual",
+    "    --mail-from <from> Mail from this address",
+    "    --mail-rcpt <to> Mail to this receiver(s)",
     "    --max-filesize <bytes> Maximum file size to download (H/F)",
     "    --max-redirs <num> Maximum number of redirects allowed (H)",
     " -m/--max-time <seconds> Maximum time allowed for the transfer",
@@ -1740,6 +1744,8 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
 #endif
     {"$8", "proxy1.0",   TRUE},
     {"$9", "tftp-blksize", TRUE},
+    {"$A", "mail-from", TRUE},
+    {"$B", "mail-rcpt", TRUE},
     {"0", "http1.0",     FALSE},
     {"1", "tlsv1",       FALSE},
     {"2", "sslv2",       FALSE},
@@ -2269,6 +2275,12 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
       case '9': /* --tftp-blksize */
         str2num(&config->tftp_blksize, nextarg);
         break;
+      case 'A': /* --mail-from */
+        GetStr(&config->mail_from, nextarg);
+        break;
+      case 'B': /* --mail-rcpt */
+        GetStr(&config->mail_rcpt, nextarg);
+        break;
       }
       break;
     case '#': /* --progress-bar */
@@ -5006,9 +5018,16 @@ operate(struct Configurable *config, int argc, argv_item_t argv[])
         my_setopt(curl, CURLOPT_POSTREDIR, config->post301 |
                   (config->post302 ? CURL_REDIR_POST_302 : FALSE));
 
+        /* curl 7.20.0 */
         if(config->tftp_blksize)
           my_setopt(curl, CURLOPT_TFTP_BLKSIZE, config->tftp_blksize);
 
+        if(config->mail_from)
+          my_setopt_str(curl, CURLOPT_MAIL_FROM, config->mail_from);
+
+        if(config->mail_rcpt)
+          my_setopt_str(curl, CURLOPT_MAIL_RCPT, config->mail_rcpt);
+
         retry_numretries = config->req_retry;
 
         retrystart = cutil_tvnow();
index d6dc4f3..7aa5f6d 100644 (file)
@@ -63,7 +63,7 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46           \
  test1089 test1090 test1091 test1092 test1093 test1094 test1095 test1096   \
  test1097 test560 test561 test1098 test1099 test562 test563 test1100       \
  test564 test1101 test1102 test1103 test1104 test299 test310 test311       \
- test312 test1105 test565
+ test312 test1105 test565 test800
 
 filecheck:
        @mkdir test-place; \
diff --git a/tests/data/test800 b/tests/data/test800
new file mode 100644 (file)
index 0000000..3281b63
--- /dev/null
@@ -0,0 +1,47 @@
+<testcase>
+<info>
+<keywords>
+POP3
+RETR
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+From: me@somewhere
+To: fake@nowhere
+
+body
+
+--
+  yours sincerely
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+pop3
+</server>
+ <name>
+POP3 RETR
+ </name>
+ <command>
+pop3://%HOSTIP:%POP3PORT/800 -u user:secret
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+USER user\r
+PASS secret\r
+RETR 800\r
+QUIT\r
+</protocol>
+</verify>
+</testcase>
index 94b6554..d7c4d54 100644 (file)
 # $Id$
 ###########################################################################
 
-# This is the FTP server designed for the curl test suite.
+# This is a server designed for the curl test suite.
+#
+# In December 2009 we started remaking the server to support more protocols
+# that are similar in spirit. Like POP3, IMAP and SMTP in addition to the
+# FTP it already supported since a long time.
 #
 # It is meant to exercise curl, it is not meant to be a fully working
 # or even very standard compliant server.
@@ -88,6 +92,8 @@ my $pidfile = ".ftpd.pid"; # a default, use --pidfile
 my $SERVERLOGS_LOCK="log/serverlogs.lock"; # server logs advisor read lock
 my $serverlogslocked=0;
 
+my $proto="ftp";
+
 do {
     if($ARGV[0] eq "-v") {
         $verbose=1;
@@ -100,6 +106,11 @@ do {
         $ftpdnum=$ARGV[1];
         shift @ARGV;
     }
+    elsif($ARGV[0] eq "--proto") {
+        # ftp pop3 imap smtp
+        $proto=$ARGV[1];
+        shift @ARGV;
+    }
     elsif($ARGV[0] eq "--pidfile") {
         $pidfile=$ARGV[1];
         shift @ARGV;
@@ -115,23 +126,28 @@ do {
     }
     elsif($ARGV[0] eq "--addr") {
         $listenaddr = $ARGV[1];
-        $listenaddr =~ s/^\[(.*)\]$/\1/;
+        $listenaddr =~ s/^\[(.*)\]$/$1/;
         shift @ARGV;
     }
 } while(shift @ARGV);
 
+# a dedicated protocol has been selected, check that it's a fine one
+if($proto !~ /^(ftp|imap|pop3|smtp)\z/) {
+    die "unsupported protocol selected";
+}
+
 sub catch_zap {
     my $signame = shift;
+    print STDERR "ftpserver.pl received SIG$signame, exiting\n";
     ftpkillslaves(1);
-    unlink($pidfile);
     if($serverlogslocked) {
         $serverlogslocked = 0;
         clear_advisor_read_lock($SERVERLOGS_LOCK);
     }
-    exit;
+    die "Somebody sent me a SIG$signame";
 }
 $SIG{INT} = \&catch_zap;
-$SIG{TERM} = \&catch_zap;
+$SIG{KILL} = \&catch_zap;
 
 my $sfpid;
 
@@ -153,7 +169,6 @@ sub sysread_or_die {
         logmsg "Error: ftp$ftpdnum$ext sysread error: $!\n";
         kill(9, $sfpid);
         waitpid($sfpid, 0);
-        unlink($pidfile);
         if($serverlogslocked) {
             $serverlogslocked = 0;
             clear_advisor_read_lock($SERVERLOGS_LOCK);
@@ -167,7 +182,6 @@ sub sysread_or_die {
         logmsg "Error: ftp$ftpdnum$ext read zero\n";
         kill(9, $sfpid);
         waitpid($sfpid, 0);
-        unlink($pidfile);
         if($serverlogslocked) {
             $serverlogslocked = 0;
             clear_advisor_read_lock($SERVERLOGS_LOCK);
@@ -193,7 +207,6 @@ sub startsf {
         logmsg "Failed sockfilt command: $cmd\n";
         kill(9, $sfpid);
         waitpid($sfpid, 0);
-        unlink($pidfile);
         if($serverlogslocked) {
             $serverlogslocked = 0;
             clear_advisor_read_lock($SERVERLOGS_LOCK);
@@ -202,9 +215,13 @@ sub startsf {
     }
 }
 
+# remove the file here so that if startsf() fails, it is very noticeable 
+unlink($pidfile);
+
 startsf();
 
-logmsg sprintf("FTP server listens on port IPv%d/$port\n", $ipv6?6:4);
+logmsg sprintf("%s server listens on port IPv%d/$port\n", uc($proto),
+               $ipv6?6:4);
 open(PID, ">$pidfile");
 print PID $$."\n";
 close(PID);
@@ -273,41 +290,66 @@ sub senddata {
     }
 }
 
-# this text is shown before the function specified below is run
-my %displaytext = ('USER' => '331 We are happy you popped in!',
-                   'PASS' => '230 Welcome you silly person',
-                   'PORT' => '200 You said PORT - I say FINE',
-                   'TYPE' => '200 I modify TYPE as you wanted',
-                   'LIST' => '150 here comes a directory',
-                   'NLST' => '150 here comes a directory',
-                   'CWD'  => '250 CWD command successful.',
-                   'SYST' => '215 UNIX Type: L8', # just fake something
-                   'QUIT' => '221 bye bye baby', # just reply something
-                   'PWD'  => '257 "/nowhere/anywhere" is current directory',
-                   'MKD'  => '257 Created your requested directory',
-                   'REST' => '350 Yeah yeah we set it there for you',
-                   'DELE' => '200 OK OK OK whatever you say',
-                   'RNFR' => '350 Received your order. Please provide more',
-                   'RNTO' => '250 Ok, thanks. File renaming completed.',
-                   'NOOP' => '200 Yes, I\'m very good at doing nothing.',
-                   'PBSZ' => '500 PBSZ not implemented',
-                   'PROT' => '500 PROT not implemented',
-                   );
+my %displaytext;
+my %commandfunc;
 
 # callback functions for certain commands
-my %commandfunc = ( 'PORT' => \&PORT_command,
-                    'EPRT' => \&PORT_command,
-                    'LIST' => \&LIST_command,
-                    'NLST' => \&NLST_command,
-                    'PASV' => \&PASV_command,
-                    'EPSV' => \&PASV_command,
-                    'RETR' => \&RETR_command,   
-                    'SIZE' => \&SIZE_command,
-                    'REST' => \&REST_command,
-                    'STOR' => \&STOR_command,
-                    'APPE' => \&STOR_command, # append looks like upload
-                    'MDTM' => \&MDTM_command,
-                    );
+# and text shown before the function specified below is run
+
+if($proto eq "ftp") {
+    %displaytext = ('USER' => '331 We are happy you popped in!',
+                    'PASS' => '230 Welcome you silly person',
+                    'PORT' => '200 You said PORT - I say FINE',
+                    'TYPE' => '200 I modify TYPE as you wanted',
+                    'LIST' => '150 here comes a directory',
+                    'NLST' => '150 here comes a directory',
+                    'CWD'  => '250 CWD command successful.',
+                    'SYST' => '215 UNIX Type: L8', # just fake something
+                    'QUIT' => '221 bye bye baby', # just reply something
+                    'PWD'  => '257 "/nowhere/anywhere" is current directory',
+                    'MKD'  => '257 Created your requested directory',
+                    'REST' => '350 Yeah yeah we set it there for you',
+                    'DELE' => '200 OK OK OK whatever you say',
+                    'RNFR' => '350 Received your order. Please provide more',
+                    'RNTO' => '250 Ok, thanks. File renaming completed.',
+                    'NOOP' => '200 Yes, I\'m very good at doing nothing.',
+                    'PBSZ' => '500 PBSZ not implemented',
+                    'PROT' => '500 PROT not implemented',
+        );
+
+    %commandfunc = ( 'PORT' => \&PORT_command,
+                     'EPRT' => \&PORT_command,
+                     'LIST' => \&LIST_command,
+                     'NLST' => \&NLST_command,
+                     'PASV' => \&PASV_command,
+                     'EPSV' => \&PASV_command,
+                     'RETR' => \&RETR_command,   
+                     'SIZE' => \&SIZE_command,
+                     'REST' => \&REST_command,
+                     'STOR' => \&STOR_command,
+                     'APPE' => \&STOR_command, # append looks like upload
+                     'MDTM' => \&MDTM_command,
+        );
+}
+elsif($proto eq "pop3") {
+    %commandfunc = ('RETR' => \&RETR_pop3,
+        );
+
+    %displaytext = ('USER' => '+OK We are happy you popped in!',
+                    'PASS' => '+OK Access granted',
+                    'QUIT' => '+OK byebye',
+        );
+
+}
+elsif($proto eq "imap") {
+    %commandfunc = ('FETCH' => \&FETCH_imap,
+        );
+
+    %displaytext = ('LOGIN' => ' OK We are happy you popped in!',
+                    'SELECT' => ' OK selection done',
+        );
+
+}
 
 
 sub close_dataconn {
@@ -330,6 +372,98 @@ sub close_dataconn {
     $slavepid=0;
 }
 
+################
+################ IMAP commands 
+################
+
+sub FETCH_imap {
+     my ($testno) = @_;
+     my @data;
+
+     if($testno =~ /^verifiedserver$/) {
+         # this is the secret command that verifies that this actually is
+         # the curl test server
+         my $response = "WE ROOLZ: $$\r\n";
+         if($verbose) {
+             print STDERR "FTPD: We returned proof we are the test server\n";
+         }
+         $data[0] = $response;
+         logmsg "return proof we are we\n";
+     }
+     else {
+         logmsg "retrieve a mail\n";
+
+         $testno =~ s/^([^0-9]*)//;
+         my $testpart = "";
+         if ($testno > 10000) {
+             $testpart = $testno % 10000;
+             $testno = int($testno / 10000);
+         }
+
+         # send mail content
+         loadtest("$srcdir/data/test$testno");
+
+         @data = getpart("reply", "data$testpart");
+     }
+
+     sendcontrol "- OK Mail transfer starts\r\n";
+
+     for my $d (@data) {
+         sendcontrol $d;
+     }
+    
+     return 0;
+}
+
+################
+################ POP3 commands 
+################
+
+sub RETR_pop3 {
+     my ($testno) = @_;
+     my @data;
+
+     if($testno =~ /^verifiedserver$/) {
+         # this is the secret command that verifies that this actually is
+         # the curl test server
+         my $response = "WE ROOLZ: $$\r\n";
+         if($verbose) {
+             print STDERR "FTPD: We returned proof we are the test server\n";
+         }
+         $data[0] = $response;
+         logmsg "return proof we are we\n";
+     }
+     else {
+         logmsg "retrieve a mail\n";
+
+         $testno =~ s/^([^0-9]*)//;
+         my $testpart = "";
+         if ($testno > 10000) {
+             $testpart = $testno % 10000;
+             $testno = int($testno / 10000);
+         }
+
+         # send mail content
+         loadtest("$srcdir/data/test$testno");
+
+         @data = getpart("reply", "data$testpart");
+     }
+
+     sendcontrol "+OK Mail transfer starts\r\n";
+
+     for my $d (@data) {
+         sendcontrol $d;
+     }
+
+     # end with the magic 5-byte end of mail marker
+     sendcontrol "\r\n.\r\n";
+    
+     return 0;
+}
+
+################
+################ FTP commands 
+################
 my $rest=0;
 sub REST_command {
     $rest = $_[0];
@@ -798,12 +932,34 @@ sub customize {
     close(CUSTOM);
 }
 
-my @welcome=(
-            '220-        _   _ ____  _     '."\r\n",
-            '220-    ___| | | |  _ \| |    '."\r\n",
-            '220-   / __| | | | |_) | |    '."\r\n",
-            '220-  | (__| |_| |  _ <| |___ '."\r\n",
-            '220    \___|\___/|_| \_\_____|'."\r\n");
+my @welcome;
+
+if($proto eq "ftp") {
+    @welcome=(
+        '220-        _   _ ____  _     '."\r\n",
+        '220-    ___| | | |  _ \| |    '."\r\n",
+        '220-   / __| | | | |_) | |    '."\r\n",
+        '220-  | (__| |_| |  _ <| |___ '."\r\n",
+        '220    \___|\___/|_| \_\_____|'."\r\n");
+}
+elsif($proto eq "pop3") {
+    @welcome=(
+        '        _   _ ____  _     '."\r\n",
+        '    ___| | | |  _ \| |    '."\r\n",
+        '   / __| | | | |_) | |    '."\r\n",
+        '  | (__| |_| |  _ <| |___ '."\r\n",
+        '   \___|\___/|_| \_\_____|'."\r\n",
+        '+OK cURL POP3 server ready to serve'."\r\n");
+}
+elsif($proto eq "imap") {
+    @welcome=(
+        '        _   _ ____  _     '."\r\n",
+        '    ___| | | |  _ \| |    '."\r\n",
+        '   / __| | | | |_) | |    '."\r\n",
+        '  | (__| |_| |  _ <| |___ '."\r\n",
+        '   \___|\___/|_| \_\_____|'."\r\n",
+        '* OK cURL IMAP server ready to serve'."\r\n");
+}
 
 
 while(1) {
@@ -872,13 +1028,28 @@ while(1) {
         # Remove trailing CRLF.
         s/[\n\r]+$//;
 
-        unless (m/^([A-Z]{3,4})\s?(.*)/i) {
-            sendcontrol "500 '$_': command not understood.\r\n";
-            last;
-        }
-        my $FTPCMD=$1;
-        my $FTPARG=$2;
+        my $cmdid;
+        my $FTPCMD;
+        my $FTPARG;
         my $full=$_;
+        if($proto eq "imap") {
+            # IMAP is different with its identifier first on the command line
+            unless (m/^([^ ]+) ([^ ]+) (.*)/i) {
+                sendcontrol "500 '$_': command not understood.\r\n";
+                last;
+            }
+            $cmdid=$1;
+            $FTPCMD=$2;
+            $FTPARG=$3;
+        }
+        else {
+            unless (m/^([A-Z]{3,4})\s?(.*)/i) {
+                sendcontrol "500 '$_': command not understood.\r\n";
+                last;
+            }
+            $FTPCMD=$1;
+            $FTPARG=$2;
+        }
                  
         logmsg "< \"$full\"\n";
 
@@ -907,7 +1078,7 @@ while(1) {
         }
         my $check;
         if($text) {
-            sendcontrol "$text\r\n";
+            sendcontrol "$cmdid$text\r\n";
         }
         else {
             $check=1; # no response yet
@@ -939,8 +1110,6 @@ while(1) {
 print SFWRITE "QUIT\n";
 waitpid $sfpid, 0;
 
-unlink($pidfile);
-
 if($serverlogslocked) {
     $serverlogslocked = 0;
     clear_advisor_read_lock($SERVERLOGS_LOCK);
index 834c866..94552d8 100755 (executable)
@@ -109,6 +109,9 @@ my $TFTPPORT; # TFTP
 my $TFTP6PORT; # TFTP
 my $SSHPORT; # SCP/SFTP
 my $SOCKSPORT; # SOCKS4/5 port
+my $POP3PORT; # POP3
+my $IMAPPORT; # IMAP
+my $SMTPPORT; # SMTP
 
 my $srcdir = $ENV{'srcdir'} || '.';
 my $CURL="../src/curl"; # what curl executable to run on the tests
@@ -147,6 +150,9 @@ my $TFTPPIDFILE=".tftpd.pid";
 my $TFTP6PIDFILE=".tftp6.pid";
 my $SSHPIDFILE=".ssh.pid";
 my $SOCKSPIDFILE=".socks.pid";
+my $POP3PIDFILE=".pop3.pid";
+my $IMAPPIDFILE=".imap.pid";
+my $SMTPPIDFILE=".smtp.pid";
 
 # invoke perl like this:
 my $perl="perl -I$srcdir";
@@ -663,7 +669,7 @@ sub verifyftp {
     }
     if($pid <= 0 && $data[0]) {
         # this is not a known server
-        logmsg "RUN: Unknown server on our FTP port: $port\n";
+        logmsg "RUN: Unknown server on our $proto port: $port\n";
         return 0;
     }
     # we can/should use the time it took to verify the FTP server as a measure
@@ -671,7 +677,7 @@ sub verifyftp {
     my $took = time()-$time;
 
     if($verbose) {
-        logmsg "RUN: Verifying our test FTP server took $took seconds\n";
+        logmsg "RUN: Verifying our test $proto server took $took seconds\n";
     }
     $ftpchecktime = $took?$took:1; # make sure it never is zero
 
@@ -773,6 +779,9 @@ sub verifysocks {
 my %protofunc = ('http' => \&verifyhttp,
                  'https' => \&verifyhttp,
                  'ftp' => \&verifyftp,
+                 'pop3' => \&verifyftp,
+                 'imap' => \&verifyftp,
+                 'smtp' => \&verifyftp,
                  'ftps' => \&verifyftp,
                  'tftp' => \&verifyftp,
                  'ssh' => \&verifyssh,
@@ -942,26 +951,48 @@ sub runhttpsserver {
 }
 
 #######################################################################
-# start the ftp server
+# start the pingpong server (FTP, POP3, IMAP, SMTP)
 #
-sub runftpserver {
-    my ($id, $verbose, $ipv6) = @_;
+sub runpingpongserver {
+    my ($proto, $id, $verbose, $ipv6) = @_;
     my $STATUS;
     my $RUNNING;
-    my $port = $id?$FTP2PORT:$FTPPORT;
-    # check for pidfile
-    my $pidfile = $id?$FTP2PIDFILE:$FTPPIDFILE;
+    my $port;
+    my $pidfile;
     my $ip=$HOSTIP;
     my $nameext;
     my $cmd;
+    my $flag;
 
-    if($ipv6) {
-        # if IPv6, use a different setup
-        $pidfile = $FTP6PIDFILE;
-        $port = $FTP6PORT;
-        $ip = $HOST6IP;
-        $nameext="-ipv6";
+    if($proto eq "ftp") {
+        $port = $id?$FTP2PORT:$FTPPORT;
+        $pidfile = $id?$FTP2PIDFILE:$FTPPIDFILE;
+
+        if($ipv6) {
+            # if IPv6, use a different setup
+            $pidfile = $FTP6PIDFILE;
+            $port = $FTP6PORT;
+            $ip = $HOST6IP;
+            $nameext="-ipv6";
+        }
+    }
+    elsif($proto eq "pop3") {
+        $port = $POP3PORT;
+        $pidfile = $POP3PIDFILE;
+    }
+    elsif($proto eq "imap") {
+        $port = $IMAPPORT;
+        $pidfile = $IMAPPIDFILE;
     }
+    elsif($proto eq "smtp") {
+        $port = $SMTPPORT;
+        $pidfile = $SMTPPIDFILE;
+    }
+    else {
+        print STDERR "Unsupported protocol $proto!!\n";
+        return 0;
+    }
+    $flag .= "--proto $proto ";
 
     # don't retry if the server doesn't work
     if ($doesntrun{$pidfile}) {
@@ -975,7 +1006,7 @@ sub runftpserver {
     unlink($pidfile);
 
     # start our server:
-    my $flag=$debugprotocol?"-v ":"";
+    $flag.=$debugprotocol?"-v ":"";
     $flag .= "-s \"$srcdir\" ";
     my $addr;
     if($id) {
@@ -993,7 +1024,7 @@ sub runftpserver {
 
     if($ftppid <= 0 || !kill(0, $ftppid)) {
         # it is NOT alive
-        logmsg "RUN: failed to start the FTP$id$nameext server\n";
+        logmsg "RUN: failed to start the $proto$id$nameext server\n";
         stopserver("$pid2");
         displaylogs($testnumcheck);
         $doesntrun{$pidfile} = 1;
@@ -1001,9 +1032,9 @@ sub runftpserver {
     }
 
     # Server is up. Verify that we can speak to it.
-    my $pid3 = verifyserver("ftp", $ip, $port);
+    my $pid3 = verifyserver($proto, $ip, $port);
     if(!$pid3) {
-        logmsg "RUN: FTP$id$nameext server failed verification\n";
+        logmsg "RUN: $proto$id$nameext server failed verification\n";
         # failed to talk to it properly. Kill the server and return failure
         stopserver("$ftppid $pid2");
         displaylogs($testnumcheck);
@@ -1013,7 +1044,7 @@ sub runftpserver {
     $pid2 = $pid3;
 
     if($verbose) {
-        logmsg "RUN: FTP$id$nameext server is now running PID $ftppid\n";
+        logmsg "RUN: $proto$id$nameext server is now running PID $ftppid\n";
     }
 
     sleep(1);
@@ -1661,41 +1692,46 @@ sub checksystem {
     "* Host: $hostname",
     "* System: $hosttype");
 
-    logmsg sprintf("* Server SSL:     %s\n", $stunnel?"ON":"OFF");
-    logmsg sprintf("* libcurl SSL:    %s\n", $ssl_version?"ON":"OFF");
-    logmsg sprintf("* debug build:    %s\n", $debug_build?"ON":"OFF");
-    logmsg sprintf("* track memory:   %s\n", $curl_debug?"ON":"OFF");
-    logmsg sprintf("* valgrind:       %s\n", $valgrind?"ON":"OFF");
-    logmsg sprintf("* HTTP IPv6       %s\n", $http_ipv6?"ON":"OFF");
-    logmsg sprintf("* FTP IPv6        %s\n", $ftp_ipv6?"ON":"OFF");
-
-    logmsg sprintf("* HTTP port:      %d\n", $HTTPPORT);
-    logmsg sprintf("* FTP port:       %d\n", $FTPPORT);
-    logmsg sprintf("* FTP port 2:     %d\n", $FTP2PORT);
+    logmsg sprintf("* Server SSL:   %8s", $stunnel?"ON ":"OFF");
+    logmsg sprintf("  libcurl SSL:  %s\n", $ssl_version?"ON ":"OFF");
+    logmsg sprintf("* debug build:  %8s", $debug_build?"ON ":"OFF");
+    logmsg sprintf("  track memory: %s\n", $curl_debug?"ON ":"OFF");
+    logmsg sprintf("* valgrind:     %8s", $valgrind?"ON ":"OFF");
+    logmsg sprintf("  HTTP IPv6     %s\n", $http_ipv6?"ON ":"OFF");
+    logmsg sprintf("* FTP IPv6      %8s", $ftp_ipv6?"ON ":"OFF");
+    logmsg sprintf("  Libtool lib:  %s\n", $libtool?"ON ":"OFF");
+    if($ssl_version) {
+        logmsg sprintf("* SSL library:       %s\n", $ssllib);
+    }
+
+    logmsg "* Ports:\n";
+
+    logmsg sprintf("*   HTTP/%d ", $HTTPPORT);
+    logmsg sprintf("FTP/%d ", $FTPPORT);
+    logmsg sprintf("FTP2/%d ", $FTP2PORT);
     if($stunnel) {
-        logmsg sprintf("* FTPS port:      %d\n", $FTPSPORT);
-        logmsg sprintf("* HTTPS port:     %d\n", $HTTPSPORT);
+        logmsg sprintf("FTPS/%d ", $FTPSPORT);
+        logmsg sprintf("HTTPS/%d ", $HTTPSPORT);
     }
+    logmsg sprintf("\n*   TFTP/%d ", $TFTPPORT);
     if($http_ipv6) {
-        logmsg sprintf("* HTTP IPv6 port: %d\n", $HTTP6PORT);
+        logmsg sprintf("HTTP-IPv6/%d ", $HTTP6PORT);
     }
     if($ftp_ipv6) {
-        logmsg sprintf("* FTP IPv6 port:  %d\n", $FTP6PORT);
+        logmsg sprintf("FTP-IPv6/%d ", $FTP6PORT);
     }
-    logmsg sprintf("* TFTP port:      %d\n", $TFTPPORT);
     if($tftp_ipv6) {
-        logmsg sprintf("* TFTP IPv6 port: %d\n", $TFTP6PORT);
+        logmsg sprintf("TFTP-IPv6/%d ", $TFTP6PORT);
     }
-    logmsg sprintf("* SCP/SFTP port:  %d\n", $SSHPORT);
-    logmsg sprintf("* SOCKS port:     %d\n", $SOCKSPORT);
+    logmsg sprintf("\n*   SSH/%d ", $SSHPORT);
+    logmsg sprintf("SOCKS/%d ", $SOCKSPORT);
+    logmsg sprintf("POP3/%d ", $POP3PORT);
+    logmsg sprintf("IMAP/%d ", $IMAPPORT);
+    logmsg sprintf("SMTP/%d\n", $SMTPPORT);
 
-    if($ssl_version) {
-        logmsg sprintf("* SSL library:    %s\n", $ssllib);
-    }
 
     $has_textaware = ($^O eq 'MSWin32') || ($^O eq 'msys');
 
-    logmsg sprintf("* Libtool lib:    %s\n", $libtool?"ON":"OFF");
     logmsg "***************************************** \n";
 }
 
@@ -1720,6 +1756,9 @@ sub subVariables {
   $$thing =~ s/%TFTP6PORT/$TFTP6PORT/g;
   $$thing =~ s/%SSHPORT/$SSHPORT/g;
   $$thing =~ s/%SOCKSPORT/$SOCKSPORT/g;
+  $$thing =~ s/%POP3PORT/$POP3PORT/g;
+  $$thing =~ s/%IMAPPORT/$IMAPPORT/g;
+  $$thing =~ s/%SMTPPORT/$SMTPPORT/g;
   $$thing =~ s/%CURL/$CURL/g;
   $$thing =~ s/%USER/$USER/g;
   $$thing =~ s/%CLIENTIP/$CLIENTIP/g;
@@ -2546,19 +2585,22 @@ sub startservers {
         my $what = lc($whatlist[0]);
         $what =~ s/[^a-z0-9-]//g;
 
-        if($what eq "ftp") {
-            if(!$run{'ftp'}) {
-                ($pid, $pid2) = runftpserver("", $verbose);
+        if(($what eq "pop3") ||
+           ($what eq "ftp") ||
+           ($what eq "imap") ||
+           ($what eq "smtp")) {
+            if(!$run{$what}) {
+                ($pid, $pid2) = runpingpongserver($what, "", $verbose);
                 if($pid <= 0) {
-                    return "failed starting FTP server";
+                    return "failed starting $what server";
                 }
-                printf ("* pid ftp => %d %d\n", $pid, $pid2) if($verbose);
-                $run{'ftp'}="$pid $pid2";
+                printf ("* pid $what => %d %d\n", $pid, $pid2) if($verbose);
+                $run{$what}="$pid $pid2";
             }
         }
         elsif($what eq "ftp2") {
             if(!$run{'ftp2'}) {
-                ($pid, $pid2) = runftpserver("2", $verbose);
+                ($pid, $pid2) = runpingpongserver("ftp", "2", $verbose);
                 if($pid <= 0) {
                     return "failed starting FTP2 server";
                 }
@@ -2568,7 +2610,7 @@ sub startservers {
         }
         elsif($what eq "ftp-ipv6") {
             if(!$run{'ftp-ipv6'}) {
-                ($pid, $pid2) = runftpserver("", $verbose, "ipv6");
+                ($pid, $pid2) = runpingpongserver("ftp", "", $verbose, "ipv6");
                 if($pid <= 0) {
                     return "failed starting FTP-IPv6 server";
                 }
@@ -2609,7 +2651,7 @@ sub startservers {
             }
 
             if(!$run{'ftp'}) {
-                ($pid, $pid2) = runftpserver("", $verbose);
+                ($pid, $pid2) = runpingpongserver("ftp", "", $verbose);
                 if($pid <= 0) {
                     return "failed starting FTP server";
                 }
@@ -2939,18 +2981,21 @@ if ($gdbthis) {
     }
 }
 
-$HTTPPORT =  $base + 0; # HTTP server port
-$HTTPSPORT = $base + 1; # HTTPS server port
-$FTPPORT =   $base + 2; # FTP server port
-$FTPSPORT =  $base + 3; # FTPS server port
-$HTTP6PORT = $base + 4; # HTTP IPv6 server port (different IP protocol
+$HTTPPORT =  $base++; # HTTP server port
+$HTTPSPORT = $base++; # HTTPS server port
+$FTPPORT =   $base++; # FTP server port
+$FTPSPORT =  $base++; # FTPS server port
+$HTTP6PORT = $base++; # HTTP IPv6 server port (different IP protocol
                         # but we follow the same port scheme anyway)
-$FTP2PORT =  $base + 5; # FTP server 2 port
-$FTP6PORT =  $base + 6; # FTP IPv6 port
-$TFTPPORT =  $base + 7; # TFTP (UDP) port
-$TFTP6PORT =  $base + 8; # TFTP IPv6 (UDP) port
-$SSHPORT =   $base + 9; # SSH (SCP/SFTP) port
-$SOCKSPORT =   $base + 10; # SOCKS port
+$FTP2PORT =  $base++; # FTP server 2 port
+$FTP6PORT =  $base++; # FTP IPv6 port
+$TFTPPORT =  $base++; # TFTP (UDP) port
+$TFTP6PORT = $base++; # TFTP IPv6 (UDP) port
+$SSHPORT =   $base++; # SSH (SCP/SFTP) port
+$SOCKSPORT = $base++; # SOCKS port
+$POP3PORT =  $base++;
+$IMAPPORT =  $base++;
+$SMTPPORT =  $base++;
 
 #######################################################################
 # clear and create logging directory: