1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-22 08:08:50 -05:00

introducing IMAP, POP3 and SMTP support (still lots of polish left to do)

This commit is contained in:
Daniel Stenberg 2009-12-12 21:54:01 +00:00
parent 463d2d395c
commit ec3bb8f727
23 changed files with 4531 additions and 753 deletions

View File

@ -613,6 +613,12 @@ typedef enum {
#define CURLPROTO_DICT (1<<9) #define CURLPROTO_DICT (1<<9)
#define CURLPROTO_FILE (1<<10) #define CURLPROTO_FILE (1<<10)
#define CURLPROTO_TFTP (1<<11) #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 */ #define CURLPROTO_ALL (~0) /* enable everything */
/* long may be 32 or 64 bits, but we should never depend on anything else /* 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 essentially places a demand on the FTP server to acknowledge commands
in a timely manner. */ in a timely manner. */
CINIT(FTP_RESPONSE_TIMEOUT, LONG, 112), 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 /* 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 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 */ /* set the SSH host key callback custom pointer */
CINIT(SSH_KEYDATA, OBJECTPOINT, 185), 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 */ CURLOPT_LASTENTRY /* the last unused */
} CURLoption; } CURLoption;

View File

@ -27,7 +27,7 @@ VCPROJ = libcurl.vcproj
DOCS = README.encoding README.memoryleak README.ares README.curlx \ DOCS = README.encoding README.memoryleak README.ares README.curlx \
README.hostip README.multi_socket README.httpauth README.pipelining \ 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 CMAKE_DIST = CMakeLists.txt curl_config.h.cmake

View File

@ -11,7 +11,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
inet_ntop.c parsedate.c select.c gtls.c sslgen.c tftp.c splay.c \ 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 \ 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 \ 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 \ 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 \ 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 \ 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 \ 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_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

30
lib/README.pingpong Normal file
View File

@ -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.

595
lib/ftp.c

File diff suppressed because it is too large Load Diff

106
lib/ftp.h
View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al. * Copyright (C) 1998 - 2007, 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
* *
* This software is licensed as described in the file COPYING, which * This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms * you should have received as part of this distribution. The terms
@ -23,6 +23,8 @@
* $Id$ * $Id$
***************************************************************************/ ***************************************************************************/
#include "pingpong.h"
#ifndef CURL_DISABLE_FTP #ifndef CURL_DISABLE_FTP
extern const struct Curl_handler Curl_handler_ftp; extern const struct Curl_handler Curl_handler_ftp;
@ -39,8 +41,108 @@ extern const struct Curl_handler Curl_handler_ftps_proxy;
#endif #endif
CURLcode Curl_ftpsendf(struct connectdata *, const char *fmt, ...); 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, CURLcode Curl_GetFTPResponse(ssize_t *nread, struct connectdata *conn,
int *ftpcode); int *ftpcode);
#endif /* CURL_DISABLE_FTP */ #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 */ #endif /* __FTP_H */

1026
lib/imap.c Normal file

File diff suppressed because it is too large Load Diff

56
lib/imap.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef __IMAP_H
#define __IMAP_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* $Id$
***************************************************************************/
#include "pingpong.h"
/****************************************************************************
* IMAP unique setup
***************************************************************************/
typedef enum {
IMAP_STOP, /* do nothing state, stops the state machine */
IMAP_SERVERGREET, /* waiting for the initial greeting immediately after
a connect */
IMAP_LOGIN,
IMAP_STARTTLS,
IMAP_SELECT,
IMAP_FETCH,
IMAP_LOGOUT,
IMAP_LAST /* never used */
} imapstate;
/* imap_conn is used for struct connection-oriented data in the connectdata
struct */
struct imap_conn {
struct pingpong pp;
char *mailbox; /* what to FETCH */
imapstate state; /* always use imap.c:state() to change state! */
int cmdid; /* id number/index */
const char *idstr; /* pointer to a string for which to wait for as id */
};
extern const struct Curl_handler Curl_handler_imap;
extern const struct Curl_handler Curl_handler_imaps;
#endif /* __IMAP_H */

536
lib/pingpong.c Normal file
View File

@ -0,0 +1,536 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* 'pingpong' is for generic back-and-forth support functions used by FTP,
* IMAP, POP3, SMTP and whatever more that likes them.
*
* $Id$
***************************************************************************/
#include "setup.h"
#include "urldata.h"
#include "sendf.h"
#include "select.h"
#include "progress.h"
#include "speedcheck.h"
#include "pingpong.h"
#include "multiif.h"
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
#include "curl_memory.h"
/* The last #include file should be: */
#include "memdebug.h"
#ifdef USE_PINGPONG
/* Returns timeout in ms. 0 or negative number means the timeout has already
triggered */
long Curl_pp_state_timeout(struct pingpong *pp)
{
struct connectdata *conn = pp->conn;
struct SessionHandle *data=conn->data;
long timeout_ms=360000; /* in milliseconds */
if(data->set.server_response_timeout )
/* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine
remaining time. Also, use pp->response because SERVER_RESPONSE_TIMEOUT
is supposed to govern the response for any given server response, not
for the time from connect to the given server response. */
timeout_ms = data->set.server_response_timeout - /* timeout time */
Curl_tvdiff(Curl_tvnow(), pp->response); /* spent time */
else if(data->set.timeout)
/* if timeout is requested, find out how much remaining time we have */
timeout_ms = data->set.timeout - /* timeout time */
Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
else
/* Without a requested timeout, we only wait 'response_time' seconds for
the full response to arrive before we bail out */
timeout_ms = pp->response_time -
Curl_tvdiff(Curl_tvnow(), pp->response); /* spent time */
return timeout_ms;
}
/*
* Curl_pp_multi_statemach()
*
* called repeatedly until done when the multi interface is used.
*/
CURLcode Curl_pp_multi_statemach(struct pingpong *pp)
{
struct connectdata *conn = pp->conn;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
int rc;
struct SessionHandle *data=conn->data;
CURLcode result = CURLE_OK;
long timeout_ms = Curl_pp_state_timeout(pp);
if(timeout_ms <= 0) {
failf(data, "server response timeout");
return CURLE_OPERATION_TIMEDOUT;
}
rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
0);
if(rc == -1) {
failf(data, "select/poll error");
return CURLE_OUT_OF_MEMORY;
}
else if(rc != 0)
result = pp->statemach_act(conn);
/* if rc == 0, then select() timed out */
return result;
}
/*
* Curl_pp_easy_statemach()
*
* called repeatedly until done when the easy interface is used.
*/
CURLcode Curl_pp_easy_statemach(struct pingpong *pp)
{
struct connectdata *conn = pp->conn;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
int rc;
long interval_ms;
long timeout_ms = Curl_pp_state_timeout(pp);
struct SessionHandle *data=conn->data;
CURLcode result;
if(timeout_ms <=0 ) {
failf(data, "server response timeout");
return CURLE_OPERATION_TIMEDOUT; /* already too little time */
}
interval_ms = 1000; /* use 1 second timeout intervals */
if(timeout_ms < interval_ms)
interval_ms = timeout_ms;
rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
(int)interval_ms);
if(Curl_pgrsUpdate(conn))
result = CURLE_ABORTED_BY_CALLBACK;
else
result = Curl_speedcheck(data, Curl_tvnow());
if(result)
;
else if(rc == -1) {
failf(data, "select/poll error");
result = CURLE_OUT_OF_MEMORY;
}
else if(rc)
result = pp->statemach_act(conn);
return result;
}
/* initialize stuff to prepare for reading a fresh new response */
void Curl_pp_init(struct pingpong *pp)
{
struct connectdata *conn = pp->conn;
pp->nread_resp = 0;
pp->linestart_resp = conn->data->state.buffer;
pp->pending_resp = TRUE;
pp->response = Curl_tvnow(); /* start response time-out now! */
}
/***********************************************************************
*
* Curl_pp_sendfv()
*
* Send the formated string as a command to a pingpong server. Note that
* the string should not have any CRLF appended, as this function will
* append the necessary things itself.
*
* NOTE: we build the command in a fixed-length buffer, which sets length
* restrictions on the command!
*
* made to never block
*/
CURLcode Curl_pp_vsendf(struct pingpong *pp,
const char *fmt,
va_list args)
{
ssize_t bytes_written;
/* may still not be big enough for some krb5 tokens */
#define SBUF_SIZE 1024
char s[SBUF_SIZE];
size_t write_len;
char *sptr=s;
CURLcode res = CURLE_OK;
struct connectdata *conn = pp->conn;
struct SessionHandle *data = conn->data;
#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
enum protection_level data_sec = conn->data_prot;
#endif
vsnprintf(s, SBUF_SIZE-3, fmt, args);
strcat(s, "\r\n"); /* append a trailing CRLF */
bytes_written=0;
write_len = strlen(s);
Curl_pp_init(pp);
#ifdef CURL_DOES_CONVERSIONS
res = Curl_convert_to_network(data, s, write_len);
/* Curl_convert_to_network calls failf if unsuccessful */
if(res != CURLE_OK) {
return res;
}
#endif /* CURL_DOES_CONVERSIONS */
#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
conn->data_prot = prot_cmd;
#endif
res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len,
&bytes_written);
#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
conn->data_prot = data_sec;
#endif
if(CURLE_OK != res)
return res;
if(conn->data->set.verbose)
Curl_debug(conn->data, CURLINFO_HEADER_OUT,
sptr, (size_t)bytes_written, conn);
if(bytes_written != (ssize_t)write_len) {
/* the whole chunk was not sent, store the rest of the data */
write_len -= bytes_written;
sptr += bytes_written;
pp->sendthis = malloc(write_len);
if(pp->sendthis) {
memcpy(pp->sendthis, sptr, write_len);
pp->sendsize = pp->sendleft = write_len;
}
else {
failf(data, "out of memory");
res = CURLE_OUT_OF_MEMORY;
}
}
else
pp->response = Curl_tvnow();
return res;
}
/***********************************************************************
*
* Curl_pp_sendf()
*
* Send the formated string as a command to a pingpong server. Note that
* the string should not have any CRLF appended, as this function will
* append the necessary things itself.
*
* NOTE: we build the command in a fixed-length buffer, which sets length
* restrictions on the command!
*
* made to never block
*/
CURLcode Curl_pp_sendf(struct pingpong *pp,
const char *fmt, ...)
{
CURLcode res;
va_list ap;
va_start(ap, fmt);
res = Curl_pp_vsendf(pp, fmt, ap);
va_end(ap);
return res;
}
/*
* Curl_pp_readresp()
*
* Reads a piece of a server response.
*/
CURLcode Curl_pp_readresp(curl_socket_t sockfd,
struct pingpong *pp,
int *code, /* return the server code if done */
size_t *size) /* size of the response */
{
ssize_t perline; /* count bytes per line */
bool keepon=TRUE;
ssize_t gotbytes;
char *ptr;
struct connectdata *conn = pp->conn;
struct SessionHandle *data = conn->data;
char * const buf = data->state.buffer;
CURLcode result = CURLE_OK;
*code = 0; /* 0 for errors or not done */
*size = 0;
ptr=buf + pp->nread_resp;
/* number of bytes in the current line, so far */
perline = (ssize_t)(ptr-pp->linestart_resp);
keepon=TRUE;
while((pp->nread_resp<BUFSIZE) && (keepon && !result)) {
if(pp->cache) {
/* we had data in the "cache", copy that instead of doing an actual
* read
*
* ftp->cache_size is cast to int here. This should be safe,
* because it would have been populated with something of size
* int to begin with, even though its datatype may be larger
* than an int.
*/
DEBUGASSERT((ptr+pp->cache_size) <= (buf+BUFSIZE+1));
memcpy(ptr, pp->cache, pp->cache_size);
gotbytes = pp->cache_size;
free(pp->cache); /* free the cache */
pp->cache = NULL; /* clear the pointer */
pp->cache_size = 0; /* zero the size just in case */
}
else {
int res;
#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
enum protection_level prot = conn->data_prot;
conn->data_prot = 0;
#endif
DEBUGASSERT((ptr+BUFSIZE-pp->nread_resp) <= (buf+BUFSIZE+1));
res = Curl_read(conn, sockfd, ptr, BUFSIZE-pp->nread_resp,
&gotbytes);
#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
conn->data_prot = prot;
#endif
if(res < 0)
/* EWOULDBLOCK */
return CURLE_OK; /* return */
#ifdef CURL_DOES_CONVERSIONS
if((res == CURLE_OK) && (gotbytes > 0)) {
/* convert from the network encoding */
res = Curl_convert_from_network(data, ptr, gotbytes);
/* Curl_convert_from_network calls failf if unsuccessful */
}
#endif /* CURL_DOES_CONVERSIONS */
if(CURLE_OK != res) {
result = (CURLcode)res; /* Set outer result variable to this error. */
keepon = FALSE;
}
}
if(!keepon)
;
else if(gotbytes <= 0) {
keepon = FALSE;
result = CURLE_RECV_ERROR;
failf(data, "FTP response reading failed");
}
else {
/* we got a whole chunk of data, which can be anything from one
* byte to a set of lines and possible just a piece of the last
* line */
ssize_t i;
ssize_t clipamount = 0;
bool restart = FALSE;
data->req.headerbytecount += gotbytes;
pp->nread_resp += gotbytes;
for(i = 0; i < gotbytes; ptr++, i++) {
perline++;
if(*ptr=='\n') {
/* a newline is CRLF in ftp-talk, so the CR is ignored as
the line isn't really terminated until the LF comes */
/* output debug output if that is requested */
#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
if(!conn->sec_complete)
#endif
if(data->set.verbose)
Curl_debug(data, CURLINFO_HEADER_IN,
pp->linestart_resp, (size_t)perline, conn);
/*
* We pass all response-lines to the callback function registered
* for "headers". The response lines can be seen as a kind of
* headers.
*/
result = Curl_client_write(conn, CLIENTWRITE_HEADER,
pp->linestart_resp, perline);
if(result)
return result;
if(pp->endofresp(pp, code)) {
/* This is the end of the last line, copy the last line to the
start of the buffer and zero terminate, for old times sake (and
krb4)! */
char *meow;
int n;
for(meow=pp->linestart_resp, n=0; meow<ptr; meow++, n++)
buf[n] = *meow;
*meow=0; /* zero terminate */
keepon=FALSE;
pp->linestart_resp = ptr+1; /* advance pointer */
i++; /* skip this before getting out */
*size = pp->nread_resp; /* size of the response */
pp->nread_resp = 0; /* restart */
break;
}
perline=0; /* line starts over here */
pp->linestart_resp = ptr+1;
}
}
if(!keepon && (i != gotbytes)) {
/* We found the end of the response lines, but we didn't parse the
full chunk of data we have read from the server. We therefore need
to store the rest of the data to be checked on the next invoke as
it may actually contain another end of response already! */
clipamount = gotbytes - i;
restart = TRUE;
}
else if(keepon) {
if((perline == gotbytes) && (gotbytes > BUFSIZE/2)) {
/* We got an excessive line without newlines and we need to deal
with it. We keep the first bytes of the line then we throw
away the rest. */
infof(data, "Excessive server response line length received, %zd bytes."
" Stripping\n", gotbytes);
restart = TRUE;
/* we keep 40 bytes since all our pingpong protocols are only
interested in the first piece */
clipamount = 40;
}
else if(pp->nread_resp > BUFSIZE/2) {
/* We got a large chunk of data and there's potentially still trailing
data to take care of, so we put any such part in the "cache", clear
the buffer to make space and restart. */
clipamount = perline;
restart = TRUE;
}
}
else if(i == gotbytes)
restart = TRUE;
if(clipamount) {
pp->cache_size = clipamount;
pp->cache = malloc(pp->cache_size);
if(pp->cache)
memcpy(pp->cache, pp->linestart_resp, pp->cache_size);
else
return CURLE_OUT_OF_MEMORY;
}
if(restart) {
/* now reset a few variables to start over nicely from the start of
the big buffer */
pp->nread_resp = 0; /* start over from scratch in the buffer */
ptr = pp->linestart_resp = buf;
perline = 0;
}
} /* there was data */
} /* while there's buffer left and loop is requested */
pp->pending_resp = FALSE;
return result;
}
int Curl_pp_getsock(struct pingpong *pp,
curl_socket_t *socks,
int numsocks)
{
struct connectdata *conn = pp->conn;
if(!numsocks)
return GETSOCK_BLANK;
socks[0] = conn->sock[FIRSTSOCKET];
if(pp->sendleft) {
/* write mode */
return GETSOCK_WRITESOCK(0);
}
/* read mode */
return GETSOCK_READSOCK(0);
}
CURLcode Curl_pp_flushsend(struct pingpong *pp)
{
/* we have a piece of a command still left to send */
struct connectdata *conn = pp->conn;
ssize_t written;
CURLcode result = CURLE_OK;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
result = Curl_write(conn, sock, pp->sendthis + pp->sendsize -
pp->sendleft, pp->sendleft, &written);
if(result)
return result;
if(written != (ssize_t)pp->sendleft) {
/* only a fraction was sent */
pp->sendleft -= written;
}
else {
free(pp->sendthis);
pp->sendthis=NULL;
pp->sendleft = pp->sendsize = 0;
pp->response = Curl_tvnow();
}
return CURLE_OK;
}
CURLcode Curl_pp_disconnect(struct pingpong *pp)
{
if(pp->cache) {
free(pp->cache);
pp->cache = NULL;
}
return CURLE_OK;
}
#endif

