From f8f3669040043a6a681fe9711cf83204f6046c2f Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 21 Jul 2004 20:14:06 +0000 Subject: - Added quarantine feature - Clarified documentation - New version --- ChangeLog | 4 +++ common/smtppass.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++------ configure.in | 4 +-- src/Makefile.am | 4 +++ src/clamsmtpd.8 | 21 +++++++++++----- src/clamsmtpd.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++------ 6 files changed, 157 insertions(+), 24 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6206103..6ade187 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +0.4 + - Option for quarantining files with viruses + - Fixed problem with returning wrong SMTP error code + 0.3 - Small log format changes - When no port specified for out address, connect back to original IP diff --git a/common/smtppass.c b/common/smtppass.c index b3decb0..b66366c 100644 --- a/common/smtppass.c +++ b/common/smtppass.c @@ -142,8 +142,9 @@ const char* g_clamname = DEFAULT_CLAMAV; const char* g_header = DEFAULT_HEADER; /* The header to add to email */ const char* g_directory = _PATH_TMP; /* The directory for temp files */ -unsigned int g_unique_id = 0x00001000; /* For connection ids */ +unsigned int g_unique_id = 0x00100000; /* For connection ids */ int g_bounce = 0; /* Send back a reject line */ +int g_quarantine = 0; /* Leave virus files in temp dir */ /* For main loop and signal handlers */ int g_quit = 0; @@ -167,6 +168,7 @@ static int connect_clam(clamsmtp_context_t* ctx); static int disconnect_clam(clamsmtp_context_t* ctx); static void add_to_logline(char* logline, char* prefix, char* line); static int avcheck_data(clamsmtp_context_t* ctx, char* logline); +static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname); static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname); static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname); static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename); @@ -191,7 +193,7 @@ int main(int argc, char* argv[]) char* t; /* Parse the arguments nicely */ - while((ch = getopt(argc, argv, "bc:d:D:h:l:m:p:t:")) != -1) + while((ch = getopt(argc, argv, "bc:d:D:h:l:m:p:qt:")) != -1) { switch(ch) { @@ -251,6 +253,11 @@ int main(int argc, char* argv[]) errx(1, "invalid timeout: %s", optarg); break; + /* Leave virus files in directory */ + case 'q': + g_quarantine = 1; + break; + /* Usage information */ case '?': default: @@ -460,7 +467,7 @@ static void on_quit(int signal) static int usage() { - fprintf(stderr, "clamsmtpd [-b] [-c clamaddr] [-d debuglevel] [-D tmpdir] [-h header] " + fprintf(stderr, "clamsmtpd [-bq] [-c clamaddr] [-d debuglevel] [-D tmpdir] [-h header] " "[-l listenaddr] [-m maxconn] [-p pidfile] [-t timeout] serveraddr\n"); exit(2); } @@ -602,7 +609,6 @@ cleanup: static int smtp_passthru(clamsmtp_context_t* ctx) { char logline[LINE_LENGTH]; - int processing = 0; int r, ret = 0; int first_rsp = 1; fd_set mask; @@ -759,10 +765,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) cleanup: if(ret == -1 && ctx->client != -1) - { - write_data(ctx, &(ctx->client), - processing ? SMTP_FAILED : SMTP_STARTFAILED); - } + write_data(ctx, &(ctx->client), SMTP_FAILED); return ret; } @@ -964,6 +967,9 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) if(write_data(ctx, &(ctx->client), g_bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) RETURN(-1); + + /* Any special post operation actions on the virus */ + quarantine_virus(ctx, buf); break; default: @@ -1022,6 +1028,58 @@ static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) return 0; } +static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname) +{ + char buf[MAXPATHLEN]; + char* t; + + if(!g_quarantine) + return 0; + + strlcpy(buf, g_directory, MAXPATHLEN); + strlcat(buf, "/virus.", MAXPATHLEN); + + /* Points to null terminator */ + t = buf + strlen(buf); + + /* + * Yes, I know we're using mktemp. And yet we're doing it in + * a safe manner due to the link command below not overwriting + * existing files. + */ + for(;;) + { + /* Null terminate off the ending, and replace with X's for mktemp */ + *t = 0; + strlcat(buf, "XXXXXX", MAXPATHLEN); + + if(!mktemp(buf)) + { + message(ctx, LOG_ERR, "couldn't create quarantine file name"); + return -1; + } + + /* Try to link the file over to the temp */ + if(link(tempname, buf) == -1) + { + /* We don't want to allow race conditions */ + if(errno == EEXIST) + { + message(ctx, LOG_WARNING, "race condition when quarantining virus file: %s", buf); + continue; + } + + message(ctx, LOG_ERR, "couldn't quarantine virus file"); + return -1; + } + + break; + } + + messagex(ctx, LOG_INFO, "quarantined virus file as: %s", buf); + return 0; +} + static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname) { /* If there aren't any lines in the message and just an diff --git a/configure.in b/configure.in index 41d3905..4bb2fc3 100644 --- a/configure.in +++ b/configure.in @@ -36,8 +36,8 @@ dnl Nate Nielsen dnl dnl Process this file with autoconf to produce a configure script. -AC_INIT(clamsmtp, 0.3, nielsen@memberwebs.com) -AM_INIT_AUTOMAKE(clamsmtp, 0.3) +AC_INIT(clamsmtp, 0.4, nielsen@memberwebs.com) +AM_INIT_AUTOMAKE(clamsmtp, 0.4) LDFLAGS="$LDFLAGS -L/usr/local/lib" CFLAGS="$CFLAGS -I/usr/local/include" diff --git a/src/Makefile.am b/src/Makefile.am index c8efa41..28671aa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,3 +6,7 @@ clamsmtpd_SOURCES = clamsmtpd.c clamsmtpd.h util.c util.h sock_any.h sock_any.c man_MANS = clamsmtpd.8 EXTRA_DIST = $(man_MANS) + +all-local: + @echo "NOTE: Ignore any warnings about mktemp(). It's used safely in this case." + @echo \ No newline at end of file diff --git a/src/clamsmtpd.8 b/src/clamsmtpd.8 index 3ded807..9594624 100644 --- a/src/clamsmtpd.8 +++ b/src/clamsmtpd.8 @@ -42,6 +42,7 @@ .Nd an SMTP server for scanning viruses via clamd .Sh SYNOPSIS .Nm +.Op Fl bq .Op Fl c Ar clamaddr .Op Fl d Ar level .Op Fl D Ar tmpdir @@ -58,8 +59,8 @@ is an SMTP filter that allows you to check for viruses using the ClamAV anti-virus software. It accepts SMTP connections and forwards the SMTP commands and responses to another SMTP server. .Pp -The DATA email body is intercepted and scanned before forwarding. Email with -viruses are rejected and logged without any additional action taken. +The DATA email body is intercepted and scanned before forwarding. By default email +with viruses are dropped silently and logged without any additional action taken. .Pp .Nm aims to be lightweight and simple rather than have a myriad of options. Your @@ -125,6 +126,12 @@ to write a file with the daemon's process id, which can be used to stop the daemon. .Ar pidfile is the location of the file. +.It Fl q +Quarantine files that contain viruses by leaving them in the +.Ar tmpdir +directory. The file names look like this (where X is a random +character or number): +.Pa virus.XXXXXX .It Fl t .Ar timeout is the number of seconds to wait while reading data from network connections. @@ -145,12 +152,14 @@ option. In some cases it's advantagous to consolidate the virus scanning and filtering for several mail servers on one machine. .Nm -allows this by providing a loopback feature to connect back to the IP that a -SMTP connection comes from. If only a port is specified for the +allows this by providing a loopback feature to connect back to the IP that an +SMTP connection comes in from. +.Pp +To use this feature specify only a port number (no IP address) for the .Ar serveraddr -(without IP) then +in which case .Nm -passes the email to the incoming peer's IP address on the said port. +will pass the email back to the said port on the incoming IP address. .Pp Make sure the .Ar maxconn diff --git a/src/clamsmtpd.c b/src/clamsmtpd.c index b3decb0..b66366c 100644 --- a/src/clamsmtpd.c +++ b/src/clamsmtpd.c @@ -142,8 +142,9 @@ const char* g_clamname = DEFAULT_CLAMAV; const char* g_header = DEFAULT_HEADER; /* The header to add to email */ const char* g_directory = _PATH_TMP; /* The directory for temp files */ -unsigned int g_unique_id = 0x00001000; /* For connection ids */ +unsigned int g_unique_id = 0x00100000; /* For connection ids */ int g_bounce = 0; /* Send back a reject line */ +int g_quarantine = 0; /* Leave virus files in temp dir */ /* For main loop and signal handlers */ int g_quit = 0; @@ -167,6 +168,7 @@ static int connect_clam(clamsmtp_context_t* ctx); static int disconnect_clam(clamsmtp_context_t* ctx); static void add_to_logline(char* logline, char* prefix, char* line); static int avcheck_data(clamsmtp_context_t* ctx, char* logline); +static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname); static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname); static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname); static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename); @@ -191,7 +193,7 @@ int main(int argc, char* argv[]) char* t; /* Parse the arguments nicely */ - while((ch = getopt(argc, argv, "bc:d:D:h:l:m:p:t:")) != -1) + while((ch = getopt(argc, argv, "bc:d:D:h:l:m:p:qt:")) != -1) { switch(ch) { @@ -251,6 +253,11 @@ int main(int argc, char* argv[]) errx(1, "invalid timeout: %s", optarg); break; + /* Leave virus files in directory */ + case 'q': + g_quarantine = 1; + break; + /* Usage information */ case '?': default: @@ -460,7 +467,7 @@ static void on_quit(int signal) static int usage() { - fprintf(stderr, "clamsmtpd [-b] [-c clamaddr] [-d debuglevel] [-D tmpdir] [-h header] " + fprintf(stderr, "clamsmtpd [-bq] [-c clamaddr] [-d debuglevel] [-D tmpdir] [-h header] " "[-l listenaddr] [-m maxconn] [-p pidfile] [-t timeout] serveraddr\n"); exit(2); } @@ -602,7 +609,6 @@ cleanup: static int smtp_passthru(clamsmtp_context_t* ctx) { char logline[LINE_LENGTH]; - int processing = 0; int r, ret = 0; int first_rsp = 1; fd_set mask; @@ -759,10 +765,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) cleanup: if(ret == -1 && ctx->client != -1) - { - write_data(ctx, &(ctx->client), - processing ? SMTP_FAILED : SMTP_STARTFAILED); - } + write_data(ctx, &(ctx->client), SMTP_FAILED); return ret; } @@ -964,6 +967,9 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) if(write_data(ctx, &(ctx->client), g_bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) RETURN(-1); + + /* Any special post operation actions on the virus */ + quarantine_virus(ctx, buf); break; default: @@ -1022,6 +1028,58 @@ static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) return 0; } +static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname) +{ + char buf[MAXPATHLEN]; + char* t; + + if(!g_quarantine) + return 0; + + strlcpy(buf, g_directory, MAXPATHLEN); + strlcat(buf, "/virus.", MAXPATHLEN); + + /* Points to null terminator */ + t = buf + strlen(buf); + + /* + * Yes, I know we're using mktemp. And yet we're doing it in + * a safe manner due to the link command below not overwriting + * existing files. + */ + for(;;) + { + /* Null terminate off the ending, and replace with X's for mktemp */ + *t = 0; + strlcat(buf, "XXXXXX", MAXPATHLEN); + + if(!mktemp(buf)) + { + message(ctx, LOG_ERR, "couldn't create quarantine file name"); + return -1; + } + + /* Try to link the file over to the temp */ + if(link(tempname, buf) == -1) + { + /* We don't want to allow race conditions */ + if(errno == EEXIST) + { + message(ctx, LOG_WARNING, "race condition when quarantining virus file: %s", buf); + continue; + } + + message(ctx, LOG_ERR, "couldn't quarantine virus file"); + return -1; + } + + break; + } + + messagex(ctx, LOG_INFO, "quarantined virus file as: %s", buf); + return 0; +} + static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname) { /* If there aren't any lines in the message and just an -- cgit v1.2.3