summaryrefslogtreecommitdiff
path: root/src/clstate.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/clstate.c')
-rw-r--r--src/clstate.c325
1 files changed, 325 insertions, 0 deletions
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));
+}