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