148
lib/pingpong.h Normal file
View File

@ -0,0 +1,148 @@
#ifndef __PINGPONG_H
#define __PINGPONG_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2007, 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* $Id$
***************************************************************************/
#include <stdarg.h>
#include "setup.h"
#if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_FTP) || \
!defined(CURL_DISABLE_POP3) || !defined(CURL_DISABLE_SMTP)
#define USE_PINGPONG
#endif
/* forward-declaration, this is defined in urldata.h */
struct connectdata;
/*
* 'pingpong' is the generic struct used for protocols doing server<->client
* conversations in a back-and-forth style such as FTP, IMAP, POP3, SMTP etc.
*
* It holds response cache and non-blocking sending data.
*/
struct pingpong {
char *cache; /* data cache between getresponse()-calls */
size_t cache_size; /* size of cache in bytes */
size_t nread_resp; /* number of bytes currently read of a server response */
char *linestart_resp; /* line start pointer for the server response
reader function */
bool pending_resp; /* set TRUE when a server response is pending or in
progress, and is cleared once the last response is
read */
char *sendthis; /* allocated pointer to a buffer that is to be sent to the
server */
size_t sendleft; /* number of bytes left to send from the sendthis buffer */
size_t sendsize; /* total size of the sendthis buffer */
struct timeval response; /* set to Curl_tvnow() when a command has been sent
off, used to time-out response reading */
long response_time; /* When no timeout is given, this is the amount of
seconds we await for a server response. */
struct connectdata *conn; /* points to the connectdata struct that this
belongs to */
/* Function pointers the protocols MUST implement and provide for the
pingpong layer to function */
CURLcode (*statemach_act)(struct connectdata *conn);
int (*endofresp)(struct pingpong *pp, int *code);
};
/*
* Curl_pp_multi_statemach()
*
* called repeatedly until done when the multi interface is used.
*/
CURLcode Curl_pp_multi_statemach(struct pingpong *pp);
/*
* Curl_pp_easy_statemach()
*
* called repeatedly until done when the easy interface is used.
*/
CURLcode Curl_pp_easy_statemach(struct pingpong *pp);
/* initialize stuff to prepare for reading a fresh new response */
void Curl_pp_init(struct pingpong *pp);
/* Returns timeout in ms. 0 or negative number means the timeout has already
triggered */
long Curl_pp_state_timeout(struct pingpong *pp);
/***********************************************************************
*
* Curl_pp_sendf()
*
* Send the formated string as a command to a pingpong server. Note that
* the string should not have any CRLF appended, as this function will
* append the necessary things itself.
*
* NOTE: we build the command in a fixed-length buffer, which sets length
* restrictions on the command!
*
* made to never block
*/
CURLcode Curl_pp_sendf(struct pingpong *pp,
const char *fmt, ...);
/***********************************************************************
*
* Curl_pp_vsendf()
*
* Send the formated string as a command to a pingpong server. Note that
* the string should not have any CRLF appended, as this function will
* append the necessary things itself.
*
* NOTE: we build the command in a fixed-length buffer, which sets length
* restrictions on the command!
*
* made to never block
*/
CURLcode Curl_pp_vsendf(struct pingpong *pp,
const char *fmt,
va_list args);
/*
* Curl_pp_readresp()
*
* Reads a piece of a server response.
*/
CURLcode Curl_pp_readresp(curl_socket_t sockfd,
struct pingpong *pp,
int *code, /* return the server code if done */
size_t *size); /* size of the response */
CURLcode Curl_pp_flushsend(struct pingpong *pp);
/* call this when a pingpong connection is disconnected */
CURLcode Curl_pp_disconnect(struct pingpong *pp);
int Curl_pp_getsock(struct pingpong *pp, curl_socket_t *socks,
int numsocks);
#endif /* __PINGPONG_H */

961
lib/pop3.c Normal file
View File

