diff options
-rw-r--r-- | AUTHORS | 3 | ||||
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | common/smtppass.c | 169 | ||||
-rw-r--r-- | common/smtppass.h | 6 | ||||
-rw-r--r-- | common/spio.c | 12 | ||||
-rw-r--r-- | common/sppriv.h | 2 | ||||
-rw-r--r-- | src/clamsmtpd.c | 21 |
7 files changed, 193 insertions, 21 deletions
@@ -4,6 +4,7 @@ Nate Nielsen <nielsen@memberwebs.com> CONTRIBUTORS: Andreas Steinmetz <ast@domdv.de> Rubio Vaughan <rubio@passim.net> +Olivier Beyssac <ob@r14.freenix.org> PATCHES: Berk D. Demir <demir@meteksan.net.tr> @@ -11,5 +12,5 @@ João Carlos Mendes Luís <jcmendes@int.gov.br> Jasper Slits <jasper@insiders.nl> Yamamoto Takao <takao@oakat.org> Ben Mesman <ben@bncnetservice.nl> -Olivier Beyssac <ob@r14.freenix.org> Chris Mason <Chris.Mason@vodafone.com> + @@ -2,6 +2,7 @@ - Documentation fixes [Sean Franklin] - Don't leak file descriptors when clamsmtpd can't connect to outgoing SMTP server [Chris Mason] + - Now accepts special format arguments on 'Header' line [Olivier Beyssac] 1.3 [2005-01-27] - Fixed crasher when outgoing connection couldn't be established diff --git a/common/smtppass.c b/common/smtppass.c index 7453e06..6189262 100644 --- a/common/smtppass.c +++ b/common/smtppass.c @@ -35,6 +35,7 @@ * Nate Nielsen <nielsen@memberwebs.com> * Andreas Steinmetz <ast@domdv.de> * Rubio Vaughan <rubio@passim.net> + * Olivier Beyssac <ob@r14.freenix.org> */ #include <sys/time.h> @@ -54,6 +55,7 @@ #include <paths.h> #include <stdarg.h> #include <pwd.h> +#include <time.h> #include "usuals.h" @@ -126,10 +128,22 @@ spthread_t; #define OK_RSP "250" #define START_RSP "220" +#define RCVD_HEADER "Received:" + /* The set of delimiters that can be present between config and value */ -#define CFG_DELIMS ": \t" +#define CFG_DELIMS ": \t" + +/* Maximum length of the header argument */ +#define MAX_HEADER_LENGTH 1024 + +/* + * asctime_r manpage: "stores the string in a user-supplied buffer of + * length at least 26". We'll need some more bytes to put timezone + * information behind + */ +#define MAX_DATE_LENGTH 64 -#define LINE_TOO_LONG(l) ((l) >= (SP_LINE_LENGTH - 2)) +#define LINE_TOO_LONG(l) ((l) >= (SP_LINE_LENGTH - 2)) /* ----------------------------------------------------------------------- * CONFIGURATION OPTIONS @@ -145,6 +159,7 @@ spthread_t; #define CFG_TIMEOUT "TimeOut" #define CFG_OUTADDR "OutAddress" #define CFG_LISTENADDR "Listen" +#define CFG_HEADER "Header" #define CFG_TRANSPARENT "TransparentProxy" #define CFG_DIRECTORY "TempDirectory" #define CFG_KEEPALIVES "KeepAlives" @@ -171,7 +186,6 @@ unsigned int g_unique_id = 0x00100000; /* For connection ids */ pthread_mutex_t g_mutex; /* The main mutex */ pthread_mutexattr_t g_mtxattr; - /* ----------------------------------------------------------------------- * FORWARD DECLARATIONS */ @@ -1309,12 +1323,133 @@ int sp_cache_data(spctx_t* ctx) return count; } -int sp_done_data(spctx_t* ctx, const char* header) +/* Important: |date| should be at least MAX_DATE_LENGTH long */ +static void make_date(spctx_t* ctx, char* date) +{ + size_t date_len; + struct tm t2; + long gmt; + time_t t; + + /* Get a basic date like: 'Wed Jun 30 21:49:08 1993' */ + if(time(&t) == (time_t)-1 || + !localtime_r(&t, &t2) || + !asctime_r(&t2, date)) + { + sp_message(ctx, LOG_WARNING, "unable to get date for header"); + date[0] = 0; + return; + } + + /* Now add the TZ */ + trim_end(date); + gmt = t2.tm_gmtoff; + date_len = strlen(date); + + snprintf(date + date_len, MAX_DATE_LENGTH - date_len, " %+03d%02d (%s)", + (int)(gmt / 3600), (int)(gmt % 3600), t2.tm_zone); + + /* Break it off just in case */ + date[MAX_DATE_LENGTH - 1] = 0; +} + +/* Important: |header| should be a buffer of MAX_HEADER_LENGTH */ +static int make_header(spctx_t* ctx, const char* format_str, char* header) +{ + char date[MAX_DATE_LENGTH]; + int remaining, l; + const char* f; + char* p; + + date[0] = 0; + remaining = MAX_HEADER_LENGTH - 1; + p = header; + + /* Parse the format string and replace special characters with our data */ + for(f = format_str; *f && remaining > 0; f++) + { + /* A backslash escapes certain characters */ + if(f[0] == '\\' && f[1] != 0) + { + switch(*(++f)) + { + case 'r': + *p = '\r'; + break; + case 'n': + *p = '\n'; + break; + case 't': + *p = '\t'; + break; + default: + *p = *f; + break; + } + + ++f; + ++p; + --remaining; + } + + /* + * Special symbols: + * %i: client's IP + * %l: server's IP + * %d: date + */ + else if(f[0] == '%' && f[1] != 0) + { + switch(*(++f)) + { + case 'i': + l = strlen(ctx->client.peername); + strncpy(p, ctx->client.peername, remaining); + remaining -= l; + p += l; + break; + case 'l': + l = strlen(ctx->client.localname); + strncpy(p, ctx->client.localname, remaining); + remaining -= l; + p += l; + break; + case 'd': + if(date[0] == 0) + make_date(ctx, date); + l = strlen(date); + strncpy(p, date, remaining); + remaining -= l; + p += l; + break; + default: + sp_message(ctx, LOG_WARNING, "invalid header symbol: %%%c", *f); + break; + }; + } + + else + { + *(p++) = *(f++); + remaining--; + } + } + + if(p < header + MAX_HEADER_LENGTH) + *p = 0; + header[MAX_HEADER_LENGTH - 1] = 0; + l = p - header; + return (l > MAX_HEADER_LENGTH ? MAX_HEADER_LENGTH : l) - 1; +} + +int sp_done_data(spctx_t* ctx) { FILE* file = 0; int had_header = 0; int ret = 0; char line[SP_LINE_LENGTH]; + char header[MAX_HEADER_LENGTH]; + size_t header_len; ASSERT(ctx->cachename[0]); /* Must still be around */ ASSERT(!ctx->cachefile); /* File must be closed */ @@ -1347,6 +1482,18 @@ int sp_done_data(spctx_t* ctx, const char* header) sp_messagex(ctx, LOG_DEBUG, "sending from cache file: %s", ctx->cachename); + if(g_state.header) + header_len = make_header(ctx, g_state.header, header); + + /* If we have to prepend the header, do it */ + if(header[0] && g_state.header_prepend) + { + if(spio_write_data_raw(ctx, &(ctx->server), (char*)header, header_len) == -1 || + spio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1) + RETURN(-1); + had_header = 1; + } + /* Transfer actual file data */ while(fgets(line, SP_LINE_LENGTH, file) != NULL) { @@ -1359,7 +1506,7 @@ int sp_done_data(spctx_t* ctx, const char* header) if(strcmp(line, "." CRLF) == 0) strncpy(line, ". " CRLF, SP_LINE_LENGTH); - if(header && !had_header) + if(header[0] && !had_header) { /* * The first blank line we see means the headers are done. @@ -1367,7 +1514,7 @@ int sp_done_data(spctx_t* ctx, const char* header) */ if(is_blank_line(line)) { - if(spio_write_data_raw(ctx, &(ctx->server), (char*)header, strlen(header)) == -1 || + if(spio_write_data_raw(ctx, &(ctx->server), (char*)header, header_len) == -1 || spio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1) RETURN(-1); @@ -1723,6 +1870,16 @@ int sp_parse_option(const char* name, const char* value) ret = 1; } + else if(strcasecmp(CFG_HEADER, name) == 0) + { + g_state.header = trim_start(value); + if(strlen(g_state.header) == 0) + g_state.header = NULL; + else if(is_first_word(RCVD_HEADER, g_state.header, KL(RCVD_HEADER)) == 0) + g_state.header_prepend = 1; + ret = 1; + } + /* Always pass through to program */ if(cb_parse_option(name, value) == 1) ret = 1; diff --git a/common/smtppass.h b/common/smtppass.h index 5522ca9..9dbe935 100644 --- a/common/smtppass.h +++ b/common/smtppass.h @@ -68,6 +68,7 @@ typedef struct spio const char* name; /* The name for logging */ time_t last_action; /* Time of last action on descriptor */ char peername[MAXPATHLEN]; /* Name of the peer on other side of socket */ + char localname[MAXPATHLEN]; /* Address where we accepted the connection */ /* Internal use only */ char line[SP_LINE_LENGTH]; @@ -210,10 +211,9 @@ int sp_cache_data(spctx_t* ctx); /* * Sends the data in file buffer off to server. This is - * completes a successful mail transfer. The optional header - * is appended to the end of the email headers. + * completes a successful mail transfer. */ -int sp_done_data(spctx_t* ctx, const char* header); +int sp_done_data(spctx_t* ctx); /* * Fails the data, deletes any temp data, and sends given diff --git a/common/spio.c b/common/spio.c index 67e0405..a723936 100644 --- a/common/spio.c +++ b/common/spio.c @@ -113,6 +113,7 @@ void spio_init(spio_t* io, const char* name) void spio_attach(spctx_t* ctx, spio_t* io, int fd, struct sockaddr_any* peer) { struct sockaddr_any peeraddr; + struct sockaddr_any locaddr; io->fd = fd; @@ -130,6 +131,17 @@ void spio_attach(spctx_t* ctx, spio_t* io, int fd, struct sockaddr_any* peer) strlcpy(io->peername, "UNKNOWN", MAXPATHLEN); } + /* Get the address on which we accepted the connection */ + memset(&locaddr, 0, sizeof(locaddr)); + SANY_LEN(locaddr) = sizeof(locaddr); + + if(getsockname(fd, &SANY_ADDR(locaddr), &SANY_LEN(locaddr)) == -1 || + sock_any_ntop(&locaddr, io->localname, MAXPATHLEN, SANY_OPT_NOPORT) == -1) + { + sp_message(ctx, LOG_WARNING, "%s: couldn't get socket address", GET_IO_NAME(io)); + strlcpy(io->localname, "UNKNOWN", MAXPATHLEN); + } + /* As a double check */ io->line[0] = 0; io->_nx = NULL; diff --git a/common/sppriv.h b/common/sppriv.h index 2ecf249..a690f3c 100644 --- a/common/sppriv.h +++ b/common/sppriv.h @@ -53,6 +53,8 @@ typedef struct spstate const char* directory; /* The temp directory */ const char* user; /* User to run as */ const char* pidfile; /* The pid file for daemon */ + const char* header; /* A header to include in the email */ + int header_prepend; /* Prepend the header or not */ struct sockaddr_any outaddr; /* The outgoing address */ const char* outname; diff --git a/src/clamsmtpd.c b/src/clamsmtpd.c index 253d1ee..f9f1bf8 100644 --- a/src/clamsmtpd.c +++ b/src/clamsmtpd.c @@ -65,7 +65,6 @@ typedef struct clstate /* Settings ------------------------------- */ struct sockaddr_any clamaddr; /* Address for connecting to clamd */ const char* clamname; - const char* header; /* The header to add to email */ const char* directory; /* The directory for temp files */ const char* virusaction; /* Program to run when event occurs */ int bounce; /* Send back a reject line */ @@ -122,8 +121,9 @@ clctx_t; */ #define CFG_CLAMADDR "ClamAddress" -#define CFG_HEADER "ScanHeader" #define CFG_DIRECTORY "TempDirectory" +#define CFG_HEADER "Header" +#define CFG_SCANHEADER "ScanHeader" #define CFG_BOUNCE "Bounce" #define CFG_QUARANTINE "Quarantine" #define CFG_DEBUGFILES "DebugFiles" @@ -165,9 +165,11 @@ int main(int argc, char* argv[]) /* Configuration defaults */ memset(&g_clstate, 0, sizeof(g_clstate)); - g_clstate.header = DEFAULT_HEADER; g_clstate.directory = _PATH_TMP; + /* Setup a default header */ + sp_parse_option(CFG_HEADER, DEFAULT_HEADER); + /* We need the default to parse into a useable form, so we do this: */ r = cb_parse_option(CFG_CLAMADDR, DEFAULT_CLAMAV); ASSERT(r == 1); @@ -358,7 +360,7 @@ int cb_check_data(spctx_t* sp) * and transfer the file to it. */ case 0: - if(sp_done_data(sp, g_clstate.header) == -1) + if(sp_done_data(sp) == -1) return -1; break; @@ -395,14 +397,11 @@ int cb_parse_option(const char* name, const char* value) return 1; } - else if(strcasecmp(CFG_HEADER, name) == 0) + else if(strcasecmp(CFG_SCANHEADER, name) == 0) { - g_clstate.header = (const char*)trim_space((char*)value); - - if(strlen(g_clstate.header) == 0) - g_clstate.header = NULL; - - return 1; + /* Pass old format header option through */ + warnx("please use \"Header\" option instead of \"ScanHeader\""); + return sp_parse_option(CFG_HEADER, optarg); } else if(strcasecmp(CFG_DIRECTORY, name) == 0) |