summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--Makefile.am2
-rw-r--r--common/compat.h3
-rw-r--r--common/smtppass.c1325
-rw-r--r--common/smtppass.h266
-rw-r--r--common/spio.c216
-rw-r--r--common/sppriv.h (renamed from src/usuals.h)55
-rw-r--r--common/stringx.c141
-rw-r--r--common/stringx.h12
-rw-r--r--common/usuals.h2
-rw-r--r--configure.in8
-rw-r--r--src/Makefile.am8
-rw-r--r--src/clamsmtpd.c1312
-rw-r--r--src/clamsmtpd.h102
-rw-r--r--src/clio.c407
-rw-r--r--src/clstate.c322
-rw-r--r--src/compat.c182
-rw-r--r--src/compat.h87
-rw-r--r--src/sock_any.c385
-rw-r--r--src/sock_any.h90
-rw-r--r--src/util.c283
-rw-r--r--src/util.h57
22 files changed, 1302 insertions, 3967 deletions
diff --git a/ChangeLog b/ChangeLog
index 96ad2a8..f502bd4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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__ */