summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS3
-rw-r--r--ChangeLog1
-rw-r--r--common/smtppass.c169
-rw-r--r--common/smtppass.h6
-rw-r--r--common/spio.c12
-rw-r--r--common/sppriv.h2
-rw-r--r--src/clamsmtpd.c21
7 files changed, 193 insertions, 21 deletions
diff --git a/AUTHORS b/AUTHORS
index a7f2ba8..93a74e2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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>
+
diff --git a/ChangeLog b/ChangeLog
index 9242981..71b5e2b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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)