@ -0,0 +1,961 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* RFC1939 POP3 protocol
* RFC2384 POP URL Scheme
* RFC2595 Using TLS with IMAP, POP3 and ACAP
*
* $Id$
***************************************************************************/
#include "setup.h"
#ifndef CURL_DISABLE_POP3
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_UTSNAME_H
#include <sys/utsname.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef VMS
#include <in.h>
#include <inet.h>
#endif
#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
#undef in_addr_t
#define in_addr_t unsigned long
#endif
#include <curl/curl.h>
#include "urldata.h"
#include "sendf.h"
#include "easyif.h" /* for Curl_convert_... prototypes */
#include "if2ip.h"
#include "hostip.h"
#include "progress.h"
#include "transfer.h"
#include "escape.h"
#include "http.h" /* for HTTP proxy tunnel stuff */
#include "socks.h"
#include "pop3.h"
#include "strtoofft.h"
#include "strequal.h"
#include "sslgen.h"
#include "connect.h"
#include "strerror.h"
#include "select.h"
#include "multiif.h"
#include "url.h"
#include "rawstr.h"
#include "strtoofft.h"
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
#include "curl_memory.h"
/* The last #include file should be: */
#include "memdebug.h"
/* Local API functions */
static CURLcode pop3_parse_url_path(struct connectdata *conn);
static CURLcode pop3_regular_transfer(struct connectdata *conn, bool *done);
static CURLcode pop3_do(struct connectdata *conn, bool *done);
static CURLcode pop3_done(struct connectdata *conn,
CURLcode, bool premature);
static CURLcode pop3_connect(struct connectdata *conn, bool *done);
static CURLcode pop3_disconnect(struct connectdata *conn);
static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done);
static int pop3_getsock(struct connectdata *conn,
curl_socket_t *socks,
int numsocks);
static CURLcode pop3_doing(struct connectdata *conn,
bool *dophase_done);
static CURLcode pop3_setup_connection(struct connectdata * conn);
/*
* POP3 protocol handler.
*/
const struct Curl_handler Curl_handler_pop3 = {
"POP3", /* scheme */
pop3_setup_connection, /* setup_connection */
pop3_do, /* do_it */
pop3_done, /* done */
ZERO_NULL, /* do_more */
pop3_connect, /* connect_it */
pop3_multi_statemach, /* connecting */
pop3_doing, /* doing */
pop3_getsock, /* proto_getsock */
pop3_getsock, /* doing_getsock */
ZERO_NULL, /* perform_getsock */
pop3_disconnect, /* disconnect */
PORT_POP3, /* defport */
PROT_POP3 /* protocol */
};
#ifdef USE_SSL
/*
* POP3S protocol handler.
*/
const struct Curl_handler Curl_handler_pop3s = {
"POP3S", /* scheme */
pop3_setup_connection, /* setup_connection */
pop3_do, /* do_it */
pop3_done, /* done */
ZERO_NULL, /* do_more */
pop3_connect, /* connect_it */
pop3_multi_statemach, /* connecting */
pop3_doing, /* doing */
pop3_getsock, /* proto_getsock */
pop3_getsock, /* doing_getsock */
ZERO_NULL, /* perform_getsock */
pop3_disconnect, /* disconnect */
PORT_POP3S, /* defport */
PROT_POP3 | PROT_POP3S | PROT_SSL /* protocol */
};
#endif
#ifndef CURL_DISABLE_HTTP
/*
* HTTP-proxyed POP3 protocol handler.
*/
const struct Curl_handler Curl_handler_pop3_proxy = {
"POP3", /* scheme */
ZERO_NULL, /* setup_connection */
Curl_http, /* do_it */
Curl_http_done, /* done */
ZERO_NULL, /* do_more */
ZERO_NULL, /* connect_it */
ZERO_NULL, /* connecting */
ZERO_NULL, /* doing */
ZERO_NULL, /* proto_getsock */
ZERO_NULL, /* doing_getsock */
ZERO_NULL, /* perform_getsock */
ZERO_NULL, /* disconnect */
PORT_POP3, /* defport */
PROT_HTTP /* protocol */
};
#ifdef USE_SSL
/*
* HTTP-proxyed POP3S protocol handler.
*/
const struct Curl_handler Curl_handler_pop3s_proxy = {
"POP3S", /* scheme */
ZERO_NULL, /* setup_connection */
Curl_http, /* do_it */
Curl_http_done, /* done */
ZERO_NULL, /* do_more */
ZERO_NULL, /* connect_it */
ZERO_NULL, /* connecting */
ZERO_NULL, /* doing */
ZERO_NULL, /* proto_getsock */
ZERO_NULL, /* doing_getsock */
ZERO_NULL, /* perform_getsock */
ZERO_NULL, /* disconnect */
PORT_POP3S, /* defport */
PROT_HTTP /* protocol */
};
#endif
#endif
/* function that checks for a pop3 status code at the start of the given
string */
static int pop3_endofresp(struct pingpong *pp,
int *resp)
{
char *line = pp->linestart_resp;
size_t len = pp->nread_resp;
if( ((len >= 3) && !memcmp("+OK", line, 3)) ||
((len >= 4) && !memcmp("-ERR", line, 4)) ) {
*resp=line[1]; /* O or E */
return TRUE;
}
return FALSE; /* nothing for us */
}
/* This is the ONLY way to change POP3 state! */
static void state(struct connectdata *conn,
pop3state newstate)
{
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
/* for debug purposes */
static const char * const names[]={
"STOP",
"SERVERGREET",
"USER",
"PASS",
"STARTTLS",
"RETR",
"QUIT",
/* LAST */
};
#endif
struct pop3_conn *pop3c = &conn->proto.pop3c;
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
if(pop3c->state != newstate)
infof(conn->data, "POP3 %p state change from %s to %s\n",
pop3c, names[pop3c->state], names[newstate]);
#endif
pop3c->state = newstate;
}
static CURLcode pop3_state_user(struct connectdata *conn)
{
CURLcode result;
struct FTP *pop3 = conn->data->state.proto.pop3;
/* send USER */
result = Curl_pp_sendf(&conn->proto.pop3c.pp, "USER %s",
pop3->user?pop3->user:"");
if(result)
return result;
state(conn, POP3_USER);
return CURLE_OK;
}
/* For the POP3 "protocol connect" and "doing" phases only */
static int pop3_getsock(struct connectdata *conn,
curl_socket_t *socks,
int numsocks)
{
return Curl_pp_getsock(&conn->proto.pop3c.pp, socks, numsocks);
}
/* for STARTTLS responses */
static CURLcode pop3_state_starttls_resp(struct connectdata *conn,
int pop3code,
pop3state instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
(void)instate; /* no use for this yet */
if(pop3code != 'O') {
failf(data, "STARTTLS denied. %c", pop3code);
result = CURLE_LOGIN_DENIED;
}
else {
/* Curl_ssl_connect is BLOCKING */
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(CURLE_OK == result) {
conn->protocol |= PROT_POP3S;
result = pop3_state_user(conn);
}
}
state(conn, POP3_STOP);
return result;
}
/* for USER responses */
static CURLcode pop3_state_user_resp(struct connectdata *conn,
int pop3code,
pop3state instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
struct FTP *pop3 = data->state.proto.pop3;
(void)instate; /* no use for this yet */
if(pop3code != 'O') {
failf(data, "Access denied. %c", pop3code);
result = CURLE_LOGIN_DENIED;
}
/* send PASS */
result = Curl_pp_sendf(&conn->proto.pop3c.pp, "PASS %s",
pop3->passwd?pop3->passwd:"");
if(result)
return result;
state(conn, POP3_PASS);
return result;
}
/* for PASS responses */
static CURLcode pop3_state_pass_resp(struct connectdata *conn,
int pop3code,
pop3state instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
(void)instate; /* no use for this yet */
if(pop3code != 'O') {
failf(data, "Access denied. %c", pop3code);
result = CURLE_LOGIN_DENIED;
}
state(conn, POP3_STOP);
return result;
}
/* for the retr response */
static CURLcode pop3_state_retr_resp(struct connectdata *conn,
int pop3code,
pop3state instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
struct FTP *pop3 = data->state.proto.pop3;
struct pop3_conn *pop3c = &conn->proto.pop3c;
struct pingpong *pp = &pop3c->pp;
(void)instate; /* no use for this yet */
if('O' != pop3code) {
state(conn, POP3_STOP);
return CURLE_RECV_ERROR;
}
/* POP3 download */
result=Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE,
pop3->bytecountp,
-1, NULL); /* no upload here */
if(pp->cache) {
/* At this point there is a bunch of data in the header "cache" that is
actually body content, send it as body and then skip it. Do note
that there may even be additional "headers" after the body. */
/* we may get the EOB already here! */
result = Curl_pop3_write(conn, pp->cache, pp->cache_size);
if(result)
return result;
/* cache is drained */
free(pp->cache);
pp->cache = NULL;
pp->cache_size = 0;
}
state(conn, POP3_STOP);
return result;
}
/* start the DO phase */
static CURLcode pop3_retr(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
struct pop3_conn *pop3c = &conn->proto.pop3c;
result = Curl_pp_sendf(&conn->proto.pop3c.pp, "RETR %s", pop3c->mailbox);
if(result)
return result;
state(conn, POP3_RETR);
return result;
}
static CURLcode pop3_statemach_act(struct connectdata *conn)
{
CURLcode result;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
struct SessionHandle *data=conn->data;
int pop3code;
struct pop3_conn *pop3c = &conn->proto.pop3c;
struct pingpong *pp = &pop3c->pp;
size_t nread = 0;
if(pp->sendleft)
return Curl_pp_flushsend(pp);
/* we read a piece of response */
result = Curl_pp_readresp(sock, pp, &pop3code, &nread);
if(result)
return result;
if(pop3code) {
/* we have now received a full POP3 server response */
switch(pop3c->state) {
case POP3_SERVERGREET:
if(pop3code != 'O') {
failf(data, "Got unexpected pop3-server response");
return CURLE_FTP_WEIRD_SERVER_REPLY;
}
if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
/* We don't have a SSL/TLS connection yet, but SSL is requested. Switch
to TLS connection now */
const char *str;
result = Curl_pp_sendf(&pop3c->pp, "STARTTLS", str);
state(conn, POP3_STARTTLS);
}
else
result = pop3_state_user(conn);
if(result)
return result;
break;
case POP3_USER:
result = pop3_state_user_resp(conn, pop3code, pop3c->state);
break;
case POP3_PASS:
result = pop3_state_pass_resp(conn, pop3code, pop3c->state);
break;
case POP3_STARTTLS:
result = pop3_state_starttls_resp(conn, pop3code, pop3c->state);
break;
case POP3_RETR:
result = pop3_state_retr_resp(conn, pop3code, pop3c->state);
break;
case POP3_QUIT:
/* fallthrough, just stop! */
default:
/* internal error */
state(conn, POP3_STOP);
break;
}
}
return result;
}
/* called repeatedly until done from multi.c */
static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done)
{
struct pop3_conn *pop3c = &conn->proto.pop3c;
CURLcode result = Curl_pp_multi_statemach(&pop3c->pp);
*done = (bool)(pop3c->state == POP3_STOP);
return result;
}
static CURLcode pop3_easy_statemach(struct connectdata *conn)
{
struct pop3_conn *pop3c = &conn->proto.pop3c;
struct pingpong *pp = &pop3c->pp;
CURLcode result = CURLE_OK;
while(pop3c->state != POP3_STOP) {
result = Curl_pp_easy_statemach(pp);
if(result)
break;
}
return result;
}
/*
* Allocate and initialize the struct POP3 for the current SessionHandle. If
* need be.
*/
static CURLcode pop3_init(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
struct FTP *pop3 = data->state.proto.pop3;
if(!pop3) {
pop3 = data->state.proto.pop3 = calloc(sizeof(struct FTP), 1);
if(!pop3)
return CURLE_OUT_OF_MEMORY;
}
/* get some initial data into the pop3 struct */
pop3->bytecountp = &data->req.bytecount;
/* No need to duplicate user+password, the connectdata struct won't change
during a session, but we re-init them here since on subsequent inits
since the conn struct may have changed or been replaced.
*/
pop3->user = conn->user;
pop3->passwd = conn->passwd;
return CURLE_OK;
}
/*
* pop3_connect() should do everything that is to be considered a part of
* the connection phase.
*
* The variable 'done' points to will be TRUE if the protocol-layer connect
* phase is done when this function returns, or FALSE is not. When called as
* a part of the easy interface, it will always be TRUE.
*/
static CURLcode pop3_connect(struct connectdata *conn,
bool *done) /* see description above */
{
CURLcode result;
struct pop3_conn *pop3c = &conn->proto.pop3c;
struct SessionHandle *data=conn->data;
struct pingpong *pp = &pop3c->pp;
*done = FALSE; /* default to not done yet */
/* If there already is a protocol-specific struct allocated for this
sessionhandle, deal with it */
Curl_reset_reqproto(conn);
result = pop3_init(conn);
if(CURLE_OK != result)
return result;
/* We always support persistant connections on pop3 */
conn->bits.close = FALSE;
pp->response_time = RESP_TIMEOUT; /* set default response time-out */
pp->statemach_act = pop3_statemach_act;
pp->endofresp = pop3_endofresp;
pp->conn = conn;
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
/* for POP3 over HTTP proxy */
struct HTTP http_proxy;
struct FTP *pop3_save;
/* BLOCKING */
/* We want "seamless" POP3 operations through HTTP proxy tunnel */
/* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member
* conn->proto.http; we want POP3 through HTTP and we have to change the
* member temporarily for connecting to the HTTP proxy. After
* Curl_proxyCONNECT we have to set back the member to the original struct
* POP3 pointer
*/
pop3_save = data->state.proto.pop3;
memset(&http_proxy, 0, sizeof(http_proxy));
data->state.proto.http = &http_proxy;
result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
conn->host.name, conn->remote_port);
data->state.proto.pop3 = pop3_save;
if(CURLE_OK != result)
return result;
}
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
if(conn->protocol & PROT_POP3S) {
/* BLOCKING */
/* POP3S is simply pop3 with SSL for the control channel */
/* now, perform the SSL initialization for this socket */
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(result)
return result;
}
Curl_pp_init(pp); /* init the response reader stuff */
/* When we connect, we start in the state where we await the server greet
response */
state(conn, POP3_SERVERGREET);
if(data->state.used_interface == Curl_if_multi)
result = pop3_multi_statemach(conn, done);
else {
result = pop3_easy_statemach(conn);
if(!result)
*done = TRUE;
}
return result;
}
/***********************************************************************
*
* pop3_done()
*
* The DONE function. This does what needs to be done after a single DO has
* performed.
*
* Input argument is already checked for validity.
*/
static CURLcode pop3_done(struct connectdata *conn, CURLcode status,
bool premature)
{
struct SessionHandle *data = conn->data;
struct FTP *pop3 = data->state.proto.pop3;
CURLcode result=CURLE_OK;
(void)premature;
if(!pop3)
/* When the easy handle is removed from the multi while libcurl is still
* trying to resolve the host name, it seems that the pop3 struct is not
* yet initialized, but the removal action calls Curl_done() which calls
* this function. So we simply return success if no pop3 pointer is set.
*/
return CURLE_OK;
if(status) {
conn->bits.close = TRUE; /* marked for closure */
result = status; /* use the already set error code */
}
/* clear these for next connection */
pop3->transfer = FTPTRANSFER_BODY;
return result;
}
/***********************************************************************
*
* pop3_perform()
*
* This is the actual DO function for POP3. Get a file/directory according to
* the options previously setup.
*/
static
CURLcode pop3_perform(struct connectdata *conn,
bool *connected, /* connect status after PASV / PORT */
bool *dophase_done)
{
/* this is POP3 and no proxy */
CURLcode result=CURLE_OK;
DEBUGF(infof(conn->data, "DO phase starts\n"));
if(conn->data->set.opt_no_body) {
/* requested no body means no transfer... */
struct FTP *pop3 = conn->data->state.proto.pop3;
pop3->transfer = FTPTRANSFER_INFO;
}
*dophase_done = FALSE; /* not done yet */
/* start the first command in the DO phase */
result = pop3_retr(conn);
if(result)
return result;
/* run the state-machine */
if(conn->data->state.used_interface == Curl_if_multi)
result = pop3_multi_statemach(conn, dophase_done);
else {
result = pop3_easy_statemach(conn);
*dophase_done = TRUE; /* with the easy interface we are done here */
}
*connected = conn->bits.tcpconnect;
if(*dophase_done)
DEBUGF(infof(conn->data, "DO phase is complete\n"));
return result;
}
/***********************************************************************
*
* pop3_do()
*
* This function is registered as 'curl_do' function. It decodes the path
* parts etc as a wrapper to the actual DO function (pop3_perform).
*
* The input argument is already checked for validity.
*/
static CURLcode pop3_do(struct connectdata *conn, bool *done)
{
CURLcode retcode = CURLE_OK;
*done = FALSE; /* default to false */
/*
Since connections can be re-used between SessionHandles, this might be a
connection already existing but on a fresh SessionHandle struct so we must
make sure we have a good 'struct POP3' to play with. For new connections,
the struct POP3 is allocated and setup in the pop3_connect() function.
*/
Curl_reset_reqproto(conn);
retcode = pop3_init(conn);
if(retcode)
return retcode;
retcode = pop3_parse_url_path(conn);
if(retcode)
return retcode;
retcode = pop3_regular_transfer(conn, done);
return retcode;
}
/***********************************************************************
*
* pop3_quit()
*
* This should be called before calling sclose(). We should then wait for the
* response from the server before returning. The calling code should then try
* to close the connection.
*
*/
static CURLcode pop3_quit(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
result = Curl_pp_sendf(&conn->proto.pop3c.pp, "QUIT", NULL);
if(result)
return result;
state(conn, POP3_QUIT);
result = pop3_easy_statemach(conn);
return result;
}
/***********************************************************************
*
* pop3_disconnect()
*
* Disconnect from an POP3 server. Cleanup protocol-specific per-connection
* resources. BLOCKING.
*/
static CURLcode pop3_disconnect(struct connectdata *conn)
{
struct pop3_conn *pop3c= &conn->proto.pop3c;
/* We cannot send quit unconditionally. If this connection is stale or
bad in any way, sending quit and waiting around here will make the
disconnect wait in vain and cause more problems than we need to.
*/
/* The POP3 session may or may not have been allocated/setup at this
point! */
(void)pop3_quit(conn); /* ignore errors on the LOGOUT */
Curl_pp_disconnect(&pop3c->pp);
return CURLE_OK;
}
/***********************************************************************
*
* pop3_parse_url_path()
*
* Parse the URL path into separate path components.
*
*/
static CURLcode pop3_parse_url_path(struct connectdata *conn)
{
/* the pop3 struct is already inited in pop3_connect() */
struct pop3_conn *pop3c = &conn->proto.pop3c;
struct SessionHandle *data = conn->data;
const char *path = data->state.path;
int len;
if(!*path)
path = "INBOX";
/* url decode the path and use this mailbox */
pop3c->mailbox = curl_easy_unescape(data, path, 0, &len);
return CURLE_OK;
}
/* call this when the DO phase has completed */
static CURLcode pop3_dophase_done(struct connectdata *conn,
bool connected)
{
CURLcode result = CURLE_OK;
struct FTP *pop3 = conn->data->state.proto.pop3;
struct pop3_conn *pop3c = &conn->proto.pop3c;
(void)connected;
if(pop3->transfer != FTPTRANSFER_BODY)
/* no data to transfer */
result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
free(pop3c->mailbox);
return result;
}
/* called from multi.c while DOing */
static CURLcode pop3_doing(struct connectdata *conn,
bool *dophase_done)
{
CURLcode result;
result = pop3_multi_statemach(conn, dophase_done);
if(*dophase_done) {
result = pop3_dophase_done(conn, FALSE /* not connected */);
DEBUGF(infof(conn->data, "DO phase is complete\n"));
}
return result;
}
/***********************************************************************
*
* pop3_regular_transfer()
*
* The input argument is already checked for validity.
*
* Performs all commands done before a regular transfer between a local and a
* remote host.
*
*/
static
CURLcode pop3_regular_transfer(struct connectdata *conn,
bool *dophase_done)
{
CURLcode result=CURLE_OK;
bool connected=FALSE;
struct SessionHandle *data = conn->data;
data->req.size = -1; /* make sure this is unknown at this point */
Curl_pgrsSetUploadCounter(data, 0);
Curl_pgrsSetDownloadCounter(data, 0);
Curl_pgrsSetUploadSize(data, 0);
Curl_pgrsSetDownloadSize(data, 0);
result = pop3_perform(conn,
&connected, /* have we connected after PASV/PORT */
dophase_done); /* all commands in the DO-phase done? */
if(CURLE_OK == result) {
if(!*dophase_done)
/* the DO phase has not completed yet */
return CURLE_OK;
result = pop3_dophase_done(conn, connected);
if(result)
return result;
}
return result;
}
static CURLcode pop3_setup_connection(struct connectdata * conn)
{
struct SessionHandle *data = conn->data;
if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) {
/* Unless we have asked to tunnel pop3 operations through the proxy, we
switch and use HTTP operations only */
#ifndef CURL_DISABLE_HTTP
if(conn->handler == &Curl_handler_pop3)
conn->handler = &Curl_handler_pop3_proxy;
else {
#ifdef USE_SSL
conn->handler = &Curl_handler_pop3s_proxy;
#else
failf(data, "POP3S not supported!");
return CURLE_UNSUPPORTED_PROTOCOL;
#endif
}
/*
* We explicitly mark this connection as persistent here as we're doing
* POP3 over HTTP and thus we accidentally avoid setting this value
* otherwise.
*/
conn->bits.close = FALSE;
#else
failf(data, "POP3 over http proxy requires HTTP support built-in!");
return CURLE_UNSUPPORTED_PROTOCOL;
#endif
}
data->state.path++; /* don't include the initial slash */
return CURLE_OK;
}
/* this is the 5-bytes End-Of-Body marker for POP3 */
#define POP3_EOB "\x0d\x0a\x2e\x0d\x0a"
#define POP3_EOB_LEN 5
/*
* This function scans the body after the end-of-body and writes everything
* until the end is found.
*/
CURLcode Curl_pop3_write(struct connectdata *conn,
char *str,
size_t nread)
{
/* This code could be made into a special function in the handler struct. */
CURLcode result;
struct SessionHandle *data = conn->data;
struct SingleRequest *k = &data->req;
/* Detect the end-of-body marker, which is 5 bytes:
0d 0a 2e 0d 0a. This marker can of course be spread out
over up to 5 different data chunks. Deal with it! */
struct pop3_conn *pop3c = &conn->proto.pop3c;
int checkmax = (nread >= POP3_EOB_LEN?POP3_EOB_LEN:nread);
int checkleft = POP3_EOB_LEN-pop3c->eob;
int check = checkmax>= checkleft?checkleft:checkmax;
if(!memcmp(POP3_EOB, &str[nread - check], check)) {
/* substring match */
pop3c->eob += check;
if(pop3c->eob == POP3_EOB_LEN) {
/* full match, the transfer is done! */
nread -= check;
k->keepon &= ~KEEP_RECV;
pop3c->eob = 0;
}
}
else if(pop3c->eob) {
/* not a match, but we matched a piece before so we must now
send that part as body first, before we move on and send
this buffer */
result = Curl_client_write(conn, CLIENTWRITE_BODY,
(char *)POP3_EOB, pop3c->eob);
if(result)
return result;
pop3c->eob = 0;
}
result = Curl_client_write(conn, CLIENTWRITE_BODY, str, nread);
return result;
}
#endif /* CURL_DISABLE_POP3 */

62
lib/pop3.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef __POP3_H
#define __POP3_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* $Id$
***************************************************************************/
/****************************************************************************
* POP3 unique setup
***************************************************************************/
typedef enum {
POP3_STOP, /* do nothing state, stops the state machine */
POP3_SERVERGREET, /* waiting for the initial greeting immediately after
a connect */
POP3_USER,
POP3_PASS,
POP3_STARTTLS,
POP3_RETR,
POP3_QUIT,
POP3_LAST /* never used */
} pop3state;
/* pop3_conn is used for struct connection-oriented data in the connectdata
struct */
struct pop3_conn {
struct pingpong pp;
char *mailbox; /* what to RETR */
int eob; /* number of bytes of the EOB (End Of Body) that has been
received thus far */
pop3state state; /* always use pop3.c:state() to change state! */
};
extern const struct Curl_handler Curl_handler_pop3;
extern const struct Curl_handler Curl_handler_pop3s;
/*
* This function scans the body after the end-of-body and writes everything
* until the end is found.
*/
CURLcode Curl_pop3_write(struct connectdata *conn,
char *str,
size_t nread);
#endif /* __POP3_H */

921
lib/smtp.c Normal file
View File

@ -0,0 +1,921 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* RFC2821 SMTP protocol
*
* $Id$
***************************************************************************/
#include "setup.h"
#ifndef CURL_DISABLE_SMTP
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_UTSNAME_H
#include <sys/utsname.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef VMS
#include <in.h>
#include <inet.h>
#endif
#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
#undef in_addr_t
#define in_addr_t unsigned long
#endif
#include <curl/curl.h>
#include "urldata.h"
#include "sendf.h"
#include "easyif.h" /* for Curl_convert_... prototypes */
#include "if2ip.h"
#include "hostip.h"
#include "progress.h"
#include "transfer.h"
#include "escape.h"
#include "http.h" /* for HTTP proxy tunnel stuff */
#include "socks.h"
#include "smtp.h"
#include "strtoofft.h"
#include "strequal.h"
#include "sslgen.h"
#include "connect.h"
#include "strerror.h"
#include "select.h"
#include "multiif.h"
#include "url.h"
#include "rawstr.h"
#include "strtoofft.h"
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
#include "curl_memory.h"
/* The last #include file should be: */
#include "memdebug.h"
/* Local API functions */
static CURLcode smtp_parse_url_path(struct connectdata *conn);
static CURLcode smtp_regular_transfer(struct connectdata *conn, bool *done);
static CURLcode smtp_do(struct connectdata *conn, bool *done);
static CURLcode smtp_done(struct connectdata *conn,
CURLcode, bool premature);
static CURLcode smtp_connect(struct connectdata *conn, bool *done);
static CURLcode smtp_disconnect(struct connectdata *conn);
static CURLcode smtp_multi_statemach(struct connectdata *conn, bool *done);
static int smtp_getsock(struct connectdata *conn,
curl_socket_t *socks,
int numsocks);
static CURLcode smtp_doing(struct connectdata *conn,
bool *dophase_done);
static CURLcode smtp_setup_connection(struct connectdata * conn);
static void smtp_respinit(struct connectdata *conn);
/*
* SMTP protocol handler.
*/
const struct Curl_handler Curl_handler_smtp = {
"SMTP", /* scheme */
smtp_setup_connection, /* setup_connection */
smtp_do, /* do_it */
smtp_done, /* done */
ZERO_NULL, /* do_more */
smtp_connect, /* connect_it */
smtp_multi_statemach, /* connecting */
smtp_doing, /* doing */
smtp_getsock, /* proto_getsock */
smtp_getsock, /* doing_getsock */
ZERO_NULL, /* perform_getsock */
smtp_disconnect, /* disconnect */
PORT_SMTP, /* defport */
PROT_SMTP /* protocol */
};
#ifdef USE_SSL
/*
* SMTPS protocol handler.
*/
const struct Curl_handler Curl_handler_smtps = {
"SMTPS", /* scheme */
smtp_setup_connection, /* setup_connection */
smtp_do, /* do_it */
smtp_done, /* done */
ZERO_NULL, /* do_more */
smtp_connect, /* connect_it */
smtp_multi_statemach, /* connecting */
smtp_doing, /* doing */
smtp_getsock, /* proto_getsock */
smtp_getsock, /* doing_getsock */
ZERO_NULL, /* perform_getsock */
smtp_disconnect, /* disconnect */
PORT_SMTPS, /* defport */
PROT_SMTP | PROT_SMTPS | PROT_SSL /* protocol */
};
#endif
#ifndef CURL_DISABLE_HTTP
/*
* HTTP-proxyed SMTP protocol handler.
*/
const struct Curl_handler Curl_handler_smtp_proxy = {
"SMTP", /* scheme */
ZERO_NULL, /* setup_connection */
Curl_http, /* do_it */
Curl_http_done, /* done */
ZERO_NULL, /* do_more */
ZERO_NULL, /* connect_it */
ZERO_NULL, /* connecting */
ZERO_NULL, /* doing */
ZERO_NULL, /* proto_getsock */
ZERO_NULL, /* doing_getsock */
ZERO_NULL, /* perform_getsock */
ZERO_NULL, /* disconnect */
PORT_SMTP, /* defport */
PROT_HTTP /* protocol */
};
#ifdef USE_SSL
/*
* HTTP-proxyed SMTPS protocol handler.
*/
const struct Curl_handler Curl_handler_smtps_proxy = {
"SMTPS", /* scheme */
ZERO_NULL, /* setup_connection */
Curl_http, /* do_it */
Curl_http_done, /* done */
ZERO_NULL, /* do_more */
ZERO_NULL, /* connect_it */
ZERO_NULL, /* connecting */
ZERO_NULL, /* doing */
ZERO_NULL, /* proto_getsock */
ZERO_NULL, /* doing_getsock */
ZERO_NULL, /* perform_getsock */
ZERO_NULL, /* disconnect */
PORT_SMTPS, /* defport */
PROT_HTTP /* protocol */
};
#endif
#endif
/* fucntion that checks for an ending smtp status code at the start of the
given string */
static int smtp_endofresp(struct pingpong *pp, int *resp)
{
char *line = pp->linestart_resp;
size_t len = pp->nread_resp;
if( (len >= 4) && (' ' == line[3]) &&
ISDIGIT(line[0]) && ISDIGIT(line[1]) && ISDIGIT(line[2])) {
*resp=atoi(line);
return TRUE;
}
return FALSE; /* nothing for us */
}
/* This is the ONLY way to change SMTP state! */
static void state(struct connectdata *conn,
smtpstate newstate)
{
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
/* for debug purposes */
static const char * const names[]={
"STOP",
"SERVERGREET",
"EHLO",
"STARTTLS",
"MAIL",
"RCPT",
"DATA",
"QUIT",
/* LAST */
};
#endif
struct smtp_conn *smtpc = &conn->proto.smtpc;
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
if(smtpc->state != newstate)
infof(conn->data, "SMTP %p state change from %s to %s\n",
smtpc, names[smtpc->state], names[newstate]);
#endif
smtpc->state = newstate;
}
static CURLcode smtp_state_ehlo(struct connectdata *conn)
{
CURLcode result;
struct FTP *smtp = conn->data->state.proto.smtp;
/* send EHLO */
result = Curl_pp_sendf(&conn->proto.smtpc.pp, "EHLO %s",
smtp->user?smtp->user:"");
if(result)
return result;
state(conn, SMTP_EHLO);
return CURLE_OK;
}
/* For the SMTP "protocol connect" and "doing" phases only */
static int smtp_getsock(struct connectdata *conn,
curl_socket_t *socks,
int numsocks)
{
return Curl_pp_getsock(&conn->proto.smtpc.pp, socks, numsocks);
}
/* for STARTTLS responses */
static CURLcode smtp_state_starttls_resp(struct connectdata *conn,
int smtpcode,
smtpstate instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
(void)instate; /* no use for this yet */
if(smtpcode != 'O') {
failf(data, "STARTTLS denied. %c", smtpcode);
result = CURLE_LOGIN_DENIED;
}
else {
/* Curl_ssl_connect is BLOCKING */
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(CURLE_OK == result) {
conn->protocol |= PROT_SMTPS;
result = smtp_state_ehlo(conn);
}
}
state(conn, SMTP_STOP);
return result;
}
/* for EHLO responses */
static CURLcode smtp_state_ehlo_resp(struct connectdata *conn,
int smtpcode,
smtpstate instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
(void)instate; /* no use for this yet */
if(smtpcode/100 != 2) {
failf(data, "Access denied: %d", smtpcode);
result = CURLE_LOGIN_DENIED;
}
/* end the connect phase */
state(conn, SMTP_STOP);
return result;
}
/* start the DO phase */
static CURLcode smtp_mail(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
struct FTP *smtp = data->state.proto.smtp;
/* send MAIL */
result = Curl_pp_sendf(&conn->proto.smtpc.pp, "MAIL FROM:<%s>",
data->set.str[STRING_MAIL_FROM]);
if(result)
return result;
state(conn, SMTP_MAIL);
return result;
}
/* for MAIL responses */
static CURLcode smtp_state_mail_resp(struct connectdata *conn,
int smtpcode,
smtpstate instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
(void)instate; /* no use for this yet */
if(smtpcode/100 != 2) {
failf(data, "Access denied: %d", smtpcode);
result = CURLE_LOGIN_DENIED;
state(conn, SMTP_STOP);
}
else {
/* send RCPT TO */
result = Curl_pp_sendf(&conn->proto.smtpc.pp, "RCPT TO:<%s>",
data->set.str[STRING_MAIL_RCPT]);
if(result)
return result;
state(conn, SMTP_RCPT);
}
return result;
}
/* for RCPT responses */
static CURLcode smtp_state_rcpt_resp(struct connectdata *conn,
int smtpcode,
smtpstate instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
(void)instate; /* no use for this yet */
if(smtpcode/100 != 2) {
failf(data, "Access denied: %d", smtpcode);
result = CURLE_LOGIN_DENIED;
state(conn, SMTP_STOP);
}
else {
/* send DATA */
result = Curl_pp_sendf(&conn->proto.smtpc.pp, "DATA", "");
if(result)
return result;
state(conn, SMTP_DATA);
}
return result;
}
/* for the DATA response */
static CURLcode smtp_state_data_resp(struct connectdata *conn,
int smtpcode,
smtpstate instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
struct FTP *smtp = data->state.proto.smtp;
(void)instate; /* no use for this yet */
if(smtpcode/100 != 2) {
state(conn, SMTP_STOP);
return CURLE_RECV_ERROR;
}
/* SMTP upload */
result=Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE,
smtp->bytecountp,
-1, NULL); /* no upload here */
state(conn, SMTP_STOP);
return result;
}
static CURLcode smtp_statemach_act(struct connectdata *conn)
{
CURLcode result;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
struct SessionHandle *data=conn->data;
int smtpcode;
struct smtp_conn *smtpc = &conn->proto.smtpc;
struct pingpong *pp = &smtpc->pp;
size_t nread = 0;
if(pp->sendleft)
/* we have a piece of a command still left to send */
return Curl_pp_flushsend(pp);
/* we read a piece of response */
result = Curl_pp_readresp(sock, pp, &smtpcode, &nread);
if(result)
return result;
if(smtpcode) {
/* we have now received a full SMTP server response */
switch(smtpc->state) {
case SMTP_SERVERGREET:
if(smtpcode/100 != 2) {
failf(data, "Got unexpected smtp-server response: %d", smtpcode);
return CURLE_FTP_WEIRD_SERVER_REPLY;
}
if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
/* We don't have a SSL/TLS connection yet, but SSL is requested. Switch
to TLS connection now */
const char *str;
result = Curl_pp_sendf(&smtpc->pp, "STARTTLS", str);
state(conn, SMTP_STARTTLS);
}
else
result = smtp_state_ehlo(conn);
if(result)
return result;
break;
case SMTP_EHLO:
result = smtp_state_ehlo_resp(conn, smtpcode, smtpc->state);
break;
case SMTP_MAIL:
result = smtp_state_mail_resp(conn, smtpcode, smtpc->state);
break;
case SMTP_RCPT:
result = smtp_state_rcpt_resp(conn, smtpcode, smtpc->state);
break;
case SMTP_STARTTLS:
result = smtp_state_starttls_resp(conn, smtpcode, smtpc->state);
break;
case SMTP_DATA:
result = smtp_state_data_resp(conn, smtpcode, smtpc->state);
break;
case SMTP_QUIT:
/* fallthrough, just stop! */
default:
/* internal error */
state(conn, SMTP_STOP);
break;
}
}
return result;
}
/* called repeatedly until done from multi.c */
static CURLcode smtp_multi_statemach(struct connectdata *conn,
bool *done)
{
struct smtp_conn *smtpc = &conn->proto.smtpc;
CURLcode result = Curl_pp_multi_statemach(&smtpc->pp);
*done = (bool)(smtpc->state == SMTP_STOP);
return result;
}
static CURLcode smtp_easy_statemach(struct connectdata *conn)
{
struct smtp_conn *smtpc = &conn->proto.smtpc;
struct pingpong *pp = &smtpc->pp;
CURLcode result = CURLE_OK;
while(smtpc->state != SMTP_STOP) {
result = Curl_pp_easy_statemach(pp);
if(result)
break;
}
return result;
}
/*
* Allocate and initialize the struct SMTP for the current SessionHandle. If
* need be.
*/
static CURLcode smtp_init(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
struct FTP *smtp = data->state.proto.smtp;
if(!smtp) {
smtp = data->state.proto.smtp = calloc(sizeof(struct FTP), 1);
if(!smtp)
return CURLE_OUT_OF_MEMORY;
}
/* get some initial data into the smtp struct */
smtp->bytecountp = &data->req.bytecount;
/* No need to duplicate user+password, the connectdata struct won't change
during a session, but we re-init them here since on subsequent inits
since the conn struct may have changed or been replaced.
*/
smtp->user = conn->user;
smtp->passwd = conn->passwd;
return CURLE_OK;
}
/*
* smtp_connect() should do everything that is to be considered a part of
* the connection phase.
*
* The variable 'done' points to will be TRUE if the protocol-layer connect
* phase is done when this function returns, or FALSE is not. When called as
* a part of the easy interface, it will always be TRUE.
*/
static CURLcode smtp_connect(struct connectdata *conn,
bool *done) /* see description above */
{
CURLcode result;
struct smtp_conn *smtpc = &conn->proto.smtpc;
struct SessionHandle *data=conn->data;
struct pingpong *pp=&smtpc->pp;
*done = FALSE; /* default to not done yet */
/* If there already is a protocol-specific struct allocated for this
sessionhandle, deal with it */
Curl_reset_reqproto(conn);
result = smtp_init(conn);
if(CURLE_OK != result)
return result;
/* We always support persistant connections on smtp */
conn->bits.close = FALSE;
pp->response_time = RESP_TIMEOUT; /* set default response time-out */
pp->statemach_act = smtp_statemach_act;
pp->endofresp = smtp_endofresp;
pp->conn = conn;
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
/* for SMTP over HTTP proxy */
struct HTTP http_proxy;
struct FTP *smtp_save;
/* BLOCKING */
/* We want "seamless" SMTP operations through HTTP proxy tunnel */
/* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member
* conn->proto.http; we want SMTP through HTTP and we have to change the
* member temporarily for connecting to the HTTP proxy. After
* Curl_proxyCONNECT we have to set back the member to the original struct
* SMTP pointer
*/
smtp_save = data->state.proto.smtp;
memset(&http_proxy, 0, sizeof(http_proxy));
data->state.proto.http = &http_proxy;
result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
conn->host.name, conn->remote_port);
data->state.proto.smtp = smtp_save;
if(CURLE_OK != result)
return result;
}
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
if(conn->protocol & PROT_SMTPS) {
/* BLOCKING */
/* SMTPS is simply smtp with SSL for the control channel */
/* now, perform the SSL initialization for this socket */
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(result)
return result;
}
Curl_pp_init(pp); /* init the response reader stuff */
pp->response_time = RESP_TIMEOUT; /* set default response time-out */
pp->statemach_act = smtp_statemach_act;
pp->endofresp = smtp_endofresp;
pp->conn = conn;
/* When we connect, we start in the state where we await the server greeting
*/
state(conn, SMTP_SERVERGREET);
if(data->state.used_interface == Curl_if_multi)
result = smtp_multi_statemach(conn, done);
else {
result = smtp_easy_statemach(conn);
if(!result)
*done = TRUE;
}
return result;
}
/***********************************************************************
*
* smtp_done()
*
* The DONE function. This does what needs to be done after a single DO has
* performed.
*
* Input argument is already checked for validity.
*/
static CURLcode smtp_done(struct connectdata *conn, CURLcode status,
bool premature)
{
struct SessionHandle *data = conn->data;
struct FTP *smtp = data->state.proto.smtp;
CURLcode result=CURLE_OK;
(void)premature;
if(!smtp)
/* When the easy handle is removed from the multi while libcurl is still
* trying to resolve the host name, it seems that the smtp struct is not
* yet initialized, but the removal action calls Curl_done() which calls
* this function. So we simply return success if no smtp pointer is set.
*/
return CURLE_OK;
if(status) {
conn->bits.close = TRUE; /* marked for closure */
result = status; /* use the already set error code */
}
/* clear these for next connection */
smtp->transfer = FTPTRANSFER_BODY;
return result;
}
/***********************************************************************
*
* smtp_perform()
*
* This is the actual DO function for SMTP. Get a file/directory according to
* the options previously setup.
*/
static
CURLcode smtp_perform(struct connectdata *conn,
bool *connected, /* connect status after PASV / PORT */
bool *dophase_done)
{
/* this is SMTP and no proxy */
CURLcode result=CURLE_OK;
DEBUGF(infof(conn->data, "DO phase starts\n"));
if(conn->data->set.opt_no_body) {
/* requested no body means no transfer... */
struct FTP *smtp = conn->data->state.proto.smtp;
smtp->transfer = FTPTRANSFER_INFO;
}
*dophase_done = FALSE; /* not done yet */
/* start the first command in the DO phase */
result = smtp_mail(conn);
if(result)
return result;
/* run the state-machine */
if(conn->data->state.used_interface == Curl_if_multi)
result = smtp_multi_statemach(conn, dophase_done);
else {
result = smtp_easy_statemach(conn);
*dophase_done = TRUE; /* with the easy interface we are done here */
}
*connected = conn->bits.tcpconnect;
if(*dophase_done)
DEBUGF(infof(conn->data, "DO phase is complete\n"));
return result;
}
/***********************************************************************
*
* smtp_do()
*
* This function is registered as 'curl_do' function. It decodes the path
* parts etc as a wrapper to the actual DO function (smtp_perform).
*
* The input argument is already checked for validity.
*/
static CURLcode smtp_do(struct connectdata *conn, bool *done)
{
CURLcode retcode = CURLE_OK;
*done = FALSE; /* default to false */
/*
Since connections can be re-used between SessionHandles, this might be a
connection already existing but on a fresh SessionHandle struct so we must
make sure we have a good 'struct SMTP' to play with. For new connections,
the struct SMTP is allocated and setup in the smtp_connect() function.
*/
Curl_reset_reqproto(conn);
retcode = smtp_init(conn);
if(retcode)
return retcode;
retcode = smtp_parse_url_path(conn);
if(retcode)
return retcode;
retcode = smtp_regular_transfer(conn, done);
return retcode;
}
/***********************************************************************
*
* smtp_quit()
*
* This should be called before calling sclose(). We should then wait for the
* response from the server before returning. The calling code should then try
* to close the connection.
*
*/
static CURLcode smtp_quit(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
result = Curl_pp_sendf(&conn->proto.smtpc.pp, "QUIT", NULL);
if(result)
return result;
state(conn, SMTP_QUIT);
result = smtp_easy_statemach(conn);
return result;
}
/***********************************************************************
*
* smtp_disconnect()
*
* Disconnect from an SMTP server. Cleanup protocol-specific per-connection
* resources. BLOCKING.
*/
static CURLcode smtp_disconnect(struct connectdata *conn)
{
struct smtp_conn *smtpc= &conn->proto.smtpc;
/* We cannot send quit unconditionally. If this connection is stale or
bad in any way, sending quit and waiting around here will make the
disconnect wait in vain and cause more problems than we need to.
*/
/* The SMTP session may or may not have been allocated/setup at this
point! */
(void)smtp_quit(conn); /* ignore errors on the LOGOUT */
Curl_pp_disconnect(&smtpc->pp);
return CURLE_OK;
}
/***********************************************************************
*
* smtp_parse_url_path()
*
* Parse the URL path into separate path components.
*
*/
static CURLcode smtp_parse_url_path(struct connectdata *conn)
{
/* the smtp struct is already inited in smtp_connect() */
struct smtp_conn *smtpc = &conn->proto.smtpc;
struct SessionHandle *data = conn->data;
/* url decode... */
return CURLE_OK;
}
/* call this when the DO phase has completed */
static CURLcode smtp_dophase_done(struct connectdata *conn,
bool connected)
{
CURLcode result = CURLE_OK;
struct FTP *smtp = conn->data->state.proto.smtp;
(void)connected;
if(smtp->transfer != FTPTRANSFER_BODY)
/* no data to transfer */
result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
return result;
}
/* called from multi.c while DOing */
static CURLcode smtp_doing(struct connectdata *conn,
bool *dophase_done)
{
CURLcode result;
result = smtp_multi_statemach(conn, dophase_done);
if(*dophase_done) {
result = smtp_dophase_done(conn, FALSE /* not connected */);
DEBUGF(infof(conn->data, "DO phase is complete\n"));
}
return result;
}
/***********************************************************************
*
* smtp_regular_transfer()
*
* The input argument is already checked for validity.
*
* Performs all commands done before a regular transfer between a local and a
* remote host.
*/
static
CURLcode smtp_regular_transfer(struct connectdata *conn,
bool *dophase_done)
{
CURLcode result=CURLE_OK;
bool connected=FALSE;
struct SessionHandle *data = conn->data;
data->req.size = -1; /* make sure this is unknown at this point */
Curl_pgrsSetUploadCounter(data, 0);
Curl_pgrsSetDownloadCounter(data, 0);
Curl_pgrsSetUploadSize(data, 0);
Curl_pgrsSetDownloadSize(data, 0);
result = smtp_perform(conn,
&connected, /* have we connected after PASV/PORT */
dophase_done); /* all commands in the DO-phase done? */
if(CURLE_OK == result) {
if(!*dophase_done)
/* the DO phase has not completed yet */
return CURLE_OK;
result = smtp_dophase_done(conn, connected);
if(result)
return result;
}
return result;
}
static CURLcode smtp_setup_connection(struct connectdata * conn)
{
struct SessionHandle *data = conn->data;
if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) {
/* Unless we have asked to tunnel smtp operations through the proxy, we
switch and use HTTP operations only */
#ifndef CURL_DISABLE_HTTP
if(conn->handler == &Curl_handler_smtp)
conn->handler = &Curl_handler_smtp_proxy;
else {
#ifdef USE_SSL
conn->handler = &Curl_handler_smtps_proxy;
#else
failf(data, "SMTPS not supported!");
return CURLE_UNSUPPORTED_PROTOCOL;
#endif
}
/*
* We explicitly mark this connection as persistent here as we're doing
* SMTP over HTTP and thus we accidentally avoid setting this value
* otherwise.
*/
conn->bits.close = FALSE;
#else
failf(data, "SMTP over http proxy requires HTTP support built-in!");
return CURLE_UNSUPPORTED_PROTOCOL;
#endif
}
data->state.path++; /* don't include the initial slash */
return CURLE_OK;
}
#endif /* CURL_DISABLE_SMTP */

