aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2009-12-12 21:54:01 +0000
committerDaniel Stenberg <daniel@haxx.se>2009-12-12 21:54:01 +0000
commitec3bb8f727405642a471b4b1b9eb0118fc003104 (patch)
treecf1eac52d5df82ba08514b43b5d058803d164b66
parent463d2d395caaafc557590a09aa01db28527e560f (diff)
introducing IMAP, POP3 and SMTP support (still lots of polish left to do)
-rw-r--r--include/curl/curl.h13
-rw-r--r--lib/Makefile.am2
-rw-r--r--lib/Makefile.inc5
-rw-r--r--lib/README.pingpong30
-rw-r--r--lib/ftp.c595
-rw-r--r--lib/ftp.h106
-rw-r--r--lib/imap.c1026
-rw-r--r--lib/imap.h56
-rw-r--r--lib/pingpong.c536
-rw-r--r--lib/pingpong.h148
-rw-r--r--lib/pop3.c961
-rw-r--r--lib/pop3.h62
-rw-r--r--lib/smtp.c921
-rw-r--r--lib/smtp.h61
-rw-r--r--lib/transfer.c10
-rw-r--r--lib/url.c44
-rw-r--r--lib/urldata.h165
-rw-r--r--lib/version.c21
-rw-r--r--src/main.c19
-rw-r--r--tests/data/Makefile.am2
-rw-r--r--tests/data/test80047
-rw-r--r--tests/ftpserver.pl283
-rwxr-xr-xtests/runtests.pl169
23 files changed, 4530 insertions, 752 deletions
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 279c1b602..f7c3f52ff 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -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;
diff --git a/lib/Makefile.am b/lib/Makefile.am
index bfc66d18c..e75aef462 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -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
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 1b619a350..ff9269e63 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -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
index 000000000..69ba9aadb
--- /dev/null
+++ b/lib/README.pingpong
@@ -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.
diff --git a/lib/ftp.c b/lib/ftp.c
index 000670fb4..331d17e9d 100644
--- a/lib/ftp.c
+++ b/lib/ftp.c
@@ -106,14 +106,6 @@
#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;
}
diff --git a/lib/ftp.h b/lib/ftp.h
index 13652acda..93581bfc9 100644
--- 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
index 000000000..fecba3c25
--- /dev/null
+++ b/lib/imap.c
@@ -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
index 000000000..75109d10c
--- /dev/null
+++ b/lib/imap.h
@@ -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
index 000000000..9de6e6f05
--- /dev/null
+++ b/lib/pingpong.c
@@ -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
index 000000000..830aa89db
--- /dev/null
+++ b/lib/pingpong.h
@@ -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
index 000000000..cf4fd1c02
--- /dev/null
+++ b/lib/pop3.c
@@ -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
index 000000000..f47aa4edd
--- /dev/null
+++ b/lib/pop3.h
@@ -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
index 000000000..7f1b6fc18
--- /dev/null
+++ b/lib/smtp.c
@@ -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
index 000000000..199481a0b
--- /dev/null
+++ b/lib/smtp.h
@@ -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 */
diff --git a/lib/transfer.c b/lib/transfer.c
index a18ad8729..820d6816e 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -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;
diff --git a/lib/url.c b/lib/url.c
index edfa3edcc..95ca31632 100644
--- 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);
diff --git a/lib/urldata.h b/lib/urldata.h
index daf6d0e58..4da4ed957 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -37,6 +37,12 @@
#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:"
@@ -51,6 +57,11 @@
/* 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"
@@ -129,6 +140,11 @@
#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 */
diff --git a/lib/version.c b/lib/version.c
index 2bcec0092..19eb3d4aa 100644
--- a/lib/version.c
+++ b/lib/version.c
@@ -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
};
diff --git a/src/main.c b/src/main.c
index 47e25fb7e..f7c2eb43e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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();
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index d6dc4f38b..7aa5f6dca 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -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
index 000000000..3281b632d
--- /dev/null
+++ b/tests/data/test800
@@ -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
+PASS secret
+RETR 800
+QUIT
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl
index 94b6554b3..d7c4d54d9 100644
--- a/tests/ftpserver.pl
+++ b/tests/ftpserver.pl
@@ -22,7 +22,11 @@
# $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);
diff --git a/tests/runtests.pl b/tests/runtests.pl
index 834c86652..94552d86d 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -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: