From ec3bb8f727405642a471b4b1b9eb0118fc003104 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 12 Dec 2009 21:54:01 +0000 Subject: introducing IMAP, POP3 and SMTP support (still lots of polish left to do) --- include/curl/curl.h | 13 + lib/Makefile.am | 2 +- lib/Makefile.inc | 5 +- lib/README.pingpong | 30 ++ lib/ftp.c | 595 +++++----------------------- lib/ftp.h | 106 ++++- lib/imap.c | 1026 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/imap.h | 56 +++ lib/pingpong.c | 536 +++++++++++++++++++++++++ lib/pingpong.h | 148 +++++++ lib/pop3.c | 961 +++++++++++++++++++++++++++++++++++++++++++++ lib/pop3.h | 62 +++ lib/smtp.c | 921 +++++++++++++++++++++++++++++++++++++++++++ lib/smtp.h | 61 +++ lib/transfer.c | 10 +- lib/url.c | 44 ++- lib/urldata.h | 165 ++------ lib/version.c | 21 + src/main.c | 19 + tests/data/Makefile.am | 2 +- tests/data/test800 | 47 +++ tests/ftpserver.pl | 283 ++++++++++--- tests/runtests.pl | 169 +++++--- 23 files changed, 4530 insertions(+), 752 deletions(-) create mode 100644 lib/README.pingpong create mode 100644 lib/imap.c create mode 100644 lib/imap.h create mode 100644 lib/pingpong.c create mode 100644 lib/pingpong.h create mode 100644 lib/pop3.c create mode 100644 lib/pop3.h create mode 100644 lib/smtp.c create mode 100644 lib/smtp.h create mode 100644 tests/data/test800 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_respcache) { - /* 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; meowlinestart_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, , et al. + * Copyright (C) 1998 - 2007, 2009, Daniel Stenberg, , 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, , 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 +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef VMS +#include +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#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 + +#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, , 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, , 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 + +#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_respcache) { + /* 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; meowlinestart_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, , 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 + +#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, , 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 +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef VMS +#include +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#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 + +#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, , 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, , 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 +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef VMS +#include +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#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 + +#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, , 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 @@ -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 Mail from this address", + " --mail-rcpt Mail to this receiver(s)", " --max-filesize Maximum file size to download (H/F)", " --max-redirs Maximum number of redirects allowed (H)", " -m/--max-time 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 @@ + + + +POP3 +RETR + + + +# +# Server-side + + +From: me@somewhere +To: fake@nowhere + +body + +-- + yours sincerely + + + +# +# Client-side + + +pop3 + + +POP3 RETR + + +pop3://%HOSTIP:%POP3PORT/800 -u user:secret + + + +# +# Verify data after the test has been "shot" + + +USER user +PASS secret +RETR 800 +QUIT + + + 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: -- cgit v1.2.3