From 9992298ca9a1549ab3267506896852f9df0369ee Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 18 Sep 2004 01:00:26 +0000 Subject: Virus action support. --- src/clamsmtpd.c | 192 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 138 insertions(+), 54 deletions(-) (limited to 'src') diff --git a/src/clamsmtpd.c b/src/clamsmtpd.c index 1d10adf..591db3a 100644 --- a/src/clamsmtpd.c +++ b/src/clamsmtpd.c @@ -66,6 +66,7 @@ typedef struct clstate 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 */ int quarantine; /* Leave virus files in temp dir */ int debug_files; /* Leave all files in temp dir */ @@ -123,6 +124,7 @@ clctx_t; #define CFG_BOUNCE "Bounce" #define CFG_QUARANTINE "Quarantine" #define CFG_DEBUGFILES "DebugFiles" +#define CFG_VIRUSACTION "VirusAction" /* ----------------------------------------------------------------------- * GLOBALS @@ -137,8 +139,8 @@ clstate_t g_clstate; static void usage(); static int connect_clam(clctx_t* ctx); static int disconnect_clam(clctx_t* ctx); -static int quarantine_virus(clctx_t* ctx); -static int clam_scan_file(clctx_t* ctx); +static int virus_action(clctx_t* ctx, const char* virus); +static int clam_scan_file(clctx_t* ctx, const char** virus); /* ----------------------------------------------------------------------- * SIMPLE MACROS @@ -322,6 +324,7 @@ static void usage() int cb_check_data(spctx_t* sp) { int r = 0; + const char* virus; clctx_t* ctx = (clctx_t*)sp; /* Connect to clamav */ @@ -331,7 +334,7 @@ int cb_check_data(spctx_t* sp) if(r != -1 && (r = sp_cache_data(sp)) > 0) /* ClamAV doesn't like empty files */ - r = clam_scan_file(ctx); + r = clam_scan_file(ctx, &virus); switch(r) { @@ -362,7 +365,7 @@ int cb_check_data(spctx_t* sp) */ case 1: /* Any special post operation actions on the virus */ - quarantine_virus(ctx); + virus_action(ctx, virus); if(sp_fail_data(sp, g_clstate.bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) @@ -424,6 +427,12 @@ int cb_parse_option(const char* name, const char* value) return 1; } + else if(strcasecmp(CFG_VIRUSACTION, name) == 0) + { + g_clstate.virusaction = value; + return 1; + } + return 0; } @@ -508,19 +517,25 @@ static int disconnect_clam(clctx_t* ctx) return 0; } -static int clam_scan_file(clctx_t* ctx) +static int clam_scan_file(clctx_t* ctx, const char** virus) { - int len; + int len, x; + char* line; spctx_t* sp = &(ctx->sp); + ASSERT(ctx && virus); + + *virus = NULL; + /* Needs to be long enough to hold path names */ ASSERT(SP_LINE_LENGTH > MAXPATHLEN + 32); - strcpy(sp->line, CLAM_SCAN); - strcat(sp->line, sp->cachename); - strcat(sp->line, "\n"); + line = ctx->clam.line; + strcpy(line, CLAM_SCAN); + strcat(line, sp->cachename); + strcat(line, "\n"); - if(spio_write_data(sp, &(ctx->clam), sp->line) == -1) + if(spio_write_data(sp, &(ctx->clam), line) == -1) return -1; len = spio_read_line(sp, &(ctx->clam), SPIO_DISCARD | SPIO_TRIM); @@ -530,35 +545,58 @@ static int clam_scan_file(clctx_t* ctx) return -1; } - if(is_last_word(sp->line, CLAM_OK, KL(CLAM_OK))) + if(is_last_word(line, CLAM_OK, KL(CLAM_OK))) { sp_add_log(sp, "status=", "CLEAN"); sp_messagex(sp, LOG_DEBUG, "no virus"); return 0; } - if(is_last_word(sp->line, CLAM_FOUND, KL(CLAM_FOUND))) + /* + * When a virus is found the returned line from + * clamd looks something like this: + * + * /path/to/virus: Virus.XXXX FOUND + */ + if(is_last_word(line, CLAM_FOUND, KL(CLAM_FOUND))) { - len = strlen(sp->cachename); + x = strlen(sp->cachename); + + /* A little sanity check ... */ + if(len > x + KL(CLAM_FOUND)) + { + /* Remove the "FOUND" from the end */ + line[len - KL(CLAM_FOUND)] = 0; + + /* Skip the filename returned, and colon */ + line += x + 1; + + line = trim_space(line); + + sp_messagex(sp, LOG_DEBUG, "found virus: %s", line); + sp_add_log(sp, "status=VIRUS:", line); + *virus = line; + } - if(sp->linelen > len) - sp_add_log(sp, "status=VIRUS:", sp->line + len + 1); else + { + sp_messagex(sp, LOG_WARNING, "couldn't parse virus name from clamd response: %s", line); sp_add_log(sp, "status=", "VIRUS"); + *virus = "Unparsable.Virus.Name"; + } - sp_messagex(sp, LOG_DEBUG, "found virus"); return 1; } - if(is_last_word(sp->line, CLAM_ERROR, KL(CLAM_ERROR))) + if(is_last_word(line, CLAM_ERROR, KL(CLAM_ERROR))) { - sp_messagex(sp, LOG_ERR, "clamav error: %s", sp->line); + sp_messagex(sp, LOG_ERR, "clamav error: %s", line); sp_add_log(sp, "status=", "CLAMAV-ERROR"); return -1; } sp_add_log(sp, "status=", "CLAMAV-ERROR"); - sp_messagex(sp, LOG_ERR, "unexepected response from clamd: %s", sp->line); + sp_messagex(sp, LOG_ERR, "unexepected response from clamd: %s", line); return -1; } @@ -566,56 +604,102 @@ static int clam_scan_file(clctx_t* ctx) * TEMP FILE HANDLING */ -static int quarantine_virus(clctx_t* ctx) +static int virus_action(clctx_t* ctx, const char* virus) { - char buf[MAXPATHLEN]; + char qfilename[MAXPATHLEN]; spctx_t* sp = &(ctx->sp); char* t; + int i; + pid_t pid; - if(!g_clstate.quarantine) - return 0; + if(g_clstate.quarantine) + { + strlcpy(qfilename, g_clstate.directory, MAXPATHLEN); + strlcat(qfilename, "/virus.", MAXPATHLEN); + + /* Points to null terminator */ + t = qfilename + strlen(qfilename); + + /* + * 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(qfilename, "XXXXXX", MAXPATHLEN); + + if(!mktemp(qfilename)) + { + sp_message(sp, LOG_ERR, "couldn't create quarantine file name"); + return -1; + } - strlcpy(buf, g_clstate.directory, MAXPATHLEN); - strlcat(buf, "/virus.", MAXPATHLEN); + /* Try to link the file over to the temp */ + if(link(sp->cachename, qfilename) == -1) + { + /* We don't want to allow race conditions */ + if(errno == EEXIST) + { + sp_message(sp, LOG_WARNING, "race condition when quarantining virus file: %s", qfilename); + continue; + } + + sp_message(sp, LOG_ERR, "couldn't quarantine virus file"); + return -1; + } - /* Points to null terminator */ - t = buf + strlen(buf); + break; + } - /* - * 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); + sp_messagex(sp, LOG_INFO, "quarantined virus file as: %s", qfilename); + } - if(!mktemp(buf)) + if(g_clstate.virusaction != NULL) + { + switch(pid = fork()) { - sp_message(sp, LOG_ERR, "couldn't create quarantine file name"); + case -1: + sp_message(sp, LOG_ERR, "couldn't fork for virus action"); return -1; - } - /* Try to link the file over to the temp */ - if(link(sp->cachename, buf) == -1) - { - /* We don't want to allow race conditions */ - if(errno == EEXIST) - { - sp_message(sp, LOG_WARNING, "race condition when quarantining virus file: %s", buf); - continue; - } + /* The child */ + case 0: + /* + * New process group because we don't care about this child, + * it's return value, waiting for it to exit or any of that. + * Apparently this can't fail when done like this. + */ + setsid(); - sp_message(sp, LOG_ERR, "couldn't quarantine virus file"); - return -1; - } + /* Close all descriptors */ + for(i = 0; i <= 2; i++) + close(i); - break; + /* Set the environment variables */ + sp_setup_env(sp, 0); + + /* When quarantining we can hand the file name off */ + if(g_clstate.quarantine) + setenv("EMAIL", qfilename, 1); + + if(virus) + setenv("VIRUS", virus, 1); + + sp_messagex(sp, LOG_DEBUG, "executing virus action: %s", g_clstate.virusaction); + + /* And execute the program */ + execl("/bin/sh", "sh", "-c", g_clstate.virusaction, NULL); + + /* If that returned then there was an error */ + sp_message(sp, LOG_ERR, "error executing the shell for virus action"); + exit(1); + break; + }; } - sp_messagex(sp, LOG_INFO, "quarantined virus file as: %s", buf); return 0; } -- cgit v1.2.3