summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2004-09-14 18:08:53 +0000
committerStef Walter <stef@memberwebs.com>2004-09-14 18:08:53 +0000
commit0dc7245bcee8971a5fc1a79c3b9c9781ca848198 (patch)
tree6cd01b733eefc97601c5c7c60d0d7775c014acbf /src
parentdb0f54bb41828dd0d02883ace183fa48c149dfda (diff)
Initial build
Diffstat (limited to 'src')
-rw-r--r--src/.cvsignore4
-rw-r--r--src/proxsmtpd.c872
2 files changed, 549 insertions, 327 deletions
diff --git a/src/.cvsignore b/src/.cvsignore
new file mode 100644
index 0000000..0564b29
--- /dev/null
+++ b/src/.cvsignore
@@ -0,0 +1,4 @@
+Makefile
+Makefile.in
+proxsmtpd
+.deps
diff --git a/src/proxsmtpd.c b/src/proxsmtpd.c
index 897faa9..3540dd2 100644
--- a/src/proxsmtpd.c
+++ b/src/proxsmtpd.c
@@ -37,6 +37,7 @@
#include <sys/types.h>
#include <sys/param.h>
+#include <sys/wait.h>
#include <paths.h>
#include <ctype.h>
@@ -44,6 +45,7 @@
#include <unistd.h>
#include <syslog.h>
#include <errno.h>
+#include <fcntl.h>
#include <err.h>
#include "usuals.h"
@@ -61,10 +63,9 @@ typedef struct pxstate
{
/* Settings ------------------------------- */
const char* command; /* The command to pipe email through */
- /* TODO: Timeout for above command */
+ struct timeval timeout; /* The command timeout */
+ int pipe_cmd; /* Whether command is a pipe or not */
const char* directory; /* The directory for temp files */
- int quarantine; /* Leave failed files in temp dir */
- int debug_files; /* Leave all files in temp dir */
}
pxstate_t;
@@ -72,39 +73,43 @@ pxstate_t;
* STRINGS
*/
-#define CRLF "\r\n"
+#define SMTP_REJECTED "550 Content Rejected\r\n"
+#define DEFAULT_CONFIG CONF_PREFIX "/proxsmtpd.conf"
-XXXXXXXXXXXXXXXXXxx
-#define SMTP_DATAVIRUSOK "250 Virus Detected; Discarded Email" CRLF
-#define SMTP_DATAVIRUS "550 Virus Detected; Content Rejected" CRLF
+#define CFG_FILTERCMD "FilterCommand"
+#define CFG_PIPECMD "Pipe"
+#define CFG_DIRECTORY "TempDirectory"
+#define CFG_DEBUGFILES "DebugFiles"
+#define CFG_CMDTIMEOUT "CommandTimeout"
-#define DEFAULT_CONFIG CONF_PREFIX "/proxsmtpd.conf"
+/* Poll time for waiting operations in milli seconds */
+#define POLL_TIME 20
-#define CFG_FILTER "FilterCommand"
-#define CFG_DIRECTORY "TempDirectory"
-#define CFG_QUARANTINE "Quarantine"
-#define CFG_DEBUGFILES "DebugFiles"
+/* read & write ends of a pipe */
+#define READ_END 0
+#define WRITE_END 1
+
+/* pre-set file descriptors */
+#define STDIN 0
+#define STDOUT 1
+#define STDERR 2
/* -----------------------------------------------------------------------
* GLOBALS
*/
-clstate_t g_clstate;
+pxstate_t g_pxstate;
/* -----------------------------------------------------------------------
* FORWARD DECLARATIONS
*/
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 transfer_to_cache(clctx_t* ctx);
-static int clam_scan_file(clctx_t* ctx);
-
-/* -----------------------------------------------------------------------
- * SIMPLE MACROS
- */
+static int process_file_command(spctx_t* sp);
+static int process_pipe_command(spctx_t* sp);
+static void buffer_reject_message(char* data, char* buf, int buflen);
+static int kill_process(spctx_t* sp, pid_t pid);
+static int wait_process(spctx_t* sp, pid_t pid, int* status);
/* ----------------------------------------------------------------------------------
* STARTUP ETC...
@@ -115,21 +120,16 @@ int main(int argc, char* argv[])
const char* configfile = DEFAULT_CONFIG;
const char* pidfile = NULL;
int dbg_level = -1;
- int warnargs = 0;
int ch = 0;
int r;
char* t;
/* Setup some defaults */
- memset(&g_clstate, 0, sizeof(g_clstate));
- g_clstate.header = DEFAULT_HEADER;
- g_clstate.directory = _PATH_TMP;
-
- /* 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);
+ memset(&g_pxstate, 0, sizeof(g_pxstate));
+ g_pxstate.directory = _PATH_TMP;
+ g_pxstate.pipe_cmd = 1;
- sp_init("clamsmtpd");
+ sp_init("proxsmtpd");
/*
* We still accept our old arguments for compatibility reasons.
@@ -137,26 +137,10 @@ int main(int argc, char* argv[])
*/
/* Parse the arguments nicely */
- while((ch = getopt(argc, argv, "bc:d:D:f:h:l:m:p:qt:v")) != -1)
+ while((ch = getopt(argc, argv, "d:f:p:v")) != -1)
{
switch(ch)
{
- /* COMPAT: Actively reject messages */
- case 'b':
- if((r = cb_parse_option(CFG_BOUNCE, "on")) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
- /* COMPAT: Change the CLAM socket */
- case 'c':
- if((r = cb_parse_option(CFG_CLAMADDR, "on")) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
/* Don't daemonize */
case 'd':
dbg_level = strtol(optarg, &t, 10);
@@ -165,64 +149,16 @@ int main(int argc, char* argv[])
dbg_level += LOG_ERR;
break;
- /* COMPAT: The directory for the files */
- case 'D':
- if((r = sp_parse_option(CFG_DIRECTORY, optarg)) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
/* The configuration file */
case 'f':
configfile = optarg;
break;
- /* COMPAT: The header to add */
- case 'h':
- if((r = cb_parse_option(CFG_HEADER, optarg)) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
- /* COMPAT: Change our listening port */
- case 'l':
- if((r = sp_parse_option("Listen", optarg)) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
- /* COMPAT: The maximum number of threads */
- case 'm':
- if((r = sp_parse_option("MaxConnections", optarg)) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
/* Write out a pid file */
case 'p':
pidfile = optarg;
break;
- /* COMPAT: The timeout */
- case 't':
- if((r = sp_parse_option("TimeOut", optarg)) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
- /* COMPAT: Leave virus files in directory */
- case 'q':
- if((r = cb_parse_option(CFG_QUARANTINE, "on")) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
/* Print version number */
case 'v':
printf("clamsmtpd (version %s)\n", VERSION);
@@ -230,14 +166,6 @@ int main(int argc, char* argv[])
exit(0);
break;
- /* COMPAT: Leave all files in the tmp directory */
- case 'X':
- if((r = cb_parse_option(CFG_DEBUGFILES, "on")) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- break;
-
/* Usage information */
case '?':
default:
@@ -249,19 +177,8 @@ int main(int argc, char* argv[])
argc -= optind;
argv += optind;
- if(argc > 1)
+ if(argc > 0)
usage();
- if(argc == 1)
- {
- /* COMPAT: The out address */
- if((r = sp_parse_option("OutAddress", argv[0])) < 0)
- usage();
- ASSERT(r == 1);
- warnargs = 1;
- }
-
- if(warnargs)
- warnx("please use configuration file instead of command-line flags: %s", configfile);
r = sp_run(configfile, pidfile, dbg_level);
@@ -272,8 +189,8 @@ int main(int argc, char* argv[])
static void usage()
{
- fprintf(stderr, "usage: clamsmtpd [-d debuglevel] [-f configfile] [-p pidfile]\n");
- fprintf(stderr, " clamsmtpd -v\n");
+ fprintf(stderr, "usage: proxsmtpd [-d debuglevel] [-f configfile] [-p pidfile]\n");
+ fprintf(stderr, " proxsmtpd -v\n");
exit(2);
}
@@ -281,109 +198,61 @@ static void usage()
* SP CALLBACKS
*/
-int cb_check_data(spctx_t* sp)
+int cb_check_data(spctx_t* ctx)
{
int r = 0;
- clctx_t* ctx = (clctx_t*)sp;
-
- /* Connect to clamav */
- if(!spio_valid(&(ctx->clam)))
- r = connect_clam(ctx);
- /* transfer_to_cache */
- if(r != -1 && (r = transfer_to_cache(ctx)) > 0)
-
- /* ClamAV doesn't like empty files */
- r = clam_scan_file(ctx);
-
- switch(r)
+ if(!g_pxstate.command)
{
+ sp_messagex(ctx, LOG_WARNING, "no filter command specified. passing message through");
- /*
- * There was an error tell the client. We haven't notified
- * the server about any of this yet
- */
- case -1:
- if(sp_fail_data(sp, NULL) == -1)
- return -1;
- break;
-
- /*
- * No virus was found. Now we initiate a connection to the server
- * and transfer the file to it.
- */
- case 0:
- if(sp_done_data(sp, g_clstate.header) == -1)
- return -1;
- break;
+ if(sp_cache_data(ctx) == -1 ||
+ sp_done_data(ctx, NULL) == -1)
+ return -1; /* Message already printed */
+ }
- /*
- * A virus was found, normally we just drop the email. But if
- * requested we can send a simple message back to our client.
- * The server doesn't know data was ever sent, and the client can
- * choose to reset the connection to reuse it if it wants.
- */
- case 1:
- /* Any special post operation actions on the virus */
- quarantine_virus(ctx);
+ if(g_pxstate.pipe_cmd)
+ r = process_pipe_command(ctx);
+ else
+ r = process_file_command(ctx);
- if(sp_fail_data(sp, g_clstate.bounce ?
- SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1)
+ if(r == -1)
+ {
+ if(sp_fail_data(ctx, NULL) == -1)
return -1;
- break;
-
- default:
- ASSERT(0 && "Invalid clam_scan_file return value");
- break;
- };
+ }
return 0;
}
int cb_parse_option(const char* name, const char* value)
{
- if(strcasecmp(CFG_CLAMADDR, name) == 0)
- {
- if(sock_any_pton(value, &(g_clstate.clamaddr), SANY_OPT_DEFLOCAL) == -1)
- errx(2, "invalid " CFG_CLAMADDR " socket name: %s", value);
- g_clstate.clamname = value;
- return 1;
- }
+ char* t;
- else if(strcasecmp(CFG_HEADER, name) == 0)
+ if(strcasecmp(CFG_FILTERCMD, name) == 0)
{
- g_clstate.header = (const char*)trim_space((char*)value);
-
- if(strlen(g_clstate.header) == 0)
- g_clstate.header = NULL;
-
+ g_pxstate.command = value;
return 1;
}
else if(strcasecmp(CFG_DIRECTORY, name) == 0)
{
- g_clstate.directory = value;
+ g_pxstate.directory = value;
return 1;
}
- else if(strcasecmp(CFG_BOUNCE, name) == 0)
+ else if(strcasecmp(CFG_CMDTIMEOUT, name) == 0)
{
- if((g_clstate.bounce = strtob(value)) == -1)
- errx(2, "invalid value for " CFG_BOUNCE);
+ g_pxstate.timeout.tv_sec = strtol(value, &t, 10);
+ if(*t || g_pxstate.timeout.tv_sec <= 0)
+ errx(2, "invalid setting: " CFG_CMDTIMEOUT);
return 1;
}
- else if(strcasecmp(CFG_QUARANTINE, name) == 0)
+ else if(strcasecmp(CFG_PIPECMD, name) == 0)
{
- if((g_clstate.quarantine = strtob(value)) == -1)
- errx(2, "invalid value for " CFG_BOUNCE);
- return 1;
- }
-
- else if(strcasecmp(CFG_DEBUGFILES, name) == 0)
- {
- if((g_clstate.debug_files = strtob(value)) == -1)
- errx(2, "invalid value for " CFG_DEBUGFILES);
+ if((g_pxstate.pipe_cmd = strtob(value)) == -1)
+ errx(2, "invalid value for " CFG_PIPECMD);
return 1;
}
@@ -392,219 +261,568 @@ int cb_parse_option(const char* name, const char* value)
spctx_t* cb_new_context()
{
- clctx_t* ctx = (clctx_t*)calloc(1, sizeof(clctx_t));
+ spctx_t* ctx = (spctx_t*)calloc(1, sizeof(spctx_t));
if(!ctx)
- {
sp_messagex(NULL, LOG_CRIT, "out of memory");
- return NULL;
- }
-
- /* Initial preparation of the structure */
- spio_init(&(ctx->clam), "CLAMAV");
- return &(ctx->sp);
+ return ctx;
}
-void cb_del_context(spctx_t* sp)
+void cb_del_context(spctx_t* ctx)
{
- clctx_t* ctx = (clctx_t*)sp;
- ASSERT(sp);
-
- disconnect_clam(ctx);
free(ctx);
}
-/* ----------------------------------------------------------------------------------
- * CLAM AV
+/* -----------------------------------------------------------------------------
+ * IMPLEMENTATION
*/
-static int connect_clam(clctx_t* ctx)
+static int process_file_command(spctx_t* sp)
{
- int ret = 0;
- spctx_t* sp = &(ctx->sp);
+ pid_t pid;
+ int ret = 0, status, r;
- ASSERT(ctx);
- ASSERT(!spio_valid(&(ctx->clam)));
+ /* For reading data from the process */
+ int pipe_e[2];
+ fd_set rmask;
+ char obuf[1024];
+ char ebuf[256];
- if(spio_connect(sp, &(ctx->clam), &(g_clstate.clamaddr), g_clstate.clamname) == -1)
- RETURN(-1);
+ ASSERT(g_pxstate.command);
- spio_read_junk(sp, &(ctx->clam));
+ memset(ebuf, 0, sizeof(ebuf));
+ memset(pipe_e, ~0, sizeof(pipe_e));
- /* Send a session and a check header to ClamAV */
+ if(sp_cache_data(sp) == -1)
+ RETURN(-1); /* message already printed */
- if(spio_write_data(sp, &(ctx->clam), "SESSION\n") == -1)
+ /* Create the pipe we need */
+ if(pipe(pipe_e) == -1)
+ {
+ sp_message(sp, LOG_ERR, "couldn't create pipe for filter command");
RETURN(-1);
+ }
- spio_read_junk(sp, &(ctx->clam));
-
-/*
- if(spio_write_data(sp, &(ctx->clam), "PING\n") == -1 ||
- spio_read_line(sp, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM) == -1)
+ /* Now fork the pipes across processes */
+ switch(pid = fork())
+ {
+ case -1:
+ sp_message(sp, LOG_ERR, "couldn't fork for filter command");
RETURN(-1);
- if(strcmp(sp->line, CONNECT_RESPONSE) != 0)
+ /* The child process */
+ case 0:
+
+ /* Fixup our ends of the pipe */
+ if(dup2(pipe_e[WRITE_END], STDERR) == -1)
+ {
+ sp_message(sp, LOG_ERR, "couldn't dup descriptor for filter command");
+ exit(1);
+ }
+
+ /* Setup environment nicely */
+ if(setenv("EMAIL", sp->cachename, 1) == -1 ||
+ setenv("TMP", g_pxstate.directory, 1) == -1)
+ {
+ sp_messagex(sp, LOG_ERR, "couldn't setup environment for filter command");
+ exit(1);
+ }
+
+ /* Now run the filter command */
+ execl("/bin/sh", "sh", "-c", g_pxstate.command, NULL);
+
+ /* If that returned then there was an error */
+ sp_message(sp, LOG_ERR, "error executing the shell for filter command");
+ exit(1);
+ break;
+ };
+
+ /* The parent process */
+
+ /* Close our copies of the pipes that we don't need */
+ close(pipe_e[WRITE_END]);
+ pipe_e[WRITE_END] = -1;
+
+ /* Pipe shouldn't be blocking */
+ fcntl(pipe_e[READ_END], F_SETFL, fcntl(pipe_e[READ_END], F_GETFL, 0) | O_NONBLOCK);
+
+ /* Main read write loop */
+ for(;;)
{
- sp_message(sp, LOG_ERR, "clamd sent an unexpected response: %s", ctx->line);
+ FD_SET(pipe_e[READ_END], &rmask);
+
+ r = select(FD_SETSIZE, &rmask, NULL, NULL, &(g_pxstate.timeout));
+
+ switch(r)
+ {
+ case -1:
+ sp_message(sp, LOG_ERR, "couldn't select while listening to filter command");
+ RETURN(-1);
+ case 0:
+ sp_messagex(sp, LOG_ERR, "timeout while listening to filter command");
+ RETURN(-1);
+ };
+
+ for(;;)
+ {
+ /* Note because we handle as string we save one byte for null-termination */
+ r = read(pipe_e[READ_END], obuf, sizeof(obuf) - 1);
+ if(r < 0)
+ {
+ if(errno != EINTR || errno != EAGAIN)
+ {
+ sp_message(sp, LOG_ERR, "couldn't read data from filter command");
+ RETURN(-1);
+ }
+ }
+
+ else if(r == 0)
+ break;
+
+ /* Null terminate */
+ obuf[r] = 0;
+
+ /* And process */
+ buffer_reject_message(obuf, ebuf, sizeof(ebuf));
+ }
+
+ /* Check if process is still around */
+ if(waitpid(pid, &status, WNOHANG) == pid)
+ {
+ pid = 0;
+ break;
+ }
+ }
+
+ ASSERT(pid == 0);
+
+ /* We only trust well behaved programs */
+ if(!WIFEXITED(status))
+ {
+ sp_messagex(sp, LOG_ERR, "filter command terminated abnormally");
RETURN(-1);
}
-*/
+
+ sp_messagex(sp, LOG_DEBUG, "filter exit code: %d", (int)WEXITSTATUS(status));
+
+ /* A successful response */
+ if(WEXITSTATUS(status) == 0)
+ {
+ if(sp_done_data(sp, NULL) == -1)
+ RETURN(-1); /* message already printed */
+ }
+
+ /* Check code and use stderr if bad code */
+ else
+ {
+ if(sp_fail_data(sp, ebuf[0] == 0 ? SMTP_REJECTED : ebuf) == -1)
+ RETURN(-1); /* message already printed */
+ }
+
+ ret = 0;
cleanup:
- if(ret < 0)
- spio_disconnect(sp, &(ctx->clam));
+ if(pipe_e[READ_END] != -1)
+ close(pipe_e[READ_END]);
+ if(pipe_e[WRITE_END] != -1)
+ close(pipe_e[WRITE_END]);
+
+ if(pid != 0)
+ kill_process(sp, pid);
return ret;
}
-static int disconnect_clam(clctx_t* ctx)
+static int process_pipe_command(spctx_t* sp)
{
- spctx_t* sp = &(ctx->sp);
+ pid_t pid;
+ int ret = 0, status;
+ int r, n, done;
+
+ /* For sending data to the process */
+ const char* ibuf = NULL;
+ int ilen = 0;
+ int pipe_i[2];
+ fd_set wmask;
+ int writing;
+
+ /* For reading data from the process */
+ int pipe_o[2];
+ int pipe_e[2];
+ fd_set rmask;
+ int reading;
+ char obuf[1024];
+ char ebuf[256];
+
+ ASSERT(g_pxstate.command);
+
+ memset(ebuf, 0, sizeof(ebuf));
+
+ memset(pipe_i, ~0, sizeof(pipe_i));
+ memset(pipe_o, ~0, sizeof(pipe_o));
+ memset(pipe_e, ~0, sizeof(pipe_e));
+
+ /* Create the pipes we need */
+ if(pipe(pipe_i) == -1 || pipe(pipe_o) == -1 || pipe(pipe_e) == -1)
+ {
+ sp_message(sp, LOG_ERR, "couldn't create pipes for filter command");
+ RETURN(-1);
+ }
- if(!spio_valid(&(ctx->clam)))
- return 0;
+ /* Now fork the pipes across processes */
+ switch(pid = fork())
+ {
+ case -1:
+ sp_message(sp, LOG_ERR, "couldn't fork for filter command");
+ RETURN(-1);
- if(spio_write_data(sp, &(ctx->clam), CLAM_DISCONNECT) != -1)
- spio_read_junk(sp, &(ctx->clam));
+ /* The child process */
+ case 0:
- spio_disconnect(sp, &(ctx->clam));
- return 0;
-}
+ /* Fixup our ends of the pipe */
+ if(dup2(pipe_i[READ_END], STDIN) == -1 ||
+ dup2(pipe_o[WRITE_END], STDOUT) == -1 ||
+ dup2(pipe_e[WRITE_END], STDERR) == -1)
+ {
+ sp_message(sp, LOG_ERR, "couldn't dup descriptors for filter command");
+ exit(1);
+ }
-static int clam_scan_file(clctx_t* ctx)
-{
- int len;
- spctx_t* sp = &(ctx->sp);
+ /* Now run the filter command */
+ execl("/bin/sh", "sh", "-c", g_pxstate.command, NULL);
- /* Needs to be long enough to hold path names */
- ASSERT(SP_LINE_LENGTH > MAXPATHLEN + 32);
+ /* If that returned then there was an error */
+ sp_message(sp, LOG_ERR, "error executing the shell for filter command");
+ exit(1);
+ break;
+ };
- strcpy(sp->line, CLAM_SCAN);
- strcat(sp->line, sp->cachename);
- strcat(sp->line, "\n");
+ /* The parent process */
- if(spio_write_data(sp, &(ctx->clam), sp->line) == -1)
- return -1;
+ /* Close our copies of the pipes that we don't need */
+ close(pipe_i[READ_END]);
+ pipe_i[READ_END] = -1;
+ close(pipe_o[WRITE_END]);
+ pipe_o[WRITE_END] = -1;
+ close(pipe_e[WRITE_END]);
+ pipe_e[WRITE_END] = -1;
+
+ /* None of our pipes should be blocking */
+ fcntl(pipe_i[WRITE_END], F_SETFL, fcntl(pipe_i[WRITE_END], F_GETFL, 0) | O_NONBLOCK);
+ fcntl(pipe_o[READ_END], F_SETFL, fcntl(pipe_o[READ_END], F_GETFL, 0) | O_NONBLOCK);
+ fcntl(pipe_e[READ_END], F_SETFL, fcntl(pipe_e[READ_END], F_GETFL, 0) | O_NONBLOCK);
- len = spio_read_line(sp, &(ctx->clam), SPIO_DISCARD | SPIO_TRIM);
- if(len == 0)
+ /* Main read write loop */
+ for(;;)
{
- sp_messagex(sp, LOG_ERR, "clamd disconnected unexpectedly");
- return -1;
+ reading = 0;
+ writing = 0;
+ done = 0;
+
+ FD_ZERO(&rmask);
+ FD_ZERO(&wmask);
+
+ /* We only select on those that are still open */
+ if(pipe_i[WRITE_END] != -1)
+ {
+ FD_SET(pipe_i[WRITE_END], &wmask);
+ writing = 1;
+ }
+ if(pipe_o[READ_END] != -1)
+ {
+ FD_SET(pipe_o[READ_END], &rmask);
+ reading = 1;
+ }
+ if(pipe_e[READ_END] != -1)
+ {
+ FD_SET(pipe_e[READ_END], &rmask);
+ reading = 1;
+ }
+
+ /* If nothing open then go away */
+ if(!reading && !writing)
+ break;
+
+ r = select(FD_SETSIZE, reading ? &rmask : NULL,
+ writing ? &wmask : NULL, NULL, &(g_pxstate.timeout));
+
+ switch(r)
+ {
+ case -1:
+ sp_message(sp, LOG_ERR, "couldn't select while listening to filter command");
+ RETURN(-1);
+ case 0:
+ sp_messagex(sp, LOG_WARNING, "timeout while listening to filter command");
+ RETURN(-1);
+ };
+
+ /* Handling of process's stdin */
+ if(FD_ISSET(pipe_i[WRITE_END], &wmask))
+ {
+ if(ilen <= 0)
+ {
+ /* Read some more data into buffer */
+ switch(r = sp_read_data(sp, &ibuf))
+ {
+ case -1:
+ RETURN(-1); /* Message already printed */
+ case 0:
+ done = 1;
+ break;
+ default:
+ ASSERT(r > 0);
+ ilen = r;
+ break;
+ };
+ }
+
+ /* Write data from buffer */
+ for(;;)
+ {
+ r = write(pipe_i[WRITE_END], ibuf, ilen);
+ if(r == -1)
+ {
+ if(errno == EAGAIN || errno == EINTR)
+ break;
+ else if(errno == EPIPE)
+ {
+ sp_message(sp, LOG_WARNING, "filter command closed input early");
+
+ /* Eat up the rest of the data */
+ while(sp_read_data(sp, &ibuf) > 0)
+ ;
+ done = 1;
+ break;
+ }
+
+ /* Otherwise it's a normal error */
+ sp_message(sp, LOG_ERR, "couldn't write to filter command");
+ RETURN(-1);
+ }
+
+ else
+ {
+ ilen -= r;
+ ibuf += r;
+ }
+
+ break;
+ }
+ }
+
+ /* Check if process is still around */
+ if(!done && waitpid(pid, &status, WNOHANG) == pid)
+ {
+ pid = 0;
+ done = 1;
+ }
+
+ /* Close output pipes if done */
+ if(done)
+ {
+ close(pipe_i[WRITE_END]);
+ pipe_i[WRITE_END] = -1;
+
+ /* Force emptying of these guys */
+ FD_SET(pipe_o[READ_END], &rmask);
+ FD_SET(pipe_e[READ_END], &rmask);
+ }
+
+ /*
+ * During normal operation we only read one block of data
+ * at a time, but once done we make sure to drain the
+ * output buffers dry.
+ */
+ do
+ {
+ /* Handling of stdout, which should be email data */
+ if(FD_ISSET(pipe_o[READ_END], &rmask))
+ {
+ r = read(pipe_o[READ_END], obuf, sizeof(obuf));
+ if(r > 0)
+ {
+ if(sp_write_data(sp, obuf, r) == -1)
+ RETURN(-1); /* message already printed */
+ }
+
+ else if(r < 0)
+ {
+ if(errno != EINTR || errno != EAGAIN)
+ {
+ sp_message(sp, LOG_ERR, "couldn't read data from filter command");
+ RETURN(-1);
+ }
+ }
+ }
+
+ /* Handling of stderr, the last line of which we use as an err message*/
+ if(FD_ISSET(pipe_e[READ_END], &rmask))
+ {
+ /* Note because we handle as string we save one byte for null-termination */
+ n = read(pipe_e[READ_END], obuf, sizeof(obuf) - 1);
+ if(n < 0)
+ {
+ if(errno != EINTR || errno != EAGAIN)
+ {
+ sp_message(sp, LOG_ERR, "couldn't read data from filter command");
+ RETURN(-1);
+ }
+ }
+
+ else if(n > 0)
+ {
+ /* Null terminate */
+ obuf[n] = 0;
+
+ /* And process */
+ buffer_reject_message(obuf, ebuf, sizeof(ebuf));
+ }
+ }
+
+ } /* when in 'done' mode we keep reading as long as there's data */
+ while(done && !(r == 0 && n == 0));
+
+ if(done)
+ break;
+
+ if(sp_is_quit())
+ break;
}
- if(is_last_word(sp->line, CLAM_OK, KL(CLAM_OK)))
+ /* exit the process if not completed */
+ if(pid != 0)
{
- sp_add_log(sp, "status=", "CLEAN");
- sp_messagex(sp, LOG_DEBUG, "no virus");
- return 0;
+ if(wait_process(sp, pid, &status) == -1)
+ {
+ sp_messagex(sp, LOG_ERR, "timeout waiting for filter command to exit");
+ RETURN(-1);
+ }
+
+ pid = 0;
}
- if(is_last_word(sp->line, CLAM_FOUND, KL(CLAM_FOUND)))
+ /* We only trust well behaved programs */
+ if(!WIFEXITED(status))
{
- len = strlen(sp->cachename);
+ sp_messagex(sp, LOG_ERR, "filter command terminated abnormally");
+ RETURN(-1);
+ }
- if(sp->linelen > len)
- sp_add_log(sp, "status=VIRUS:", sp->line + len + 1);
- else
- sp_add_log(sp, "status=", "VIRUS");
+ sp_messagex(sp, LOG_DEBUG, "filter exit code: %d", (int)WEXITSTATUS(status));
- sp_messagex(sp, LOG_DEBUG, "found virus");
- return 1;
+ /* A successful response */
+ if(WEXITSTATUS(status) == 0)
+ {
+ if(sp_done_data(sp, NULL) == -1)
+ RETURN(-1); /* message already printed */
}
- if(is_last_word(sp->line, CLAM_ERROR, KL(CLAM_ERROR)))
+ /* Check code and use stderr if bad code */
+ else
{
- sp_messagex(sp, LOG_ERR, "clamav error: %s", sp->line);
- sp_add_log(sp, "status=", "CLAMAV-ERROR");
- return -1;
+ if(sp_fail_data(sp, ebuf[0] == 0 ? SMTP_REJECTED : ebuf) == -1)
+ RETURN(-1); /* message already printed */
}
- sp_add_log(sp, "status=", "CLAMAV-ERROR");
- sp_messagex(sp, LOG_ERR, "unexepected response from clamd: %s", sp->line);
- return -1;
-}
+ ret = 0;
-/* ----------------------------------------------------------------------------------
- * TEMP FILE HANDLING
- */
+cleanup:
-static int quarantine_virus(clctx_t* ctx)
-{
- char buf[MAXPATHLEN];
- spctx_t* sp = &(ctx->sp);
- char* t;
+ if(pipe_i[READ_END] != -1)
+ close(pipe_i[READ_END]);
+ if(pipe_i[WRITE_END] != -1)
+ close(pipe_i[WRITE_END]);
+ if(pipe_o[READ_END] != -1)
+ close(pipe_o[READ_END]);
+ if(pipe_o[WRITE_END] != -1)
+ close(pipe_o[WRITE_END]);
+ if(pipe_e[READ_END] != -1)
+ close(pipe_e[READ_END]);
+ if(pipe_e[WRITE_END] != -1)
+ close(pipe_e[WRITE_END]);
+
+ if(pid != 0)
+ kill_process(sp, pid);
- if(!g_clstate.quarantine)
- return 0;
+ return ret;
+}
- strlcpy(buf, g_clstate.directory, MAXPATHLEN);
- strlcat(buf, "/virus.", MAXPATHLEN);
+static void buffer_reject_message(char* data, char* buf, int buflen)
+{
+ char* t;
- /* Points to null terminator */
- t = buf + strlen(buf);
+ /* Take away all junk at beginning and end */
+ data = trim_space(data);
/*
- * 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.
+ * Look for the last new line in the message. We
+ * don't care about stuff before that.
*/
- for(;;)
+ t = strchr(data, '\n');
+ if(t == NULL)
+ {
+ t = data;
+ }
+ else
{
- /* Null terminate off the ending, and replace with X's for mktemp */
- *t = 0;
- strlcat(buf, "XXXXXX", MAXPATHLEN);
+ t++;
+ buf[0] = 0; /* Start a new message */
+ }
- if(!mktemp(buf))
- {
- sp_message(sp, LOG_ERR, "couldn't create quarantine file name");
- return -1;
- }
+ strlcat(buf, t, buflen);
+}
- /* 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;
- }
+static int wait_process(spctx_t* sp, pid_t pid, int* status)
+{
+ /* We poll x times a second */
+ int waits = g_pxstate.timeout.tv_sec * (1000 / POLL_TIME);
- sp_message(sp, LOG_ERR, "couldn't quarantine virus file");
+ while(waits > 0)
+ {
+ switch(waitpid(pid, status, WNOHANG))
+ {
+ case 0:
+ continue;
+ case -1:
+ sp_message(sp, LOG_CRIT, "error waiting on process");
return -1;
+ default:
+ return 0;
}
- break;
+ usleep(POLL_TIME * 1000);
+ waits--;
}
- sp_messagex(sp, LOG_INFO, "quarantined virus file as: %s", buf);
- return 0;
+ return -1;
}
-static int transfer_to_cache(clctx_t* ctx)
+static int kill_process(spctx_t* sp, pid_t pid)
{
- spctx_t* sp = &(ctx->sp);
- int r, count = 0;
- const char* data;
+ int status;
- while((r = sp_read_data(sp, &data)) != 0)
+ if(kill(pid, SIGTERM) == -1)
{
- if(r < 0)
- return -1; /* Message already printed */
+ if(errno == ESRCH)
+ return 0;
- count += r;
-
- if((r = sp_write_data(sp, data, r)) < 0)
- return -1; /* Message already printed */
+ sp_message(sp, LOG_ERR, "couldn't send signal to process");
+ return -1;
}
- /* End the caching */
- if(sp_write_data(sp, NULL, 0) < 0)
- return -1;
+ if(wait_process(sp, pid, &status) == -1)
+ {
+ if(kill(pid, SIGKILL) == -1)
+ {
+ if(errno == ESRCH)
+ return 0;
- sp_messagex(sp, LOG_DEBUG, "wrote %d bytes to temp file", count);
- return count;
-}
+ sp_message(sp, LOG_ERR, "couldn't send signal to process");
+ return -1;
+ }
+ sp_messagex(sp, LOG_ERR, "process wouldn't quit. forced termination");
+ }
+ return 0;
+}