From 1c4ed8a00cd6c5804055bc72d453591854d8ecf7 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 3 Sep 2004 01:34:14 +0000 Subject: Configuration file for clamsmtp --- src/clstate.c | 325 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 src/clstate.c (limited to 'src/clstate.c') 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 + * Yamamoto Takao + */ + +#include +#include +#include +#include +#include +#include +#include + +#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)); +} -- cgit v1.2.3