61
lib/smtp.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef __SMTP_H
#define __SMTP_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* $Id$
***************************************************************************/
#include "pingpong.h"
/****************************************************************************
* SMTP unique setup
***************************************************************************/
typedef enum {
SMTP_STOP, /* do nothing state, stops the state machine */
SMTP_SERVERGREET, /* waiting for the initial greeting immediately after
a connect */
SMTP_EHLO,
SMTP_STARTTLS,
SMTP_MAIL, /* MAIL FROM */
SMTP_RCPT, /* RCPT TO */
SMTP_DATA,
SMTP_QUIT,
SMTP_LAST /* never used */
} smtpstate;
/* smtp_conn is used for struct connection-oriented data in the connectdata
struct */
struct smtp_conn {
struct pingpong pp;
char *domain; /* what to send in the EHLO */
int eob; /* number of bytes of the EOB (End Of Body) that has been
received thus far */
smtpstate state; /* always use smtp.c:state() to change state! */
};
extern const struct Curl_handler Curl_handler_smtp;
extern const struct Curl_handler Curl_handler_smtps;
/* this is the 5-bytes End-Of-Body marker for SMTP */
#define SMTP_EOB "\x0d\x0a\x2e\x0d\x0a"
#define SMTP_EOB_LEN 5
#endif /* __SMTP_H */

