summaryrefslogtreecommitdiff
path: root/src/clamsmtpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/clamsmtpd.c')
-rw-r--r--src/clamsmtpd.c247
1 files changed, 112 insertions, 135 deletions
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);