diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | common/smtppass.c | 247 | ||||
-rw-r--r-- | common/smtppass.h | 57 | ||||
-rw-r--r-- | common/spio.c | 12 | ||||
-rw-r--r-- | common/stringx.c | 14 | ||||
-rw-r--r-- | common/stringx.h | 3 | ||||
-rw-r--r-- | common/usuals.h | 2 | ||||
-rw-r--r-- | configure.in | 9 | ||||
-rw-r--r-- | doc/Makefile.am | 3 | ||||
-rw-r--r-- | doc/clamsmtpd.8 (renamed from src/clamsmtpd.8) | 128 | ||||
-rw-r--r-- | doc/clamsmtpd.conf | 40 | ||||
-rw-r--r-- | doc/clamsmtpd.conf.5 | 142 | ||||
-rw-r--r-- | src/Makefile.am | 7 | ||||
-rw-r--r-- | src/clamsmtpd.c | 247 | ||||
-rw-r--r-- | src/clamsmtpd.h | 57 | ||||
-rw-r--r-- | src/clio.c | 12 | ||||
-rw-r--r-- | src/clstate.c | 325 | ||||
-rw-r--r-- | src/usuals.h | 2 | ||||
-rw-r--r-- | src/util.c | 14 | ||||
-rw-r--r-- | src/util.h | 3 |
20 files changed, 903 insertions, 423 deletions
diff --git a/Makefile.am b/Makefile.am index f4143f2..0bb743d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ EXTRA_DIST = config.sub acsite.m4 config.guess scripts -SUBDIRS = src +SUBDIRS = src doc dist-hook: rm -rf `find $(distdir)/ -name CVS` diff --git a/common/smtppass.c b/common/smtppass.c index e68bdf1..4ffb003 100644 --- a/common/smtppass.c +++ b/common/smtppass.c @@ -43,7 +43,6 @@ #include <sys/stat.h> #include <ctype.h> -#include <paths.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> @@ -51,7 +50,6 @@ #include <signal.h> #include <errno.h> #include <err.h> -#include <pthread.h> #include "usuals.h" #include "compat.h" @@ -74,8 +72,6 @@ clamsmtp_thread_t; * STRINGS */ -#define KL(s) ((sizeof(s) - 1) / sizeof(char)) - #define CRLF "\r\n" #define SMTP_TOOLONG "500 Line too long" CRLF @@ -126,44 +122,14 @@ clamsmtp_thread_t; #define CLAM_CONNECT "SESSION\nPING\n" #define CLAM_DISCONNECT "END\n" -/* ----------------------------------------------------------------------- - * DEFAULT SETTINGS - */ - -#define DEFAULT_SOCKET "10025" -#define DEFAULT_PORT 10025 -#define DEFAULT_CLAMAV "/var/run/clamav/clamd" -#define DEFAULT_MAXTHREADS 64 -#define DEFAULT_TIMEOUT 180 -#define DEFAULT_HEADER "X-AV-Checked: ClamAV using ClamSMTP" +#define DEFAULT_CONFIG CONF_PREFIX "/httpauthd.conf" /* ----------------------------------------------------------------------- * GLOBALS */ -int g_daemonized = 0; /* Currently running as a daemon */ -int g_debuglevel = LOG_ERR; /* what gets logged to console */ -int g_maxthreads = DEFAULT_MAXTHREADS; /* The maximum number of threads */ -struct timeval g_timeout = { DEFAULT_TIMEOUT, 0 }; - -struct sockaddr_any g_outaddr; /* The outgoing address */ -const char* g_outname = NULL; -struct sockaddr_any g_clamaddr; /* Address for connecting to clamd */ -const char* g_clamname = DEFAULT_CLAMAV; - -char* g_header = DEFAULT_HEADER; /* The header to add to email */ -const char* g_directory = _PATH_TMP; /* The directory for temp files */ +clstate_t g_state; /* The state and configuration of the daemon */ 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 */ -int g_debugfiles = 0; /* Leave all files in temp dir */ - -/* For main loop and signal handlers */ -int g_quit = 0; - -/* The main mutex and condition variables */ -pthread_mutex_t g_mutex; -pthread_mutexattr_t g_mutexattr; /* ----------------------------------------------------------------------- @@ -172,7 +138,7 @@ pthread_mutexattr_t g_mutexattr; static void usage(); static void on_quit(int signal); -static void pid_file(const char* pid, int write); +static void pid_file(int write); static void connection_loop(int sock); static void* thread_main(void* arg); static int smtp_passthru(clamsmtp_context_t* ctx); @@ -195,86 +161,90 @@ static void read_junk(clamsmtp_context_t* ctx, int fd); int main(int argc, char* argv[]) { - const char* listensock = DEFAULT_SOCKET; - struct sockaddr_any addr; - char* pidfile = NULL; - int daemonize = 1; + const char* configfile = DEFAULT_CONFIG; + int warnargs = 0; int sock; int true = 1; int ch = 0; char* t; + clstate_init(&g_state); + /* Parse the arguments nicely */ - while((ch = getopt(argc, argv, "bc:d:D:h:l:m:p:qt:vX")) != -1) + while((ch = getopt(argc, argv, "bc:d:D:h:l:m:p:qt:v")) != -1) { switch(ch) { /* Actively reject messages */ case 'b': - g_bounce = 1; + g_state.bounce = 1; + warnargs = 1; break; /* Change the CLAM socket */ case 'c': - g_clamname = optarg; + g_state.clamname = optarg; + warnargs = 1; break; /* Don't daemonize */ case 'd': - daemonize = 0; - g_debuglevel = strtol(optarg, &t, 10); - if(*t || g_debuglevel > 4) + g_state.debug_level = strtol(optarg, &t, 10); + if(*t) /* parse error */ errx(1, "invalid debug log level"); - g_debuglevel += LOG_ERR; + g_state.debug_level += LOG_ERR; break; /* The directory for the files */ case 'D': - g_directory = optarg; + g_state.directory = optarg; + warnargs = 1; + break; + + /* The configuration file */ + case 'f': + configfile = optarg; break; /* The header to add */ case 'h': if(strlen(optarg) == 0) - g_header = NULL; + g_state.header = NULL; else - { - g_header = optarg; - - /* Trim off any ending newline chars */ - t = g_header + strlen(g_header); - while(t > g_header && (*(t - 1) == '\r' || *(t - 1) == '\n')) - *(--t) = 0; - } + g_state.header = optarg; + warnargs = 1; break; /* Change our listening port */ case 'l': - listensock = optarg; + g_state.listenname = optarg; + warnargs = 1; break; /* The maximum number of threads */ case 'm': - g_maxthreads = strtol(optarg, &t, 10); - if(*t || g_maxthreads <= 1 || g_maxthreads >= 1024) - errx(1, "invalid max threads (must be between 1 and 1024"); + g_state.max_threads = strtol(optarg, &t, 10); + if(*t) /* parse error */ + errx(1, "invalid max threads"); + warnargs = 1; break; /* Write out a pid file */ case 'p': - pidfile = optarg; + g_state.pidfile = optarg; break; /* The timeout */ case 't': - g_timeout.tv_sec = strtol(optarg, &t, 10); - if(*t || g_timeout.tv_sec <= 0) - errx(1, "invalid timeout: %s", optarg); + g_state.timeout.tv_sec = strtol(optarg, &t, 10); + if(*t) /* parse error */ + errx(1, "invalid timeout"); + warnargs = 1; break; /* Leave virus files in directory */ case 'q': - g_quarantine = 1; + g_state.quarantine = 1; break; /* Print version number */ @@ -285,7 +255,8 @@ int main(int argc, char* argv[]) /* Leave all files in the tmp directory */ case 'X': - g_debugfiles = 1; + g_state.debug_files = 1; + warnargs = 1; break; /* Usage information */ @@ -296,25 +267,33 @@ int main(int argc, char* argv[]) } } + if(warnargs); + warnx("please use configuration file instead of command-line flags: %s", configfile); + argc -= optind; argv += optind; - if(argc != 1) + if(argc > 1) usage(); + if(argc == 1) + g_state.outname = argv[0]; + + /* Now parse the configuration file */ + if(clstate_parse_config(&g_state, configfile) == -1) + { + /* Only error when it was forced */ + if(configfile != DEFAULT_CONFIG) + err(1, "couldn't open config file: %s", configfile); + else + warnx("default configuration file not found: %s", configfile); + } - g_outname = argv[0]; + clstate_validate(&g_state); messagex(NULL, LOG_DEBUG, "starting up..."); - /* Parse all the addresses */ - if(sock_any_pton(listensock, &addr, SANY_OPT_DEFANY | SANY_OPT_DEFPORT(DEFAULT_PORT)) == -1) - errx(1, "invalid listen socket name or ip: %s", listensock); - if(sock_any_pton(g_outname, &g_outaddr, SANY_OPT_DEFPORT(25)) == -1) - errx(1, "invalid connect socket name or ip: %s", g_outname); - if(sock_any_pton(g_clamname, &g_clamaddr, SANY_OPT_DEFLOCAL) == -1) - errx(1, "invalid clam socket name: %s", g_clamname); - - if(daemonize) + /* When set to this we daemonize */ + if(g_state.debug_level == -1) { /* Fork a daemon nicely here */ if(daemon(0, 0) == -1) @@ -324,51 +303,61 @@ int main(int argc, char* argv[]) } messagex(NULL, LOG_DEBUG, "running as a daemon"); - g_daemonized = 1; + g_state.daemonized = 1; /* Open the system log */ openlog("clamsmtpd", 0, LOG_MAIL); } /* Create the socket */ - sock = socket(SANY_TYPE(addr), SOCK_STREAM, 0); + sock = socket(SANY_TYPE(g_state.listenaddr), SOCK_STREAM, 0); if(sock < 0) - err(1, "couldn't open socket"); + { + message(NULL, LOG_CRIT, "couldn't open socket"); + exit(1); + } setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&true, sizeof(true)); /* Unlink the socket file if it exists */ - if(SANY_TYPE(addr) == AF_UNIX) - unlink(listensock); + if(SANY_TYPE(g_state.listenaddr) == AF_UNIX) + unlink(g_state.listenname); - if(bind(sock, &SANY_ADDR(addr), SANY_LEN(addr)) != 0) - err(1, "couldn't bind to address: %s", listensock); + if(bind(sock, &SANY_ADDR(g_state.listenaddr), SANY_LEN(g_state.listenaddr)) != 0) + { + message(NULL, LOG_CRIT, "couldn't bind to address: %s", g_state.listenname); + exit(1); + } /* Let 5 connections queue up */ if(listen(sock, 5) != 0) - err(1, "couldn't listen on socket"); + { + message(NULL, LOG_CRIT, "couldn't listen on socket"); + exit(1); + } - messagex(NULL, LOG_DEBUG, "created socket: %s", listensock); + messagex(NULL, LOG_DEBUG, "created socket: %s", g_state.listenname); /* Handle some signals */ signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); + signal(SIGHUP, SIG_IGN); signal(SIGINT, on_quit); signal(SIGTERM, on_quit); siginterrupt(SIGINT, 1); siginterrupt(SIGTERM, 1); - if(pidfile) - pid_file(pidfile, 1); + if(g_state.pidfile) + pid_file(1); messagex(NULL, LOG_DEBUG, "accepting connections"); connection_loop(sock); - if(pidfile) - pid_file(pidfile, 0); + if(g_state.pidfile) + pid_file(0); + clstate_cleanup(&g_state); messagex(NULL, LOG_DEBUG, "stopped"); return 0; @@ -376,45 +365,43 @@ int main(int argc, char* argv[]) static void on_quit(int signal) { - g_quit = 1; - + g_state.quit = 1; /* fprintf(stderr, "clamsmtpd: got signal to quit\n"); */ } static void usage() { - fprintf(stderr, "usage: clamsmtpd [-bq] [-c clamaddr] [-d debuglevel] [-D tmpdir] [-h header] " - "[-l listenaddr] [-m maxconn] [-p pidfile] [-t timeout] serveraddr\n"); + fprintf(stderr, "usage: clamsmtpd [-d debuglevel] [-f configfile] \n"); fprintf(stderr, " clamsmtpd -v\n"); exit(2); } -static void pid_file(const char* pidfile, int write) +static void pid_file(int write) { if(write) { - FILE* f = fopen(pidfile, "w"); + FILE* f = fopen(g_state.pidfile, "w"); if(f == NULL) { - message(NULL, LOG_ERR, "couldn't open pid file: %s", pidfile); + message(NULL, LOG_ERR, "couldn't open pid file: %s", g_state.pidfile); } else { fprintf(f, "%d\n", (int)getpid()); if(ferror(f)) - message(NULL, LOG_ERR, "couldn't write to pid file: %s", pidfile); + message(NULL, LOG_ERR, "couldn't write to pid file: %s", g_state.pidfile); fclose(f); } - messagex(NULL, LOG_DEBUG, "wrote pid file: %s", pidfile); + messagex(NULL, LOG_DEBUG, "wrote pid file: %s", g_state.pidfile); } else { - unlink(pidfile); - messagex(NULL, LOG_DEBUG, "removed pid file: %s", pidfile); + unlink(g_state.pidfile); + messagex(NULL, LOG_DEBUG, "removed pid file: %s", g_state.pidfile); } } @@ -429,18 +416,12 @@ static void connection_loop(int sock) int fd, i, x, r; /* Create the thread buffers */ - threads = (clamsmtp_thread_t*)calloc(g_maxthreads, sizeof(clamsmtp_thread_t)); + threads = (clamsmtp_thread_t*)calloc(g_state.max_threads, sizeof(clamsmtp_thread_t)); if(!threads) errx(1, "out of memory"); - /* Create the main mutex and condition variable */ - if(pthread_mutexattr_init(&g_mutexattr) != 0 || - pthread_mutexattr_settype(&g_mutexattr, MUTEX_TYPE) || - pthread_mutex_init(&g_mutex, &g_mutexattr) != 0) - errx(1, "threading problem. can't create mutex or condition var"); - /* Now loop and accept the connections */ - while(!g_quit) + while(!g_state.quit) { fd = accept(sock, NULL, NULL); if(fd == -1) @@ -457,23 +438,23 @@ static void connection_loop(int sock) default: message(NULL, LOG_ERR, "couldn't accept a connection"); - g_quit = 1; + g_state.quit = 1; break; }; - if(g_quit) + if(g_state.quit) break; continue; } /* Set timeouts on client */ - if(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &g_timeout, sizeof(g_timeout)) < 0 || - setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &g_timeout, sizeof(g_timeout)) < 0) + if(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &g_state.timeout, sizeof(g_state.timeout)) < 0 || + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &g_state.timeout, sizeof(g_state.timeout)) < 0) message(NULL, LOG_WARNING, "couldn't set timeouts on incoming connection"); /* Look for thread and also clean up others */ - for(i = 0; i < g_maxthreads; i++) + for(i = 0; i < g_state.max_threads; i++) { /* Find a thread to run or clean up old threads */ if(threads[i].tid != 0) @@ -507,7 +488,7 @@ static void connection_loop(int sock) { errno = r; message(NULL, LOG_ERR, "couldn't create thread"); - g_quit = 1; + g_state.quit = 1; break; } @@ -520,7 +501,7 @@ static void connection_loop(int sock) /* Check to make sure we have a thread */ if(fd != -1) { - messagex(NULL, LOG_ERR, "too many connections open (max %d). sent 554 response", g_maxthreads); + messagex(NULL, LOG_ERR, "too many connections open (max %d). sent 554 response", g_state.max_threads); write(fd, SMTP_STARTBUSY, KL(SMTP_STARTBUSY)); shutdown(fd, SHUT_RDWR); @@ -532,7 +513,7 @@ static void connection_loop(int sock) messagex(NULL, LOG_DEBUG, "waiting for threads to quit"); /* Quit all threads here */ - for(i = 0; i < g_maxthreads; i++) + for(i = 0; i < g_state.max_threads; i++) { /* Clean up quit threads */ if(threads[i].tid != 0) @@ -551,10 +532,6 @@ static void connection_loop(int sock) pthread_join(threads[i].tid, NULL); } } - - /* Close the mutex */ - pthread_mutex_destroy(&g_mutex); - pthread_mutexattr_destroy(&g_mutexattr); } static void* thread_main(void* arg) @@ -616,15 +593,15 @@ static void* thread_main(void* arg) /* Create the server connection address */ - outaddr = &g_outaddr; - outname = g_outname; + outaddr = &(g_state.outaddr); + outname = g_state.outname; if(SANY_TYPE(*outaddr) == AF_INET && outaddr->s.in.sin_addr.s_addr == 0) { /* Use the incoming IP as the default */ in_addr_t in = addr.s.in.sin_addr.s_addr; - memcpy(&addr, &g_outaddr, sizeof(addr)); + memcpy(&addr, &(g_state.outaddr), sizeof(addr)); addr.s.in.sin_addr.s_addr = in; outaddr = &addr; @@ -954,7 +931,7 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) int havefile = 0; int r, ret = 0; - strlcpy(buf, g_directory, MAXPATHLEN); + strlcpy(buf, g_state.directory, MAXPATHLEN); strlcat(buf, "/clamsmtpd.XXXXXX", MAXPATHLEN); /* transfer_to_file deletes the temp file on failure */ @@ -993,7 +970,7 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) */ case 1: if(clio_write_data(ctx, &(ctx->client), - g_bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) + g_state.bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) RETURN(-1); /* Any special post operation actions on the virus */ @@ -1006,7 +983,7 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) }; cleanup: - if(havefile && !g_debugfiles) + if(havefile && !g_state.debug_files) { messagex(ctx, LOG_DEBUG, "deleting temporary file: %s", buf); unlink(buf); @@ -1089,7 +1066,7 @@ static int connect_clam(clamsmtp_context_t* ctx) ASSERT(ctx); ASSERT(!clio_valid(&(ctx->clam))); - if(clio_connect(ctx, &(ctx->clam), &g_clamaddr, g_clamname) == -1) + if(clio_connect(ctx, &(ctx->clam), &g_state.clamaddr, g_state.clamname) == -1) RETURN(-1); read_junk(ctx, ctx->clam.fd); @@ -1193,10 +1170,10 @@ static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname) char buf[MAXPATHLEN]; char* t; - if(!g_quarantine) + if(!g_state.quarantine) return 0; - strlcpy(buf, g_directory, MAXPATHLEN); + strlcpy(buf, g_state.directory, MAXPATHLEN); strlcat(buf, "/virus.", MAXPATHLEN); /* Points to null terminator */ @@ -1328,7 +1305,7 @@ static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename) while(fgets(ctx->line, LINE_LENGTH, file) != NULL) { - if(g_header && !header) + if(g_state.header && !header) { /* * The first blank line we see means the headers are done. @@ -1336,7 +1313,7 @@ static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename) */ if(is_blank_line(ctx->line)) { - if(clio_write_data_raw(ctx, &(ctx->server), (char*)g_header, strlen(g_header)) == -1 || + if(clio_write_data_raw(ctx, &(ctx->server), (char*)g_state.header, strlen(g_state.header)) == -1 || clio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1) RETURN(-1); diff --git a/common/smtppass.h b/common/smtppass.h index ca3df37..deb2cfb 100644 --- a/common/smtppass.h +++ b/common/smtppass.h @@ -39,6 +39,10 @@ #ifndef __CLAMSMTPD_H__ #define __CLAMSMTPD_H__ +#include <sock_any.h> + +/* IO Buffers see clio.c ---------------------------------------------------- */ + #define BUF_LEN 256 typedef struct clio @@ -50,6 +54,8 @@ typedef struct clio } clio_t; +/* The main context --------------------------------------------------------- */ + /* * A generous maximum line length. It needs to be longer than * a full path on this system can be, because we pass the file @@ -75,17 +81,12 @@ typedef struct clamsmtp_context } clamsmtp_context_t; -extern int g_daemonized; /* Currently running as a daemon */ -extern int g_debuglevel; /* what gets logged to console */ -extern pthread_mutex_t g_mutex; /* The main mutex */ -extern struct timeval g_timeout; -extern int g_quit; - -struct sockaddr_any; #define LINE_TOO_LONG(ctx) ((ctx)->linelen >= (LINE_LENGTH - 2)) #define RETURN(x) { ret = x; goto cleanup; } +/* Implemented in clio.c ---------------------------------------------------- */ + #define CLIO_TRIM 0x00000001 #define CLIO_DISCARD 0x00000002 #define CLIO_QUIET 0x00000004 @@ -99,4 +100,46 @@ int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int trim); int clio_write_data(clamsmtp_context_t* ctx, clio_t* io, const char* data); int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, int len); + +/* Implemented in clstate.c ------------------------------------------------ */ + +typedef struct clstate +{ + /* Settings ------------------------------- */ + int debug_level; /* The level to print stuff to console */ + int max_threads; /* Maximum number of threads to process at once */ + struct timeval timeout; /* Timeout for communication */ + + struct sockaddr_any outaddr; /* The outgoing address */ + const char* outname; + struct sockaddr_any clamaddr; /* Address for connecting to clamd */ + const char* clamname; + struct sockaddr_any listenaddr; /* Address to listen on */ + const char* listenname; + + const char* header; /* The header to add to email */ + const char* directory; /* The directory for temp files */ + const char* pidfile; /* The process id file */ + int bounce; /* Send back a reject line */ + int quarantine; /* Leave virus files in temp dir */ + int debug_files; /* Leave all files in temp dir */ + + /* State --------------------------------- */ + int daemonized; /* Whether process is daemonized or not */ + pthread_mutex_t mutex; /* The main mutex */ + int quit; /* Quit the process */ + + /* Internal Use ------------------------- */ + char* _p; + pthread_mutexattr_t _mtxattr; +} +clstate_t; + +extern clstate_t g_state; + +void clstate_init(clstate_t* state); +int clstate_parse_config(clstate_t* state, const char* configfile); +void clstate_validate(clstate_t* state); +void clstate_cleanup(clstate_t* state); + #endif /* __CLAMSMTPD_H__ */ diff --git a/common/spio.c b/common/spio.c index 1794118..a2694bb 100644 --- a/common/spio.c +++ b/common/spio.c @@ -90,7 +90,7 @@ static void log_io_data(clamsmtp_context_t* ctx, clio_t* io, const char* data, i memcpy(buf, data, len); buf[len] = 0; - messagex(ctx, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io), + messagex(0, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io), read ? " < " : " > ", buf); data += pos; @@ -116,8 +116,8 @@ int clio_connect(clamsmtp_context_t* ctx, clio_t* io, struct sockaddr_any* sany, if((io->fd = socket(SANY_TYPE(*sany), SOCK_STREAM, 0)) == -1) RETURN(-1); - if(setsockopt(io->fd, SOL_SOCKET, SO_RCVTIMEO, &g_timeout, sizeof(g_timeout)) == -1 || - setsockopt(io->fd, SOL_SOCKET, SO_SNDTIMEO, &g_timeout, sizeof(g_timeout)) == -1) + if(setsockopt(io->fd, SOL_SOCKET, SO_RCVTIMEO, &g_state.timeout, sizeof(g_state.timeout)) == -1 || + setsockopt(io->fd, SOL_SOCKET, SO_SNDTIMEO, &g_state.timeout, sizeof(g_state.timeout)) == -1) messagex(ctx, LOG_WARNING, "couldn't set timeouts on connection"); if(connect(io->fd, &SANY_ADDR(*sany), SANY_LEN(*sany)) == -1) @@ -183,7 +183,7 @@ int clio_select(clamsmtp_context_t* ctx, clio_t** io) /* Select on the above */ - switch(select(FD_SETSIZE, &mask, NULL, NULL, &g_timeout)) + switch(select(FD_SETSIZE, &mask, NULL, NULL, &g_state.timeout)) { case 0: messagex(ctx, LOG_ERR, "network operation timed out"); @@ -244,7 +244,7 @@ int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) if(errno == EINTR) { /* When the application is quiting */ - if(g_quit) + if(g_state.quit) return -1; /* For any other signal we go again */ @@ -380,7 +380,7 @@ int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, if(errno == EINTR) { /* When the application is quiting */ - if(g_quit) + if(g_state.quit) return -1; /* For any other signal we go again */ diff --git a/common/stringx.c b/common/stringx.c index 8b6a816..c0a46bc 100644 --- a/common/stringx.c +++ b/common/stringx.c @@ -67,14 +67,14 @@ static void vmessage(clamsmtp_context_t* ctx, int level, int err, char* m; int e = errno; - if(g_daemonized) + if(g_state.daemonized) { if(level >= LOG_DEBUG) return; } else { - if(g_debuglevel < level) + if(g_state.debug_level < level) return; } @@ -102,7 +102,7 @@ static void vmessage(clamsmtp_context_t* ctx, int level, int err, } /* Either to syslog or stderr */ - if(g_daemonized) + if(g_state.daemonized) vsyslog(level, msg, ap); else vwarnx(msg, ap); @@ -244,16 +244,16 @@ void plock() #endif #ifdef _DEBUG - r = pthread_mutex_trylock(&g_mutex); + r = pthread_mutex_trylock(&(g_state.mutex)); if(r == EBUSY) { wait = 1; message(NULL, LOG_DEBUG, "thread will block: %d", pthread_self()); - r = pthread_mutex_lock(&g_mutex); + r = pthread_mutex_lock(&(g_state.mutex)); } #else - r = pthread_mutex_lock(&g_mutex); + r = pthread_mutex_lock(&(g_state.mutex)); #endif @@ -273,7 +273,7 @@ void plock() void punlock() { - int r = pthread_mutex_unlock(&g_mutex); + int r = pthread_mutex_unlock(&(g_state.mutex)); if(r != 0) { errno = r; diff --git a/common/stringx.h b/common/stringx.h index 8e6f2f4..37fa245 100644 --- a/common/stringx.h +++ b/common/stringx.h @@ -42,9 +42,6 @@ void messagex(clamsmtp_context_t* ctx, int level, const char* msg, ...); void message(clamsmtp_context_t* ctx, int level, const char* msg, ...); -void log_fd_data(clamsmtp_context_t* ctx, const char* data, int* fd, int read); -void log_data(clamsmtp_context_t* ctx, const char* data, const char* prefix); - int check_first_word(const char* line, const char* word, int len, char* delims); int is_first_word(const char* line, const char* word, int len); int is_last_word(const char* line, const char* word, int len); diff --git a/common/usuals.h b/common/usuals.h index 00410aa..385bcf9 100644 --- a/common/usuals.h +++ b/common/usuals.h @@ -71,4 +71,6 @@ #define ASSERT #endif +#define KL(s) ((sizeof(s) - 1) / sizeof(char)) + #endif /* __USUALS_H__ */ diff --git a/configure.in b/configure.in index 18be253..de40fb5 100644 --- a/configure.in +++ b/configure.in @@ -87,5 +87,12 @@ AC_CHECK_FUNCS([memset strerror malloc realloc getopt strchr tolower getaddrinfo [echo "ERROR: Required function missing"; exit 1]) AC_CHECK_FUNCS([strlwr strlcat strlcpy strncat strncpy]) -AC_CONFIG_FILES([Makefile src/Makefile]) +# Have to resolve this for the path below +if test "${prefix}" = "NONE"; then + prefix=$ac_default_prefix +fi + +AC_DEFINE_UNQUOTED(CONF_PREFIX, "`eval echo ${sysconfdir}`", [Installation Prefix] ) + +AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile]) AC_OUTPUT diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..44dfc12 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,3 @@ + +man_MANS = clamsmtpd.8 clamsmtpd.conf.5 +EXTRA_DIST = $(man_MANS) clamsmtpd.conf diff --git a/src/clamsmtpd.8 b/doc/clamsmtpd.8 index 75a1cae..8ee3d80 100644 --- a/src/clamsmtpd.8 +++ b/doc/clamsmtpd.8 @@ -34,7 +34,7 @@ .\" CONTRIBUTORS .\" Nate Nielsen <nielsen@memberwebs.com> .\" -.Dd July, 2004 +.Dd September, 2004 .Dt clamsmtpd 8 .Os clamsmtp .Sh NAME @@ -42,17 +42,8 @@ .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 -.Op Fl h Ar header -.Op Fl l Ar listenaddr -.Op Fl m Ar maxconn -.Op Fl p Ar pidfile -.Op Fl r -.Op Fl t Ar timeout -.Ar serveraddr +.Op Fl f Ar configfile .Nm .Fl v .Sh DESCRIPTION @@ -65,84 +56,31 @@ The DATA email body is intercepted and scanned before forwarding. By default ema 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 -basic usage would look like the following (Be sure to see the SECURITY section -below): -.Pp -.Dl clamsmtpd -c /path/to/clam.sock mysmtp.com:25 -.Pp -The above command would start -.Nm -listening on port 10025 (the default) and forward email to mysmtp.com on port 25. -It also specifies the socket where -.Xr clamd 8 -is listening for connections. +aims to be lightweight and simple rather than have a myriad of options. The options +it does have are configured by editing the +.Xr clamsmtpd.conf 5 +file. See the man page for +.Xr clamsmtpd.conf 5 +for more info on the default location of the configuration file. .Sh OPTIONS -The options are as follows: +Previous versions had more options. These still work for now but have equivalents in +.Xr clamsmtpd.conf 5 +and are not documented here. The options are as follows. .Bl -tag -width Fl -.It Fl b -When this flag is set -.Nm -actively rejects messages with viruses. This may cause the sender to receive -a message back notifying them of the virus. In most cases this is not a good -idea since many viruses spoof sender addresses. -.It Fl c -.Ar clamaddr -specifies the address to connect to -.XR clamd 8 -on. See syntax of addresses below. -[Default: -.Pa /var/run/clamav/clamd -] .It Fl d Don't detach from the console and run as a daemon. In addition the .Ar level argument specifies what level of error messages to display. 0 being the least, 4 the most. -.It Fl D -.Ar tmpdir -is the directory to write temp files too. This directory needs to be -accessible to both -.Xr clamd 8 -and -.Nm -[Default: -.Pa /tmp -] -.It Fl h -.Ar header -is a header to add to scanned messages. Add a blank argument to not add -a header. [Default: 'X-AV-Checked: ClamAV using ClamSMTP'] -.It Fl l -.Ar listenaddr -is the address and port to listen for SMTP connections on. See syntax of -addresses below. [Default: port 10025 on all local IP addresses] -.It Fl m -.Ar maxconn -specifies the maximum number of connections to accept at once. -[Default: 64] -.It Fl p -This option causes +.It Fl f +.Ar configfile +specifies an alternate location for the .Nm -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. -[Default: 180 seconds] +configuration file. See +.Xr clamsmtpd.conf 5 +for more details on where the configuration file is located by default. .It Fl v Prints the clamsmtp version number and exits. -.It serveraddr -The address of the SMTP server to send email to once it's been scanned. This -option must be specified. See syntax of addreses below. .El .Sh LOGGING .Nm @@ -160,13 +98,13 @@ 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 -in which case +.Ar OutAddress +setting in the configuration file. This will cause .Nm -will pass the email back to the said port on the incoming IP address. +to pass the email back to the said port on the incoming IP address. .Pp Make sure the -.Ar maxconn +.Ar MaxConnections setting is set high enough to handle the mail from all the servers without refusing connections. .Sh SECURITY @@ -181,26 +119,16 @@ Care should be taken with the directory that .Nm writes its temporary files to. In order to be secure, it should not be a world writeable location. Specify the directory using the -.Fl t -option. +.Ar TempDirectory +setting. .Pp +If running .Nm -should probably not be run on a publicly accessible IP address or without a -firewall. This is especially true if the loopback feature is used (see above). -.Sh ADDRESSES -Addresses can be specified in multiple formats: -.Bl -bullet -.It -Unix local addresses can be specified by specifying their full path. -(ie: '/var/run/clamav/clamd'). -.It -IP addresses can be specified using dotted notation with a colon before -the port number (ie: '127.0.0.1:3310'). -.It -IPv6 addresses can be specified using bracketted notation with a colon -before the port number (ie: '[::1]:3310') -.El +on a publicly accessible IP address or without a firewall please be sure to +understand all the possible security issues. This is especially true if the +loopback feature is used (see above). .Sh SEE ALSO +.Xr clamsmtpd.conf 5 .Xr clamd 8 , .Xr clamdscan 1 .Sh AUTHOR diff --git a/doc/clamsmtpd.conf b/doc/clamsmtpd.conf new file mode 100644 index 0000000..979103c --- /dev/null +++ b/doc/clamsmtpd.conf @@ -0,0 +1,40 @@ +# ------------------------------------------------------------------------------ +# SAMPLE CLAMSMTPD CONFIG FILE +# ------------------------------------------------------------------------------ +# +# - Comments are a line that starts with a # +# - All the options are found below with their defaults commented out + + +# The address to send scanned mail to. Required +OutAddress: 10026 + + + +# The maximum number of connection allowed at once +#MaxConnections: 64 + +# Amount of time (in seconds) to wait on network IO +#TimeOut: 180 + +# Address to listen on (defaults to all local addresses on port 10025) +#Listen: 0.0.0.0:10025 + +# The address clamd is listening on +#ClamAddress: /var/run/clamav/clamd + +# A header to add to all scanned email +#ScanHeader: X-AV-Checked: ClamAV using ClamSMTP + +# Directory for temporary files +#TempDirectory: /tmp + +# Whether or not to bounce email (default is to silently drop) +#Bounce: off + +# Whether or not to keep virus files +#Quarantine: off + +# The location for a pid file for stopping clamsmtpd +#PidFile: (none) + diff --git a/doc/clamsmtpd.conf.5 b/doc/clamsmtpd.conf.5 new file mode 100644 index 0000000..ffbd204 --- /dev/null +++ b/doc/clamsmtpd.conf.5 @@ -0,0 +1,142 @@ +.\" +.\" Copyright (c) 2004, Nate Nielsen +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" * Redistributions of source code must retain the above +.\" copyright notice, this list of conditions and the +.\" following disclaimer. +.\" * Redistributions in binary form must reproduce the +.\" above copyright notice, this list of conditions and +.\" the following disclaimer in the documentation and/or +.\" other materials provided with the distribution. +.\" * The names of contributors to this software may not be +.\" used to endorse or promote products derived from this +.\" software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +.\" COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +.\" OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +.\" THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +.\" DAMAGE. +.\" +.\" +.\" CONTRIBUTORS +.\" Nate Nielsen <nielsen@memberwebs.com> +.\" +.Dd September, 2004 +.Dt clamsmtpd.conf 5 +.Os clamsmtp +.Sh NAME +.Nm clamsmtpd.conf +.Nd the configuration file for +.Xr clamsmtpd 8 +.Sh DESCRIPTION +.Xr clamsmtpd 8 +reads a configuration file when starting up. The location of the file is dependent +on how you compiled clamsmtp but it should usually be in either the +.Pa /usr/local/etc/ +or +.Pa /etc/ +directories. If +.Xr clamsmtpd 8 +does not find it's configuration file it'll print a warning when it starts up along +with the location it's expecting to find it in. You can also specify a different +location for a config file by passing the +.Fl f +argument to +.Xr clamsmtpd 8 +.Pp +The settings are specified one per line. The setting names comes first, followed +by a colon and then the value. Comments start with the '#' character on a line +of their own. Whitespace is ignored at the beginning of line, end of line and +around the colons. +.Pp +A sample configuration file can be found in the +.Pa doc/ +directory of the clamsmtp distribution. +.Sh SETTINGS +The various settings are as follows: +.Bl -tag -width Fl +.It Ar Bounce +When this flag is set +.Xr clamsmtpd 8 +actively rejects messages with viruses. This may cause the sender to receive +a message back notifying them of the virus. In most cases this is not a good +idea since many viruses spoof sender addresses. +[ Default: off ] +.It Ar ClamAddress +Specifies the address to connect to +.Xr clamd 8 +on. See syntax of addresses below. +[ Default: +.Pa /var/run/clamav/clamd +] +.It Ar Listen +The address and port to listen for SMTP connections on. See syntax of +addresses below. +[ Default: port 10025 on all local IP addresses ] +.It Ar Quarantine +Quarantine files that contain viruses by leaving them in the +.Ar TempDirectory +directory. The file names look like this (where X is a random +character or number): +.Pa virus.XXXXXX +[ Default: off ] +.It Ar MaxConnections +Specifies the maximum number of connections to accept at once. +[ Default: 64 ] +.It Ar PidFile +This option causes +.Xr clamsmtpd 8 +to write a file with the daemon's process id, which can be used to stop the +daemon. +[ Optional ] +.It Ar ScanHeader +A header to add to scanned messages. Put an empty value to supress adding +a header. +[ Default: 'X-AV-Checked: ClamAV using ClamSMTP' ] +.It Ar OutAddress +The address of the SMTP server to send email to once it's been scanned. See +syntax of addreses below. +[ Required ] +.It TempDirectory +The directory to write temp files too. This directory needs to be +accessible to both +.Xr clamd 8 +and +.Xr clamsmtpd 8 +[ Default: +.Pa /tmp +] +.It Ar TimeOut +The number of seconds to wait while reading data from network connections. +[ Default: 180 seconds ] +.El +.Sh ADDRESSES +Addresses can be specified in multiple formats: +.Bl -bullet +.It +Unix local addresses can be specified by specifying their full path. +(ie: '/var/run/clamav/clamd'). +.It +IP addresses can be specified using dotted notation with a colon before +the port number (ie: '127.0.0.1:3310'). +.It +IPv6 addresses can be specified using bracketted notation with a colon +before the port number (ie: '[::1]:3310') +.El +.Sh SEE ALSO +.Xr clamsmtpd 8 +.Sh AUTHOR +.An Nate Nielsen Aq nielsen@memberwebs.com diff --git a/src/Makefile.am b/src/Makefile.am index 5b18c6b..3c8deb6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,11 +2,8 @@ sbin_PROGRAMS = clamsmtpd clamsmtpd_SOURCES = clamsmtpd.c clamsmtpd.h util.c util.h sock_any.h sock_any.c \ - compat.c compat.h usuals.h clio.c - -man_MANS = clamsmtpd.8 -EXTRA_DIST = $(man_MANS) + compat.c compat.h usuals.h clio.c clstate.c all-local: @echo "NOTE: Ignore any warnings about mktemp(). It's used safely in this case." - @echo
\ No newline at end of file + @echo diff --git a/src/clamsmtpd.c b/src/clamsmtpd.c index e68bdf1..4ffb003 100644 --- a/src/clamsmtpd.c +++ b/src/clamsmtpd.c @@ -43,7 +43,6 @@ #include <sys/stat.h> #include <ctype.h> -#include <paths.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> @@ -51,7 +50,6 @@ #include <signal.h> #include <errno.h> #include <err.h> -#include <pthread.h> #include "usuals.h" #include "compat.h" @@ -74,8 +72,6 @@ clamsmtp_thread_t; * STRINGS */ -#define KL(s) ((sizeof(s) - 1) / sizeof(char)) - #define CRLF "\r\n" #define SMTP_TOOLONG "500 Line too long" CRLF @@ -126,44 +122,14 @@ clamsmtp_thread_t; #define CLAM_CONNECT "SESSION\nPING\n" #define CLAM_DISCONNECT "END\n" -/* ----------------------------------------------------------------------- - * DEFAULT SETTINGS - */ - -#define DEFAULT_SOCKET "10025" -#define DEFAULT_PORT 10025 -#define DEFAULT_CLAMAV "/var/run/clamav/clamd" -#define DEFAULT_MAXTHREADS 64 -#define DEFAULT_TIMEOUT 180 -#define DEFAULT_HEADER "X-AV-Checked: ClamAV using ClamSMTP" +#define DEFAULT_CONFIG CONF_PREFIX "/httpauthd.conf" /* ----------------------------------------------------------------------- * GLOBALS */ -int g_daemonized = 0; /* Currently running as a daemon */ -int g_debuglevel = LOG_ERR; /* what gets logged to console */ -int g_maxthreads = DEFAULT_MAXTHREADS; /* The maximum number of threads */ -struct timeval g_timeout = { DEFAULT_TIMEOUT, 0 }; - -struct sockaddr_any g_outaddr; /* The outgoing address */ -const char* g_outname = NULL; -struct sockaddr_any g_clamaddr; /* Address for connecting to clamd */ -const char* g_clamname = DEFAULT_CLAMAV; - -char* g_header = DEFAULT_HEADER; /* The header to add to email */ -const char* g_directory = _PATH_TMP; /* The directory for temp files */ +clstate_t g_state; /* The state and configuration of the daemon */ 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 */ -int g_debugfiles = 0; /* Leave all files in temp dir */ - -/* For main loop and signal handlers */ -int g_quit = 0; - -/* The main mutex and condition variables */ -pthread_mutex_t g_mutex; -pthread_mutexattr_t g_mutexattr; /* ----------------------------------------------------------------------- @@ -172,7 +138,7 @@ pthread_mutexattr_t g_mutexattr; static void usage(); static void on_quit(int signal); -static void pid_file(const char* pid, int write); +static void pid_file(int write); static void connection_loop(int sock); static void* thread_main(void* arg); static int smtp_passthru(clamsmtp_context_t* ctx); @@ -195,86 +161,90 @@ static void read_junk(clamsmtp_context_t* ctx, int fd); int main(int argc, char* argv[]) { - const char* listensock = DEFAULT_SOCKET; - struct sockaddr_any addr; - char* pidfile = NULL; - int daemonize = 1; + const char* configfile = DEFAULT_CONFIG; + int warnargs = 0; int sock; int true = 1; int ch = 0; char* t; + clstate_init(&g_state); + /* Parse the arguments nicely */ - while((ch = getopt(argc, argv, "bc:d:D:h:l:m:p:qt:vX")) != -1) + while((ch = getopt(argc, argv, "bc:d:D:h:l:m:p:qt:v")) != -1) { switch(ch) { /* Actively reject messages */ case 'b': - g_bounce = 1; + g_state.bounce = 1; + warnargs = 1; break; /* Change the CLAM socket */ case 'c': - g_clamname = optarg; + g_state.clamname = optarg; + warnargs = 1; break; /* Don't daemonize */ case 'd': - daemonize = 0; - g_debuglevel = strtol(optarg, &t, 10); - if(*t || g_debuglevel > 4) + g_state.debug_level = strtol(optarg, &t, 10); + if(*t) /* parse error */ errx(1, "invalid debug log level"); - g_debuglevel += LOG_ERR; + g_state.debug_level += LOG_ERR; break; /* The directory for the files */ case 'D': - g_directory = optarg; + g_state.directory = optarg; + warnargs = 1; + break; + + /* The configuration file */ + case 'f': + configfile = optarg; break; /* The header to add */ case 'h': if(strlen(optarg) == 0) - g_header = NULL; + g_state.header = NULL; else - { - g_header = optarg; - - /* Trim off any ending newline chars */ - t = g_header + strlen(g_header); - while(t > g_header && (*(t - 1) == '\r' || *(t - 1) == '\n')) - *(--t) = 0; - } + g_state.header = optarg; + warnargs = 1; break; /* Change our listening port */ case 'l': - listensock = optarg; + g_state.listenname = optarg; + warnargs = 1; break; /* The maximum number of threads */ case 'm': - g_maxthreads = strtol(optarg, &t, 10); - if(*t || g_maxthreads <= 1 || g_maxthreads >= 1024) - errx(1, "invalid max threads (must be between 1 and 1024"); + g_state.max_threads = strtol(optarg, &t, 10); + if(*t) /* parse error */ + errx(1, "invalid max threads"); + warnargs = 1; break; /* Write out a pid file */ case 'p': - pidfile = optarg; + g_state.pidfile = optarg; break; /* The timeout */ case 't': - g_timeout.tv_sec = strtol(optarg, &t, 10); - if(*t || g_timeout.tv_sec <= 0) - errx(1, "invalid timeout: %s", optarg); + g_state.timeout.tv_sec = strtol(optarg, &t, 10); + if(*t) /* parse error */ + errx(1, "invalid timeout"); + warnargs = 1; break; /* Leave virus files in directory */ case 'q': - g_quarantine = 1; + g_state.quarantine = 1; break; /* Print version number */ @@ -285,7 +255,8 @@ int main(int argc, char* argv[]) /* Leave all files in the tmp directory */ case 'X': - g_debugfiles = 1; + g_state.debug_files = 1; + warnargs = 1; break; /* Usage information */ @@ -296,25 +267,33 @@ int main(int argc, char* argv[]) } } + if(warnargs); + warnx("please use configuration file instead of command-line flags: %s", configfile); + argc -= optind; argv += optind; - if(argc != 1) + if(argc > 1) usage(); + if(argc == 1) + g_state.outname = argv[0]; + + /* Now parse the configuration file */ + if(clstate_parse_config(&g_state, configfile) == -1) + { + /* Only error when it was forced */ + if(configfile != DEFAULT_CONFIG) + err(1, "couldn't open config file: %s", configfile); + else + warnx("default configuration file not found: %s", configfile); + } - g_outname = argv[0]; + clstate_validate(&g_state); messagex(NULL, LOG_DEBUG, "starting up..."); - /* Parse all the addresses */ - if(sock_any_pton(listensock, &addr, SANY_OPT_DEFANY | SANY_OPT_DEFPORT(DEFAULT_PORT)) == -1) - errx(1, "invalid listen socket name or ip: %s", listensock); - if(sock_any_pton(g_outname, &g_outaddr, SANY_OPT_DEFPORT(25)) == -1) - errx(1, "invalid connect socket name or ip: %s", g_outname); - if(sock_any_pton(g_clamname, &g_clamaddr, SANY_OPT_DEFLOCAL) == -1) - errx(1, "invalid clam socket name: %s", g_clamname); - - if(daemonize) + /* When set to this we daemonize */ + if(g_state.debug_level == -1) { /* Fork a daemon nicely here */ if(daemon(0, 0) == -1) @@ -324,51 +303,61 @@ int main(int argc, char* argv[]) } messagex(NULL, LOG_DEBUG, "running as a daemon"); - g_daemonized = 1; + g_state.daemonized = 1; /* Open the system log */ openlog("clamsmtpd", 0, LOG_MAIL); } /* Create the socket */ - sock = socket(SANY_TYPE(addr), SOCK_STREAM, 0); + sock = socket(SANY_TYPE(g_state.listenaddr), SOCK_STREAM, 0); if(sock < 0) - err(1, "couldn't open socket"); + { + message(NULL, LOG_CRIT, "couldn't open socket"); + exit(1); + } setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&true, sizeof(true)); /* Unlink the socket file if it exists */ - if(SANY_TYPE(addr) == AF_UNIX) - unlink(listensock); + if(SANY_TYPE(g_state.listenaddr) == AF_UNIX) + unlink(g_state.listenname); - if(bind(sock, &SANY_ADDR(addr), SANY_LEN(addr)) != 0) - err(1, "couldn't bind to address: %s", listensock); + if(bind(sock, &SANY_ADDR(g_state.listenaddr), SANY_LEN(g_state.listenaddr)) != 0) + { + message(NULL, LOG_CRIT, "couldn't bind to address: %s", g_state.listenname); + exit(1); + } /* Let 5 connections queue up */ if(listen(sock, 5) != 0) - err(1, "couldn't listen on socket"); + { + message(NULL, LOG_CRIT, "couldn't listen on socket"); + exit(1); + } - messagex(NULL, LOG_DEBUG, "created socket: %s", listensock); + messagex(NULL, LOG_DEBUG, "created socket: %s", g_state.listenname); /* Handle some signals */ signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); + signal(SIGHUP, SIG_IGN); signal(SIGINT, on_quit); signal(SIGTERM, on_quit); siginterrupt(SIGINT, 1); siginterrupt(SIGTERM, 1); - if(pidfile) - pid_file(pidfile, 1); + if(g_state.pidfile) + pid_file(1); messagex(NULL, LOG_DEBUG, "accepting connections"); connection_loop(sock); - if(pidfile) - pid_file(pidfile, 0); + if(g_state.pidfile) + pid_file(0); + clstate_cleanup(&g_state); messagex(NULL, LOG_DEBUG, "stopped"); return 0; @@ -376,45 +365,43 @@ int main(int argc, char* argv[]) static void on_quit(int signal) { - g_quit = 1; - + g_state.quit = 1; /* fprintf(stderr, "clamsmtpd: got signal to quit\n"); */ } static void usage() { - fprintf(stderr, "usage: clamsmtpd [-bq] [-c clamaddr] [-d debuglevel] [-D tmpdir] [-h header] " - "[-l listenaddr] [-m maxconn] [-p pidfile] [-t timeout] serveraddr\n"); + fprintf(stderr, "usage: clamsmtpd [-d debuglevel] [-f configfile] \n"); fprintf(stderr, " clamsmtpd -v\n"); exit(2); } -static void pid_file(const char* pidfile, int write) +static void pid_file(int write) { if(write) { - FILE* f = fopen(pidfile, "w"); + FILE* f = fopen(g_state.pidfile, "w"); if(f == NULL) { - message(NULL, LOG_ERR, "couldn't open pid file: %s", pidfile); + message(NULL, LOG_ERR, "couldn't open pid file: %s", g_state.pidfile); } else { fprintf(f, "%d\n", (int)getpid()); if(ferror(f)) - message(NULL, LOG_ERR, "couldn't write to pid file: %s", pidfile); + message(NULL, LOG_ERR, "couldn't write to pid file: %s", g_state.pidfile); fclose(f); } - messagex(NULL, LOG_DEBUG, "wrote pid file: %s", pidfile); + messagex(NULL, LOG_DEBUG, "wrote pid file: %s", g_state.pidfile); } else { - unlink(pidfile); - messagex(NULL, LOG_DEBUG, "removed pid file: %s", pidfile); + unlink(g_state.pidfile); + messagex(NULL, LOG_DEBUG, "removed pid file: %s", g_state.pidfile); } } @@ -429,18 +416,12 @@ static void connection_loop(int sock) int fd, i, x, r; /* Create the thread buffers */ - threads = (clamsmtp_thread_t*)calloc(g_maxthreads, sizeof(clamsmtp_thread_t)); + threads = (clamsmtp_thread_t*)calloc(g_state.max_threads, sizeof(clamsmtp_thread_t)); if(!threads) errx(1, "out of memory"); - /* Create the main mutex and condition variable */ - if(pthread_mutexattr_init(&g_mutexattr) != 0 || - pthread_mutexattr_settype(&g_mutexattr, MUTEX_TYPE) || - pthread_mutex_init(&g_mutex, &g_mutexattr) != 0) - errx(1, "threading problem. can't create mutex or condition var"); - /* Now loop and accept the connections */ - while(!g_quit) + while(!g_state.quit) { fd = accept(sock, NULL, NULL); if(fd == -1) @@ -457,23 +438,23 @@ static void connection_loop(int sock) default: message(NULL, LOG_ERR, "couldn't accept a connection"); - g_quit = 1; + g_state.quit = 1; break; }; - if(g_quit) + if(g_state.quit) break; continue; } /* Set timeouts on client */ - if(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &g_timeout, sizeof(g_timeout)) < 0 || - setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &g_timeout, sizeof(g_timeout)) < 0) + if(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &g_state.timeout, sizeof(g_state.timeout)) < 0 || + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &g_state.timeout, sizeof(g_state.timeout)) < 0) message(NULL, LOG_WARNING, "couldn't set timeouts on incoming connection"); /* Look for thread and also clean up others */ - for(i = 0; i < g_maxthreads; i++) + for(i = 0; i < g_state.max_threads; i++) { /* Find a thread to run or clean up old threads */ if(threads[i].tid != 0) @@ -507,7 +488,7 @@ static void connection_loop(int sock) { errno = r; message(NULL, LOG_ERR, "couldn't create thread"); - g_quit = 1; + g_state.quit = 1; break; } @@ -520,7 +501,7 @@ static void connection_loop(int sock) /* Check to make sure we have a thread */ if(fd != -1) { - messagex(NULL, LOG_ERR, "too many connections open (max %d). sent 554 response", g_maxthreads); + messagex(NULL, LOG_ERR, "too many connections open (max %d). sent 554 response", g_state.max_threads); write(fd, SMTP_STARTBUSY, KL(SMTP_STARTBUSY)); shutdown(fd, SHUT_RDWR); @@ -532,7 +513,7 @@ static void connection_loop(int sock) messagex(NULL, LOG_DEBUG, "waiting for threads to quit"); /* Quit all threads here */ - for(i = 0; i < g_maxthreads; i++) + for(i = 0; i < g_state.max_threads; i++) { /* Clean up quit threads */ if(threads[i].tid != 0) @@ -551,10 +532,6 @@ static void connection_loop(int sock) pthread_join(threads[i].tid, NULL); } } - - /* Close the mutex */ - pthread_mutex_destroy(&g_mutex); - pthread_mutexattr_destroy(&g_mutexattr); } static void* thread_main(void* arg) @@ -616,15 +593,15 @@ static void* thread_main(void* arg) /* Create the server connection address */ - outaddr = &g_outaddr; - outname = g_outname; + outaddr = &(g_state.outaddr); + outname = g_state.outname; if(SANY_TYPE(*outaddr) == AF_INET && outaddr->s.in.sin_addr.s_addr == 0) { /* Use the incoming IP as the default */ in_addr_t in = addr.s.in.sin_addr.s_addr; - memcpy(&addr, &g_outaddr, sizeof(addr)); + memcpy(&addr, &(g_state.outaddr), sizeof(addr)); addr.s.in.sin_addr.s_addr = in; outaddr = &addr; @@ -954,7 +931,7 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) int havefile = 0; int r, ret = 0; - strlcpy(buf, g_directory, MAXPATHLEN); + strlcpy(buf, g_state.directory, MAXPATHLEN); strlcat(buf, "/clamsmtpd.XXXXXX", MAXPATHLEN); /* transfer_to_file deletes the temp file on failure */ @@ -993,7 +970,7 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) */ case 1: if(clio_write_data(ctx, &(ctx->client), - g_bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) + g_state.bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) RETURN(-1); /* Any special post operation actions on the virus */ @@ -1006,7 +983,7 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) }; cleanup: - if(havefile && !g_debugfiles) + if(havefile && !g_state.debug_files) { messagex(ctx, LOG_DEBUG, "deleting temporary file: %s", buf); unlink(buf); @@ -1089,7 +1066,7 @@ static int connect_clam(clamsmtp_context_t* ctx) ASSERT(ctx); ASSERT(!clio_valid(&(ctx->clam))); - if(clio_connect(ctx, &(ctx->clam), &g_clamaddr, g_clamname) == -1) + if(clio_connect(ctx, &(ctx->clam), &g_state.clamaddr, g_state.clamname) == -1) RETURN(-1); read_junk(ctx, ctx->clam.fd); @@ -1193,10 +1170,10 @@ static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname) char buf[MAXPATHLEN]; char* t; - if(!g_quarantine) + if(!g_state.quarantine) return 0; - strlcpy(buf, g_directory, MAXPATHLEN); + strlcpy(buf, g_state.directory, MAXPATHLEN); strlcat(buf, "/virus.", MAXPATHLEN); /* Points to null terminator */ @@ -1328,7 +1305,7 @@ static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename) while(fgets(ctx->line, LINE_LENGTH, file) != NULL) { - if(g_header && !header) + if(g_state.header && !header) { /* * The first blank line we see means the headers are done. @@ -1336,7 +1313,7 @@ static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename) */ if(is_blank_line(ctx->line)) { - if(clio_write_data_raw(ctx, &(ctx->server), (char*)g_header, strlen(g_header)) == -1 || + if(clio_write_data_raw(ctx, &(ctx->server), (char*)g_state.header, strlen(g_state.header)) == -1 || clio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1) RETURN(-1); diff --git a/src/clamsmtpd.h b/src/clamsmtpd.h index ca3df37..deb2cfb 100644 --- a/src/clamsmtpd.h +++ b/src/clamsmtpd.h @@ -39,6 +39,10 @@ #ifndef __CLAMSMTPD_H__ #define __CLAMSMTPD_H__ +#include <sock_any.h> + +/* IO Buffers see clio.c ---------------------------------------------------- */ + #define BUF_LEN 256 typedef struct clio @@ -50,6 +54,8 @@ typedef struct clio } clio_t; +/* The main context --------------------------------------------------------- */ + /* * A generous maximum line length. It needs to be longer than * a full path on this system can be, because we pass the file @@ -75,17 +81,12 @@ typedef struct clamsmtp_context } clamsmtp_context_t; -extern int g_daemonized; /* Currently running as a daemon */ -extern int g_debuglevel; /* what gets logged to console */ -extern pthread_mutex_t g_mutex; /* The main mutex */ -extern struct timeval g_timeout; -extern int g_quit; - -struct sockaddr_any; #define LINE_TOO_LONG(ctx) ((ctx)->linelen >= (LINE_LENGTH - 2)) #define RETURN(x) { ret = x; goto cleanup; } +/* Implemented in clio.c ---------------------------------------------------- */ + #define CLIO_TRIM 0x00000001 #define CLIO_DISCARD 0x00000002 #define CLIO_QUIET 0x00000004 @@ -99,4 +100,46 @@ int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int trim); int clio_write_data(clamsmtp_context_t* ctx, clio_t* io, const char* data); int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, int len); + +/* Implemented in clstate.c ------------------------------------------------ */ + +typedef struct clstate +{ + /* Settings ------------------------------- */ + int debug_level; /* The level to print stuff to console */ + int max_threads; /* Maximum number of threads to process at once */ + struct timeval timeout; /* Timeout for communication */ + + struct sockaddr_any outaddr; /* The outgoing address */ + const char* outname; + struct sockaddr_any clamaddr; /* Address for connecting to clamd */ + const char* clamname; + struct sockaddr_any listenaddr; /* Address to listen on */ + const char* listenname; + + const char* header; /* The header to add to email */ + const char* directory; /* The directory for temp files */ + const char* pidfile; /* The process id file */ + int bounce; /* Send back a reject line */ + int quarantine; /* Leave virus files in temp dir */ + int debug_files; /* Leave all files in temp dir */ + + /* State --------------------------------- */ + int daemonized; /* Whether process is daemonized or not */ + pthread_mutex_t mutex; /* The main mutex */ + int quit; /* Quit the process */ + + /* Internal Use ------------------------- */ + char* _p; + pthread_mutexattr_t _mtxattr; +} +clstate_t; + +extern clstate_t g_state; + +void clstate_init(clstate_t* state); +int clstate_parse_config(clstate_t* state, const char* configfile); +void clstate_validate(clstate_t* state); +void clstate_cleanup(clstate_t* state); + #endif /* __CLAMSMTPD_H__ */ @@ -90,7 +90,7 @@ static void log_io_data(clamsmtp_context_t* ctx, clio_t* io, const char* data, i memcpy(buf, data, len); buf[len] = 0; - messagex(ctx, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io), + messagex(0, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io), read ? " < " : " > ", buf); data += pos; @@ -116,8 +116,8 @@ int clio_connect(clamsmtp_context_t* ctx, clio_t* io, struct sockaddr_any* sany, if((io->fd = socket(SANY_TYPE(*sany), SOCK_STREAM, 0)) == -1) RETURN(-1); - if(setsockopt(io->fd, SOL_SOCKET, SO_RCVTIMEO, &g_timeout, sizeof(g_timeout)) == -1 || - setsockopt(io->fd, SOL_SOCKET, SO_SNDTIMEO, &g_timeout, sizeof(g_timeout)) == -1) + if(setsockopt(io->fd, SOL_SOCKET, SO_RCVTIMEO, &g_state.timeout, sizeof(g_state.timeout)) == -1 || + setsockopt(io->fd, SOL_SOCKET, SO_SNDTIMEO, &g_state.timeout, sizeof(g_state.timeout)) == -1) messagex(ctx, LOG_WARNING, "couldn't set timeouts on connection"); if(connect(io->fd, &SANY_ADDR(*sany), SANY_LEN(*sany)) == -1) @@ -183,7 +183,7 @@ int clio_select(clamsmtp_context_t* ctx, clio_t** io) /* Select on the above */ - switch(select(FD_SETSIZE, &mask, NULL, NULL, &g_timeout)) + switch(select(FD_SETSIZE, &mask, NULL, NULL, &g_state.timeout)) { case 0: messagex(ctx, LOG_ERR, "network operation timed out"); @@ -244,7 +244,7 @@ int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) if(errno == EINTR) { /* When the application is quiting */ - if(g_quit) + if(g_state.quit) return -1; /* For any other signal we go again */ @@ -380,7 +380,7 @@ int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, if(errno == EINTR) { /* When the application is quiting */ - if(g_quit) + if(g_state.quit) return -1; /* For any other signal we go again */ diff --git a/src/clstate.c b/src/clstate.c new file mode 100644 index 0000000..1d5f2af --- /dev/null +++ b/src/clstate.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * Yamamoto Takao <takao@oakat.org> + */ + +#include <paths.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <err.h> +#include <pthread.h> +#include <syslog.h> + +#include "usuals.h" +#include "compat.h" +#include "clamsmtpd.h" +#include "util.h" + +/* ----------------------------------------------------------------------- + * DIRECTIONS FOR ADDING A CONFIGURATION OPTION + * + * - Add field to clstate_t structure in clamsmtpd.h + * - Add default and set in clstate_init (below) + * - Add config keyword (below) + * - Parsing of option in clstate_parse_config (below) + * - Validation of option in clstate_validate (below) + * - Document in the sample doc/clamsmtpd.conf + * - Document in doc/clamsmtpd.conf.5 + */ + +/* ----------------------------------------------------------------------- + * DEFAULT SETTINGS + */ + +#define DEFAULT_SOCKET "10025" +#define DEFAULT_PORT 10025 +#define DEFAULT_CLAMAV "/var/run/clamav/clamd" +#define DEFAULT_MAXTHREADS 64 +#define DEFAULT_TIMEOUT 180 +#define DEFAULT_HEADER "X-AV-Checked: ClamAV using ClamSMTP" + +/* ----------------------------------------------------------------------- + * CONFIG KEYWORDS + */ + +#define CFG_MAXTHREADS "MaxConnections" +#define CFG_TIMEOUT "TimeOut" +#define CFG_OUTADDR "OutAddress" +#define CFG_LISTENADDR "Listen" +#define CFG_CLAMADDR "ClamAddress" +#define CFG_HEADER "ScanHeader" +#define CFG_DIRECTORY "TempDirectory" +#define CFG_BOUNCE "Bounce" +#define CFG_QUARANTINE "Quarantine" +#define CFG_DEBUGFILES "DebugFiles" +#define CFG_PIDFILE "PidFile" + +/* The set of delimiters that can be present between config and value */ +#define CFG_DELIMS ": \t" + +/* ----------------------------------------------------------------------- + * CODE + */ + +/* String to bool helper function */ +static int strtob(const char* str) +{ + if(strcasecmp(str, "0") == 0 || + strcasecmp(str, "no") == 0 || + strcasecmp(str, "false") == 0 || + strcasecmp(str, "f") == 0 || + strcasecmp(str, "off") == 0) + return 0; + + if(strcasecmp(str, "1") == 0 || + strcasecmp(str, "yes") == 0 || + strcasecmp(str, "true") == 0 || + strcasecmp(str, "t") == 0 || + strcasecmp(str, "on") == 0) + return 1; + + return -1; +} + +void clstate_init(clstate_t* state) +{ + ASSERT(state); + memset(state, 0, sizeof(*state)); + + /* Setup the defaults */ + state->debug_level = -1; + state->max_threads = DEFAULT_MAXTHREADS; + state->timeout.tv_sec = DEFAULT_TIMEOUT; + state->clamname = DEFAULT_CLAMAV; + state->listenname = DEFAULT_SOCKET; + state->header = DEFAULT_HEADER; + state->directory = _PATH_TMP; + + /* Create the main mutex and condition variable */ + if(pthread_mutexattr_init(&(state->_mtxattr)) != 0 || + pthread_mutexattr_settype(&(state->_mtxattr), MUTEX_TYPE) || + pthread_mutex_init(&(state->mutex), &(state->_mtxattr)) != 0) + errx(1, "threading problem. can't create mutex or condition var"); +} + +int clstate_parse_config(clstate_t* state, const char* configfile) +{ + FILE* f = NULL; + long len; + int r; + char* p; + char* t; + char* n; + + ASSERT(state); + ASSERT(configfile); + ASSERT(!state->_p); + + f = fopen(configfile, "r"); + if(f == NULL) + { + /* Soft errors when default config file and not found */ + if((errno == ENOENT || errno == ENOTDIR)) + return -1; + else + err(1, "couldn't open config file: %s", configfile); + } + + if(fseek(f, 0, SEEK_END) == -1 || (len = ftell(f)) == -1) + err(1, "couldn't seek config file: %s", configfile); + + if((state->_p = (char*)malloc(len + 2)) == NULL) + errx(1, "out of memory"); + + if(fread(state->_p, 1, len, f) != len) + err(1, "couldn't read config file: %s", configfile); + + fclose(f); + messagex(NULL, LOG_DEBUG, "successfully opened config file: %s", configfile); + + /* Double null terminate the data */ + p = state->_p; + p[len] = 0; + p[len + 1] = 0; + + /* Now split string at new lines */ + while((t = strchr(p, '\n')) != NULL) + { + *t = 0; + p = t + 1; + } + + n = state->_p; + + /* Go through lines and process them */ + while(*n != 0) + { + p = n; /* Do this before trimming below */ + n = p + strlen(p) + 1; + + p = trim_space(p); + + /* Comments and empty lines */ + if(*p == 0 || *p == '#') + continue; + + /* Save some code typing below */ + #define PARSE(o) \ + (r = check_first_word(p, (o), KL(o), CFG_DELIMS)) + #define VAL \ + (p + r) + + /* + * Note that we don't validate here. If something's wrong + * set it to an invalid value. + */ + + if(PARSE(CFG_MAXTHREADS)) + { + state->max_threads = strtol(VAL, &t, 10); + if(*t) /* parse failed */ + state->max_threads = -1; + } + + else if(PARSE(CFG_TIMEOUT)) + { + state->timeout.tv_sec = strtol(VAL, &t, 10); + if(*t) /* parse failed */ + state->timeout.tv_sec = -1; + } + + else if(PARSE(CFG_OUTADDR)) + state->outname = VAL; + + else if(PARSE(CFG_LISTENADDR)) + state->listenname = VAL; + + else if(PARSE(CFG_CLAMADDR)) + state->clamname = VAL; + + else if(PARSE(CFG_HEADER)) + state->header = VAL; + + else if(PARSE(CFG_DIRECTORY)) + state->directory = VAL; + + else if(PARSE(CFG_PIDFILE)) + state->pidfile = VAL; + + else if(PARSE(CFG_BOUNCE)) + state->bounce = strtob(VAL); + + else if(PARSE(CFG_QUARANTINE)) + state->quarantine = strtob(VAL); + + else if(PARSE(CFG_DEBUGFILES)) + state->debug_files = strtob(VAL); + + /* Unrecognized option */ + else + errx(2, "unrecognized line in config file: %s", p); + + messagex(NULL, LOG_DEBUG, "successfully parsed line: %s", p); + } + + return 0; +} + +void clstate_validate(clstate_t* state) +{ + ASSERT(state); + messagex(NULL, LOG_DEBUG, "validating configuration options"); + + if(state->debug_level < -1 || state->debug_level > 4) + errx(2, "invalid debug log level (must be between 1 and 4)"); + + if(state->max_threads <= 1 || state->max_threads >= 1024) + errx(2, "invalid " CFG_MAXTHREADS " (must be between 1 and 1024)"); + + if(state->timeout.tv_sec <= 0) + errx(2, "invalid " CFG_TIMEOUT); + + /* This option has no default, but is required */ + if(state->outname == NULL) + errx(2, "no " CFG_OUTADDR " specified in config file."); + + if(state->bounce == -1) + errx(2, "invalid value for " CFG_BOUNCE); + if(state->quarantine == -1) + errx(2, "invalid value for " CFG_QUARANTINE); + if(state->debug_files == -1) + errx(2, "invalid value for " CFG_DEBUGFILES); + + /* Parse all the addresses */ + if(sock_any_pton(state->listenname, &(state->listenaddr), SANY_OPT_DEFANY | SANY_OPT_DEFPORT(DEFAULT_PORT)) == -1) + errx(2, "invalid " CFG_LISTENADDR " socket name or ip: %s", state->listenname); + if(sock_any_pton(state->outname, &(state->outaddr), SANY_OPT_DEFPORT(25)) == -1) + errx(2, "invalid " CFG_OUTADDR " socket name or ip: %s", state->outname); + if(sock_any_pton(state->clamname, &(state->clamaddr), SANY_OPT_DEFLOCAL) == -1) + errx(2, "invalid " CFG_CLAMADDR " socket name: %s", state->clamname); + + if(strlen(state->directory) == 0) + errx(2, "invalid " CFG_DIRECTORY); + if(state->pidfile && strlen(state->pidfile) == 0) + errx(2, "invalid " CFG_PIDFILE); + + if(state->header) + { + /* + * This is for when it comes from the command-line. + * Once command line args are phased out this can be removed + */ + state->header = (const char*)trim_space((char*)state->header); + + if(strlen(state->header) == 0) + state->header = NULL; + } +} + +void clstate_cleanup(clstate_t* state) +{ + if(state->_p) + { + free(state->_p); + memset(state, 0, sizeof(*state)); + messagex(NULL, LOG_DEBUG, "freed configuration option memory"); + } + + /* Close the mutex */ + pthread_mutex_destroy(&(state->mutex)); + pthread_mutexattr_destroy(&(state->_mtxattr)); +} diff --git a/src/usuals.h b/src/usuals.h index 00410aa..385bcf9 100644 --- a/src/usuals.h +++ b/src/usuals.h @@ -71,4 +71,6 @@ #define ASSERT #endif +#define KL(s) ((sizeof(s) - 1) / sizeof(char)) + #endif /* __USUALS_H__ */ @@ -67,14 +67,14 @@ static void vmessage(clamsmtp_context_t* ctx, int level, int err, char* m; int e = errno; - if(g_daemonized) + if(g_state.daemonized) { if(level >= LOG_DEBUG) return; } else { - if(g_debuglevel < level) + if(g_state.debug_level < level) return; } @@ -102,7 +102,7 @@ static void vmessage(clamsmtp_context_t* ctx, int level, int err, } /* Either to syslog or stderr */ - if(g_daemonized) + if(g_state.daemonized) vsyslog(level, msg, ap); else vwarnx(msg, ap); @@ -244,16 +244,16 @@ void plock() #endif #ifdef _DEBUG - r = pthread_mutex_trylock(&g_mutex); + r = pthread_mutex_trylock(&(g_state.mutex)); if(r == EBUSY) { wait = 1; message(NULL, LOG_DEBUG, "thread will block: %d", pthread_self()); - r = pthread_mutex_lock(&g_mutex); + r = pthread_mutex_lock(&(g_state.mutex)); } #else - r = pthread_mutex_lock(&g_mutex); + r = pthread_mutex_lock(&(g_state.mutex)); #endif @@ -273,7 +273,7 @@ void plock() void punlock() { - int r = pthread_mutex_unlock(&g_mutex); + int r = pthread_mutex_unlock(&(g_state.mutex)); if(r != 0) { errno = r; @@ -42,9 +42,6 @@ void messagex(clamsmtp_context_t* ctx, int level, const char* msg, ...); void message(clamsmtp_context_t* ctx, int level, const char* msg, ...); -void log_fd_data(clamsmtp_context_t* ctx, const char* data, int* fd, int read); -void log_data(clamsmtp_context_t* ctx, const char* data, const char* prefix); - int check_first_word(const char* line, const char* word, int len, char* delims); int is_first_word(const char* line, const char* word, int len); int is_last_word(const char* line, const char* word, int len); |