diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | common/compat.h | 3 | ||||
-rw-r--r-- | common/smtppass.c | 1325 | ||||
-rw-r--r-- | common/smtppass.h | 266 | ||||
-rw-r--r-- | common/spio.c | 216 | ||||
-rw-r--r-- | common/sppriv.h (renamed from src/usuals.h) | 55 | ||||
-rw-r--r-- | common/stringx.c | 141 | ||||
-rw-r--r-- | common/stringx.h | 12 | ||||
-rw-r--r-- | common/usuals.h | 2 | ||||
-rw-r--r-- | configure.in | 8 | ||||
-rw-r--r-- | src/Makefile.am | 8 | ||||
-rw-r--r-- | src/clamsmtpd.c | 1312 | ||||
-rw-r--r-- | src/clamsmtpd.h | 102 | ||||
-rw-r--r-- | src/clio.c | 407 | ||||
-rw-r--r-- | src/clstate.c | 322 | ||||
-rw-r--r-- | src/compat.c | 182 | ||||
-rw-r--r-- | src/compat.h | 87 | ||||
-rw-r--r-- | src/sock_any.c | 385 | ||||
-rw-r--r-- | src/sock_any.h | 90 | ||||
-rw-r--r-- | src/util.c | 283 | ||||
-rw-r--r-- | src/util.h | 57 |
22 files changed, 1302 insertions, 3967 deletions
@@ -1,5 +1,9 @@ 0.9 - Don't quit when too many threads created + - Restructured smtp passthru system so it can be used by other apps + - Fixed some minor IO bugs + - Changed default header to be similar to Amavis + - Compiles and runs on systems without error checking mutexes 0.8 - clamsmtpd now uses a configuration file diff --git a/Makefile.am b/Makefile.am index 0bb743d..33d8e51 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ -EXTRA_DIST = config.sub acsite.m4 config.guess scripts +EXTRA_DIST = config.sub acsite.m4 config.guess scripts common SUBDIRS = src doc dist-hook: diff --git a/common/compat.h b/common/compat.h index 98ebade..62d37b4 100644 --- a/common/compat.h +++ b/common/compat.h @@ -57,14 +57,13 @@ void* reallocf(void* p, size_t sz); #include <pthread.h> -/* TODO: Move this logic to configure */ #if HAVE_ERR_MUTEX == 1 # define MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK_NP #else # if HAVE_ERR_MUTEX == 2 # define MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK # else -# error "Need error checking mutex functionality" +# undef MUTEX_TYPE # endif #endif diff --git a/common/smtppass.c b/common/smtppass.c index d2c5f9c..2e590f6 100644 --- a/common/smtppass.c +++ b/common/smtppass.c @@ -36,6 +36,7 @@ * Andreas Steinmetz <ast@domdv.de> */ +/* TODO: Remove unneeded headers */ #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> @@ -50,6 +51,8 @@ #include <signal.h> #include <errno.h> #include <err.h> +#include <paths.h> +#include <stdarg.h> #include "usuals.h" @@ -59,19 +62,19 @@ #include "compat.h" #include "sock_any.h" -#include "smtppass.h" -#include "util.h" +#include "stringx.h" +#include "sppriv.h" /* ----------------------------------------------------------------------- * STRUCTURES */ -typedef struct smtppass_thread +typedef struct spthread { pthread_t tid; /* Written to by the main thread */ int fd; /* The file descriptor or -1 */ } -smtppass_thread_t; +spthread_t; /* ----------------------------------------------------------------------- * STRINGS @@ -82,17 +85,16 @@ smtppass_thread_t; #define SMTP_TOOLONG "500 Line too long" CRLF #define SMTP_STARTBUSY "554 Server Busy" CRLF #define SMTP_STARTFAILED "554 Local Error" CRLF -#define SMTP_DATAVIRUS "550 Virus Detected; Content Rejected" CRLF #define SMTP_DATAINTERMED "354 Start mail input; end with <CRLF>.<CRLF>" CRLF #define SMTP_FAILED "451 Local Error" CRLF #define SMTP_NOTSUPP "502 Command not implemented" CRLF -#define SMTP_DATAVIRUSOK "250 Virus Detected; Discarded Email" CRLF #define SMTP_OK "250 Ok" CRLF +#define SMTP_REJPREFIX "550 Content Rejected; " #define SMTP_DATA "DATA" CRLF -#define SMTP_BANNER "220 clamsmtp" CRLF -#define SMTP_HELO_RSP "250 clamsmtp" CRLF -#define SMTP_EHLO_RSP "250-clamsmtp" CRLF +#define SMTP_BANNER "220 smtp.passthru" CRLF +#define SMTP_HELO_RSP "250 smtp.passthru" CRLF +#define SMTP_EHLO_RSP "250-smtp.passthru" CRLF #define SMTP_DELIMS "\r\n\t :" #define SMTP_MULTI_DELIMS " -" @@ -117,227 +119,165 @@ smtppass_thread_t; #define OK_RSP "250" #define START_RSP "220" +#define CFG_MAXTHREADS "MaxConnections" +#define CFG_TIMEOUT "TimeOut" +#define CFG_OUTADDR "OutAddress" +#define CFG_LISTENADDR "Listen" +#define CFG_TRANSPARENT "TransparentProxy" +#define CFG_DIRECTORY "TempDirectory" + +/* The set of delimiters that can be present between config and value */ +#define CFG_DELIMS ": \t" + + +#define LINE_TOO_LONG(ctx) ((ctx)->linelen >= (SP_LINE_LENGTH - 2)) + +/* ----------------------------------------------------------------------- + * DEFAULT SETTINGS + */ + +#define DEFAULT_SOCKET "10025" +#define DEFAULT_PORT 10025 +#define DEFAULT_MAXTHREADS 64 +#define DEFAULT_TIMEOUT 180 /* ----------------------------------------------------------------------- * GLOBALS */ -const clstate_t* g_state = NULL; /* The state and configuration of the daemon */ +spstate_t g_state; /* The state and configuration of the daemon */ unsigned int g_unique_id = 0x00100000; /* For connection ids */ +pthread_mutex_t g_mutex; /* The main mutex */ +pthread_mutexattr_t g_mtxattr; /* ----------------------------------------------------------------------- * FORWARD DECLARATIONS */ -static void usage(); static void on_quit(int signal); static void pid_file(const char* pidfile, int write); static void connection_loop(int sock); static void* thread_main(void* arg); -static int smtp_passthru(clamsmtp_context_t* ctx); -static int connect_out(clamsmtp_context_t* ctx); -static int connect_clam(clamsmtp_context_t* ctx); -static int disconnect_clam(clamsmtp_context_t* ctx); -static void add_to_logline(char* logline, char* prefix, char* line); -static int avcheck_data(clamsmtp_context_t* ctx, char* logline); -static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname); -static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname); -static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname); -static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename); -static int clam_scan_file(clamsmtp_context_t* ctx, const char* tempname, char* logline); -static int read_server_response(clamsmtp_context_t* ctx); -static void read_junk(clamsmtp_context_t* ctx, int fd); +static int smtp_passthru(spctx_t* ctx); +static int connect_out(spctx_t* ctx); +static int read_server_response(spctx_t* ctx); +static int parse_config_file(const char* configfile); +/* Used externally in some cases */ +int sp_parse_option(const char* name, const char* option); /* ---------------------------------------------------------------------------------- - * STARTUP ETC... + * BASIC RUN FUNCTIONALITY */ -int main(int argc, char* argv[]) +void sp_init(const char* name) { - const char* configfile = DEFAULT_CONFIG; - const char* pidfile = NULL; - clstate_t state; - int warnargs = 0; - int sock; - int true = 1; - int ch = 0; - char* t; + int r; - clstate_init(&state); - g_state = &state; + ASSERT(name); - /* Parse the arguments nicely */ - while((ch = getopt(argc, argv, "bc:d:D:f:h:l:m:p:qt:v")) != -1) - { - switch(ch) - { - /* Actively reject messages */ - case 'b': - state.bounce = 1; - warnargs = 1; - break; - - /* Change the CLAM socket */ - case 'c': - state.clamname = optarg; - warnargs = 1; - break; - - /* Don't daemonize */ - case 'd': - state.debug_level = strtol(optarg, &t, 10); - if(*t) /* parse error */ - errx(1, "invalid debug log level"); - state.debug_level += LOG_ERR; - break; - - /* The directory for the files */ - case 'D': - state.directory = optarg; - warnargs = 1; - break; - - /* The configuration file */ - case 'f': - configfile = optarg; - break; - - /* The header to add */ - case 'h': - if(strlen(optarg) == 0) - state.header = NULL; - else - state.header = optarg; - warnargs = 1; - break; - - /* Change our listening port */ - case 'l': - state.listenname = optarg; - warnargs = 1; - break; - - /* The maximum number of threads */ - case 'm': - 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; - break; - - /* The timeout */ - case 't': - 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': - state.quarantine = 1; - break; - - /* Print version number */ - case 'v': - printf("clamsmtpd (version %s)\n", VERSION); - exit(0); - break; - - /* Leave all files in the tmp directory */ - case 'X': - state.debug_files = 1; - warnargs = 1; - break; - - /* Usage information */ - case '?': - default: - usage(); - break; - } - } + memset(&g_state, 0, sizeof(g_state)); - argc -= optind; - argv += optind; + /* Setup the defaults */ + g_state.debug_level = -1; + g_state.max_threads = DEFAULT_MAXTHREADS; + g_state.timeout.tv_sec = DEFAULT_TIMEOUT; + g_state.directory = _PATH_TMP; + g_state.name = name; - if(argc > 1) - usage(); - if(argc == 1) - { - state.outname = argv[0]; - warnargs = 1; - } + /* We need the default to parse into a useable form, so we do this: */ + r = sp_parse_option(CFG_LISTENADDR, DEFAULT_SOCKET); + ASSERT(r == 1); + + /* Create the main mutex and condition variable */ + if(pthread_mutexattr_init(&g_mtxattr) != 0 || +#ifdef HAVE_ERR_MUTEX + pthread_mutexattr_settype(&g_mtxattr, MUTEX_TYPE) || +#endif + pthread_mutex_init(&g_mutex, &g_mtxattr) != 0) + errx(1, "threading problem. can't create mutex or condition var"); +} + +int sp_run(const char* configfile, const char* pidfile, int dbg_level) +{ + int sock; + int true = 1; - if(warnargs) - warnx("please use configuration file instead of command-line flags: %s", configfile); + ASSERT(configfile); + ASSERT(g_state.name); + + if(!(dbg_level == -1 || dbg_level <= LOG_DEBUG)) + errx(2, "invalid debug log level (must be between 1 and 4)"); + g_state.debug_level = dbg_level; /* Now parse the configuration file */ - if(clstate_parse_config(&state, configfile) == -1) + if(parse_config_file(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); + /* + * We used to do a check here before whether it was the default + * configuration file or not, but we can't do that any longer + * as it comes from the app. Usually lack of a configuration + * file will cause the following checks to fail + */ + warnx("configuration file not found: %s", configfile); } - clstate_validate(&state); + /* This option has no default, but is required ... */ + if(g_state.outname == NULL && !g_state.transparent) + errx(2, "no " CFG_OUTADDR " specified."); - messagex(NULL, LOG_DEBUG, "starting up..."); + /* ... unless we're in transparent proxy mode */ + else if(g_state.outname != NULL && g_state.transparent) + warnx("the " CFG_OUTADDR " option will be ignored when " CFG_TRANSPARENT " is enabled"); + + sp_messagex(NULL, LOG_DEBUG, "starting up..."); /* When set to this we daemonize */ - if(g_state->debug_level == -1) + if(g_state.debug_level == -1) { /* Fork a daemon nicely here */ if(daemon(0, 0) == -1) { - message(NULL, LOG_ERR, "couldn't run as daemon"); + sp_message(NULL, LOG_ERR, "couldn't run as daemon"); exit(1); } - messagex(NULL, LOG_DEBUG, "running as a daemon"); - state.daemonized = 1; + sp_messagex(NULL, LOG_DEBUG, "running as a daemon"); + g_state.daemonized = 1; /* Open the system log */ - openlog("clamsmtpd", 0, LOG_MAIL); + openlog(g_state.name, 0, LOG_MAIL); } /* Create the socket */ - sock = socket(SANY_TYPE(g_state->listenaddr), SOCK_STREAM, 0); + sock = socket(SANY_TYPE(g_state.listenaddr), SOCK_STREAM, 0); if(sock < 0) { - message(NULL, LOG_CRIT, "couldn't open socket"); + sp_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(g_state->listenaddr) == AF_UNIX) - unlink(g_state->listenname); + if(SANY_TYPE(g_state.listenaddr) == AF_UNIX) + unlink(g_state.listenname); - if(bind(sock, &SANY_ADDR(g_state->listenaddr), SANY_LEN(g_state->listenaddr)) != 0) + 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); + sp_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) { - message(NULL, LOG_CRIT, "couldn't listen on socket"); + sp_message(NULL, LOG_CRIT, "couldn't listen on socket"); exit(1); } - messagex(NULL, LOG_DEBUG, "created socket: %s", g_state->listenname); + sp_messagex(NULL, LOG_DEBUG, "created socket: %s", g_state.listenname); /* Handle some signals */ signal(SIGPIPE, SIG_IGN); @@ -351,34 +291,46 @@ int main(int argc, char* argv[]) if(pidfile) pid_file(pidfile, 1); - messagex(NULL, LOG_DEBUG, "accepting connections"); + sp_messagex(NULL, LOG_DEBUG, "accepting connections"); connection_loop(sock); if(pidfile) pid_file(pidfile, 0); - messagex(NULL, LOG_DEBUG, "stopped"); + /* Our listen socket */ + close(sock); - /* - * We have to do this at the very end because even printing - * messages requires that g_state is valid. - */ - clstate_cleanup(&state); + sp_messagex(NULL, LOG_DEBUG, "stopped processing"); return 0; } -static void on_quit(int signal) +void sp_quit() +{ + /* The handler sets the flag and this also interrupts io */ + kill(getpid(), SIGTERM); +} + +int sp_is_quit() +{ + return g_state.quit ? 1 : 0; +} + +void sp_done() { - ((clstate_t*)g_state)->quit = 1; - /* fprintf(stderr, "clamsmtpd: got signal to quit\n"); */ + /* Close the mutex */ + pthread_mutex_destroy(&g_mutex); + pthread_mutexattr_destroy(&g_mtxattr); + + if(g_state._p) + free(g_state._p); + + memset(&g_state, 0, sizeof(g_state)); } -static void usage() +static void on_quit(int signal) { - fprintf(stderr, "usage: clamsmtpd [-d debuglevel] [-f configfile] [-p pidfile]\n"); - fprintf(stderr, " clamsmtpd -v\n"); - exit(2); + g_state.quit = 1; } static void pid_file(const char* pidfile, int write) @@ -388,45 +340,44 @@ static void pid_file(const char* pidfile, int write) FILE* f = fopen(pidfile, "w"); if(f == NULL) { - message(NULL, LOG_ERR, "couldn't open pid file: %s", pidfile); + sp_message(NULL, LOG_ERR, "couldn't open pid file: %s", pidfile); } else { fprintf(f, "%d\n", (int)getpid()); if(ferror(f)) - message(NULL, LOG_ERR, "couldn't write to pid file: %s", pidfile); + sp_message(NULL, LOG_ERR, "couldn't write to pid file: %s", pidfile); + if(fclose(f) == EOF) + sp_message(NULL, LOG_ERR, "couldn't write to pid file: %s", pidfile); - fclose(f); } - messagex(NULL, LOG_DEBUG, "wrote pid file: %s", pidfile); + sp_messagex(NULL, LOG_DEBUG, "wrote pid file: %s", pidfile); } else { unlink(pidfile); - messagex(NULL, LOG_DEBUG, "removed pid file: %s", pidfile); + sp_messagex(NULL, LOG_DEBUG, "removed pid file: %s", pidfile); } } - -/* ---------------------------------------------------------------------------------- - * CONNECTION HANDLING - */ - static void connection_loop(int sock) { - clamsmtp_thread_t* threads = NULL; + spthread_t* threads = NULL; int fd, i, x, r; /* Create the thread buffers */ - threads = (clamsmtp_thread_t*)calloc(g_state->max_threads, sizeof(clamsmtp_thread_t)); + threads = (spthread_t*)calloc(g_state.max_threads, sizeof(spthread_t)); if(!threads) - errx(1, "out of memory"); + { + sp_messagex(NULL, LOG_CRIT, "out of memory"); + return; + } /* Now loop and accept the connections */ - while(!g_state->quit) + while(!sp_is_quit()) { fd = accept(sock, NULL, NULL); if(fd == -1) @@ -438,39 +389,38 @@ static void connection_loop(int sock) break; case ECONNABORTED: - message(NULL, LOG_ERR, "couldn't accept a connection"); + sp_message(NULL, LOG_ERR, "couldn't accept a connection"); break; default: - message(NULL, LOG_ERR, "couldn't accept a connection"); - ((clstate_t*)g_state)->quit = 1; + sp_message(NULL, LOG_ERR, "couldn't accept a connection"); break; }; - if(g_state->quit) + if(sp_is_quit()) break; continue; } /* Set timeouts on client */ - 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"); + 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) + sp_message(NULL, LOG_WARNING, "couldn't set timeouts on incoming connection"); /* Look for thread and also clean up others */ - for(i = 0; i < g_state->max_threads; 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) { - plock(); + sp_lock(); x = threads[i].fd; - punlock(); + sp_unlock(); if(x == -1) { - messagex(NULL, LOG_DEBUG, "cleaning up completed thread"); + sp_messagex(NULL, LOG_DEBUG, "cleaning up completed thread"); pthread_join(threads[i].tid, NULL); threads[i].tid = 0; } @@ -478,7 +428,7 @@ static void connection_loop(int sock) else { /* For debugging connection problems: */ - messagex(NULL, LOG_DEBUG, "active connection thread: %x", (int)threads[i].tid); + sp_messagex(NULL, LOG_DEBUG, "active connection thread: %x", (int)threads[i].tid); } #endif } @@ -492,12 +442,16 @@ static void connection_loop(int sock) if(r != 0) { errno = r; - message(NULL, LOG_ERR, "couldn't create thread"); - ((clstate_t*)g_state)->quit = 1; + sp_message(NULL, LOG_ERR, "couldn't create thread"); + + write(fd, SMTP_STARTFAILED, KL(SMTP_STARTFAILED)); + shutdown(fd, SHUT_RDWR); + close(fd); + fd = -1; break; } - messagex(NULL, LOG_DEBUG, "created thread for connection"); + sp_messagex(NULL, LOG_DEBUG, "created thread for connection"); fd = -1; break; } @@ -506,8 +460,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_state->max_threads); - + sp_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); close(fd); @@ -515,34 +468,105 @@ static void connection_loop(int sock) } } - messagex(NULL, LOG_DEBUG, "waiting for threads to quit"); + sp_messagex(NULL, LOG_DEBUG, "waiting for threads to quit"); /* Quit all threads here */ - for(i = 0; i < g_state->max_threads; i++) + for(i = 0; i < g_state.max_threads; i++) { /* Clean up quit threads */ if(threads[i].tid != 0) { if(threads[i].fd != -1) { - plock(); + sp_lock(); fd = threads[i].fd; threads[i].fd = -1; - punlock(); + sp_unlock(); shutdown(fd, SHUT_RDWR); close(fd); } pthread_join(threads[i].tid, NULL); + threads[i].tid = 0; + } + } + + free(threads); +} + +static spctx_t* init_thread(int fd) +{ + spctx_t* ctx; + + ctx = cb_new_context(); + if(ctx) + { + memset(ctx, 0, sizeof(*ctx)); + + spio_init(&(ctx->server), "SERVER"); + spio_init(&(ctx->client), "CLIENT"); + + sp_lock(); + /* Assign a unique id to the connection */ + ctx->id = g_unique_id++; + + /* We don't care about wraps, but we don't want zero */ + if(g_unique_id == 0) + g_unique_id++; + sp_unlock(); + + ctx->client.fd = fd; + ASSERT(ctx->client.fd != -1); + sp_messagex(ctx, LOG_DEBUG, "processing %d on thread %x", ctx->client.fd, (int)pthread_self()); + + /* Connect to the outgoing server ... */ + if(connect_out(ctx) == -1) + { + cb_del_context(ctx); + ctx = NULL; } } + + return ctx; +} + +static void cleanup_context(spctx_t* ctx) +{ + ASSERT(ctx); + + if(ctx->cachefile) + { + fclose(ctx->cachefile); + ctx->cachefile = NULL; + } + + if(ctx->cachename[0]) + { + unlink(ctx->cachename); + ctx->cachename[0] = 0; + } + + ctx->logline[0] = 0; +} + + +static void done_thread(spctx_t* ctx) +{ + ASSERT(ctx); + + spio_disconnect(ctx, &(ctx->client)); + spio_disconnect(ctx, &(ctx->server)); + + /* Clean up file stuff */ + cleanup_context(ctx); + cb_del_context(ctx); } static void* thread_main(void* arg) { - clamsmtp_thread_t* thread = (clamsmtp_thread_t*)arg; - clamsmtp_context_t* ctx = NULL; + spthread_t* thread = (spthread_t*)arg; + spctx_t* ctx = NULL; int processing = 0; int ret = 0; int fd; @@ -552,48 +576,21 @@ static void* thread_main(void* arg) siginterrupt(SIGINT, 1); siginterrupt(SIGTERM, 1); - plock(); + sp_lock(); /* Get the client socket */ fd = thread->fd; - punlock(); + sp_unlock(); - ctx = (clamsmtp_context_t*)calloc(1, sizeof(clamsmtp_context_t)); + ctx = init_thread(fd); if(!ctx) { /* Special case. We don't have a context so clean up descriptor */ close(fd); - messagex(NULL, LOG_CRIT, "out of memory"); + /* new_context() should have already logged reason */ RETURN(-1); } - memset(ctx, 0, sizeof(*ctx)); - - clio_init(&(ctx->server), "SERVER"); - clio_init(&(ctx->client), "CLIENT"); - clio_init(&(ctx->clam), "CLAM "); - - plock(); - /* Assign a unique id to the connection */ - ctx->id = g_unique_id++; - - /* We don't care about wraps, but we don't want zero */ - if(g_unique_id == 0) - g_unique_id++; - punlock(); - - ctx->client.fd = fd; - ASSERT(ctx->client.fd != -1); - messagex(ctx, LOG_DEBUG, "processing %d on thread %x", ctx->client.fd, (int)pthread_self()); - - /* Connect to the outgoing server ... */ - if(connect_out(ctx) == -1) - RETURN(-1); - - /* ... and to the AV daemon */ - if(connect_clam(ctx) == -1) - RETURN(-1); - /* call the processor */ processing = 1; ret = smtp_passthru(ctx); @@ -602,25 +599,22 @@ cleanup: if(ctx) { - disconnect_clam(ctx); - /* Let the client know about fatal errors */ - if(!processing && ret == -1 && clio_valid(&(ctx->client))) - clio_write_data(ctx, &(ctx->client), SMTP_STARTFAILED); + if(!processing && ret == -1 && spio_valid(&(ctx->client))) + spio_write_data(ctx, &(ctx->client), SMTP_STARTFAILED); - clio_disconnect(ctx, &(ctx->client)); - clio_disconnect(ctx, &(ctx->server)); + done_thread(ctx); } /* mark this as done */ - plock(); + sp_lock(); thread->fd = -1; - punlock(); + sp_unlock(); return (void*)(ret == 0 ? 0 : 1); } -static int connect_out(clamsmtp_context_t* ctx) +static int connect_out(spctx_t* ctx) { struct sockaddr_any peeraddr; struct sockaddr_any addr; @@ -634,16 +628,16 @@ static int connect_out(clamsmtp_context_t* ctx) /* Get the peer name */ if(getpeername(ctx->client.fd, &SANY_ADDR(peeraddr), &SANY_LEN(peeraddr)) == -1 || sock_any_ntop(&peeraddr, buf, MAXPATHLEN, SANY_OPT_NOPORT) == -1) - message(ctx, LOG_WARNING, "couldn't get peer address"); + sp_message(ctx, LOG_WARNING, "couldn't get peer address"); else - messagex(ctx, LOG_INFO, "accepted connection from: %s", buf); + sp_messagex(ctx, LOG_INFO, "accepted connection from: %s", buf); /* Create the server connection address */ - outaddr = &(g_state->outaddr); - outname = g_state->outname; + outaddr = &(g_state.outaddr); + outname = g_state.outname; /* For transparent proxying we have to discover the address to connect to */ - if(g_state->transparent) + if(g_state.transparent) { memset(&addr, 0, sizeof(addr)); SANY_LEN(addr) = sizeof(addr); @@ -654,14 +648,14 @@ static int connect_out(clamsmtp_context_t* ctx) if(getsockname(ctx->client.fd, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1) #endif { - message(ctx, LOG_ERR, "couldn't get source address for transparent proxying"); + sp_message(ctx, LOG_ERR, "couldn't get source address for transparent proxying"); return -1; } /* Check address types */ if(sock_any_cmp(&addr, &peeraddr, SANY_OPT_NOPORT) == 0) { - messagex(ctx, LOG_ERR, "loop detected in transparent proxying"); + sp_messagex(ctx, LOG_ERR, "loop detected in transparent proxying"); return -1; } @@ -675,7 +669,7 @@ static int connect_out(clamsmtp_context_t* ctx) outaddr->s.in.sin_addr.s_addr == 0) { /* Use the incoming IP as the default */ - memcpy(&addr, &(g_state->outaddr), sizeof(addr)); + memcpy(&addr, &(g_state.outaddr), sizeof(addr)); memcpy(&(addr.s.in.sin_addr), &(peeraddr.s.in.sin_addr), sizeof(addr.s.in.sin_addr)); outaddr = &addr; } @@ -684,7 +678,7 @@ static int connect_out(clamsmtp_context_t* ctx) outaddr->s.in.in6.sin_addr.s_addr == 0) { /* Use the incoming IP as the default */ - memcpy(&addr, &(g_state->outaddr), sizeof(addr)); + memcpy(&addr, &(g_state.outaddr), sizeof(addr)); memcpy(&(addr.s.in.sin6_addr), &(peeraddr.s.in.sin6_addr), sizeof(addr.s.in.sin6_addr)); outaddr = &addr; } @@ -692,7 +686,7 @@ static int connect_out(clamsmtp_context_t* ctx) } /* Reparse name if possible */ - if(outaddr != &(g_state->outaddr)) + if(outaddr != &(g_state.outaddr)) { if(sock_any_ntop(outaddr, buf, MAXPATHLEN, 0) != -1) outname = buf; @@ -701,7 +695,7 @@ static int connect_out(clamsmtp_context_t* ctx) } /* Connect to the server */ - if(clio_connect(ctx, &(ctx->server), outaddr, outname) == -1) + if(spio_connect(ctx, &(ctx->server), outaddr, outname) == -1) return -1; return 0; @@ -711,33 +705,33 @@ static int connect_out(clamsmtp_context_t* ctx) * SMTP HANDLING */ -static int smtp_passthru(clamsmtp_context_t* ctx) +static int smtp_passthru(spctx_t* ctx) { - clio_t* io = NULL; - char logline[LINE_LENGTH]; int r, ret = 0; + unsigned int mask; int neterror = 0; int first_rsp = 1; /* The first 220 response from server to be filtered */ int filter_ehlo = 0; /* Filtering parts of an EHLO extensions response */ int filter_host = 0; /* Next response is 250 hostname, which we change */ - ASSERT(clio_valid(&(ctx->clam)) && - clio_valid(&(ctx->clam))); - logline[0] = 0; + ASSERT(spio_valid(&(ctx->client)) && + spio_valid(&(ctx->server))); for(;;) { - if(clio_select(ctx, &io) == -1) + mask = spio_select(ctx, &(ctx->client), &(ctx->server), NULL); + + if(mask == ~0) { neterror = 1; RETURN(-1); } /* Client has data available, read a line and process */ - if(io == &(ctx->client)) + if(mask & 1) { - if(clio_read_line(ctx, &(ctx->client), CLIO_DISCARD) == -1) + if(spio_read_line(ctx, &(ctx->client), SPIO_DISCARD) == -1) RETURN(-1); /* Client disconnected, we're done */ @@ -747,7 +741,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) /* We don't let clients send really long lines */ if(LINE_TOO_LONG(ctx)) { - if(clio_write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1) + if(spio_write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1) RETURN(-1); continue; @@ -761,7 +755,7 @@ static int smtp_passthru(clamsmtp_context_t* ctx) if(is_first_word(ctx->line, DATA_CMD, KL(DATA_CMD))) { /* Send back the intermediate response to the client */ - if(clio_write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1) + if(spio_write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1) RETURN(-1); /* @@ -769,14 +763,14 @@ static int smtp_passthru(clamsmtp_context_t* ctx) * sending of the data to the server, making the av check * transparent */ - if(avcheck_data(ctx, logline) == -1) + if(cb_check_data(ctx) == -1) RETURN(-1); /* Print the log out for this email */ - messagex(ctx, LOG_INFO, "%s", logline); + sp_messagex(ctx, LOG_INFO, "%s", ctx->logline); - /* Reset log line */ - logline[0] = 0; + /* Done with that email */ + cleanup_context(ctx); /* Command handled */ continue; @@ -788,12 +782,12 @@ static int smtp_passthru(clamsmtp_context_t* ctx) */ else if(is_first_word(ctx->line, EHLO_CMD, KL(EHLO_CMD))) { - messagex(ctx, LOG_DEBUG, "filtering EHLO response"); + sp_messagex(ctx, LOG_DEBUG, "filtering EHLO response"); filter_ehlo = 1; filter_host = 1; - /* A new message */ - logline[0] = 0; + /* New email so cleanup */ + cleanup_context(ctx); } /* @@ -804,8 +798,8 @@ static int smtp_passthru(clamsmtp_context_t* ctx) { filter_host = 1; - /* A new message line */ - logline[0] = 0; + /* A new email so cleanup */ + cleanup_context(ctx); } /* @@ -816,9 +810,9 @@ static int smtp_passthru(clamsmtp_context_t* ctx) else if(is_first_word(ctx->line, STARTTLS_CMD, KL(STARTTLS_CMD)) || is_first_word(ctx->line, BDAT_CMD, KL(BDAT_CMD))) { - messagex(ctx, LOG_DEBUG, "ESMTP feature not supported"); + sp_messagex(ctx, LOG_DEBUG, "ESMTP feature not supported"); - if(clio_write_data(ctx, &(ctx->client), SMTP_NOTSUPP) == -1) + if(spio_write_data(ctx, &(ctx->client), SMTP_NOTSUPP) == -1) RETURN(-1); /* Command handled */ @@ -827,34 +821,34 @@ static int smtp_passthru(clamsmtp_context_t* ctx) /* Append recipients to log line */ else if((r = check_first_word(ctx->line, FROM_CMD, KL(FROM_CMD), SMTP_DELIMS)) > 0) - add_to_logline(logline, "from=", ctx->line + r); + sp_add_log(ctx, "from=", ctx->line + r); /* Append sender to log line */ else if((r = check_first_word(ctx->line, TO_CMD, KL(TO_CMD), SMTP_DELIMS)) > 0) - add_to_logline(logline, "to=", ctx->line + r); + sp_add_log(ctx, "to=", ctx->line + r); /* Reset log line */ else if(is_first_word(ctx->line, RSET_CMD, KL(RSET_CMD))) - logline[0] = 0; + cleanup_context(ctx); /* All other commands just get passed through to server */ - if(clio_write_data(ctx, &(ctx->server), ctx->line) == -1) + if(spio_write_data(ctx, &(ctx->server), ctx->line) == -1) RETURN(-1); continue; } /* Server has data available, read a line and forward */ - if(io == &(ctx->server)) + if(mask & 2) { - if(clio_read_line(ctx, &(ctx->server), CLIO_DISCARD) == -1) + if(spio_read_line(ctx, &(ctx->server), SPIO_DISCARD) == -1) RETURN(-1); if(ctx->linelen == 0) RETURN(0); if(LINE_TOO_LONG(ctx)) - messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra"); + sp_messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra"); /* * We intercept the first response we get from the server. @@ -874,9 +868,9 @@ static int smtp_passthru(clamsmtp_context_t* ctx) if(is_first_word(ctx->line, START_RSP, KL(START_RSP))) { - messagex(ctx, LOG_DEBUG, "intercepting initial response"); + sp_messagex(ctx, LOG_DEBUG, "intercepting initial response"); - if(clio_write_data(ctx, &(ctx->client), SMTP_BANNER) == -1) + if(spio_write_data(ctx, &(ctx->client), SMTP_BANNER) == -1) RETURN(-1); /* Command handled */ @@ -896,9 +890,9 @@ static int smtp_passthru(clamsmtp_context_t* ctx) /* Check for a simple '250 xxxx' */ if(is_first_word(ctx->line, OK_RSP, KL(OK_RSP))) { - messagex(ctx, LOG_DEBUG, "intercepting host response"); + sp_messagex(ctx, LOG_DEBUG, "intercepting host response"); - if(clio_write_data(ctx, &(ctx->client), SMTP_HELO_RSP) == -1) + if(spio_write_data(ctx, &(ctx->client), SMTP_HELO_RSP) == -1) RETURN(-1); continue; @@ -907,9 +901,9 @@ static int smtp_passthru(clamsmtp_context_t* ctx) /* Check for the continued response '250-xxxx' */ if(check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS) > 0) { - messagex(ctx, LOG_DEBUG, "intercepting host response"); + sp_messagex(ctx, LOG_DEBUG, "intercepting host response"); - if(clio_write_data(ctx, &(ctx->client), SMTP_EHLO_RSP) == -1) + if(spio_write_data(ctx, &(ctx->client), SMTP_EHLO_RSP) == -1) RETURN(-1); continue; @@ -931,13 +925,13 @@ static int smtp_passthru(clamsmtp_context_t* ctx) is_first_word(p, ESMTP_BINARY, KL(ESMTP_BINARY)) || is_first_word(p, ESMTP_CHECK, KL(ESMTP_CHECK))) { - messagex(ctx, LOG_DEBUG, "filtered ESMTP feature: %s", trim_space(p)); + sp_messagex(ctx, LOG_DEBUG, "filtered ESMTP feature: %s", trim_space(p)); continue; } } } - if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) + if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1) RETURN(-1); continue; @@ -946,498 +940,553 @@ static int smtp_passthru(clamsmtp_context_t* ctx) cleanup: - if(!neterror && ret == -1 && clio_valid(&(ctx->client))) - clio_write_data(ctx, &(ctx->client), SMTP_FAILED); + if(!neterror && ret == -1 && spio_valid(&(ctx->client))) + spio_write_data(ctx, &(ctx->client), SMTP_FAILED); return ret; } -static void add_to_logline(char* logline, char* prefix, char* line) -{ - int l = strlen(logline); - char* t = logline; +/* ----------------------------------------------------------------------------- + * SMTP PASSTHRU FUNCTIONS FOR DATA CHECK + */ - /* Simple optimization */ - logline += l; - l = LINE_LENGTH - l; +void sp_add_log(spctx_t* ctx, char* prefix, char* line) +{ + int l = SP_LINE_LENGTH; + char* t = ctx->logline; ASSERT(l >= 0); if(t[0] != 0) - strlcat(logline, ", ", l); + strlcat(ctx->logline, ", ", l); - strlcat(logline, prefix, l); + strlcat(ctx->logline, prefix, l); /* Skip initial white space */ line = trim_start(line); - strlcat(logline, line, l); + strlcat(ctx->logline, line, l); /* Skip later white space */ - trim_end(logline); + trim_end(ctx->logline); } -static int avcheck_data(clamsmtp_context_t* ctx, char* logline) +int sp_read_data(spctx_t* ctx, const char** data) { - /* - * Note that most failures are non fatal in this function. - * We only return -1 for data connection errors and the like, - * For most others we actually send a response back to the - * client letting them know what happened and let the SMTP - * connection continue. - */ + ASSERT(ctx); + ASSERT(data); - char buf[MAXPATHLEN]; - int havefile = 0; - int r, ret = 0; + *data = NULL; + + switch(spio_read_line(ctx, &(ctx->client), SPIO_QUIET)) + { + case 0: + sp_messagex(ctx, LOG_ERR, "unexpected end of data from client"); + return -1; + case -1: + /* Message already printed */ + return -1; + }; + + if(ctx->_crlf && strcmp(ctx->line, DATA_END_SIG) == 0) + return 0; + + /* Check if this line ended with a CRLF */ + ctx->_crlf = (strcmp(CRLF, ctx->line + (ctx->linelen - KL(CRLF))) == 0); + *data = ctx->line; + return ctx->linelen; +} + +int sp_write_data(spctx_t* ctx, const char* buf, int len) +{ + int r = 0; - strlcpy(buf, g_state->directory, MAXPATHLEN); - strlcat(buf, "/clamsmtpd.XXXXXX", MAXPATHLEN); + ASSERT(ctx); - /* transfer_to_file deletes the temp file on failure */ - if((r = transfer_to_file(ctx, buf)) > 0) + /* When a null buffer close the cache file */ + if(!buf) { - havefile = 1; - r = clam_scan_file(ctx, buf, logline); + if(ctx->cachefile) + { + if(fclose(ctx->cachefile) == EOF) + { + sp_message(ctx, LOG_ERR, "couldn't write to cache file: %s", ctx->cachename); + r = -1; + } + + ctx->cachefile = NULL; + } + + return r; } - switch(r) + /* Make sure we have a file open */ + if(!ctx->cachefile) { + int tfd; - /* - * There was an error tell the client. We haven't notified - * the server about any of this yet - */ - case -1: - if(clio_write_data(ctx, &(ctx->client), SMTP_FAILED)) - RETURN(-1); - break; + /* Make sure afore mentioned file is gone */ + if(ctx->cachename[0]) + unlink(ctx->cachename); - /* - * No virus was found. Now we initiate a connection to the server - * and transfer the file to it. - */ - case 0: - if(complete_data_transfer(ctx, buf) == -1) - RETURN(-1); - break; - - /* - * 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: - if(clio_write_data(ctx, &(ctx->client), - g_state->bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) - RETURN(-1); + snprintf(ctx->cachename, MAXPATHLEN, "%s/%s.XXXXXX", + g_state.directory, g_state.name); - /* Any special post operation actions on the virus */ - quarantine_virus(ctx, buf); - break; + if((tfd = mkstemp(ctx->cachename)) == -1 || + (ctx->cachefile = fdopen(tfd, "w")) == NULL) + { + if(tfd != -1) + close(tfd); - default: - ASSERT(0 && "Invalid clam_scan_file return value"); - break; - }; + sp_message(ctx, LOG_ERR, "couldn't open cache file"); + return -1; + } -cleanup: - if(havefile && !g_state->debug_files) + sp_messagex(ctx, LOG_DEBUG, "created cache file: %s", ctx->cachename); + } + + fwrite(buf, 1, len, ctx->cachefile); + + if(ferror(ctx->cachefile)) { - messagex(ctx, LOG_DEBUG, "deleting temporary file: %s", buf); - unlink(buf); + sp_message(ctx, LOG_ERR, "couldn't write to cache file: %s", ctx->cachename); + return -1; } - return ret; + return len; } -static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) +int sp_cache_data(spctx_t* ctx) { - ASSERT(ctx); - ASSERT(tempname); + int r, count = 0; + const char* data; - /* Ask the server for permission to send data */ - if(clio_write_data(ctx, &(ctx->server), SMTP_DATA) == -1) + while((r = sp_read_data(ctx, &data)) != 0) + { + if(r < 0) + return -1; /* Message already printed */ + + count += r; + + if((r = sp_write_data(ctx, data, r)) < 0) + return -1; /* Message already printed */ + } + + /* End the caching */ + if(sp_write_data(ctx, NULL, 0) < 0) return -1; + sp_messagex(ctx, LOG_DEBUG, "wrote %d bytes to cache", count); + return count; +} + +int sp_done_data(spctx_t* ctx, const char* header) +{ + FILE* file = 0; + int had_header = 0; + int ret = 0; + + ASSERT(ctx->cachename[0]); /* Must still be around */ + ASSERT(!ctx->cachefile); /* File must be closed */ + + /* Open the file */ + file = fopen(ctx->cachename, "r"); + if(file == NULL) + { + sp_message(ctx, LOG_ERR, "couldn't open cache file: %s", ctx->cachename); + RETURN(-1); + } + + /* Ask the server for permission to send data */ + if(spio_write_data(ctx, &(ctx->server), SMTP_DATA) == -1) + RETURN(-1); + if(read_server_response(ctx) == -1) - return -1; + RETURN(-1); /* If server returns an error then tell the client */ if(!is_first_word(ctx->line, DATA_RSP, KL(DATA_RSP))) { - if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) - return -1; + if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1) + RETURN(-1); - messagex(ctx, LOG_DEBUG, "server refused data transfer"); + sp_messagex(ctx, LOG_DEBUG, "server refused data transfer"); - return 0; + RETURN(-1); } - /* Now pull up the file and send it to the server */ - if(transfer_from_file(ctx, tempname) == -1) + sp_messagex(ctx, LOG_DEBUG, "sending from cache file: %s", ctx->cachename); + + /* Transfer actual file data */ + while(fgets(ctx->line, SP_LINE_LENGTH, file) != NULL) + { + if(header && !had_header) + { + /* + * The first blank line we see means the headers are done. + * At this point we add in our virus checked header. + */ + if(is_blank_line(ctx->line)) + { + if(spio_write_data_raw(ctx, &(ctx->server), (char*)header, strlen(header)) == -1 || + spio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1) + RETURN(-1); + + had_header = 1; + } + } + + if(spio_write_data_raw(ctx, &(ctx->server), ctx->line, strlen(ctx->line)) == -1) + RETURN(-1); + } + + if(ferror(file)) + sp_message(ctx, LOG_ERR, "error reading cache file: %s", ctx->cachename); + + if(ferror(file) || spio_write_data(ctx, &(ctx->server), DATA_END_SIG) == -1) { /* Tell the client it went wrong */ - clio_write_data(ctx, &(ctx->client), SMTP_FAILED); - return -1; + spio_write_data(ctx, &(ctx->client), SMTP_FAILED); + RETURN(-1); } + sp_messagex(ctx, LOG_DEBUG, "sent email data"); + /* Okay read the response from the server and echo it to the client */ if(read_server_response(ctx) == -1) - return -1; + RETURN(-1); + + if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1) + RETURN(-1); + +cleanup: + + if(file) + fclose(file); /* read-only so no error check */ + + return ret; +} + +int sp_fail_data(spctx_t* ctx, const char* smtp_status) +{ + char* t; + int len, x; + + if(smtp_status == NULL) + { + smtp_status = SMTP_FAILED; + } + + else + { + len = strlen(smtp_status); + + /* We need 3 digits and CRLF at the end for a premade SMTP message */ + if(strtol(smtp_status, &t, 10) == 0 || t != smtp_status + 3 || + strcmp(smtp_status + (len - KL(CRLF)), CRLF) != 0) + { + if(len > 256) + len = 256; + + x = len + KL(SMTP_REJPREFIX) + KL(CRLF) + 1; + t = (char*)alloca(x); - if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) + /* Note that we truncate long lines */ + snprintf(t, x, "%s%.256s%s", SMTP_REJPREFIX, smtp_status, CRLF); + smtp_status = t; + } + } + + if(spio_write_data(ctx, &(ctx->client), smtp_status) == -1) return -1; return 0; } -static int read_server_response(clamsmtp_context_t* ctx) +static int read_server_response(spctx_t* ctx) { /* Read response line from the server */ - if(clio_read_line(ctx, &(ctx->server), CLIO_DISCARD) == -1) + if(spio_read_line(ctx, &(ctx->server), SPIO_DISCARD) == -1) return -1; if(ctx->linelen == 0) { - messagex(ctx, LOG_ERR, "server disconnected unexpectedly"); + sp_messagex(ctx, LOG_ERR, "server disconnected unexpectedly"); /* Tell the client it went wrong */ - clio_write_data(ctx, &(ctx->client), SMTP_FAILED); + spio_write_data(ctx, &(ctx->client), SMTP_FAILED); return 0; } if(LINE_TOO_LONG(ctx)) - messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra"); + sp_messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra"); return 0; } - /* ---------------------------------------------------------------------------------- - * CLAM AV + * LOGGING */ -static int connect_clam(clamsmtp_context_t* ctx) -{ - int ret = 0; +const char kMsgDelimiter[] = ": "; +#define MAX_MSGLEN 256 - ASSERT(ctx); - ASSERT(!clio_valid(&(ctx->clam))); +static void vmessage(spctx_t* ctx, int level, int err, + const char* msg, va_list ap) +{ + size_t len; + char* m; + int e = errno; - if(clio_connect(ctx, &(ctx->clam), &(g_state->clamaddr), g_state->clamname) == -1) - RETURN(-1); + if(g_state.daemonized) + { + if(level >= LOG_DEBUG) + return; + } + else + { + if(g_state.debug_level < level) + return; + } - read_junk(ctx, ctx->clam.fd); + ASSERT(msg); - /* Send a session and a check header to ClamAV */ + len = strlen(msg) + 20 + MAX_MSGLEN; + m = (char*)alloca(len); - if(clio_write_data(ctx, &(ctx->clam), "SESSION\n") == -1) - RETURN(-1); + if(m) + { + if(ctx) + snprintf(m, len, "%06X: %s%s", ctx->id, msg, err ? ": " : ""); + else + snprintf(m, len, "%s%s", msg, err ? ": " : ""); - read_junk(ctx, ctx->clam.fd); -/* - if(clio_write_data(ctx, &(ctx->clam), "PING\n") == -1 || - clio_read_line(ctx, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM) == -1) - RETURN(-1); + if(err) + { + /* TODO: strerror_r doesn't want to work for us + strerror_r(e, m + strlen(m), MAX_MSGLEN); */ + strncat(m, strerror(e), len); + } - if(strcmp(ctx->line, CONNECT_RESPONSE) != 0) - { - message(ctx, LOG_ERR, "clamd sent an unexpected response: %s", ctx->line); - RETURN(-1); + m[len - 1] = 0; + msg = m; } -*/ -cleanup: + /* Either to syslog or stderr */ + if(g_state.daemonized) + vsyslog(level, msg, ap); + else + vwarnx(msg, ap); +} - if(ret < 0) - clio_disconnect(ctx, &(ctx->clam)); +void sp_messagex(spctx_t* ctx, int level, const char* msg, ...) +{ + va_list ap; - return ret; + va_start(ap, msg); + vmessage(ctx, level, 0, msg, ap); + va_end(ap); } -static int disconnect_clam(clamsmtp_context_t* ctx) +void sp_message(spctx_t* ctx, int level, const char* msg, ...) { - if(!clio_valid(&(ctx->clam))) - return 0; + va_list ap; - if(clio_write_data(ctx, &(ctx->clam), CLAM_DISCONNECT) != -1) - read_junk(ctx, ctx->clam.fd); - - clio_disconnect(ctx, &(ctx->clam)); - return 0; + va_start(ap, msg); + vmessage(ctx, level, 1, msg, ap); + va_end(ap); } -static int clam_scan_file(clamsmtp_context_t* ctx, const char* tempname, char* logline) -{ - int len; - ASSERT(LINE_LENGTH > MAXPATHLEN + 32); +/* ----------------------------------------------------------------------- + * LOCKING + */ - strcpy(ctx->line, CLAM_SCAN); - strcat(ctx->line, tempname); - strcat(ctx->line, "\n"); +void sp_lock() +{ + int r; - if(clio_write_data(ctx, &(ctx->clam), ctx->line) == -1) - return -1; +#ifdef _DEBUG + int wait = 0; +#endif - len = clio_read_line(ctx, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM); - if(len == 0) +#ifdef _DEBUG + r = pthread_mutex_trylock(&g_mutex); + if(r == EBUSY) { - messagex(ctx, LOG_ERR, "clamd disconnected unexpectedly"); - return -1; + wait = 1; + sp_message(NULL, LOG_DEBUG, "thread will block: %d", pthread_self()); + r = pthread_mutex_lock(&g_mutex); } - if(is_last_word(ctx->line, CLAM_OK, KL(CLAM_OK))) +#else + r = pthread_mutex_lock(&g_mutex); + +#endif + + if(r != 0) { - add_to_logline(logline, "status=", "CLEAN"); - messagex(ctx, LOG_DEBUG, "no virus"); - return 0; + errno = r; + sp_message(NULL, LOG_CRIT, "threading problem. couldn't lock mutex"); } - if(is_last_word(ctx->line, CLAM_FOUND, KL(CLAM_FOUND))) +#ifdef _DEBUG + else if(wait) { - len = strlen(tempname); - - if(ctx->linelen > len) - add_to_logline(logline, "status=VIRUS:", ctx->line + len + 1); - else - add_to_logline(logline, "status=", "VIRUS"); - - messagex(ctx, LOG_DEBUG, "found virus"); - return 1; + sp_message(NULL, LOG_DEBUG, "thread unblocked: %d", pthread_self()); } +#endif +} - if(is_last_word(ctx->line, CLAM_ERROR, KL(CLAM_ERROR))) +void sp_unlock() +{ + int r = pthread_mutex_unlock(&g_mutex); + if(r != 0) { - messagex(ctx, LOG_ERR, "clamav error: %s", ctx->line); - add_to_logline(logline, "status=", "CLAMAV-ERROR"); - return -1; + errno = r; + sp_message(NULL, LOG_CRIT, "threading problem. couldn't unlock mutex"); } - - add_to_logline(logline, "status=", "CLAMAV-ERROR"); - messagex(ctx, LOG_ERR, "unexepected response from clamd: %s", ctx->line); - return -1; } - -/* ---------------------------------------------------------------------------------- - * TEMP FILE HANDLING +/* ----------------------------------------------------------------------------- + * CONFIG FILE */ -static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname) +int sp_parse_option(const char* name, const char* value) { - char buf[MAXPATHLEN]; char* t; + int ret = 0; - if(!g_state->quarantine) - return 0; - - strlcpy(buf, g_state->directory, MAXPATHLEN); - strlcat(buf, "/virus.", MAXPATHLEN); - - /* Points to null terminator */ - t = buf + strlen(buf); - - /* - * Yes, I know we're using mktemp. And yet we're doing it in - * a safe manner due to the link command below not overwriting - * existing files. - */ - for(;;) + if(strcasecmp(CFG_MAXTHREADS, name) == 0) { - /* Null terminate off the ending, and replace with X's for mktemp */ - *t = 0; - strlcat(buf, "XXXXXX", MAXPATHLEN); - - if(!mktemp(buf)) - { - message(ctx, LOG_ERR, "couldn't create quarantine file name"); - return -1; - } - - /* Try to link the file over to the temp */ - if(link(tempname, buf) == -1) - { - /* We don't want to allow race conditions */ - if(errno == EEXIST) - { - message(ctx, LOG_WARNING, "race condition when quarantining virus file: %s", buf); - continue; - } - - message(ctx, LOG_ERR, "couldn't quarantine virus file"); - return -1; - } - - break; + g_state.max_threads = strtol(value, &t, 10); + if(*t || g_state.max_threads <= 1 || g_state.max_threads >= 1024) + errx(2, "invalid setting: " CFG_MAXTHREADS " (must be between 1 and 1024)"); + ret = 1; } - messagex(ctx, LOG_INFO, "quarantined virus file as: %s", buf); - return 0; -} - -static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname) -{ - FILE* tfile = NULL; - int tfd = -1; - int ended_crlf = 1; /* If the last line ended with a CRLF */ - int ret = 0; - int count = 0; - - if((tfd = mkstemp(tempname)) == -1 || - (tfile = fdopen(tfd, "w")) == NULL) + else if(strcasecmp(CFG_TIMEOUT, name) == 0) { - message(ctx, LOG_ERR, "couldn't open temp file"); - RETURN(-1); + g_state.timeout.tv_sec = strtol(value, &t, 10); + if(*t || g_state.timeout.tv_sec <= 0) + errx(2, "invalid setting: " CFG_TIMEOUT); + ret = 1; } - messagex(ctx, LOG_DEBUG, "created temporary file: %s", tempname); - - for(;;) + else if(strcasecmp(CFG_OUTADDR, name) == 0) { - switch(clio_read_line(ctx, &(ctx->client), CLIO_QUIET)) - { - case 0: - messagex(ctx, LOG_ERR, "unexpected end of data from client"); - RETURN(-1); - - case -1: - /* Message already printed */ - RETURN(-1); - }; - - if(ended_crlf && strcmp(ctx->line, DATA_END_SIG) == 0) - break; - - /* We check errors on this later */ - fwrite(ctx->line, 1, ctx->linelen, tfile); - count += ctx->linelen; - - /* Check if this line ended with a CRLF */ - ended_crlf = (strcmp(CRLF, ctx->line + (ctx->linelen - KL(CRLF))) == 0); + if(sock_any_pton(value, &(g_state.outaddr), SANY_OPT_DEFPORT(25)) == -1) + errx(2, "invalid " CFG_OUTADDR " socket name or ip: %s", value); + g_state.outname = value; + ret = 1; } - if(ferror(tfile)) + else if(strcasecmp(CFG_LISTENADDR, name) == 0) { - message(ctx, LOG_ERR, "error writing to temp file: %s", tempname); - RETURN(-1); + if(sock_any_pton(value, &(g_state.listenaddr), SANY_OPT_DEFANY | SANY_OPT_DEFPORT(DEFAULT_PORT)) == -1) + errx(2, "invalid " CFG_LISTENADDR " socket name or ip: %s", value); + g_state.listenname = value; + ret = 1; } - ret = count; - messagex(ctx, LOG_DEBUG, "wrote %d bytes to temp file", count); - -cleanup: - - if(tfile) - fclose(tfile); - - if(tfd != -1) + else if(strcasecmp(CFG_TRANSPARENT, name) == 0) { - /* Only close this if not opened as a stream */ - if(tfile == NULL) - close(tfd); + if((g_state.transparent = strtob(value)) == -1) + errx(2, "invalid value for " CFG_TRANSPARENT); + ret = 1; + } - if(ret <= 0) - { - messagex(ctx, LOG_DEBUG, "discarding temporary file"); - unlink(tempname); - } + else if(strcasecmp(CFG_DIRECTORY, name) == 0) + { + if(strlen(value) == 0) + errx(2, "invalid setting: " CFG_DIRECTORY); + g_state.directory = value; + ret = 1; } + /* Always pass through to program */ + if(cb_parse_option(name, value) == 1) + ret = 1; + return ret; } -static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename) +static int parse_config_file(const char* configfile) { - FILE* file = NULL; - int header = 0; - int ret = 0; - - file = fopen(filename, "r"); - if(file == NULL) - { - message(ctx, LOG_ERR, "couldn't open temporary file: %s", filename); - RETURN(-1); - } + FILE* f = NULL; + long len; + char* p; + char* t; + char* n; - messagex(ctx, LOG_DEBUG, "sending from temporary file: %s", filename); + ASSERT(configfile); + ASSERT(!g_state._p); - while(fgets(ctx->line, LINE_LENGTH, file) != NULL) + f = fopen(configfile, "r"); + if(f == NULL) { - if(g_state->header && !header) - { - /* - * The first blank line we see means the headers are done. - * At this point we add in our virus checked header. - */ - if(is_blank_line(ctx->line)) - { - 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); - - header = 1; - } - } - - if(clio_write_data_raw(ctx, &(ctx->server), ctx->line, strlen(ctx->line)) == -1) - RETURN(-1); + /* 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(ferror(file)) - { - message(ctx, LOG_ERR, "error reading temporary file: %s", filename); - RETURN(-1); - } + /* Figure out size */ + if(fseek(f, 0, SEEK_END) == -1 || (len = ftell(f)) == -1 || fseek(f, 0, SEEK_SET) == -1) + err(1, "couldn't seek config file: %s", configfile); - if(clio_write_data(ctx, &(ctx->server), DATA_END_SIG) == -1) - RETURN(-1); + if((g_state._p = (char*)malloc(len + 2)) == NULL) + errx(1, "out of memory"); - messagex(ctx, LOG_DEBUG, "sent email data"); + /* And read in one block */ + if(fread(g_state._p, 1, len, f) != len) + err(1, "couldn't read config file: %s", configfile); -cleanup: + fclose(f); + sp_messagex(NULL, LOG_DEBUG, "read config file: %s", configfile); - if(file != NULL) - fclose(file); + /* Double null terminate the data */ + p = g_state._p; + p[len] = 0; + p[len + 1] = 0; - return ret; -} + n = g_state._p; + /* Go through lines and process them */ + while((t = strchr(n, '\n')) != NULL) + { + *t = 0; + p = n; /* Do this before cleaning below */ + n = t + 1; -/* ---------------------------------------------------------------------------------- - * NETWORKING - */ + p = trim_start(p); -static void read_junk(clamsmtp_context_t* ctx, int fd) -{ - char buf[16]; - const char* t; - int said = 0; - int l; + /* Comments and empty lines */ + if(*p == 0 || *p == '#') + continue; - if(fd == -1) - return; + /* Look for the break between name: value */ + t = strchr(p, ':'); + if(t == NULL) + errx(2, "invalid config line: %s", p); - /* Make it non blocking */ - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + /* Null terminate and split value part */ + *t = 0; + t++; - for(;;) - { - l = read(fd, buf, sizeof(buf) - 1); - if(l <= 0) - break; + t = trim_space(t); + p = trim_space(p); - buf[l] = 0; - t = trim_start(buf); + /* Pass it through our options parsers */ + if(sp_parse_option(p, t) == 0) - if(!said && *t) - { - messagex(ctx, LOG_DEBUG, "received junk data from daemon"); - said = 1; - } + /* If not recognized then it's invalid */ + errx(2, "invalid config line: %s", p); + + sp_messagex(NULL, LOG_DEBUG, "parsed option: %s: %s", p, t); } - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK); + return 0; } + diff --git a/common/smtppass.h b/common/smtppass.h index 10b4a1f..08faa34 100644 --- a/common/smtppass.h +++ b/common/smtppass.h @@ -36,25 +36,61 @@ * */ -#ifndef __CLAMSMTPD_H__ -#define __CLAMSMTPD_H__ +#ifndef __SMTPPASS_H__ +#define __SMTPPASS_H__ -#include <sock_any.h> +/* Forward declarations */ +struct sockaddr_any; +struct spctx; -/* IO Buffers see clio.c ---------------------------------------------------- */ - -#define BUF_LEN 256 +/* ----------------------------------------------------------------------------- + * BUFFERED MULTIPLEXING IO + * + * This isn't meant to be a replacement library for all sorts of IO + * only things that are currently used go here. + */ -typedef struct clio +typedef struct spio { - int fd; - const char* name; - unsigned char buf[BUF_LEN]; - size_t buflen; + int fd; /* The file descriptor wrapped */ + const char* name; /* The name for logging */ + #define SPIO_BUFLEN 256 + unsigned char _bf[SPIO_BUFLEN]; + size_t _ln; } -clio_t; +spio_t; + +#define SPIO_TRIM 0x00000001 +#define SPIO_DISCARD 0x00000002 +#define SPIO_QUIET 0x00000004 + +#define spio_valid(io) ((io)->fd != -1) + +/* Setup the io structure (allocated elsewhere */ +void spio_init(spio_t* io, const char* name); + +/* Connect and disconnect from sockets */ +int spio_connect(struct spctx* ctx, spio_t* io, const struct sockaddr_any* sany, const char* addrname); +void spio_disconnect(struct spctx* ctx, spio_t* io); + +/* Read a line from a socket. Use options above */ +int spio_read_line(struct spctx* ctx, spio_t* io, int opts); + +/* Write data to socket (must supply line endings if needed). + * Guaranteed to accept all data or fail. */ +int spio_write_data(struct spctx* ctx, spio_t* io, const char* data); +int spio_write_data_raw(struct spctx* ctx, spio_t* io, unsigned char* buf, int len); -/* The main context --------------------------------------------------------- */ +/* Empty the given socket */ +void spio_read_junk(struct spctx* sp, spio_t* io); + +/* Pass up to 31 spio_t*, followed by NULL. Returns bitmap of ready for reading */ +unsigned int spio_select(struct spctx* ctx, ...); + + +/* ----------------------------------------------------------------------------- + * SMTP PASS THROUGH FUNCTIONALITY + */ /* * A generous maximum line length. It needs to be longer than @@ -63,83 +99,163 @@ clio_t; */ #if 2000 > MAXPATHLEN - #define LINE_LENGTH 2000 + #define SP_LINE_LENGTH 2000 #else - #define LINE_LENGTH (MAXPATHLEN + 128) + #define SP_LINE_LENGTH (MAXPATHLEN + 128) #endif -typedef struct clamsmtp_context +typedef struct spctx { - unsigned int id; /* Identifier for the connection */ + unsigned int id; /* Identifier for the connection */ - clio_t client; /* Connection to client */ - clio_t server; /* Connection to server */ - clio_t clam; /* Connection to clamd */ + spio_t client; /* Connection to client */ + spio_t server; /* Connection to server */ - char line[LINE_LENGTH]; /* Working buffer */ - int linelen; /* Length of valid data in above */ + char logline[SP_LINE_LENGTH]; /* Log line */ + char line[SP_LINE_LENGTH]; /* Working buffer */ + int linelen; /* Length of valid data in above */ + + FILE* cachefile; /* The file handle for the cached file */ + char cachename[MAXPATHLEN]; /* The name of the file that we cache into */ + + int _crlf; /* Private data */ } -clamsmtp_context_t; +spctx_t; + +/* + * sp_init initializes the SMTP Pass-Through functionality + * The name passed is the name of the app + */ +void sp_init(const char* name); -#define LINE_TOO_LONG(ctx) ((ctx)->linelen >= (LINE_LENGTH - 2)) -#define RETURN(x) { ret = x; goto cleanup; } +/* + * This starts up the smtp pass thru program. It will call + * the cb_* functions as appropriate. + */ +int sp_run(const char* configfile, const char* pidfile, int dbg_level); +/* + * Mark the application as shutting down. + * A signal will interupt most IO. + */ +void sp_quit(); -/* Implemented in clio.c ---------------------------------------------------- */ +/* + * Check if the application has been marked to quit. + * Useful for checking after interupted IO. + */ +int sp_is_quit(); -#define CLIO_TRIM 0x00000001 -#define CLIO_DISCARD 0x00000002 -#define CLIO_QUIET 0x00000004 -#define clio_valid(io) ((io)->fd != -1) +/* + * Called to cleanup SMTP Pass-Through functionality just + * before the application quits. + */ +void sp_done(); -void clio_init(clio_t* io, const char* name); -int clio_connect(clamsmtp_context_t* ctx, clio_t* io, const struct sockaddr_any* sany, const char* addrname); -void clio_disconnect(clamsmtp_context_t* ctx, clio_t* io); -int clio_select(clamsmtp_context_t* ctx, clio_t** io); -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); +/* + * clamsmtpd used to accept command line args. In order to + * process those args it needs to send the values to the + * config file routines. This is how it does that. + */ +#ifdef SP_LEGACY_OPTIONS +int sp_parse_option(const char* name, const char* option); +#endif -/* Implemented in clstate.c ------------------------------------------------ */ +/* + * The following functions are to be called from within + * the spc_check_data function. + */ -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 */ - int bounce; /* Send back a reject line */ - int quarantine; /* Leave virus files in temp dir */ - int debug_files; /* Leave all files in temp dir */ - int transparent; /* Transparent proxying */ - - /* 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; +/* + * Adds a piece of info to the log line + */ +void sp_add_log(spctx_t* ctx, char* prefix, char* line); + +/* + * Reads a line of DATA from client. Or less than a line if + * line is longer than LINE_LENGTH. No trimming or anything + * is done on the read line. This will end automatically + * when <CRLF>.<CRLF> is detected (in which case 0 will + * be returned). The data is returned in data. + */ +int sp_read_data(spctx_t* ctx, const char** data); + +/* + * Writes a line (or piece) of data to a file buffer which is + * later sent to the client using sp_done_data. Calling it with + * a NULL buffer closes the cache file. Guaranteed to accept + * all data given to it or fail. + */ +int sp_write_data(spctx_t* ctx, const char* buf, int buflen); + +/* + * Sends all DATA from the client into the cache. The cache + * file is then available (in spctx_t->cachename) for use. + */ +int sp_cache_data(spctx_t* ctx); + +/* + * Sends the data in file buffer off to server. This is + * completes a successful mail transfer. The optional header + * is appended to the end of the email headers. + */ +int sp_done_data(spctx_t* ctx, const char* header); + +/* + * Fails the data, deletes any temp data, and sends given + * status to client or if NULL then SMTP_DATAFAILED + */ +int sp_fail_data(spctx_t* ctx, const char* smtp_status); + +/* + * Log a message. levels are syslog levels. Syntax is just + * like printf etc.. Can specify a ctx of NULL in which case + * no connection prefix is prepended. + */ +void sp_message(spctx_t* ctx, int level, const char* msg, ...); +void sp_messagex(spctx_t* ctx, int level, const char* msg, ...); + +/* + * Lock or unlock the main mutex around thread common + * functionality. + */ +void sp_lock(); +void sp_unlock(); -extern const 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); +/* ----------------------------------------------------------------------------- + * CALLBACKS IMPLMEMENTED BY PROGRAM + */ + +/* + * The following functions create and destroy contexts for a new + * thread. Perform initialization in there and return a spctx + * structure for the thread to use. Return NULL failed. Be sure + * to log message. + */ +extern spctx_t* cb_new_context(); +extern void cb_del_context(spctx_t* ctx); + +/* + * Called when the data section of an email is being transferred. + * Once inside this function you can transfer files using + * sp_read_data, sp_write_data. + * + * After scanning or figuring out the status call either + * sp_done_data or sp_fail_data. Most failures should be handled + * internally using sp_fail_data, unless it's an out of memory + * condition, or sp_fail_data failed. + */ +extern int cb_check_data(spctx_t* ctx); + +/* + * Parse options from the config file. The memory for these + * options will stay around until sp_done is called. Return 0 + * for unrecognized options, 1 for recognized, and quit the + * program for invalid. + */ +extern int cb_parse_option(const char* name, const char* value); + -#endif /* __CLAMSMTPD_H__ */ +#endif /* __SMTPPASS_H__ */ diff --git a/common/spio.c b/common/spio.c index 36b495e..ff5a473 100644 --- a/common/spio.c +++ b/common/spio.c @@ -53,11 +53,15 @@ #include <fcntl.h> #include <syslog.h> #include <errno.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> +#include <err.h> #include "usuals.h" #include "sock_any.h" -#include "clamsmtpd.h" -#include "util.h" +#include "stringx.h" +#include "sppriv.h" #define MAX_LOG_LINE 79 #define GET_IO_NAME(io) ((io)->name ? (io)->name : "??? ") @@ -70,7 +74,7 @@ static void close_raw(int* fd) *fd = -1; } -static void log_io_data(clamsmtp_context_t* ctx, clio_t* io, const char* data, int read) +static void log_io_data(spctx_t* ctx, spio_t* io, const char* data, int read) { char buf[MAX_LOG_LINE + 1]; int pos, len; @@ -90,14 +94,14 @@ 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), + sp_messagex(ctx, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io), read ? " < " : " > ", buf); data += pos; } } -void clio_init(clio_t* io, const char* name) +void spio_init(spio_t* io, const char* name) { ASSERT(io && name); memset(io, 0, sizeof(*io)); @@ -105,7 +109,7 @@ void clio_init(clio_t* io, const char* name) io->fd = -1; } -int clio_connect(clamsmtp_context_t* ctx, clio_t* io, const struct sockaddr_any* sany, +int spio_connect(spctx_t* ctx, spio_t* io, const struct sockaddr_any* sany, const char* addrname) { int ret = 0; @@ -116,9 +120,9 @@ int clio_connect(clamsmtp_context_t* ctx, clio_t* io, const struct sockaddr_any* if((io->fd = socket(SANY_TYPE(*sany), SOCK_STREAM, 0)) == -1) RETURN(-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(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) + sp_messagex(ctx, LOG_WARNING, "couldn't set timeouts on connection"); if(connect(io->fd, &SANY_ADDR(*sany), SANY_LEN(*sany)) == -1) RETURN(-1); @@ -129,89 +133,95 @@ cleanup: if(io->fd != -1) close(io->fd); - message(ctx, LOG_ERR, "couldn't connect to: %s", addrname); + sp_message(ctx, LOG_ERR, "couldn't connect to: %s", addrname); return -1; } ASSERT(io->fd != -1); - messagex(ctx, LOG_DEBUG, "%s connected to: %s", GET_IO_NAME(io), addrname); + sp_messagex(ctx, LOG_DEBUG, "%s connected to: %s", GET_IO_NAME(io), addrname); return 0; } -void clio_disconnect(clamsmtp_context_t* ctx, clio_t* io) +void spio_disconnect(spctx_t* ctx, spio_t* io) { ASSERT(ctx && io); - if(clio_valid(io)) + if(spio_valid(io)) { close_raw(&(io->fd)); - messagex(ctx, LOG_DEBUG, "%s connection closed", GET_IO_NAME(io)); + sp_messagex(ctx, LOG_DEBUG, "%s connection closed", GET_IO_NAME(io)); } } -int clio_select(clamsmtp_context_t* ctx, clio_t** io) +unsigned int spio_select(spctx_t* ctx, ...) { fd_set mask; + spio_t* io; + int ret = 0; + int i = 0; + va_list ap; - ASSERT(ctx && io); + ASSERT(ctx); FD_ZERO(&mask); - *io = NULL; - /* First check if buffers have any data */ + va_start(ap, ctx); - if(clio_valid(&(ctx->server))) + while((io = va_arg(ap, spio_t*)) != NULL) { - if(ctx->server.buflen > 0) - { - *io = &(ctx->server); - return 0; - } + /* We can't handle more than 31 args */ + if(i > (sizeof(int) * 8) - 2) + break; - FD_SET(ctx->server.fd, &mask); - } + /* Check if the buffer has something in it */ + if(io->_ln > 0) + ret |= (1 << i); - if(clio_valid(&(ctx->client))) - { - if(ctx->client.buflen > 0) - { - *io = &(ctx->client); - return 0; - } + /* Mark for select */ + FD_SET(io->fd, &mask); - FD_SET(ctx->client.fd, &mask); + i++; } - /* Select on the above */ + va_end(ap); + + /* If any buffers had something present, then return */ + if(ret != 0) + return ret; - switch(select(FD_SETSIZE, &mask, NULL, NULL, (struct timeval*)&(g_state->timeout))) + /* Otherwise wait on more data */ + switch(select(FD_SETSIZE, &mask, NULL, NULL, + (struct timeval*)&(g_state.timeout))) { case 0: - messagex(ctx, LOG_ERR, "network operation timed out"); - return -1; + sp_messagex(ctx, LOG_ERR, "network operation timed out"); + return ~0; case -1: - message(ctx, LOG_ERR, "couldn't select on sockets"); - return -1; + sp_message(ctx, LOG_ERR, "couldn't select on sockets"); + return ~0; }; /* See what came in */ + i = 0; - if(FD_ISSET(ctx->server.fd, &mask)) - { - *io = &(ctx->server); - return 0; - } + va_start(ap, ctx); - if(FD_ISSET(ctx->client.fd, &mask)) + while((io = va_arg(ap, spio_t*)) != NULL) { - *io = &(ctx->client); - return 0; + /* We can't handle more than 31 args */ + if(i > (sizeof(int) * 8) - 2) + break; + + /* Check if the buffer has something in it */ + if(FD_ISSET(io->fd, &mask)) + ret |= (1 << i); + + i++; } - ASSERT(0 && "invalid result from select"); - return -1; + return ret; } -int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) +int spio_read_line(spctx_t* ctx, spio_t* io, int opts) { int l, x; char* t; @@ -219,32 +229,32 @@ int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) ASSERT(ctx && io); - if(!clio_valid(io)) + if(!spio_valid(io)) { - messagex(ctx, LOG_WARNING, "tried to read from a closed connection"); + sp_messagex(ctx, LOG_WARNING, "tried to read from a closed connection"); return 0; } ctx->line[0] = 0; t = ctx->line; - l = LINE_LENGTH - 1; + l = SP_LINE_LENGTH - 1; for(;;) { /* refil buffer if necessary */ - if(io->buflen == 0) + if(io->_ln == 0) { ASSERT(io->fd != -1); - io->buflen = read(io->fd, io->buf, sizeof(char) * BUF_LEN); + io->_ln = read(io->fd, io->_bf, sizeof(char) * SPIO_BUFLEN); - if(io->buflen == -1) + if(io->_ln == -1) { - io->buflen = 0; + io->_ln = 0; if(errno == EINTR) { /* When the application is quiting */ - if(g_state->quit) + if(sp_is_quit()) return -1; /* For any other signal we go again */ @@ -252,11 +262,11 @@ int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) } if(errno == ECONNRESET) /* Not usually a big deal so supresse the error */ - messagex(ctx, LOG_DEBUG, "connection disconnected by peer: %s", GET_IO_NAME(io)); + sp_messagex(ctx, LOG_DEBUG, "connection disconnected by peer: %s", GET_IO_NAME(io)); else if(errno == EAGAIN) - messagex(ctx, LOG_WARNING, "network read operation timed out: %s", GET_IO_NAME(io)); + sp_messagex(ctx, LOG_WARNING, "network read operation timed out: %s", GET_IO_NAME(io)); else - message(ctx, LOG_ERR, "couldn't read data from socket: %s", GET_IO_NAME(io)); + sp_message(ctx, LOG_ERR, "couldn't read data from socket: %s", GET_IO_NAME(io)); /* * The basic logic here is that if we've had a fatal error @@ -270,50 +280,50 @@ int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) } /* End of data */ - if(io->buflen == 0) + if(io->_ln == 0) break; /* Check for a new line */ - p = (unsigned char*)memchr(io->buf, '\n', io->buflen); + p = (unsigned char*)memchr(io->_bf, '\n', io->_ln); if(p != NULL) { - x = (p - io->buf) + 1; - io->buflen -= x; + x = (p - io->_bf) + 1; + io->_ln -= x; } else { - x = io->buflen; - io->buflen = 0; + x = io->_ln; + io->_ln = 0; } if(x > l) x = l; /* Copy from buffer line */ - memcpy(t, io->buf, x); + memcpy(t, io->_bf, x); t += x; l -= x; /* Move whatever we have in the buffer to the front */ - if(io->buflen > 0) - memmove(io->buf, io->buf + x, io->buflen); + if(io->_ln > 0) + memmove(io->_bf, io->_bf + x, io->_ln); /* Found a new line, done */ if(p != NULL) break; /* If discarding then don't break when full */ - if(!(opts && CLIO_DISCARD) && l == 0) + if(!(opts && SPIO_DISCARD) && l == 0) break; } - ctx->linelen = (LINE_LENGTH - l) - 1; - ASSERT(ctx->linelen < LINE_LENGTH); + ctx->linelen = (SP_LINE_LENGTH - l) - 1; + ASSERT(ctx->linelen < SP_LINE_LENGTH); ctx->line[ctx->linelen] = 0; - if(opts & CLIO_TRIM && ctx->linelen > 0) + if(opts & SPIO_TRIM && ctx->linelen > 0) { t = ctx->line; @@ -335,28 +345,28 @@ int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) } } - if(!(opts & CLIO_QUIET)) + if(!(opts & SPIO_QUIET)) log_io_data(ctx, io, ctx->line, 1); return ctx->linelen; } -int clio_write_data(clamsmtp_context_t* ctx, clio_t* io, const char* data) +int spio_write_data(spctx_t* ctx, spio_t* io, const char* data) { int len = strlen(data); ASSERT(ctx && io && data); - if(!clio_valid(io)) + if(!spio_valid(io)) { - message(ctx, LOG_ERR, "connection closed. can't write data."); + sp_message(ctx, LOG_ERR, "connection closed. can't write data."); return -1; } log_io_data(ctx, io, data, 0); - return clio_write_data_raw(ctx, io, (unsigned char*)data, len); + return spio_write_data_raw(ctx, io, (unsigned char*)data, len); } -int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, int len) +int spio_write_data_raw(spctx_t* ctx, spio_t* io, unsigned char* buf, int len) { int r; @@ -380,7 +390,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_state->quit) + if(sp_is_quit()) return -1; /* For any other signal we go again */ @@ -395,9 +405,9 @@ int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, close_raw(&(io->fd)); if(errno == EAGAIN) - messagex(ctx, LOG_WARNING, "network write operation timed out: %s", GET_IO_NAME(io)); + sp_messagex(ctx, LOG_WARNING, "network write operation timed out: %s", GET_IO_NAME(io)); else - message(ctx, LOG_ERR, "couldn't write data to socket: %s", GET_IO_NAME(io)); + sp_message(ctx, LOG_ERR, "couldn't write data to socket: %s", GET_IO_NAME(io)); return -1; } @@ -405,3 +415,41 @@ int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, return 0; } + +void spio_read_junk(spctx_t* ctx, spio_t* io) +{ + char buf[16]; + const char* t; + int said = 0; + int l; + + ASSERT(ctx); + ASSERT(io); + + /* Truncate any data in buffer */ + io->_ln = 0; + + if(!spio_valid(io)) + return; + + /* Make it non blocking */ + fcntl(io->fd, F_SETFL, fcntl(io->fd, F_GETFL, 0) | O_NONBLOCK); + + for(;;) + { + l = read(io->fd, buf, sizeof(buf) - 1); + if(l <= 0) + break; + + buf[l] = 0; + t = trim_start(buf); + + if(!said && *t) + { + sp_messagex(ctx, LOG_DEBUG, "received junk data from daemon"); + said = 1; + } + } + + fcntl(io->fd, F_SETFL, fcntl(io->fd, F_GETFL, 0) & ~O_NONBLOCK); +} diff --git a/src/usuals.h b/common/sppriv.h index 385bcf9..89a89f9 100644 --- a/src/usuals.h +++ b/common/sppriv.h @@ -36,41 +36,36 @@ * */ -#ifndef __USUALS_H__ -#define __USUALS_H__ +#ifndef __SPPRIV_H__ +#define __SPPRIV_H__ -#include <sys/types.h> +#include "smtppass.h" -#include "config.h" +typedef struct spstate +{ + /* 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 */ + int transparent; /* Transparent proxying */ + const char* directory; /* The temp directory */ -#include <stdio.h> -#include <stdlib.h> -#include <errno.h> -#include <string.h> + struct sockaddr_any outaddr; /* The outgoing address */ + const char* outname; + struct sockaddr_any listenaddr; /* Address to listen on */ + const char* listenname; -#include "compat.h" + /* State --------------------------------- */ + const char* name; /* The name of the program */ + int quit; /* Quit the process */ + int daemonized; /* Whether process is daemonized or not */ -#ifndef NULL -#define NULL 0 -#endif + /* Internal Use ------------------------- */ + char* _p; +} +spstate_t; -#ifndef max -#define max(a,b) (((a) > (b)) ? (a) : (b)) -#endif +extern spstate_t g_state; -#ifndef min -#define min(a,b) (((a) < (b)) ? (a) : (b)) -#endif +#endif /* __SPPRIV_H__ */ -#define countof(x) (sizeof(x) / sizeof(x[0])) - -#ifdef _DEBUG - #include "assert.h" - #define ASSERT assert -#else - #define ASSERT -#endif - -#define KL(s) ((sizeof(s) - 1) / sizeof(char)) - -#endif /* __USUALS_H__ */ diff --git a/common/stringx.c b/common/stringx.c index 1067f27..f3c788e 100644 --- a/common/stringx.c +++ b/common/stringx.c @@ -42,89 +42,11 @@ #include <syslog.h> #include <stdlib.h> #include <stdio.h> -#include <unistd.h> -#include <errno.h> -#include <err.h> -#include <stdarg.h> #include <strings.h> #include "usuals.h" #include "compat.h" -#include "clamsmtpd.h" -#include "util.h" - -/* ---------------------------------------------------------------------------------- - * Logging - */ - -const char kMsgDelimiter[] = ": "; -#define MAX_MSGLEN 256 - -static void vmessage(clamsmtp_context_t* ctx, int level, int err, - const char* msg, va_list ap) -{ - size_t len; - char* m; - int e = errno; - - if(g_state->daemonized) - { - if(level >= LOG_DEBUG) - return; - } - else - { - if(g_state->debug_level < level) - return; - } - - ASSERT(msg); - - len = strlen(msg) + 20 + MAX_MSGLEN; - m = (char*)alloca(len); - - if(m) - { - if(ctx) - snprintf(m, len, "%06X: %s%s", ctx->id, msg, err ? ": " : ""); - else - snprintf(m, len, "%s%s", msg, err ? ": " : ""); - - if(err) - { - /* TODO: strerror_r doesn't want to work for us - strerror_r(e, m + strlen(m), MAX_MSGLEN); */ - strncat(m, strerror(e), len); - } - - m[len - 1] = 0; - msg = m; - } - - /* Either to syslog or stderr */ - if(g_state->daemonized) - vsyslog(level, msg, ap); - else - vwarnx(msg, ap); -} - -void messagex(clamsmtp_context_t* ctx, int level, const char* msg, ...) -{ - va_list ap; - - va_start(ap, msg); - vmessage(ctx, level, 0, msg, ap); - va_end(ap); -} - -void message(clamsmtp_context_t* ctx, int level, const char* msg, ...) -{ - va_list ap; - - va_start(ap, msg); - vmessage(ctx, level, 1, msg, ap); - va_end(ap); -} +#include "stringx.h" /* ---------------------------------------------------------------------------------- * Parsing @@ -231,53 +153,22 @@ char* trim_space(char* data) return trim_end(data); } -/* ----------------------------------------------------------------------- - * Locking - */ - -void plock() +/* String to bool helper function */ +int strtob(const char* str) { - int r; - -#ifdef _DEBUG - int wait = 0; -#endif - -#ifdef _DEBUG - r = pthread_mutex_trylock((pthread_mutex_t*)&(g_state->mutex)); - if(r == EBUSY) - { - wait = 1; - message(NULL, LOG_DEBUG, "thread will block: %d", pthread_self()); - r = pthread_mutex_lock((pthread_mutex_t*)&(g_state->mutex)); - } - -#else - r = pthread_mutex_lock(&(g_state->mutex)); - -#endif - - if(r != 0) - { - errno = r; - message(NULL, LOG_CRIT, "threading problem. couldn't lock mutex"); - } + if(strcasecmp(str, "0") == 0 || + strcasecmp(str, "no") == 0 || + strcasecmp(str, "false") == 0 || + strcasecmp(str, "f") == 0 || + strcasecmp(str, "off") == 0) + return 0; -#ifdef _DEBUG - else if(wait) - { - message(NULL, LOG_DEBUG, "thread unblocked: %d", pthread_self()); - } -#endif -} + if(strcasecmp(str, "1") == 0 || + strcasecmp(str, "yes") == 0 || + strcasecmp(str, "true") == 0 || + strcasecmp(str, "t") == 0 || + strcasecmp(str, "on") == 0) + return 1; -void punlock() -{ - int r = pthread_mutex_unlock((pthread_mutex_t*)&(g_state->mutex)); - if(r != 0) - { - errno = r; - message(NULL, LOG_CRIT, "threading problem. couldn't unlock mutex"); - } + return -1; } - diff --git a/common/stringx.h b/common/stringx.h index 37fa245..deb8f4b 100644 --- a/common/stringx.h +++ b/common/stringx.h @@ -36,11 +36,8 @@ * */ -#ifndef __UTIL_H__ -#define __UTIL_H__ - -void messagex(clamsmtp_context_t* ctx, int level, const char* msg, ...); -void message(clamsmtp_context_t* ctx, int level, const char* msg, ...); +#ifndef __STRINGX_H__ +#define __STRINGX_H__ 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); @@ -51,7 +48,6 @@ char* trim_start(const char* data); char* trim_end(char* data); char* trim_space(char* data); -void plock(); -void punlock(); +int strtob(const char* str); -#endif /* __UTIL_H__ */ +#endif /* __STRINGX_H__ */ diff --git a/common/usuals.h b/common/usuals.h index 385bcf9..48d372f 100644 --- a/common/usuals.h +++ b/common/usuals.h @@ -72,5 +72,7 @@ #endif #define KL(s) ((sizeof(s) - 1) / sizeof(char)) +#define RETURN(x) { ret = (x); goto cleanup; } + #endif /* __USUALS_H__ */ diff --git a/configure.in b/configure.in index 1ad301d..28a81e5 100644 --- a/configure.in +++ b/configure.in @@ -36,8 +36,8 @@ dnl Nate Nielsen <nielsen@memberwebs.com> dnl dnl Process this file with autoconf to produce a configure script. -AC_INIT(clamsmtp, 0.8.90, nielsen@memberwebs.com) -AM_INIT_AUTOMAKE(clamsmtp, 0.8.90) +AC_INIT(clamsmtp, 0.8.91, nielsen@memberwebs.com) +AM_INIT_AUTOMAKE(clamsmtp, 0.8.91) LDFLAGS="$LDFLAGS -L/usr/local/lib" CFLAGS="$CFLAGS -I/usr/local/include" @@ -88,9 +88,9 @@ AC_CHECK_HEADERS([linux/netfilter_ipv4.h], AC_C_CONST AC_TYPE_SIZE_T +# We use error checking mutexes whenever possible AC_CHECK_DECL(PTHREAD_MUTEX_ERRORCHECK_NP, [AC_DEFINE(HAVE_ERR_MUTEX, 1, "Error Mutex Type")], - [AC_CHECK_DECL(PTHREAD_MUTEX_ERRORCHECK, [AC_DEFINE(HAVE_ERR_MUTEX, 2)], - [echo "ERROR: Missing error checking mutex functionality in pthread.h"], + [AC_CHECK_DECL(PTHREAD_MUTEX_ERRORCHECK, [AC_DEFINE(HAVE_ERR_MUTEX, 2)], , [ #include <pthread.h> ])], [ #include <pthread.h> ]) # Required Functions diff --git a/src/Makefile.am b/src/Makefile.am index 3c8deb6..5c40237 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,8 +1,12 @@ 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 clstate.c +clamsmtpd_SOURCES = clamsmtpd.c clamsmtpd.h \ + ../common/spio.c ../common/smtppass.c ../common/smtppass.h ../common/sppriv.h \ + ../common/stringx.c ../common/stringx.h ../common/sock_any.c ../common/sock_any.h \ + ../common/usuals.h ../common/compat.c ../common/compat.h + +clamsmtpd_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir}/ all-local: @echo "NOTE: Ignore any warnings about mktemp(). It's used safely in this case." diff --git a/src/clamsmtpd.c b/src/clamsmtpd.c index 4c08919..9f35165 100644 --- a/src/clamsmtpd.c +++ b/src/clamsmtpd.c @@ -33,45 +33,51 @@ * * CONTRIBUTORS * Nate Nielsen <nielsen@memberwebs.com> - * Andreas Steinmetz <ast@domdv.de> */ -#include <sys/time.h> #include <sys/types.h> -#include <sys/socket.h> #include <sys/param.h> -#include <sys/stat.h> +#include <paths.h> #include <ctype.h> #include <stdio.h> #include <unistd.h> -#include <fcntl.h> #include <syslog.h> -#include <signal.h> #include <errno.h> #include <err.h> #include "usuals.h" -#ifdef LINUX_TRANSPARENT_PROXY -#include <linux/netfilter_ipv4.h> -#endif - #include "compat.h" #include "sock_any.h" -#include "clamsmtpd.h" -#include "util.h" +#include "stringx.h" + +#define SP_LEGACY_OPTIONS +#include "smtppass.h" /* ----------------------------------------------------------------------- * STRUCTURES */ -typedef struct clamsmtp_thread +typedef struct clstate { - pthread_t tid; /* Written to by the main thread */ - int fd; /* The file descriptor or -1 */ + /* Settings ------------------------------- */ + struct sockaddr_any clamaddr; /* Address for connecting to clamd */ + const char* clamname; + const char* header; /* The header to add to email */ + const char* directory; /* The directory for temp files */ + int bounce; /* Send back a reject line */ + int quarantine; /* Leave virus files in temp dir */ + int debug_files; /* Leave all files in temp dir */ } -clamsmtp_thread_t; +clstate_t; + +typedef struct clctx +{ + spctx_t sp; /* The main sp context */ + spio_t clam; /* Connection to clamd */ +} +clctx_t; /* ----------------------------------------------------------------------- * STRINGS @@ -79,43 +85,8 @@ clamsmtp_thread_t; #define CRLF "\r\n" -#define SMTP_TOOLONG "500 Line too long" CRLF -#define SMTP_STARTBUSY "554 Server Busy" CRLF -#define SMTP_STARTFAILED "554 Local Error" CRLF -#define SMTP_DATAVIRUS "550 Virus Detected; Content Rejected" CRLF -#define SMTP_DATAINTERMED "354 Start mail input; end with <CRLF>.<CRLF>" CRLF -#define SMTP_FAILED "451 Local Error" CRLF -#define SMTP_NOTSUPP "502 Command not implemented" CRLF #define SMTP_DATAVIRUSOK "250 Virus Detected; Discarded Email" CRLF -#define SMTP_OK "250 Ok" CRLF - -#define SMTP_DATA "DATA" CRLF -#define SMTP_BANNER "220 clamsmtp" CRLF -#define SMTP_HELO_RSP "250 clamsmtp" CRLF -#define SMTP_EHLO_RSP "250-clamsmtp" CRLF -#define SMTP_DELIMS "\r\n\t :" -#define SMTP_MULTI_DELIMS " -" - -#define ESMTP_PIPELINE "PIPELINING" -#define ESMTP_TLS "STARTTLS" -#define ESMTP_CHUNK "CHUNKING" -#define ESMTP_BINARY "BINARYMIME" -#define ESMTP_CHECK "CHECKPOINT" - -#define HELO_CMD "HELO" -#define EHLO_CMD "EHLO" -#define FROM_CMD "MAIL FROM" -#define TO_CMD "RCPT TO" -#define DATA_CMD "DATA" -#define RSET_CMD "RSET" -#define STARTTLS_CMD "STARTTLS" -#define BDAT_CMD "BDAT" - -#define DATA_END_SIG "." CRLF - -#define DATA_RSP "354" -#define OK_RSP "250" -#define START_RSP "220" +#define SMTP_DATAVIRUS "550 Virus Detected; Content Rejected" CRLF #define CLAM_OK "OK" #define CLAM_ERROR "ERROR" @@ -129,37 +100,35 @@ clamsmtp_thread_t; #define DEFAULT_CONFIG CONF_PREFIX "/clamsmtpd.conf" +#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 DEFAULT_CLAMAV "/var/run/clamav/clamd" +#define DEFAULT_HEADER "X-Virus-Scanned: ClamAV using ClamSMTP" + /* ----------------------------------------------------------------------- * GLOBALS */ -const clstate_t* g_state = NULL; /* The state and configuration of the daemon */ -unsigned int g_unique_id = 0x00100000; /* For connection ids */ - +clstate_t g_clstate; /* ----------------------------------------------------------------------- * FORWARD DECLARATIONS */ static void usage(); -static void on_quit(int signal); -static void pid_file(const char* pidfile, int write); -static void connection_loop(int sock); -static void* thread_main(void* arg); -static int smtp_passthru(clamsmtp_context_t* ctx); -static int connect_out(clamsmtp_context_t* ctx); -static int connect_clam(clamsmtp_context_t* ctx); -static int disconnect_clam(clamsmtp_context_t* ctx); -static void add_to_logline(char* logline, char* prefix, char* line); -static int avcheck_data(clamsmtp_context_t* ctx, char* logline); -static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname); -static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname); -static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname); -static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename); -static int clam_scan_file(clamsmtp_context_t* ctx, const char* tempname, char* logline); -static int read_server_response(clamsmtp_context_t* ctx); -static void read_junk(clamsmtp_context_t* ctx, int fd); +static int connect_clam(clctx_t* ctx); +static int disconnect_clam(clctx_t* ctx); +static int quarantine_virus(clctx_t* ctx); +static int clam_scan_file(clctx_t* ctx); +/* ----------------------------------------------------------------------- + * SIMPLE MACROS + */ /* ---------------------------------------------------------------------------------- * STARTUP ETC... @@ -169,44 +138,62 @@ int main(int argc, char* argv[]) { const char* configfile = DEFAULT_CONFIG; const char* pidfile = NULL; - clstate_t state; + int dbg_level = -1; int warnargs = 0; - int sock; - int true = 1; int ch = 0; + int r; char* t; - clstate_init(&state); - g_state = &state; + /* 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); + + sp_init("clamsmtpd"); + + /* + * We still accept our old arguments for compatibility reasons. + * We fill them into the spstate structure directly + */ /* Parse the arguments nicely */ while((ch = getopt(argc, argv, "bc:d:D:f:h:l:m:p:qt:v")) != -1) { switch(ch) { - /* Actively reject messages */ + /* COMPAT: Actively reject messages */ case 'b': - state.bounce = 1; + if((r = cb_parse_option(CFG_BOUNCE, "on")) < 0) + usage(); + ASSERT(r == 1); warnargs = 1; break; - /* Change the CLAM socket */ + /* COMPAT: Change the CLAM socket */ case 'c': - state.clamname = optarg; + if((r = cb_parse_option(CFG_CLAMADDR, "on")) < 0) + usage(); + ASSERT(r == 1); warnargs = 1; break; /* Don't daemonize */ case 'd': - state.debug_level = strtol(optarg, &t, 10); + dbg_level = strtol(optarg, &t, 10); if(*t) /* parse error */ errx(1, "invalid debug log level"); - state.debug_level += LOG_ERR; + dbg_level += LOG_ERR; break; - /* The directory for the files */ + /* COMPAT: The directory for the files */ case 'D': - state.directory = optarg; + if((r = sp_parse_option(CFG_DIRECTORY, optarg)) < 0) + usage(); + ASSERT(r == 1); warnargs = 1; break; @@ -215,26 +202,27 @@ int main(int argc, char* argv[]) configfile = optarg; break; - /* The header to add */ + /* COMPAT: The header to add */ case 'h': - if(strlen(optarg) == 0) - state.header = NULL; - else - state.header = optarg; + if((r = cb_parse_option(CFG_HEADER, optarg)) < 0) + usage(); + ASSERT(r == 1); warnargs = 1; break; - /* Change our listening port */ + /* COMPAT: Change our listening port */ case 'l': - state.listenname = optarg; + if((r = sp_parse_option("Listen", optarg)) < 0) + usage(); + ASSERT(r == 1); warnargs = 1; break; - /* The maximum number of threads */ + /* COMPAT: The maximum number of threads */ case 'm': - state.max_threads = strtol(optarg, &t, 10); - if(*t) /* parse error */ - errx(1, "invalid max threads"); + if((r = sp_parse_option("MaxConnections", optarg)) < 0) + usage(); + ASSERT(r == 1); warnargs = 1; break; @@ -243,28 +231,34 @@ int main(int argc, char* argv[]) pidfile = optarg; break; - /* The timeout */ + /* COMPAT: The timeout */ case 't': - state.timeout.tv_sec = strtol(optarg, &t, 10); - if(*t) /* parse error */ - errx(1, "invalid timeout"); + if((r = sp_parse_option("TimeOut", optarg)) < 0) + usage(); + ASSERT(r == 1); warnargs = 1; break; - /* Leave virus files in directory */ + /* COMPAT: Leave virus files in directory */ case 'q': - state.quarantine = 1; + 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); + printf(" (config: %s)\n", DEFAULT_CONFIG); exit(0); break; - /* Leave all files in the tmp directory */ + /* COMPAT: Leave all files in the tmp directory */ case 'X': - state.debug_files = 1; + if((r = cb_parse_option(CFG_DEBUGFILES, "on")) < 0) + usage(); + ASSERT(r == 1); warnargs = 1; break; @@ -283,106 +277,21 @@ int main(int argc, char* argv[]) usage(); if(argc == 1) { - state.outname = argv[0]; + /* 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); - /* Now parse the configuration file */ - if(clstate_parse_config(&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); - } - - clstate_validate(&state); - - messagex(NULL, LOG_DEBUG, "starting up..."); - - /* When set to this we daemonize */ - if(g_state->debug_level == -1) - { - /* Fork a daemon nicely here */ - if(daemon(0, 0) == -1) - { - message(NULL, LOG_ERR, "couldn't run as daemon"); - exit(1); - } - - messagex(NULL, LOG_DEBUG, "running as a daemon"); - state.daemonized = 1; - - /* Open the system log */ - openlog("clamsmtpd", 0, LOG_MAIL); - } - - /* Create the socket */ - sock = socket(SANY_TYPE(g_state->listenaddr), SOCK_STREAM, 0); - if(sock < 0) - { - 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(g_state->listenaddr) == AF_UNIX) - unlink(g_state->listenname); - - 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) - { - message(NULL, LOG_CRIT, "couldn't listen on socket"); - exit(1); - } - - messagex(NULL, LOG_DEBUG, "created socket: %s", g_state->listenname); - - /* Handle some signals */ - signal(SIGPIPE, 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); - - messagex(NULL, LOG_DEBUG, "accepting connections"); - - connection_loop(sock); - - if(pidfile) - pid_file(pidfile, 0); + r = sp_run(configfile, pidfile, dbg_level); - messagex(NULL, LOG_DEBUG, "stopped"); + sp_done(); - /* - * We have to do this at the very end because even printing - * messages requires that g_state is valid. - */ - clstate_cleanup(&state); - return 0; -} - -static void on_quit(int signal) -{ - ((clstate_t*)g_state)->quit = 1; - /* fprintf(stderr, "clamsmtpd: got signal to quit\n"); */ + return r; } static void usage() @@ -392,633 +301,23 @@ static void usage() exit(2); } -static void pid_file(const char* pidfile, int write) -{ - if(write) - { - FILE* f = fopen(pidfile, "w"); - if(f == NULL) - { - message(NULL, LOG_ERR, "couldn't open pid file: %s", pidfile); - } - else - { - fprintf(f, "%d\n", (int)getpid()); - - if(ferror(f)) - message(NULL, LOG_ERR, "couldn't write to pid file: %s", pidfile); - - fclose(f); - } - - messagex(NULL, LOG_DEBUG, "wrote pid file: %s", pidfile); - } - - else - { - unlink(pidfile); - messagex(NULL, LOG_DEBUG, "removed pid file: %s", pidfile); - } -} - - -/* ---------------------------------------------------------------------------------- - * CONNECTION HANDLING - */ - -static void connection_loop(int sock) -{ - clamsmtp_thread_t* threads = NULL; - int fd, i, x, r; - - /* Create the thread buffers */ - threads = (clamsmtp_thread_t*)calloc(g_state->max_threads, sizeof(clamsmtp_thread_t)); - if(!threads) - errx(1, "out of memory"); - - /* Now loop and accept the connections */ - while(!g_state->quit) - { - fd = accept(sock, NULL, NULL); - if(fd == -1) - { - switch(errno) - { - case EINTR: - case EAGAIN: - break; - - case ECONNABORTED: - message(NULL, LOG_ERR, "couldn't accept a connection"); - break; - - default: - message(NULL, LOG_ERR, "couldn't accept a connection"); - ((clstate_t*)g_state)->quit = 1; - break; - }; - - if(g_state->quit) - break; - - continue; - } - - /* Set timeouts on client */ - 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_state->max_threads; i++) - { - /* Find a thread to run or clean up old threads */ - if(threads[i].tid != 0) - { - plock(); - x = threads[i].fd; - punlock(); - - if(x == -1) - { - messagex(NULL, LOG_DEBUG, "cleaning up completed thread"); - pthread_join(threads[i].tid, NULL); - threads[i].tid = 0; - } -#ifdef _DEBUG - else - { - /* For debugging connection problems: */ - messagex(NULL, LOG_DEBUG, "active connection thread: %x", (int)threads[i].tid); - } -#endif - } - - /* Start a new thread if neccessary */ - if(fd != -1 && threads[i].tid == 0) - { - threads[i].fd = fd; - r = pthread_create(&(threads[i].tid), NULL, thread_main, - (void*)(threads + i)); - if(r != 0) - { - threads[i].fd = -1; - threads[i].tid = 0; - - errno = r; - message(NULL, LOG_ERR, "couldn't create thread for connection"); - write(fd, SMTP_STARTFAILED, KL(SMTP_STARTFAILED)); - - shutdown(fd, SHUT_RDWR); - close(fd); - fd = -1; - - break; - } - - messagex(NULL, LOG_DEBUG, "created thread for connection"); - fd = -1; - break; - } - } - - /* 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_state->max_threads); - - write(fd, SMTP_STARTBUSY, KL(SMTP_STARTBUSY)); - shutdown(fd, SHUT_RDWR); - close(fd); - fd = -1; - } - } - - messagex(NULL, LOG_DEBUG, "waiting for threads to quit"); - - /* Quit all threads here */ - for(i = 0; i < g_state->max_threads; i++) - { - /* Clean up quit threads */ - if(threads[i].tid != 0) - { - if(threads[i].fd != -1) - { - plock(); - fd = threads[i].fd; - threads[i].fd = -1; - punlock(); - - shutdown(fd, SHUT_RDWR); - close(fd); - } - - pthread_join(threads[i].tid, NULL); - } - } -} - -static void* thread_main(void* arg) -{ - clamsmtp_thread_t* thread = (clamsmtp_thread_t*)arg; - clamsmtp_context_t* ctx = NULL; - int processing = 0; - int ret = 0; - int fd; - - ASSERT(thread); - - siginterrupt(SIGINT, 1); - siginterrupt(SIGTERM, 1); - - plock(); - /* Get the client socket */ - fd = thread->fd; - punlock(); - - ctx = (clamsmtp_context_t*)calloc(1, sizeof(clamsmtp_context_t)); - if(!ctx) - { - /* Special case. We don't have a context so clean up descriptor */ - close(fd); - - messagex(NULL, LOG_CRIT, "out of memory"); - RETURN(-1); - } - - memset(ctx, 0, sizeof(*ctx)); - - clio_init(&(ctx->server), "SERVER"); - clio_init(&(ctx->client), "CLIENT"); - clio_init(&(ctx->clam), "CLAM "); - - plock(); - /* Assign a unique id to the connection */ - ctx->id = g_unique_id++; - - /* We don't care about wraps, but we don't want zero */ - if(g_unique_id == 0) - g_unique_id++; - punlock(); - - ctx->client.fd = fd; - ASSERT(ctx->client.fd != -1); - messagex(ctx, LOG_DEBUG, "processing %d on thread %x", ctx->client.fd, (int)pthread_self()); - - /* Connect to the outgoing server ... */ - if(connect_out(ctx) == -1) - RETURN(-1); - - /* ... and to the AV daemon */ - if(connect_clam(ctx) == -1) - RETURN(-1); - - /* call the processor */ - processing = 1; - ret = smtp_passthru(ctx); - -cleanup: - - if(ctx) - { - disconnect_clam(ctx); - - /* Let the client know about fatal errors */ - if(!processing && ret == -1 && clio_valid(&(ctx->client))) - clio_write_data(ctx, &(ctx->client), SMTP_STARTFAILED); - - clio_disconnect(ctx, &(ctx->client)); - clio_disconnect(ctx, &(ctx->server)); - } - - /* mark this as done */ - plock(); - thread->fd = -1; - punlock(); - - return (void*)(ret == 0 ? 0 : 1); -} - -static int connect_out(clamsmtp_context_t* ctx) -{ - struct sockaddr_any peeraddr; - struct sockaddr_any addr; - const struct sockaddr_any* outaddr; - char buf[MAXPATHLEN]; - const char* outname; - - memset(&peeraddr, 0, sizeof(peeraddr)); - SANY_LEN(peeraddr) = sizeof(peeraddr); - - /* Get the peer name */ - if(getpeername(ctx->client.fd, &SANY_ADDR(peeraddr), &SANY_LEN(peeraddr)) == -1 || - sock_any_ntop(&peeraddr, buf, MAXPATHLEN, SANY_OPT_NOPORT) == -1) - message(ctx, LOG_WARNING, "couldn't get peer address"); - else - messagex(ctx, LOG_INFO, "accepted connection from: %s", buf); - - /* Create the server connection address */ - outaddr = &(g_state->outaddr); - outname = g_state->outname; - - /* For transparent proxying we have to discover the address to connect to */ - if(g_state->transparent) - { - memset(&addr, 0, sizeof(addr)); - SANY_LEN(addr) = sizeof(addr); - -#ifdef LINUX_TRANSPARENT_PROXY - if(getsockopt(ctx->client.fd, SOL_IP, SO_ORIGINAL_DST, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1) -#else - if(getsockname(ctx->client.fd, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1) -#endif - { - message(ctx, LOG_ERR, "couldn't get source address for transparent proxying"); - return -1; - } - - /* Check address types */ - if(sock_any_cmp(&addr, &peeraddr, SANY_OPT_NOPORT) == 0) - { - messagex(ctx, LOG_ERR, "loop detected in transparent proxying"); - return -1; - } - - outaddr = &addr; - } - - /* No transparent proxy but check for loopback option */ - else - { - if(SANY_TYPE(*outaddr) == AF_INET && - outaddr->s.in.sin_addr.s_addr == 0) - { - /* Use the incoming IP as the default */ - memcpy(&addr, &(g_state->outaddr), sizeof(addr)); - memcpy(&(addr.s.in.sin_addr), &(peeraddr.s.in.sin_addr), sizeof(addr.s.in.sin_addr)); - outaddr = &addr; - } -#ifdef HAVE_INET6 - else if(SANY_TYPE(*outaddr) == AF_INET6 && - outaddr->s.in.in6.sin_addr.s_addr == 0) - { - /* Use the incoming IP as the default */ - memcpy(&addr, &(g_state->outaddr), sizeof(addr)); - memcpy(&(addr.s.in.sin6_addr), &(peeraddr.s.in.sin6_addr), sizeof(addr.s.in.sin6_addr)); - outaddr = &addr; - } -#endif - } - - /* Reparse name if possible */ - if(outaddr != &(g_state->outaddr)) - { - if(sock_any_ntop(outaddr, buf, MAXPATHLEN, 0) != -1) - outname = buf; - else - outname = "unknown"; - } - - /* Connect to the server */ - if(clio_connect(ctx, &(ctx->server), outaddr, outname) == -1) - return -1; - - return 0; -} - /* ---------------------------------------------------------------------------------- - * SMTP HANDLING + * SP CALLBACKS */ -static int smtp_passthru(clamsmtp_context_t* ctx) +int cb_check_data(spctx_t* sp) { - clio_t* io = NULL; - char logline[LINE_LENGTH]; - int r, ret = 0; - int neterror = 0; + int r = 0; + clctx_t* ctx = (clctx_t*)sp; - int first_rsp = 1; /* The first 220 response from server to be filtered */ - int filter_ehlo = 0; /* Filtering parts of an EHLO extensions response */ - int filter_host = 0; /* Next response is 250 hostname, which we change */ + /* Connect to clamav */ + if(!spio_valid(&(ctx->clam))) + r = connect_clam(ctx); - ASSERT(clio_valid(&(ctx->clam)) && - clio_valid(&(ctx->clam))); - logline[0] = 0; + if(r != -1 && (r = sp_cache_data(sp)) > 0) - for(;;) - { - if(clio_select(ctx, &io) == -1) - { - neterror = 1; - RETURN(-1); - } - - /* Client has data available, read a line and process */ - if(io == &(ctx->client)) - { - if(clio_read_line(ctx, &(ctx->client), CLIO_DISCARD) == -1) - RETURN(-1); - - /* Client disconnected, we're done */ - if(ctx->linelen == 0) - RETURN(0); - - /* We don't let clients send really long lines */ - if(LINE_TOO_LONG(ctx)) - { - if(clio_write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1) - RETURN(-1); - - continue; - } - - /* Only valid after EHLO or HELO commands */ - filter_ehlo = 0; - filter_host = 0; - - /* Handle the DATA section via our AV checker */ - if(is_first_word(ctx->line, DATA_CMD, KL(DATA_CMD))) - { - /* Send back the intermediate response to the client */ - if(clio_write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1) - RETURN(-1); - - /* - * Now go into avcheck mode. This also handles the eventual - * sending of the data to the server, making the av check - * transparent - */ - if(avcheck_data(ctx, logline) == -1) - RETURN(-1); - - /* Print the log out for this email */ - messagex(ctx, LOG_INFO, "%s", logline); - - /* Reset log line */ - logline[0] = 0; - - /* Command handled */ - continue; - } - - /* - * We filter out features that we can't support in - * the EHLO response (ESMTP). See below - */ - else if(is_first_word(ctx->line, EHLO_CMD, KL(EHLO_CMD))) - { - messagex(ctx, LOG_DEBUG, "filtering EHLO response"); - filter_ehlo = 1; - filter_host = 1; - - /* A new message */ - logline[0] = 0; - } - - /* - * We need our response to HELO to be modified in order - * to prevent complaints about mail loops - */ - else if(is_first_word(ctx->line, HELO_CMD, KL(HELO_CMD))) - { - filter_host = 1; - - /* A new message line */ - logline[0] = 0; - } - - /* - * We don't like these commands. Filter them out. We should have - * filtered out their service extensions earlier in the EHLO response. - * This is just for errant clients. - */ - else if(is_first_word(ctx->line, STARTTLS_CMD, KL(STARTTLS_CMD)) || - is_first_word(ctx->line, BDAT_CMD, KL(BDAT_CMD))) - { - messagex(ctx, LOG_DEBUG, "ESMTP feature not supported"); - - if(clio_write_data(ctx, &(ctx->client), SMTP_NOTSUPP) == -1) - RETURN(-1); - - /* Command handled */ - continue; - } - - /* Append recipients to log line */ - else if((r = check_first_word(ctx->line, FROM_CMD, KL(FROM_CMD), SMTP_DELIMS)) > 0) - add_to_logline(logline, "from=", ctx->line + r); - - /* Append sender to log line */ - else if((r = check_first_word(ctx->line, TO_CMD, KL(TO_CMD), SMTP_DELIMS)) > 0) - add_to_logline(logline, "to=", ctx->line + r); - - /* Reset log line */ - else if(is_first_word(ctx->line, RSET_CMD, KL(RSET_CMD))) - logline[0] = 0; - - /* All other commands just get passed through to server */ - if(clio_write_data(ctx, &(ctx->server), ctx->line) == -1) - RETURN(-1); - - continue; - } - - /* Server has data available, read a line and forward */ - if(io == &(ctx->server)) - { - if(clio_read_line(ctx, &(ctx->server), CLIO_DISCARD) == -1) - RETURN(-1); - - if(ctx->linelen == 0) - RETURN(0); - - if(LINE_TOO_LONG(ctx)) - messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra"); - - /* - * We intercept the first response we get from the server. - * This allows us to change header so that it doesn't look - * to the client server that we're in a wierd loop. - * - * In different situations using the local hostname or - * 'localhost' don't work because the receiving mail server - * expects one of those to be its own name. We use 'clamsmtp' - * instead. No properly configured server would have this - * as their domain name, and RFC 2821 allows us to use - * an arbitrary but identifying string. - */ - if(first_rsp) - { - first_rsp = 0; - - if(is_first_word(ctx->line, START_RSP, KL(START_RSP))) - { - messagex(ctx, LOG_DEBUG, "intercepting initial response"); - - if(clio_write_data(ctx, &(ctx->client), SMTP_BANNER) == -1) - RETURN(-1); - - /* Command handled */ - continue; - } - } - - /* - * Certain mail servers (Postfix 1.x in particular) do a loop check - * on the 250 response after a EHLO or HELO. This is where we - * filter that to prevent loopback errors. - */ - if(filter_host) - { - filter_host = 0; - - /* Check for a simple '250 xxxx' */ - if(is_first_word(ctx->line, OK_RSP, KL(OK_RSP))) - { - messagex(ctx, LOG_DEBUG, "intercepting host response"); - - if(clio_write_data(ctx, &(ctx->client), SMTP_HELO_RSP) == -1) - RETURN(-1); - - continue; - } - - /* Check for the continued response '250-xxxx' */ - if(check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS) > 0) - { - messagex(ctx, LOG_DEBUG, "intercepting host response"); - - if(clio_write_data(ctx, &(ctx->client), SMTP_EHLO_RSP) == -1) - RETURN(-1); - - continue; - } - } - - /* - * Filter out any EHLO responses that we can't or don't want - * to support. For example pipelining or TLS. - */ - if(filter_ehlo) - { - if((r = check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS)) > 0) - { - char* p = ctx->line + r; - if(is_first_word(p, ESMTP_PIPELINE, KL(ESMTP_PIPELINE)) || - is_first_word(p, ESMTP_TLS, KL(ESMTP_TLS)) || - is_first_word(p, ESMTP_CHUNK, KL(ESMTP_CHUNK)) || - is_first_word(p, ESMTP_BINARY, KL(ESMTP_BINARY)) || - is_first_word(p, ESMTP_CHECK, KL(ESMTP_CHECK))) - { - messagex(ctx, LOG_DEBUG, "filtered ESMTP feature: %s", trim_space(p)); - continue; - } - } - } - - if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) - RETURN(-1); - - continue; - } - } - -cleanup: - - if(!neterror && ret == -1 && clio_valid(&(ctx->client))) - clio_write_data(ctx, &(ctx->client), SMTP_FAILED); - - return ret; -} - -static void add_to_logline(char* logline, char* prefix, char* line) -{ - int l = strlen(logline); - char* t = logline; - - /* Simple optimization */ - logline += l; - l = LINE_LENGTH - l; - - ASSERT(l >= 0); - - if(t[0] != 0) - strlcat(logline, ", ", l); - - strlcat(logline, prefix, l); - - /* Skip initial white space */ - line = trim_start(line); - - strlcat(logline, line, l); - - /* Skip later white space */ - trim_end(logline); -} - -static int avcheck_data(clamsmtp_context_t* ctx, char* logline) -{ - /* - * Note that most failures are non fatal in this function. - * We only return -1 for data connection errors and the like, - * For most others we actually send a response back to the - * client letting them know what happened and let the SMTP - * connection continue. - */ - - char buf[MAXPATHLEN]; - int havefile = 0; - int r, ret = 0; - - strlcpy(buf, g_state->directory, MAXPATHLEN); - strlcat(buf, "/clamsmtpd.XXXXXX", MAXPATHLEN); - - /* transfer_to_file deletes the temp file on failure */ - if((r = transfer_to_file(ctx, buf)) > 0) - { - havefile = 1; - r = clam_scan_file(ctx, buf, logline); - } + /* ClamAV doesn't like empty files */ + r = clam_scan_file(ctx); switch(r) { @@ -1028,8 +327,8 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) * the server about any of this yet */ case -1: - if(clio_write_data(ctx, &(ctx->client), SMTP_FAILED)) - RETURN(-1); + if(sp_fail_data(sp, NULL) == -1) + return -1; break; /* @@ -1037,8 +336,8 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) * and transfer the file to it. */ case 0: - if(complete_data_transfer(ctx, buf) == -1) - RETURN(-1); + if(sp_done_data(sp, g_clstate.header) == -1) + return -1; break; /* @@ -1048,12 +347,12 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) * choose to reset the connection to reuse it if it wants. */ case 1: - if(clio_write_data(ctx, &(ctx->client), - g_state->bounce ? SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) - RETURN(-1); - /* Any special post operation actions on the virus */ - quarantine_virus(ctx, buf); + quarantine_virus(ctx); + + if(sp_fail_data(sp, g_clstate.bounce ? + SMTP_DATAVIRUS : SMTP_DATAVIRUSOK) == -1) + return -1; break; default: @@ -1061,109 +360,114 @@ static int avcheck_data(clamsmtp_context_t* ctx, char* logline) break; }; -cleanup: - if(havefile && !g_state->debug_files) - { - messagex(ctx, LOG_DEBUG, "deleting temporary file: %s", buf); - unlink(buf); - } - - return ret; + return 0; } -static int complete_data_transfer(clamsmtp_context_t* ctx, const char* tempname) +int cb_parse_option(const char* name, const char* value) { - ASSERT(ctx); - ASSERT(tempname); - - /* Ask the server for permission to send data */ - if(clio_write_data(ctx, &(ctx->server), SMTP_DATA) == -1) - return -1; - - if(read_server_response(ctx) == -1) - return -1; + 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; + } - /* If server returns an error then tell the client */ - if(!is_first_word(ctx->line, DATA_RSP, KL(DATA_RSP))) + else if(strcasecmp(CFG_HEADER, name) == 0) { - if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) - return -1; + g_clstate.header = (const char*)trim_space((char*)value); - messagex(ctx, LOG_DEBUG, "server refused data transfer"); + if(strlen(g_clstate.header) == 0) + g_clstate.header = NULL; - return 0; + return 1; } - /* Now pull up the file and send it to the server */ - if(transfer_from_file(ctx, tempname) == -1) + else if(strcasecmp(CFG_DIRECTORY, name) == 0) { - /* Tell the client it went wrong */ - clio_write_data(ctx, &(ctx->client), SMTP_FAILED); - return -1; + g_clstate.directory = value; + return 1; } - /* Okay read the response from the server and echo it to the client */ - if(read_server_response(ctx) == -1) - return -1; + else if(strcasecmp(CFG_BOUNCE, name) == 0) + { + if((g_clstate.bounce = strtob(value)) == -1) + errx(2, "invalid value for " CFG_BOUNCE); + return 1; + } - if(clio_write_data(ctx, &(ctx->client), ctx->line) == -1) - return -1; + else if(strcasecmp(CFG_QUARANTINE, 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); + return 1; + } return 0; } -static int read_server_response(clamsmtp_context_t* ctx) +spctx_t* cb_new_context() { - /* Read response line from the server */ - if(clio_read_line(ctx, &(ctx->server), CLIO_DISCARD) == -1) - return -1; - - if(ctx->linelen == 0) + clctx_t* ctx = (clctx_t*)calloc(1, sizeof(clctx_t)); + if(!ctx) { - messagex(ctx, LOG_ERR, "server disconnected unexpectedly"); - - /* Tell the client it went wrong */ - clio_write_data(ctx, &(ctx->client), SMTP_FAILED); - return 0; + sp_messagex(NULL, LOG_CRIT, "out of memory"); + return NULL; } - if(LINE_TOO_LONG(ctx)) - messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra"); - - return 0; + /* Initial preparation of the structure */ + spio_init(&(ctx->clam), "CLAMAV"); + return &(ctx->sp); } +void cb_del_context(spctx_t* sp) +{ + clctx_t* ctx = (clctx_t*)sp; + ASSERT(sp); + + disconnect_clam(ctx); + free(ctx); +} /* ---------------------------------------------------------------------------------- * CLAM AV */ -static int connect_clam(clamsmtp_context_t* ctx) +static int connect_clam(clctx_t* ctx) { int ret = 0; + spctx_t* sp = &(ctx->sp); ASSERT(ctx); - ASSERT(!clio_valid(&(ctx->clam))); + ASSERT(!spio_valid(&(ctx->clam))); - if(clio_connect(ctx, &(ctx->clam), &(g_state->clamaddr), g_state->clamname) == -1) + if(spio_connect(sp, &(ctx->clam), &(g_clstate.clamaddr), g_clstate.clamname) == -1) RETURN(-1); - read_junk(ctx, ctx->clam.fd); + spio_read_junk(sp, &(ctx->clam)); /* Send a session and a check header to ClamAV */ - if(clio_write_data(ctx, &(ctx->clam), "SESSION\n") == -1) + if(spio_write_data(sp, &(ctx->clam), "SESSION\n") == -1) RETURN(-1); - read_junk(ctx, ctx->clam.fd); + spio_read_junk(sp, &(ctx->clam)); + /* - if(clio_write_data(ctx, &(ctx->clam), "PING\n") == -1 || - clio_read_line(ctx, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM) == -1) + if(spio_write_data(sp, &(ctx->clam), "PING\n") == -1 || + spio_read_line(sp, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM) == -1) RETURN(-1); - if(strcmp(ctx->line, CONNECT_RESPONSE) != 0) + if(strcmp(sp->line, CONNECT_RESPONSE) != 0) { - message(ctx, LOG_ERR, "clamd sent an unexpected response: %s", ctx->line); + sp_message(sp, LOG_ERR, "clamd sent an unexpected response: %s", ctx->line); RETURN(-1); } */ @@ -1171,89 +475,93 @@ static int connect_clam(clamsmtp_context_t* ctx) cleanup: if(ret < 0) - clio_disconnect(ctx, &(ctx->clam)); + spio_disconnect(sp, &(ctx->clam)); return ret; } -static int disconnect_clam(clamsmtp_context_t* ctx) +static int disconnect_clam(clctx_t* ctx) { - if(!clio_valid(&(ctx->clam))) + spctx_t* sp = &(ctx->sp); + + if(!spio_valid(&(ctx->clam))) return 0; - if(clio_write_data(ctx, &(ctx->clam), CLAM_DISCONNECT) != -1) - read_junk(ctx, ctx->clam.fd); + if(spio_write_data(sp, &(ctx->clam), CLAM_DISCONNECT) != -1) + spio_read_junk(sp, &(ctx->clam)); - clio_disconnect(ctx, &(ctx->clam)); + spio_disconnect(sp, &(ctx->clam)); return 0; } -static int clam_scan_file(clamsmtp_context_t* ctx, const char* tempname, char* logline) +static int clam_scan_file(clctx_t* ctx) { int len; + spctx_t* sp = &(ctx->sp); - ASSERT(LINE_LENGTH > MAXPATHLEN + 32); + /* Needs to be long enough to hold path names */ + ASSERT(SP_LINE_LENGTH > MAXPATHLEN + 32); - strcpy(ctx->line, CLAM_SCAN); - strcat(ctx->line, tempname); - strcat(ctx->line, "\n"); + strcpy(sp->line, CLAM_SCAN); + strcat(sp->line, sp->cachename); + strcat(sp->line, "\n"); - if(clio_write_data(ctx, &(ctx->clam), ctx->line) == -1) + if(spio_write_data(sp, &(ctx->clam), sp->line) == -1) return -1; - len = clio_read_line(ctx, &(ctx->clam), CLIO_DISCARD | CLIO_TRIM); + len = spio_read_line(sp, &(ctx->clam), SPIO_DISCARD | SPIO_TRIM); if(len == 0) { - messagex(ctx, LOG_ERR, "clamd disconnected unexpectedly"); + sp_messagex(sp, LOG_ERR, "clamd disconnected unexpectedly"); return -1; } - if(is_last_word(ctx->line, CLAM_OK, KL(CLAM_OK))) + if(is_last_word(sp->line, CLAM_OK, KL(CLAM_OK))) { - add_to_logline(logline, "status=", "CLEAN"); - messagex(ctx, LOG_DEBUG, "no virus"); + sp_add_log(sp, "status=", "CLEAN"); + sp_messagex(sp, LOG_DEBUG, "no virus"); return 0; } - if(is_last_word(ctx->line, CLAM_FOUND, KL(CLAM_FOUND))) + if(is_last_word(sp->line, CLAM_FOUND, KL(CLAM_FOUND))) { - len = strlen(tempname); + len = strlen(sp->cachename); - if(ctx->linelen > len) - add_to_logline(logline, "status=VIRUS:", ctx->line + len + 1); + if(sp->linelen > len) + sp_add_log(sp, "status=VIRUS:", sp->line + len + 1); else - add_to_logline(logline, "status=", "VIRUS"); + sp_add_log(sp, "status=", "VIRUS"); - messagex(ctx, LOG_DEBUG, "found virus"); + sp_messagex(sp, LOG_DEBUG, "found virus"); return 1; } - if(is_last_word(ctx->line, CLAM_ERROR, KL(CLAM_ERROR))) + if(is_last_word(sp->line, CLAM_ERROR, KL(CLAM_ERROR))) { - messagex(ctx, LOG_ERR, "clamav error: %s", ctx->line); - add_to_logline(logline, "status=", "CLAMAV-ERROR"); + sp_messagex(sp, LOG_ERR, "clamav error: %s", sp->line); + sp_add_log(sp, "status=", "CLAMAV-ERROR"); return -1; } - add_to_logline(logline, "status=", "CLAMAV-ERROR"); - messagex(ctx, LOG_ERR, "unexepected response from clamd: %s", ctx->line); + sp_add_log(sp, "status=", "CLAMAV-ERROR"); + sp_messagex(sp, LOG_ERR, "unexepected response from clamd: %s", sp->line); return -1; } - /* ---------------------------------------------------------------------------------- * TEMP FILE HANDLING */ -static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname) +static int quarantine_virus(clctx_t* ctx) { char buf[MAXPATHLEN]; + spctx_t* sp = &(ctx->sp); char* t; - if(!g_state->quarantine) + if(!g_clstate.quarantine) return 0; - strlcpy(buf, g_state->directory, MAXPATHLEN); + strlcpy(buf, g_clstate.directory, MAXPATHLEN); strlcat(buf, "/virus.", MAXPATHLEN); /* Points to null terminator */ @@ -1272,191 +580,29 @@ static int quarantine_virus(clamsmtp_context_t* ctx, char* tempname) if(!mktemp(buf)) { - message(ctx, LOG_ERR, "couldn't create quarantine file name"); + sp_message(sp, LOG_ERR, "couldn't create quarantine file name"); return -1; } /* Try to link the file over to the temp */ - if(link(tempname, buf) == -1) + if(link(sp->cachename, buf) == -1) { /* We don't want to allow race conditions */ if(errno == EEXIST) { - message(ctx, LOG_WARNING, "race condition when quarantining virus file: %s", buf); + sp_message(sp, LOG_WARNING, "race condition when quarantining virus file: %s", buf); continue; } - message(ctx, LOG_ERR, "couldn't quarantine virus file"); + sp_message(sp, LOG_ERR, "couldn't quarantine virus file"); return -1; } break; } - messagex(ctx, LOG_INFO, "quarantined virus file as: %s", buf); + sp_messagex(sp, LOG_INFO, "quarantined virus file as: %s", buf); return 0; } -static int transfer_to_file(clamsmtp_context_t* ctx, char* tempname) -{ - FILE* tfile = NULL; - int tfd = -1; - int ended_crlf = 1; /* If the last line ended with a CRLF */ - int ret = 0; - int count = 0; - - if((tfd = mkstemp(tempname)) == -1 || - (tfile = fdopen(tfd, "w")) == NULL) - { - message(ctx, LOG_ERR, "couldn't open temp file"); - RETURN(-1); - } - - messagex(ctx, LOG_DEBUG, "created temporary file: %s", tempname); - - for(;;) - { - switch(clio_read_line(ctx, &(ctx->client), CLIO_QUIET)) - { - case 0: - messagex(ctx, LOG_ERR, "unexpected end of data from client"); - RETURN(-1); - - case -1: - /* Message already printed */ - RETURN(-1); - }; - - if(ended_crlf && strcmp(ctx->line, DATA_END_SIG) == 0) - break; - - /* We check errors on this later */ - fwrite(ctx->line, 1, ctx->linelen, tfile); - count += ctx->linelen; - - /* Check if this line ended with a CRLF */ - ended_crlf = (strcmp(CRLF, ctx->line + (ctx->linelen - KL(CRLF))) == 0); - } - - if(ferror(tfile)) - { - message(ctx, LOG_ERR, "error writing to temp file: %s", tempname); - RETURN(-1); - } - ret = count; - messagex(ctx, LOG_DEBUG, "wrote %d bytes to temp file", count); - -cleanup: - - if(tfile) - fclose(tfile); - - if(tfd != -1) - { - /* Only close this if not opened as a stream */ - if(tfile == NULL) - close(tfd); - - if(ret <= 0) - { - messagex(ctx, LOG_DEBUG, "discarding temporary file"); - unlink(tempname); - } - } - - return ret; -} - -static int transfer_from_file(clamsmtp_context_t* ctx, const char* filename) -{ - FILE* file = NULL; - int header = 0; - int ret = 0; - - file = fopen(filename, "r"); - if(file == NULL) - { - message(ctx, LOG_ERR, "couldn't open temporary file: %s", filename); - RETURN(-1); - } - - messagex(ctx, LOG_DEBUG, "sending from temporary file: %s", filename); - - while(fgets(ctx->line, LINE_LENGTH, file) != NULL) - { - if(g_state->header && !header) - { - /* - * The first blank line we see means the headers are done. - * At this point we add in our virus checked header. - */ - if(is_blank_line(ctx->line)) - { - 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); - - header = 1; - } - } - - if(clio_write_data_raw(ctx, &(ctx->server), ctx->line, strlen(ctx->line)) == -1) - RETURN(-1); - } - - if(ferror(file)) - { - message(ctx, LOG_ERR, "error reading temporary file: %s", filename); - RETURN(-1); - } - - if(clio_write_data(ctx, &(ctx->server), DATA_END_SIG) == -1) - RETURN(-1); - - messagex(ctx, LOG_DEBUG, "sent email data"); - -cleanup: - - if(file != NULL) - fclose(file); - - return ret; -} - - -/* ---------------------------------------------------------------------------------- - * NETWORKING - */ - -static void read_junk(clamsmtp_context_t* ctx, int fd) -{ - char buf[16]; - const char* t; - int said = 0; - int l; - - if(fd == -1) - return; - - /* Make it non blocking */ - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); - - for(;;) - { - l = read(fd, buf, sizeof(buf) - 1); - if(l <= 0) - break; - - buf[l] = 0; - t = trim_start(buf); - - if(!said && *t) - { - messagex(ctx, LOG_DEBUG, "received junk data from daemon"); - said = 1; - } - } - - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK); -} diff --git a/src/clamsmtpd.h b/src/clamsmtpd.h index 10b4a1f..9224f32 100644 --- a/src/clamsmtpd.h +++ b/src/clamsmtpd.h @@ -39,107 +39,5 @@ #ifndef __CLAMSMTPD_H__ #define __CLAMSMTPD_H__ -#include <sock_any.h> - -/* IO Buffers see clio.c ---------------------------------------------------- */ - -#define BUF_LEN 256 - -typedef struct clio -{ - int fd; - const char* name; - unsigned char buf[BUF_LEN]; - size_t buflen; -} -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 - * name to clamd. - */ - -#if 2000 > MAXPATHLEN - #define LINE_LENGTH 2000 -#else - #define LINE_LENGTH (MAXPATHLEN + 128) -#endif - -typedef struct clamsmtp_context -{ - unsigned int id; /* Identifier for the connection */ - - clio_t client; /* Connection to client */ - clio_t server; /* Connection to server */ - clio_t clam; /* Connection to clamd */ - - char line[LINE_LENGTH]; /* Working buffer */ - int linelen; /* Length of valid data in above */ -} -clamsmtp_context_t; - -#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 -#define clio_valid(io) ((io)->fd != -1) - -void clio_init(clio_t* io, const char* name); -int clio_connect(clamsmtp_context_t* ctx, clio_t* io, const struct sockaddr_any* sany, const char* addrname); -void clio_disconnect(clamsmtp_context_t* ctx, clio_t* io); -int clio_select(clamsmtp_context_t* ctx, clio_t** io); -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 */ - int bounce; /* Send back a reject line */ - int quarantine; /* Leave virus files in temp dir */ - int debug_files; /* Leave all files in temp dir */ - int transparent; /* Transparent proxying */ - - /* 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 const 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/src/clio.c b/src/clio.c deleted file mode 100644 index 36b495e..0000000 --- a/src/clio.c +++ /dev/null @@ -1,407 +0,0 @@ -/* - * 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> - */ - -/* - * select() and stdio are basically mutually exclusive. - * Hence all of this code to try to get some buffering - * along with select IO multiplexing. - */ - -#include <sys/time.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/param.h> -#include <sys/stat.h> - -#include <ctype.h> -#include <stdio.h> -#include <unistd.h> -#include <fcntl.h> -#include <syslog.h> -#include <errno.h> - -#include "usuals.h" -#include "sock_any.h" -#include "clamsmtpd.h" -#include "util.h" - -#define MAX_LOG_LINE 79 -#define GET_IO_NAME(io) ((io)->name ? (io)->name : "??? ") - -static void close_raw(int* fd) -{ - ASSERT(fd); - shutdown(*fd, SHUT_RDWR); - close(*fd); - *fd = -1; -} - -static void log_io_data(clamsmtp_context_t* ctx, clio_t* io, const char* data, int read) -{ - char buf[MAX_LOG_LINE + 1]; - int pos, len; - - ASSERT(ctx && io && data); - - for(;;) - { - data += strspn(data, "\r\n"); - - if(!*data) - break; - - pos = strcspn(data, "\r\n"); - - len = pos < MAX_LOG_LINE ? pos : MAX_LOG_LINE; - memcpy(buf, data, len); - buf[len] = 0; - - messagex(ctx, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io), - read ? " < " : " > ", buf); - - data += pos; - } -} - -void clio_init(clio_t* io, const char* name) -{ - ASSERT(io && name); - memset(io, 0, sizeof(*io)); - io->name = name; - io->fd = -1; -} - -int clio_connect(clamsmtp_context_t* ctx, clio_t* io, const struct sockaddr_any* sany, - const char* addrname) -{ - int ret = 0; - - ASSERT(ctx && io && sany && addrname); - ASSERT(io->fd == -1); - - if((io->fd = socket(SANY_TYPE(*sany), SOCK_STREAM, 0)) == -1) - RETURN(-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) - RETURN(-1); - -cleanup: - if(ret < 0) - { - if(io->fd != -1) - close(io->fd); - - message(ctx, LOG_ERR, "couldn't connect to: %s", addrname); - return -1; - } - - ASSERT(io->fd != -1); - messagex(ctx, LOG_DEBUG, "%s connected to: %s", GET_IO_NAME(io), addrname); - return 0; -} - -void clio_disconnect(clamsmtp_context_t* ctx, clio_t* io) -{ - ASSERT(ctx && io); - - if(clio_valid(io)) - { - close_raw(&(io->fd)); - messagex(ctx, LOG_DEBUG, "%s connection closed", GET_IO_NAME(io)); - } -} - -int clio_select(clamsmtp_context_t* ctx, clio_t** io) -{ - fd_set mask; - - ASSERT(ctx && io); - FD_ZERO(&mask); - *io = NULL; - - /* First check if buffers have any data */ - - if(clio_valid(&(ctx->server))) - { - if(ctx->server.buflen > 0) - { - *io = &(ctx->server); - return 0; - } - - FD_SET(ctx->server.fd, &mask); - } - - if(clio_valid(&(ctx->client))) - { - if(ctx->client.buflen > 0) - { - *io = &(ctx->client); - return 0; - } - - FD_SET(ctx->client.fd, &mask); - } - - /* Select on the above */ - - switch(select(FD_SETSIZE, &mask, NULL, NULL, (struct timeval*)&(g_state->timeout))) - { - case 0: - messagex(ctx, LOG_ERR, "network operation timed out"); - return -1; - case -1: - message(ctx, LOG_ERR, "couldn't select on sockets"); - return -1; - }; - - /* See what came in */ - - if(FD_ISSET(ctx->server.fd, &mask)) - { - *io = &(ctx->server); - return 0; - } - - if(FD_ISSET(ctx->client.fd, &mask)) - { - *io = &(ctx->client); - return 0; - } - - ASSERT(0 && "invalid result from select"); - return -1; -} - -int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts) -{ - int l, x; - char* t; - unsigned char* p; - - ASSERT(ctx && io); - - if(!clio_valid(io)) - { - messagex(ctx, LOG_WARNING, "tried to read from a closed connection"); - return 0; - } - - ctx->line[0] = 0; - t = ctx->line; - l = LINE_LENGTH - 1; - - for(;;) - { - /* refil buffer if necessary */ - if(io->buflen == 0) - { - ASSERT(io->fd != -1); - io->buflen = read(io->fd, io->buf, sizeof(char) * BUF_LEN); - - if(io->buflen == -1) - { - io->buflen = 0; - - if(errno == EINTR) - { - /* When the application is quiting */ - if(g_state->quit) - return -1; - - /* For any other signal we go again */ - continue; - } - - if(errno == ECONNRESET) /* Not usually a big deal so supresse the error */ - messagex(ctx, LOG_DEBUG, "connection disconnected by peer: %s", GET_IO_NAME(io)); - else if(errno == EAGAIN) - messagex(ctx, LOG_WARNING, "network read operation timed out: %s", GET_IO_NAME(io)); - else - message(ctx, LOG_ERR, "couldn't read data from socket: %s", GET_IO_NAME(io)); - - /* - * The basic logic here is that if we've had a fatal error - * reading from the socket once then we shut it down as it's - * no good trying to read from again later. - */ - close_raw(&(io->fd)); - - return -1; - } - } - - /* End of data */ - if(io->buflen == 0) - break; - - /* Check for a new line */ - p = (unsigned char*)memchr(io->buf, '\n', io->buflen); - - if(p != NULL) - { - x = (p - io->buf) + 1; - io->buflen -= x; - } - - else - { - x = io->buflen; - io->buflen = 0; - } - - if(x > l) - x = l; - - /* Copy from buffer line */ - memcpy(t, io->buf, x); - t += x; - l -= x; - - /* Move whatever we have in the buffer to the front */ - if(io->buflen > 0) - memmove(io->buf, io->buf + x, io->buflen); - - /* Found a new line, done */ - if(p != NULL) - break; - - /* If discarding then don't break when full */ - if(!(opts && CLIO_DISCARD) && l == 0) - break; - } - - ctx->linelen = (LINE_LENGTH - l) - 1; - ASSERT(ctx->linelen < LINE_LENGTH); - ctx->line[ctx->linelen] = 0; - - if(opts & CLIO_TRIM && ctx->linelen > 0) - { - t = ctx->line; - - while(*t && isspace(*t)) - t++; - - /* Bump the entire line down */ - l = t - ctx->line; - memmove(ctx->line, t, (ctx->linelen + 1) - l); - ctx->linelen -= l; - - /* Now the end */ - t = ctx->line + ctx->linelen; - - while(t > ctx->line && isspace(*(t - 1))) - { - *(--t) = 0; - ctx->linelen--; - } - } - - if(!(opts & CLIO_QUIET)) - log_io_data(ctx, io, ctx->line, 1); - - return ctx->linelen; -} - -int clio_write_data(clamsmtp_context_t* ctx, clio_t* io, const char* data) -{ - int len = strlen(data); - ASSERT(ctx && io && data); - - if(!clio_valid(io)) - { - message(ctx, LOG_ERR, "connection closed. can't write data."); - return -1; - } - - log_io_data(ctx, io, data, 0); - return clio_write_data_raw(ctx, io, (unsigned char*)data, len); -} - -int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, int len) -{ - int r; - - ASSERT(ctx && io && buf); - - if(io->fd == -1) - return 0; - - while(len > 0) - { - r = write(io->fd, buf, len); - - if(r > 0) - { - buf += r; - len -= r; - } - - else if(r == -1) - { - if(errno == EINTR) - { - /* When the application is quiting */ - if(g_state->quit) - return -1; - - /* For any other signal we go again */ - continue; - } - - /* - * The basic logic here is that if we've had a fatal error - * writing to the socket once then we shut it down as it's - * no good trying to write to it again later. - */ - close_raw(&(io->fd)); - - if(errno == EAGAIN) - messagex(ctx, LOG_WARNING, "network write operation timed out: %s", GET_IO_NAME(io)); - else - message(ctx, LOG_ERR, "couldn't write data to socket: %s", GET_IO_NAME(io)); - - return -1; - } - } - - return 0; -} diff --git a/src/clstate.c b/src/clstate.c deleted file mode 100644 index 2c74e11..0000000 --- a/src/clstate.c +++ /dev/null @@ -1,322 +0,0 @@ -/* - * 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_TRANSPARENT "TransparentProxy" - -/* 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 || fseek(f, 0, SEEK_SET) == -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, "opened config file: %s", configfile); - - /* Double null terminate the data */ - p = state->_p; - p[len] = 0; - p[len + 1] = 0; - - n = state->_p; - - /* Go through lines and process them */ - while((t = strchr(n, '\n')) != NULL) - { - *t = 0; - p = n; /* Do this before cleaning below */ - n = t + 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_BOUNCE)) - state->bounce = strtob(VAL); - - else if(PARSE(CFG_QUARANTINE)) - state->quarantine = strtob(VAL); - - else if(PARSE(CFG_DEBUGFILES)) - state->debug_files = strtob(VAL); - - else if(PARSE(CFG_TRANSPARENT)) - state->transparent = strtob(VAL); - - /* Unrecognized option */ - else - errx(2, "invalid config line: %s", p); - - messagex(NULL, LOG_DEBUG, "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 <= LOG_DEBUG)) - errx(2, "invalid debug log level (must be between 1 and 4)"); - - if(state->max_threads <= 1 || state->max_threads >= 1024) - errx(2, "invalid setting: " CFG_MAXTHREADS " (must be between 1 and 1024)"); - - if(state->timeout.tv_sec <= 0) - errx(2, "invalid setting: " CFG_TIMEOUT); - - 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); - - /* This option has no default, but is required */ - if(state->outname == NULL && !state->transparent) - errx(2, "no " CFG_OUTADDR " specified."); - - /* Parse all the addresses */ - if(state->outname != NULL) - { - if(state->transparent) - warnx("the " CFG_OUTADDR " option will be ignored when " CFG_TRANSPARENT " is enabled"); - else - 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->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->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 setting: " CFG_DIRECTORY); - - 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) -{ - /* Close the mutex */ - pthread_mutex_destroy(&(state->mutex)); - pthread_mutexattr_destroy(&(state->_mtxattr)); - - if(state->_p) - free(state->_p); - - memset(state, 0, sizeof(*state)); -} diff --git a/src/compat.c b/src/compat.c deleted file mode 100644 index 262f94d..0000000 --- a/src/compat.c +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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> - * - * - * PORTIONS FROM OPENBSD: ------------------------------------------------- - * - * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. 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. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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. - */ - - - -#include "usuals.h" -#include "compat.h" - -#include <ctype.h> -#include <stdlib.h> - -#ifndef HAVE_REALLOCF - -void* reallocf(void* ptr, size_t size) -{ - void* ret = realloc(ptr, size); - - if(!ret && size) - free(ptr); - - return ret; -} - -#endif - -#ifndef HAVE_STRLWR -char* strlwr(char* s) -{ - char* t = s; - while(*t) - { - *t = tolower(*t); - t++; - } - return s; -} -#endif - -#ifndef HAVE_STRUPR -char* strupr(char* s) -{ - char* t = s; - while(*t) - { - *t = toupper(*t); - t++; - } - return s; -} -#endif - -#ifndef HAVE_STRLCPY - -size_t strlcpy(char* dst, const char* src, size_t siz) -{ - char* d = dst; - const char* s = src; - size_t n = siz; - - /* Copy as many bytes as will fit */ - if(n != 0 && --n != 0) - { - do - { - if((*d++ = *s++) == 0) - break; - } - while(--n != 0); - } - - /* Not enough room in dst, add NUL and traverse rest of src */ - if(n == 0) - { - if(siz != 0) - *d = '\0'; /* NUL-terminate dst */ - while (*s++) - ; - } - - return s - src - 1; /* count does not include NUL */ -} - -#endif - -#ifndef HAVE_STRLCAT - -size_t strlcat(char* dst, const char* src, size_t siz) -{ - char* d = dst; - const char* s = src; - size_t n = siz; - size_t dlen; - - /* Find the end of dst and adjust bytes left but don't go past end */ - while(n-- != 0 && *d != '\0') - d++; - dlen = d - dst; - n = siz - dlen; - - if(n == 0) - return dlen + strlen(s); - while(*s != '\0') - { - if(n != 1) - { - *d++ = *s; - n--; - } - - s++; - } - - *d = '\0'; - - return dlen + (s - src); /* count does not include NUL */ -} - -#endif - - diff --git a/src/compat.h b/src/compat.h deleted file mode 100644 index 98ebade..0000000 --- a/src/compat.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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> - * - */ - -#ifndef _COMPAT_H_ -#define _COMPAT_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include <sys/types.h> - -#ifndef HAVE_STDARG_H -#error ERROR: Must have a working stdarg.h header -#else -#include <stdarg.h> -#endif - -#ifndef HAVE_REALLOCF -void* reallocf(void* p, size_t sz); -#endif - -#include <pthread.h> - -/* TODO: Move this logic to configure */ -#if HAVE_ERR_MUTEX == 1 -# define MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK_NP -#else -# if HAVE_ERR_MUTEX == 2 -# define MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK -# else -# error "Need error checking mutex functionality" -# endif -#endif - -#ifndef HAVE_STRLWR -char* strlwr(char* s); -#endif - -#ifndef HAVE_STRUPR -char* strupr(char* s); -#endif - -#ifndef HAVE_STRLCAT -size_t strlcat(char *dst, const char *src, size_t size); -#endif - -#ifndef HAVE_STRLCPY -size_t strlcpy(char *dst, const char *src, size_t size); -#endif - -#endif /* _COMPAT_H_ */ diff --git a/src/sock_any.c b/src/sock_any.c deleted file mode 100644 index 7535e02..0000000 --- a/src/sock_any.c +++ /dev/null @@ -1,385 +0,0 @@ -/* - * 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> - * - */ - -#include <sys/types.h> -#include <sys/socket.h> - -#include <ctype.h> -#include <stdlib.h> -#include <errno.h> -#include <netdb.h> -#include <string.h> -#include <stdio.h> - -#include "sock_any.h" - -#include <arpa/inet.h> - -#define LOCALHOST_ADDR 0x7F000001 - -int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts) -{ - size_t l; - char buf[256]; - char* t; - char* t2; - int defport = (opts & 0xFFFF); - - memset(any, 0, sizeof(*any)); - - /* Just a port? */ - do - { - #define PORT_CHARS "0123456789" - #define PORT_MIN 1 - #define PORT_MAX 5 - - int port = 0; - - l = strspn(addr, PORT_CHARS); - if(l < PORT_MIN || l > PORT_MAX || addr[l] != 0) - break; - - port = strtol(addr, &t2, 10); - if(*t2 || port <= 0 || port >= 65536) - break; - - any->s.in.sin_port = htons(port); - - /* Fill in the type based on defaults */ -#ifdef HAVE_INET6 - if(opts & SANY_OPT_DEFINET6) - any->s.in.sin_family = AF_INET6; - else -#endif - any->s.in.sin_family = AF_INET; - - /* Fill in the address based on defaults */ - if(opts & SANY_OPT_DEFLOCAL) - { -#ifdef HAVE_INET6 - if(opts & SANY_OPT_DEFINET6) - memcpy(&(any->s.in.sin6_addr), &in6addr_loopback, sizeof(struct in6_addr)); - else -#endif - any->s.in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - } - - /* - * Note the 'any' option is the default since we zero out - * the entire structure above. - */ - - any->namelen = sizeof(any->s.in); - return AF_INET; - } - while(0); - - /* Look and see if we can parse an ipv4 address */ - do - { - #define IPV4_PORT_CHARS - #define IPV4_CHARS "0123456789." - #define IPV4_MIN 3 - #define IPV4_MAX 18 - - int port = 0; - t = NULL; - - l = strlen(addr); - if(l < IPV4_MIN || l > IPV4_MAX) - break; - - strcpy(buf, addr); - - /* Find the last set that contains just numbers */ - l = strspn(buf, IPV4_CHARS); - if(l < IPV4_MIN) - break; - - /* Either end of string or port */ - if(buf[l] != 0 && buf[l] != ':') - break; - - /* Get the port out */ - if(buf[l] != 0) - { - t = buf + l + 1; - buf[l] = 0; - } - - if(t) - { - port = strtol(t, &t2, 10); - if(*t2 || port <= 0 || port >= 65536) - break; - } - - any->s.in.sin_family = AF_INET; - any->s.in.sin_port = htons((unsigned short)(port <= 0 ? defport : port)); - - if(inet_pton(AF_INET, buf, &(any->s.in.sin_addr)) <= 0) - break; - - any->namelen = sizeof(any->s.in); - return AF_INET; - } - while(0); - -#ifdef HAVE_INET6 - do - { - #define IPV6_CHARS "0123456789:" - #define IPV6_MIN 3 - #define IPV6_MAX 48 - - int port = -1; - t = NULL; - - l = strlen(addr); - if(l < IPV6_MIN || l > IPV6_MAX) - break; - - /* If it starts with a '[' then we can get port */ - if(buf[0] == '[') - { - port = 0; - addr++; - } - - strcpy(buf, addr); - - /* Find the last set that contains just numbers */ - l = strspn(buf, IPV6_CHARS); - if(l < IPV6_MIN) - break; - - /* Either end of string or port */ - if(buf[l] != 0) - { - /* If had bracket, then needs to end with a bracket */ - if(port != 0 || buf[l] != ']') - break; - - /* Get the port out */ - t = buf + l + 1; - - if(*t = ':') - t++; - } - - if(t) - { - port = strtol(t, &t, 10); - if(*t || port <= 0 || port >= 65536) - break; - } - - any->s.in6.sin6_family = AF_INET6; - any->s.in6.sin6_port = htons((unsigned short)port <= 0 : defport : port); - - if(inet_pton(AF_INET6, buf, &(any->s.in6.sin6_addr)) >= 0) - break; - - any->namelen = sizeof(any->s.in6); - return AF_INET6; - } - while(0); -#endif - - /* A unix socket path */ - do - { - /* No colon and must have a path component */ - if(strchr(addr, ':') || !strchr(addr, '/')) - break; - - l = strlen(addr); - if(l >= sizeof(any->s.un.sun_path)) - break; - - any->s.un.sun_family = AF_UNIX; - strcpy(any->s.un.sun_path, addr); - - any->namelen = sizeof(any->s.un) - (sizeof(any->s.un.sun_path) - l); - return AF_UNIX; - } - while(0); - - /* A DNS name and a port? */ - do - { - struct addrinfo* res; - int port = 0; - t = NULL; - - l = strlen(addr); - if(l >= 255 || !isalpha(addr[0])) - break; - - /* Some basic illegal character checks */ - if(strcspn(addr, " /\\") != l) - break; - - strcpy(buf, addr); - - /* Find the last set that contains just numbers */ - t = strchr(buf, ':'); - if(t) - { - *t = 0; - t++; - } - - if(t) - { - port = strtol(t, &t2, 10); - if(*t2 || port <= 0 || port >= 65536) - break; - } - - /* Try and resolve the domain name */ - if(getaddrinfo(buf, NULL, NULL, &res) != 0 || !res) - break; - - memcpy(&(any->s.a), res->ai_addr, sizeof(struct sockaddr)); - any->namelen = res->ai_addrlen; - freeaddrinfo(res); - - port = htons((unsigned short)(port <= 0 ? defport : port)); - - switch(any->s.a.sa_family) - { - case PF_INET: - any->s.in.sin_port = port; - break; -#ifdef HAVE_INET6 - case PF_INET6: - any->s.in6.sin6_port = port; - break; -#endif - }; - - return any->s.a.sa_family; - } - while(0); - - return -1; -} - -int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts) -{ - int len = 0; - int port = 0; - - switch(any->s.a.sa_family) - { - case AF_UNIX: - len = strlen(any->s.un.sun_path); - if(addrlen < len + 1) - { - errno = ENOSPC; - return -1; - } - - strcpy(addr, any->s.un.sun_path); - break; - - case AF_INET: - if(inet_ntop(any->s.a.sa_family, &(any->s.in.sin_addr), addr, addrlen) == NULL) - return -1; - port = ntohs(any->s.in.sin_port); - break; - -#ifdef HAVE_INET6 - case AF_INET6: - if(inet_ntop(any->s.a.sa_family, &(any->s.in6.sin6_addr), addr, addrlen) == NULL) - return -1; - port = ntohs(any->s.in6.sin6_port); - break; -#endif - - default: - errno = EAFNOSUPPORT; - return -1; - } - - if(!(opts & SANY_OPT_NOPORT) && port != 0) - { - strncat(addr, ":", addrlen); - addr[addrlen - 1] = 0; - - len = strlen(addr); - addr += len; - addrlen -= len; - - snprintf(addr, addrlen, "%d", port); - } - - return 0; -} - -int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts) -{ - if(a1->s.a.sa_family != a2->s.a.sa_family) - return -1; - - switch(a1->s.a.sa_family) - { - case AF_UNIX: - return strcmp(a1->s.un.sun_path, a2->s.un.sun_path); - - case AF_INET: - if(memcmp(&(a1->s.in.sin_addr), &(a2->s.in.sin_addr), sizeof(a2->s.in.sin_addr)) != 0) - return -1; - if(!(opts && SANY_OPT_NOPORT) && a1->s.in.sin_port != a2->s.in.sin_port) - return -1; - return 0; -#ifdef HAVE_INET6 - case AF_INET6: - if(memcmp(&(a1->s.in6.sin6_addr), &(a2->s.in6.sin6_addr), sizeof(a2->s.in6.sin6_addr)) != 0) - return -1; - if(!(opts && SANY_OPT_NOPORT) && a1->s.in6.sin6_port != a2->s.in6.sin6_port) - return -1; - return 0; -#endif - default: - errno = EAFNOSUPPORT; - return -1; - } -} diff --git a/src/sock_any.h b/src/sock_any.h deleted file mode 100644 index 31cb13b..0000000 --- a/src/sock_any.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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> - * - */ - -#ifndef __SOCK_ANY_H__ -#define __SOCK_ANY_H__ - -#include <sys/socket.h> -#include <sys/un.h> -#include <netinet/in.h> - -struct sockaddr_any -{ - union _sockaddr_any - { - /* The header */ - struct sockaddr a; - - /* The different types */ - struct sockaddr_un un; - struct sockaddr_in in; -#ifdef HAVE_INET6 - struct sockaddr_in6 in6; -#endif - } s; - size_t namelen; -}; - -#define SANY_ADDR(any) ((any).s.a) -#define SANY_LEN(any) ((any).namelen) -#define SANY_TYPE(any) ((any).s.a.sa_family) - -int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts); - -/* The default port to fill in when no IP/IPv6 port specified */ -#define SANY_OPT_DEFPORT(p) (int)((p) & 0xFFFF) - -/* When only port specified default to IPANY */ -#define SANY_OPT_DEFANY 0x00000000 - -/* When only port specified default to LOCALHOST */ -#define SANY_OPT_DEFLOCAL 0x00100000 - -/* When only port specified default to IPv6 */ -#ifdef HAVE_INET6 -#define SANY_OPT_DEFINET6 0x00200000 -#endif - -int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts); - -/* Don't print or compare the port */ -#define SANY_OPT_NOPORT 0x01000000 - -int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts); - -#endif /* __SOCK_ANY_H__ */ diff --git a/src/util.c b/src/util.c deleted file mode 100644 index 1067f27..0000000 --- a/src/util.c +++ /dev/null @@ -1,283 +0,0 @@ -/* - * 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> - * - */ - -#include <sys/types.h> - -#include <ctype.h> -#include <syslog.h> -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <errno.h> -#include <err.h> -#include <stdarg.h> -#include <strings.h> - -#include "usuals.h" -#include "compat.h" -#include "clamsmtpd.h" -#include "util.h" - -/* ---------------------------------------------------------------------------------- - * Logging - */ - -const char kMsgDelimiter[] = ": "; -#define MAX_MSGLEN 256 - -static void vmessage(clamsmtp_context_t* ctx, int level, int err, - const char* msg, va_list ap) -{ - size_t len; - char* m; - int e = errno; - - if(g_state->daemonized) - { - if(level >= LOG_DEBUG) - return; - } - else - { - if(g_state->debug_level < level) - return; - } - - ASSERT(msg); - - len = strlen(msg) + 20 + MAX_MSGLEN; - m = (char*)alloca(len); - - if(m) - { - if(ctx) - snprintf(m, len, "%06X: %s%s", ctx->id, msg, err ? ": " : ""); - else - snprintf(m, len, "%s%s", msg, err ? ": " : ""); - - if(err) - { - /* TODO: strerror_r doesn't want to work for us - strerror_r(e, m + strlen(m), MAX_MSGLEN); */ - strncat(m, strerror(e), len); - } - - m[len - 1] = 0; - msg = m; - } - - /* Either to syslog or stderr */ - if(g_state->daemonized) - vsyslog(level, msg, ap); - else - vwarnx(msg, ap); -} - -void messagex(clamsmtp_context_t* ctx, int level, const char* msg, ...) -{ - va_list ap; - - va_start(ap, msg); - vmessage(ctx, level, 0, msg, ap); - va_end(ap); -} - -void message(clamsmtp_context_t* ctx, int level, const char* msg, ...) -{ - va_list ap; - - va_start(ap, msg); - vmessage(ctx, level, 1, msg, ap); - va_end(ap); -} - -/* ---------------------------------------------------------------------------------- - * Parsing - */ - -int is_first_word(const char* line, const char* word, int len) -{ - ASSERT(line); - ASSERT(word); - ASSERT(len > 0); - - while(*line && isspace(*line)) - line++; - - if(strncasecmp(line, word, len) != 0) - return 0; - - line += len; - return !*line || isspace(*line); -} - -int check_first_word(const char* line, const char* word, int len, char* delims) -{ - const char* t; - int found = 0; - - ASSERT(line); - ASSERT(word); - ASSERT(len > 0); - - t = line; - - while(*t && strchr(delims, *t)) - t++; - - if(strncasecmp(t, word, len) != 0) - return 0; - - t += len; - - while(*t && strchr(delims, *t)) - { - found = 1; - t++; - } - - return (!*t || found) ? t - line : 0; -} - -int is_last_word(const char* line, const char* word, int len) -{ - const char* t; - - ASSERT(line); - ASSERT(word); - ASSERT(len > 0); - - t = line + strlen(line); - - while(t > line && isspace(*(t - 1))) - --t; - - if(t - len < line) - return 0; - - return strncasecmp(t - len, word, len) == 0; -} - -int is_blank_line(const char* line) -{ - /* Small optimization */ - if(!*line) - return 1; - - while(*line && isspace(*line)) - line++; - - return *line == 0; -} - -char* trim_start(const char* data) -{ - while(*data && isspace(*data)) - ++data; - return (char*)data; -} - -char* trim_end(char* data) -{ - char* t = data + strlen(data); - - while(t > data && isspace(*(t - 1))) - { - t--; - *t = 0; - } - - return data; -} - -char* trim_space(char* data) -{ - data = (char*)trim_start(data); - return trim_end(data); -} - -/* ----------------------------------------------------------------------- - * Locking - */ - -void plock() -{ - int r; - -#ifdef _DEBUG - int wait = 0; -#endif - -#ifdef _DEBUG - r = pthread_mutex_trylock((pthread_mutex_t*)&(g_state->mutex)); - if(r == EBUSY) - { - wait = 1; - message(NULL, LOG_DEBUG, "thread will block: %d", pthread_self()); - r = pthread_mutex_lock((pthread_mutex_t*)&(g_state->mutex)); - } - -#else - r = pthread_mutex_lock(&(g_state->mutex)); - -#endif - - if(r != 0) - { - errno = r; - message(NULL, LOG_CRIT, "threading problem. couldn't lock mutex"); - } - -#ifdef _DEBUG - else if(wait) - { - message(NULL, LOG_DEBUG, "thread unblocked: %d", pthread_self()); - } -#endif -} - -void punlock() -{ - int r = pthread_mutex_unlock((pthread_mutex_t*)&(g_state->mutex)); - if(r != 0) - { - errno = r; - message(NULL, LOG_CRIT, "threading problem. couldn't unlock mutex"); - } -} - diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 37fa245..0000000 --- a/src/util.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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> - * - */ - -#ifndef __UTIL_H__ -#define __UTIL_H__ - -void messagex(clamsmtp_context_t* ctx, int level, const char* msg, ...); -void message(clamsmtp_context_t* ctx, int level, const char* msg, ...); - -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); -int is_blank_line(const char* line); - -char* trim_start(const char* data); -char* trim_end(char* data); -char* trim_space(char* data); - -void plock(); -void punlock(); - -#endif /* __UTIL_H__ */ |