View File

@ -690,9 +690,17 @@ static CURLcode readwrite_data(struct SessionHandle *data,
/* This is the default when the server sends no /* This is the default when the server sends no
Content-Encoding header. See Curl_readwrite_init; the Content-Encoding header. See Curl_readwrite_init; the
memset() call initializes k->content_encoding to zero. */ 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, result = Curl_client_write(conn, CLIENTWRITE_BODY, k->str,
nread); nread);
}
#ifdef HAVE_LIBZ #ifdef HAVE_LIBZ
break; break;

View File

@ -130,6 +130,7 @@ void idn_free (void *ptr); /* prototype from idn-free.h, not provided by
#include "file.h" #include "file.h"
#include "curl_ldap.h" #include "curl_ldap.h"
#include "ssh.h" #include "ssh.h"
#include "imap.h"
#include "url.h" #include "url.h"
#include "connect.h" #include "connect.h"
#include "inet_ntop.h" #include "inet_ntop.h"
@ -204,6 +205,27 @@ static const struct Curl_handler * const protocols[] = {
&Curl_handler_sftp, &Curl_handler_sftp,
#endif #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 (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); data->set.ftp_create_missing_dirs = (int)va_arg(param, long);
break; break;
case CURLOPT_FTP_RESPONSE_TIMEOUT: case CURLOPT_SERVER_RESPONSE_TIMEOUT:
/* /*
* An FTP option that specifies how quickly an FTP response must be * Option that specifies how quickly an server response must be obtained
* obtained before it is considered failure. * 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; break;
case CURLOPT_TFTP_BLKSIZE: 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; data->set.redir_protocols = va_arg(param, long) & PROT_EXTMASK;
break; 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: default:
/* unknown tag and its companion, just ignore: */ /* unknown tag and its companion, just ignore: */
result = CURLE_FAILED_INIT; /* correct this */ result = CURLE_FAILED_INIT; /* correct this */
@ -3334,6 +3366,8 @@ static CURLcode ParseURLAndFillConnection(struct SessionHandle *data,
strcpy(conn->protostr, "DICT"); strcpy(conn->protostr, "DICT");
else if(checkprefix("LDAP.", conn->host.name)) else if(checkprefix("LDAP.", conn->host.name))
strcpy(conn->protostr, "LDAP"); strcpy(conn->protostr, "LDAP");
else if(checkprefix("IMAP.", conn->host.name))
strcpy(conn->protostr, "IMAP");
else { else {
strcpy(conn->protostr, "http"); strcpy(conn->protostr, "http");
} }
@ -4069,7 +4103,7 @@ static CURLcode set_userpass(struct connectdata *conn,
const char *user, const char *passwd) const char *user, const char *passwd)
{ {
/* If our protocol needs a password and we have none, use the defaults */ /* 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->bits.user_passwd) {
conn->user = strdup(CURL_DEFAULT_USER); conn->user = strdup(CURL_DEFAULT_USER);

View File

@ -37,6 +37,12 @@
#define PORT_LDAPS 636 #define PORT_LDAPS 636
#define PORT_TFTP 69 #define PORT_TFTP 69
#define PORT_SSH 22 #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_MATCH "/MATCH:"
#define DICT_MATCH2 "/M:" #define DICT_MATCH2 "/M:"
@ -51,6 +57,11 @@
/* length of longest IPv6 address string including the trailing null */ /* 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") #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 "cookie.h"
#include "formdata.h" #include "formdata.h"
@ -129,6 +140,11 @@
#include "hash.h" #include "hash.h"
#include "splay.h" #include "splay.h"
#include "imap.h"
#include "pop3.h"
#include "smtp.h"
#include "ftp.h"
#ifdef HAVE_GSSAPI #ifdef HAVE_GSSAPI
# ifdef HAVE_GSSGNU # ifdef HAVE_GSSGNU
# include <gss.h> # include <gss.h>
@ -351,121 +367,6 @@ struct HTTP {
points to an allocated send_buffer struct */ 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 * SSH unique setup
@ -922,18 +823,23 @@ struct connectdata {
#define PROT_TFTP CURLPROTO_TFTP #define PROT_TFTP CURLPROTO_TFTP
#define PROT_SCP CURLPROTO_SCP #define PROT_SCP CURLPROTO_SCP
#define PROT_SFTP CURLPROTO_SFTP #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 /* (1<<17) is currently the highest used bit in the public bitmask. We make
bitmask. We make sure we use "private bits" above the first 16 to make sure we use "private bits" above the public ones to make things easier. */
things easier. */
#define PROT_EXTMASK 0xffff #define PROT_EXTMASK 0xfffff
#define PROT_SSL (1<<22) /* protocol requires SSL */ #define PROT_SSL (1<<25) /* protocol requires SSL */
#define PROT_MISSING (1<<23) #define PROT_MISSING (1<<26)
/* these ones need action before socket close */ /* 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 */ #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 /* '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 struct curl_llist *pend_pipe; /* List of pending handles on
this pipeline */ this pipeline */
struct curl_llist *done_pipe; /* Handles that are finished, but struct curl_llist *done_pipe; /* Handles that are finished, but
still reference this connectdata */ still reference this connectdata */
#define MAX_PIPELINE_LENGTH 5 #define MAX_PIPELINE_LENGTH 5
char* master_buffer; /* The master buffer allocated on-demand; char* master_buffer; /* The master buffer allocated on-demand;
@ -1075,6 +981,9 @@ struct connectdata {
struct ftp_conn ftpc; struct ftp_conn ftpc;
struct ssh_conn sshc; struct ssh_conn sshc;
struct tftp_state_data *tftpc; struct tftp_state_data *tftpc;
struct imap_conn imapc;
struct pop3_conn pop3c;
struct smtp_conn smtpc;
} proto; } proto;
int cselect_bits; /* bitmask of socket events */ int cselect_bits; /* bitmask of socket events */
@ -1331,6 +1240,9 @@ struct UrlState {
void *telnet; /* private for telnet.c-eyes only */ void *telnet; /* private for telnet.c-eyes only */
void *generic; void *generic;
struct SSHPROTO *ssh; struct SSHPROTO *ssh;
struct FTP *imap;
struct FTP *pop3;
struct FTP *smtp;
} proto; } proto;
/* current user of this SessionHandle instance, or NULL */ /* current user of this SessionHandle instance, or NULL */
struct connectdata *current_conn; struct connectdata *current_conn;
@ -1412,6 +1324,8 @@ enum dupstring {
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
STRING_SOCKS5_GSSAPI_SERVICE, /* GSSAPI service name */ STRING_SOCKS5_GSSAPI_SERVICE, /* GSSAPI service name */
#endif #endif
STRING_MAIL_FROM,
STRING_MAIL_RCPT,
/* -- end of strings -- */ /* -- end of strings -- */
STRING_LAST /* not used, just an end-of-list marker */ 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 */ void *ioctl_client; /* pointer to pass to the ioctl callback */
long timeout; /* in milliseconds, 0 means no timeout */ long timeout; /* in milliseconds, 0 means no timeout */
long connecttimeout; /* 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 */ long tftp_blksize ; /* in bytes, 0 means use default */
curl_off_t infilesize; /* size of file to upload, -1 means unknown */ curl_off_t infilesize; /* size of file to upload, -1 means unknown */
long low_speed_limit; /* bytes/second */ 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_epsv; /* if EPSV is to be attempted or not */
bool ftp_use_eprt; /* if EPRT 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_ftpauth ftpsslauth; /* what AUTH XXX to be attempted */
curl_ftpccc ftp_ccc; /* FTP CCC options */ curl_ftpccc ftp_ccc; /* FTP CCC options */
bool no_signal; /* do not use any signal/alarm handler */ bool no_signal; /* do not use any signal/alarm handler */

View File

@ -158,6 +158,27 @@ static const char * const protocols[] = {
"sftp", "sftp",
#endif #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 NULL
}; };

View File

@ -500,6 +500,8 @@ struct Configurable {
char *proxy; char *proxy;
int proxyver; /* set to CURLPROXY_HTTP* define */ int proxyver; /* set to CURLPROXY_HTTP* define */
char *noproxy; char *noproxy;
char *mail_from;
char *mail_rcpt;
bool proxytunnel; bool proxytunnel;
bool ftp_append; /* APPE on ftp */ bool ftp_append; /* APPE on ftp */
bool mute; /* shutup */ bool mute; /* shutup */
@ -822,6 +824,8 @@ static void help(void)
" -L/--location Follow Location: hints (H)", " -L/--location Follow Location: hints (H)",
" --location-trusted Follow Location: and send auth to other hosts (H)", " --location-trusted Follow Location: and send auth to other hosts (H)",
" -M/--manual Display the full manual", " -M/--manual Display the full manual",
" --mail-from <from> Mail from this address",
" --mail-rcpt <to> Mail to this receiver(s)",
" --max-filesize <bytes> Maximum file size to download (H/F)", " --max-filesize <bytes> Maximum file size to download (H/F)",
" --max-redirs <num> Maximum number of redirects allowed (H)", " --max-redirs <num> Maximum number of redirects allowed (H)",
" -m/--max-time <seconds> Maximum time allowed for the transfer", " -m/--max-time <seconds> Maximum time allowed for the transfer",
@ -1740,6 +1744,8 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
#endif #endif
{"$8", "proxy1.0", TRUE}, {"$8", "proxy1.0", TRUE},
{"$9", "tftp-blksize", TRUE}, {"$9", "tftp-blksize", TRUE},
{"$A", "mail-from", TRUE},
{"$B", "mail-rcpt", TRUE},
{"0", "http1.0", FALSE}, {"0", "http1.0", FALSE},
{"1", "tlsv1", FALSE}, {"1", "tlsv1", FALSE},
{"2", "sslv2", FALSE}, {"2", "sslv2", FALSE},
@ -2269,6 +2275,12 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
case '9': /* --tftp-blksize */ case '9': /* --tftp-blksize */
str2num(&config->tftp_blksize, nextarg); str2num(&config->tftp_blksize, nextarg);
break; break;
case 'A': /* --mail-from */
GetStr(&config->mail_from, nextarg);
break;
case 'B': /* --mail-rcpt */
GetStr(&config->mail_rcpt, nextarg);
break;
} }
break; break;
case '#': /* --progress-bar */ case '#': /* --progress-bar */
@ -5006,9 +5018,16 @@ operate(struct Configurable *config, int argc, argv_item_t argv[])
my_setopt(curl, CURLOPT_POSTREDIR, config->post301 | my_setopt(curl, CURLOPT_POSTREDIR, config->post301 |
(config->post302 ? CURL_REDIR_POST_302 : FALSE)); (config->post302 ? CURL_REDIR_POST_302 : FALSE));
/* curl 7.20.0 */
if(config->tftp_blksize) if(config->tftp_blksize)
my_setopt(curl, CURLOPT_TFTP_BLKSIZE, 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; retry_numretries = config->req_retry;
retrystart = cutil_tvnow(); retrystart = cutil_tvnow();

View File

@ -63,7 +63,7 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46 \
test1089 test1090 test1091 test1092 test1093 test1094 test1095 test1096 \ test1089 test1090 test1091 test1092 test1093 test1094 test1095 test1096 \
test1097 test560 test561 test1098 test1099 test562 test563 test1100 \ test1097 test560 test561 test1098 test1099 test562 test563 test1100 \
test564 test1101 test1102 test1103 test1104 test299 test310 test311 \ test564 test1101 test1102 test1103 test1104 test299 test310 test311 \
test312 test1105 test565 test312 test1105 test565 test800
filecheck: filecheck:
@mkdir test-place; \ @mkdir test-place; \

47
tests/data/test800 Normal file
View File

@ -0,0 +1,47 @@
<testcase>
<info>
<keywords>
POP3
RETR
</keywords>
</info>
#
# Server-side
<reply>
<data>
From: me@somewhere
To: fake@nowhere
body
--
yours sincerely
</data>
</reply>
#
# Client-side
<client>
<server>
pop3
</server>
<name>
POP3 RETR
</name>
<command>
pop3://%HOSTIP:%POP3PORT/800 -u user:secret
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
USER user
PASS secret
RETR 800
QUIT
</protocol>
</verify>
</testcase>

View File

@ -22,7 +22,11 @@
# $Id$ # $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 # It is meant to exercise curl, it is not meant to be a fully working
# or even very standard compliant server. # 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 $SERVERLOGS_LOCK="log/serverlogs.lock"; # server logs advisor read lock
my $serverlogslocked=0; my $serverlogslocked=0;
my $proto="ftp";
do { do {
if($ARGV[0] eq "-v") { if($ARGV[0] eq "-v") {
$verbose=1; $verbose=1;
@ -100,6 +106,11 @@ do {
$ftpdnum=$ARGV[1]; $ftpdnum=$ARGV[1];
shift @ARGV; shift @ARGV;
} }
elsif($ARGV[0] eq "--proto") {
# ftp pop3 imap smtp
$proto=$ARGV[1];
shift @ARGV;
}
elsif($ARGV[0] eq "--pidfile") { elsif($ARGV[0] eq "--pidfile") {
$pidfile=$ARGV[1]; $pidfile=$ARGV[1];
shift @ARGV; shift @ARGV;
@ -115,23 +126,28 @@ do {
} }
elsif($ARGV[0] eq "--addr") { elsif($ARGV[0] eq "--addr") {
$listenaddr = $ARGV[1]; $listenaddr = $ARGV[1];
$listenaddr =~ s/^\[(.*)\]$/\1/; $listenaddr =~ s/^\[(.*)\]$/$1/;
shift @ARGV; shift @ARGV;
} }
} while(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 { sub catch_zap {
my $signame = shift; my $signame = shift;
print STDERR "ftpserver.pl received SIG$signame, exiting\n";
ftpkillslaves(1); ftpkillslaves(1);
unlink($pidfile);
if($serverlogslocked) { if($serverlogslocked) {
$serverlogslocked = 0; $serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK); clear_advisor_read_lock($SERVERLOGS_LOCK);
} }
exit; die "Somebody sent me a SIG$signame";
} }
$SIG{INT} = \&catch_zap; $SIG{INT} = \&catch_zap;
$SIG{TERM} = \&catch_zap; $SIG{KILL} = \&catch_zap;
my $sfpid; my $sfpid;
@ -153,7 +169,6 @@ sub sysread_or_die {
logmsg "Error: ftp$ftpdnum$ext sysread error: $!\n"; logmsg "Error: ftp$ftpdnum$ext sysread error: $!\n";
kill(9, $sfpid); kill(9, $sfpid);
waitpid($sfpid, 0); waitpid($sfpid, 0);
unlink($pidfile);
if($serverlogslocked) { if($serverlogslocked) {
$serverlogslocked = 0; $serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK); clear_advisor_read_lock($SERVERLOGS_LOCK);
@ -167,7 +182,6 @@ sub sysread_or_die {
logmsg "Error: ftp$ftpdnum$ext read zero\n"; logmsg "Error: ftp$ftpdnum$ext read zero\n";
kill(9, $sfpid); kill(9, $sfpid);
waitpid($sfpid, 0); waitpid($sfpid, 0);
unlink($pidfile);
if($serverlogslocked) { if($serverlogslocked) {
$serverlogslocked = 0; $serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK); clear_advisor_read_lock($SERVERLOGS_LOCK);
@ -193,7 +207,6 @@ sub startsf {
logmsg "Failed sockfilt command: $cmd\n"; logmsg "Failed sockfilt command: $cmd\n";
kill(9, $sfpid); kill(9, $sfpid);
waitpid($sfpid, 0); waitpid($sfpid, 0);
unlink($pidfile);
if($serverlogslocked) { if($serverlogslocked) {
$serverlogslocked = 0; $serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK); 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(); 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"); open(PID, ">$pidfile");
print PID $$."\n"; print PID $$."\n";
close(PID); close(PID);
@ -273,41 +290,66 @@ sub senddata {
} }
} }
# this text is shown before the function specified below is run my %displaytext;
my %displaytext = ('USER' => '331 We are happy you popped in!', my %commandfunc;
'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',
);
# callback functions for certain commands # callback functions for certain commands
my %commandfunc = ( 'PORT' => \&PORT_command, # and text shown before the function specified below is run
'EPRT' => \&PORT_command,
'LIST' => \&LIST_command, if($proto eq "ftp") {
'NLST' => \&NLST_command, %displaytext = ('USER' => '331 We are happy you popped in!',
'PASV' => \&PASV_command, 'PASS' => '230 Welcome you silly person',
'EPSV' => \&PASV_command, 'PORT' => '200 You said PORT - I say FINE',
'RETR' => \&RETR_command, 'TYPE' => '200 I modify TYPE as you wanted',
'SIZE' => \&SIZE_command, 'LIST' => '150 here comes a directory',
'REST' => \&REST_command, 'NLST' => '150 here comes a directory',
'STOR' => \&STOR_command, 'CWD' => '250 CWD command successful.',
'APPE' => \&STOR_command, # append looks like upload 'SYST' => '215 UNIX Type: L8', # just fake something
'MDTM' => \&MDTM_command, '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 { sub close_dataconn {
@ -330,6 +372,98 @@ sub close_dataconn {
$slavepid=0; $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; my $rest=0;
sub REST_command { sub REST_command {
$rest = $_[0]; $rest = $_[0];
@ -798,12 +932,34 @@ sub customize {
close(CUSTOM); close(CUSTOM);
} }
my @welcome=( my @welcome;
'220- _ _ ____ _ '."\r\n",
'220- ___| | | | _ \| | '."\r\n", if($proto eq "ftp") {
'220- / __| | | | |_) | | '."\r\n", @welcome=(
'220- | (__| |_| | _ <| |___ '."\r\n", '220- _ _ ____ _ '."\r\n",
'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) { while(1) {
@ -872,13 +1028,28 @@ while(1) {
# Remove trailing CRLF. # Remove trailing CRLF.
s/[\n\r]+$//; s/[\n\r]+$//;
unless (m/^([A-Z]{3,4})\s?(.*)/i) { my $cmdid;
sendcontrol "500 '$_': command not understood.\r\n"; my $FTPCMD;
last; my $FTPARG;
}
my $FTPCMD=$1;
my $FTPARG=$2;
my $full=$_; 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"; logmsg "< \"$full\"\n";
@ -907,7 +1078,7 @@ while(1) {
} }
my $check; my $check;
if($text) { if($text) {
sendcontrol "$text\r\n"; sendcontrol "$cmdid$text\r\n";
} }
else { else {
$check=1; # no response yet $check=1; # no response yet
@ -939,8 +1110,6 @@ while(1) {
print SFWRITE "QUIT\n"; print SFWRITE "QUIT\n";
waitpid $sfpid, 0; waitpid $sfpid, 0;
unlink($pidfile);
if($serverlogslocked) { if($serverlogslocked) {
$serverlogslocked = 0; $serverlogslocked = 0;
clear_advisor_read_lock($SERVERLOGS_LOCK); clear_advisor_read_lock($SERVERLOGS_LOCK);

View File

@ -109,6 +109,9 @@ my $TFTPPORT; # TFTP
my $TFTP6PORT; # TFTP my $TFTP6PORT; # TFTP
my $SSHPORT; # SCP/SFTP my $SSHPORT; # SCP/SFTP
my $SOCKSPORT; # SOCKS4/5 port my $SOCKSPORT; # SOCKS4/5 port
my $POP3PORT; # POP3
my $IMAPPORT; # IMAP
my $SMTPPORT; # SMTP
my $srcdir = $ENV{'srcdir'} || '.'; my $srcdir = $ENV{'srcdir'} || '.';
my $CURL="../src/curl"; # what curl executable to run on the tests 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 $TFTP6PIDFILE=".tftp6.pid";
my $SSHPIDFILE=".ssh.pid"; my $SSHPIDFILE=".ssh.pid";
my $SOCKSPIDFILE=".socks.pid"; my $SOCKSPIDFILE=".socks.pid";
my $POP3PIDFILE=".pop3.pid";
my $IMAPPIDFILE=".imap.pid";
my $SMTPPIDFILE=".smtp.pid";
# invoke perl like this: # invoke perl like this:
my $perl="perl -I$srcdir"; my $perl="perl -I$srcdir";
@ -663,7 +669,7 @@ sub verifyftp {
} }
if($pid <= 0 && $data[0]) { if($pid <= 0 && $data[0]) {
# this is not a known server # 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; return 0;
} }
# we can/should use the time it took to verify the FTP server as a measure # 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; my $took = time()-$time;
if($verbose) { 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 $ftpchecktime = $took?$took:1; # make sure it never is zero
@ -773,6 +779,9 @@ sub verifysocks {
my %protofunc = ('http' => \&verifyhttp, my %protofunc = ('http' => \&verifyhttp,
'https' => \&verifyhttp, 'https' => \&verifyhttp,
'ftp' => \&verifyftp, 'ftp' => \&verifyftp,
'pop3' => \&verifyftp,
'imap' => \&verifyftp,
'smtp' => \&verifyftp,
'ftps' => \&verifyftp, 'ftps' => \&verifyftp,
'tftp' => \&verifyftp, 'tftp' => \&verifyftp,
'ssh' => \&verifyssh, 'ssh' => \&verifyssh,
@ -942,26 +951,48 @@ sub runhttpsserver {
} }
####################################################################### #######################################################################
# start the ftp server # start the pingpong server (FTP, POP3, IMAP, SMTP)
# #
sub runftpserver { sub runpingpongserver {
my ($id, $verbose, $ipv6) = @_; my ($proto, $id, $verbose, $ipv6) = @_;
my $STATUS; my $STATUS;
my $RUNNING; my $RUNNING;
my $port = $id?$FTP2PORT:$FTPPORT; my $port;
# check for pidfile my $pidfile;
my $pidfile = $id?$FTP2PIDFILE:$FTPPIDFILE;
my $ip=$HOSTIP; my $ip=$HOSTIP;
my $nameext; my $nameext;
my $cmd; my $cmd;
my $flag;
if($ipv6) { if($proto eq "ftp") {
# if IPv6, use a different setup $port = $id?$FTP2PORT:$FTPPORT;
$pidfile = $FTP6PIDFILE; $pidfile = $id?$FTP2PIDFILE:$FTPPIDFILE;
$port = $FTP6PORT;
$ip = $HOST6IP; if($ipv6) {
$nameext="-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 # don't retry if the server doesn't work
if ($doesntrun{$pidfile}) { if ($doesntrun{$pidfile}) {
@ -975,7 +1006,7 @@ sub runftpserver {
unlink($pidfile); unlink($pidfile);
# start our server: # start our server:
my $flag=$debugprotocol?"-v ":""; $flag.=$debugprotocol?"-v ":"";
$flag .= "-s \"$srcdir\" "; $flag .= "-s \"$srcdir\" ";
my $addr; my $addr;
if($id) { if($id) {
@ -993,7 +1024,7 @@ sub runftpserver {
if($ftppid <= 0 || !kill(0, $ftppid)) { if($ftppid <= 0 || !kill(0, $ftppid)) {
# it is NOT alive # 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"); stopserver("$pid2");
displaylogs($testnumcheck); displaylogs($testnumcheck);
$doesntrun{$pidfile} = 1; $doesntrun{$pidfile} = 1;
@ -1001,9 +1032,9 @@ sub runftpserver {
} }
# Server is up. Verify that we can speak to it. # Server is up. Verify that we can speak to it.
my $pid3 = verifyserver("ftp", $ip, $port); my $pid3 = verifyserver($proto, $ip, $port);
if(!$pid3) { 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 # failed to talk to it properly. Kill the server and return failure
stopserver("$ftppid $pid2"); stopserver("$ftppid $pid2");
displaylogs($testnumcheck); displaylogs($testnumcheck);
@ -1013,7 +1044,7 @@ sub runftpserver {
$pid2 = $pid3; $pid2 = $pid3;
if($verbose) { 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); sleep(1);
@ -1661,41 +1692,46 @@ sub checksystem {
"* Host: $hostname", "* Host: $hostname",
"* System: $hosttype"); "* System: $hosttype");
logmsg sprintf("* Server SSL: %s\n", $stunnel?"ON":"OFF"); logmsg sprintf("* Server SSL: %8s", $stunnel?"ON ":"OFF");
logmsg sprintf("* libcurl SSL: %s\n", $ssl_version?"ON":"OFF"); logmsg sprintf(" libcurl SSL: %s\n", $ssl_version?"ON ":"OFF");
logmsg sprintf("* debug build: %s\n", $debug_build?"ON":"OFF"); logmsg sprintf("* debug build: %8s", $debug_build?"ON ":"OFF");
logmsg sprintf("* track memory: %s\n", $curl_debug?"ON":"OFF"); logmsg sprintf(" track memory: %s\n", $curl_debug?"ON ":"OFF");
logmsg sprintf("* valgrind: %s\n", $valgrind?"ON":"OFF"); logmsg sprintf("* valgrind: %8s", $valgrind?"ON ":"OFF");
logmsg sprintf("* HTTP IPv6 %s\n", $http_ipv6?"ON":"OFF"); logmsg sprintf(" HTTP IPv6 %s\n", $http_ipv6?"ON ":"OFF");
logmsg sprintf("* FTP IPv6 %s\n", $ftp_ipv6?"ON":"OFF"); logmsg sprintf("* FTP IPv6 %8s", $ftp_ipv6?"ON ":"OFF");
logmsg sprintf(" Libtool lib: %s\n", $libtool?"ON ":"OFF");
logmsg sprintf("* HTTP port: %d\n", $HTTPPORT); if($ssl_version) {
logmsg sprintf("* FTP port: %d\n", $FTPPORT); logmsg sprintf("* SSL library: %s\n", $ssllib);
logmsg sprintf("* FTP port 2: %d\n", $FTP2PORT);
if($stunnel) {
logmsg sprintf("* FTPS port: %d\n", $FTPSPORT);
logmsg sprintf("* HTTPS port: %d\n", $HTTPSPORT);
} }
logmsg "* Ports:\n";
logmsg sprintf("* HTTP/%d ", $HTTPPORT);
logmsg sprintf("FTP/%d ", $FTPPORT);
logmsg sprintf("FTP2/%d ", $FTP2PORT);
if($stunnel) {
logmsg sprintf("FTPS/%d ", $FTPSPORT);
logmsg sprintf("HTTPS/%d ", $HTTPSPORT);
}
logmsg sprintf("\n* TFTP/%d ", $TFTPPORT);
if($http_ipv6) { if($http_ipv6) {
logmsg sprintf("* HTTP IPv6 port: %d\n", $HTTP6PORT); logmsg sprintf("HTTP-IPv6/%d ", $HTTP6PORT);
} }
if($ftp_ipv6) { 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) { 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("\n* SSH/%d ", $SSHPORT);
logmsg sprintf("* SOCKS port: %d\n", $SOCKSPORT); 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'); $has_textaware = ($^O eq 'MSWin32') || ($^O eq 'msys');
logmsg sprintf("* Libtool lib: %s\n", $libtool?"ON":"OFF");
logmsg "***************************************** \n"; logmsg "***************************************** \n";
} }
@ -1720,6 +1756,9 @@ sub subVariables {
$$thing =~ s/%TFTP6PORT/$TFTP6PORT/g; $$thing =~ s/%TFTP6PORT/$TFTP6PORT/g;
$$thing =~ s/%SSHPORT/$SSHPORT/g; $$thing =~ s/%SSHPORT/$SSHPORT/g;
$$thing =~ s/%SOCKSPORT/$SOCKSPORT/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/%CURL/$CURL/g;
$$thing =~ s/%USER/$USER/g; $$thing =~ s/%USER/$USER/g;
$$thing =~ s/%CLIENTIP/$CLIENTIP/g; $$thing =~ s/%CLIENTIP/$CLIENTIP/g;
@ -2546,19 +2585,22 @@ sub startservers {
my $what = lc($whatlist[0]); my $what = lc($whatlist[0]);
$what =~ s/[^a-z0-9-]//g; $what =~ s/[^a-z0-9-]//g;
if($what eq "ftp") { if(($what eq "pop3") ||
if(!$run{'ftp'}) { ($what eq "ftp") ||
($pid, $pid2) = runftpserver("", $verbose); ($what eq "imap") ||
($what eq "smtp")) {
if(!$run{$what}) {
($pid, $pid2) = runpingpongserver($what, "", $verbose);
if($pid <= 0) { if($pid <= 0) {
return "failed starting FTP server"; return "failed starting $what server";
} }
printf ("* pid ftp => %d %d\n", $pid, $pid2) if($verbose); printf ("* pid $what => %d %d\n", $pid, $pid2) if($verbose);
$run{'ftp'}="$pid $pid2"; $run{$what}="$pid $pid2";
} }
} }
elsif($what eq "ftp2") { elsif($what eq "ftp2") {
if(!$run{'ftp2'}) { if(!$run{'ftp2'}) {
($pid, $pid2) = runftpserver("2", $verbose); ($pid, $pid2) = runpingpongserver("ftp", "2", $verbose);
if($pid <= 0) { if($pid <= 0) {
return "failed starting FTP2 server"; return "failed starting FTP2 server";
} }
@ -2568,7 +2610,7 @@ sub startservers {
} }
elsif($what eq "ftp-ipv6") { elsif($what eq "ftp-ipv6") {
if(!$run{'ftp-ipv6'}) { if(!$run{'ftp-ipv6'}) {
($pid, $pid2) = runftpserver("", $verbose, "ipv6"); ($pid, $pid2) = runpingpongserver("ftp", "", $verbose, "ipv6");
if($pid <= 0) { if($pid <= 0) {
return "failed starting FTP-IPv6 server"; return "failed starting FTP-IPv6 server";
} }
@ -2609,7 +2651,7 @@ sub startservers {
} }
if(!$run{'ftp'}) { if(!$run{'ftp'}) {
($pid, $pid2) = runftpserver("", $verbose); ($pid, $pid2) = runpingpongserver("ftp", "", $verbose);
if($pid <= 0) { if($pid <= 0) {
return "failed starting FTP server"; return "failed starting FTP server";
} }
@ -2939,18 +2981,21 @@ if ($gdbthis) {
} }
} }
$HTTPPORT = $base + 0; # HTTP server port $HTTPPORT = $base++; # HTTP server port
$HTTPSPORT = $base + 1; # HTTPS server port $HTTPSPORT = $base++; # HTTPS server port
$FTPPORT = $base + 2; # FTP server port $FTPPORT = $base++; # FTP server port
$FTPSPORT = $base + 3; # FTPS server port $FTPSPORT = $base++; # FTPS server port
$HTTP6PORT = $base + 4; # HTTP IPv6 server port (different IP protocol $HTTP6PORT = $base++; # HTTP IPv6 server port (different IP protocol
# but we follow the same port scheme anyway) # but we follow the same port scheme anyway)
$FTP2PORT = $base + 5; # FTP server 2 port $FTP2PORT = $base++; # FTP server 2 port
$FTP6PORT = $base + 6; # FTP IPv6 port $FTP6PORT = $base++; # FTP IPv6 port
$TFTPPORT = $base + 7; # TFTP (UDP) port $TFTPPORT = $base++; # TFTP (UDP) port
$TFTP6PORT = $base + 8; # TFTP IPv6 (UDP) port $TFTP6PORT = $base++; # TFTP IPv6 (UDP) port
$SSHPORT = $base + 9; # SSH (SCP/SFTP) port $SSHPORT = $base++; # SSH (SCP/SFTP) port
$SOCKSPORT = $base + 10; # SOCKS port $SOCKSPORT = $base++; # SOCKS port
$POP3PORT = $base++;
$IMAPPORT = $base++;
$SMTPPORT = $base++;
####################################################################### #######################################################################
# clear and create logging directory: # clear and create logging directory: