/* * 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_TRANSPARENT "TransparentProxy" /* The set of delimiters that can be present between config and value */ #define CFG_DELIMS ": \t" /* ----------------------------------------------------------------------- * CODE */ /* String to bool helper function */ static int strtob(const char* str) { if(strcasecmp(str, "0") == 0 || strcasecmp(str, "no") == 0 || strcasecmp(str, "false") == 0 || strcasecmp(str, "f") == 0 || strcasecmp(str, "off") == 0) return 0; if(strcasecmp(str, "1") == 0 || strcasecmp(str, "yes") == 0 || strcasecmp(str, "true") == 0 || strcasecmp(str, "t") == 0 || strcasecmp(str, "on") == 0) return 1; return -1; } void clstate_init(clstate_t* state) { ASSERT(state); memset(state, 0, sizeof(*state)); /* Setup the defaults */ state->debug_level = -1; state->max_threads = DEFAULT_MAXTHREADS; state->timeout.tv_sec = DEFAULT_TIMEOUT; state->clamname = DEFAULT_CLAMAV; state->listenname = DEFAULT_SOCKET; state->header = DEFAULT_HEADER; state->directory = _PATH_TMP; /* Create the main mutex and condition variable */ if(pthread_mutexattr_init(&(state->_mtxattr)) != 0 || pthread_mutexattr_settype(&(state->_mtxattr), MUTEX_TYPE) || pthread_mutex_init(&(state->mutex), &(state->_mtxattr)) != 0) errx(1, "threading problem. can't create mutex or condition var"); } int clstate_parse_config(clstate_t* state, const char* configfile) { FILE* f = NULL; long len; int r; char* p; char* t; char* n; ASSERT(state); ASSERT(configfile); ASSERT(!state->_p); f = fopen(configfile, "r"); if(f == NULL) { /* Soft errors when default config file and not found */ if((errno == ENOENT || errno == ENOTDIR)) return -1; else err(1, "couldn't open config file: %s", configfile); } if(fseek(f, 0, SEEK_END) == -1 || (len = ftell(f)) == -1 || fseek(f, 0, SEEK_SET) == -1) err(1, "couldn't seek config file: %s", configfile); if((state->_p = (char*)malloc(len + 2)) == NULL) errx(1, "out of memory"); if(fread(state->_p, 1, len, f) != len) err(1, "couldn't read config file: %s", configfile); fclose(f); messagex(NULL, LOG_DEBUG, "opened config file: %s", configfile); /* Double null terminate the data */ p = state->_p; p[len] = 0; p[len + 1] = 0; n = state->_p; /* Go through lines and process them */ while((t = strchr(n, '\n')) != NULL) { *t = 0; p = n; /* Do this before cleaning below */ n = t + 1; p = trim_space(p); /* Comments and empty lines */ if(*p == 0 || *p == '#') continue; /* Save some code typing below */ #define PARSE(o) \ (r = check_first_word(p, (o), KL(o), CFG_DELIMS)) #define VAL \ (p + r) /* * Note that we don't validate here. If something's wrong * set it to an invalid value. */ if(PARSE(CFG_MAXTHREADS)) { state->max_threads = strtol(VAL, &t, 10); if(*t) /* parse failed */ state->max_threads = -1; } else if(PARSE(CFG_TIMEOUT)) { state->timeout.tv_sec = strtol(VAL, &t, 10); if(*t) /* parse failed */ state->timeout.tv_sec = -1; } else if(PARSE(CFG_OUTADDR)) state->outname = VAL; else if(PARSE(CFG_LISTENADDR)) state->listenname = VAL; else if(PARSE(CFG_CLAMADDR)) state->clamname = VAL; else if(PARSE(CFG_HEADER)) state->header = VAL; else if(PARSE(CFG_DIRECTORY)) state->directory = VAL; else if(PARSE(CFG_BOUNCE)) state->bounce = strtob(VAL); else if(PARSE(CFG_QUARANTINE)) state->quarantine = strtob(VAL); else if(PARSE(CFG_DEBUGFILES)) state->debug_files = strtob(VAL); else if(PARSE(CFG_TRANSPARENT)) state->transparent = strtob(VAL); /* Unrecognized option */ else errx(2, "invalid config line: %s", p); messagex(NULL, LOG_DEBUG, "parsed line: %s", p); } return 0; } void clstate_validate(clstate_t* state) { ASSERT(state); messagex(NULL, LOG_DEBUG, "validating configuration options"); if(!(state->debug_level == -1 || state->debug_level <= LOG_DEBUG)) errx(2, "invalid debug log level (must be between 1 and 4)"); if(state->max_threads <= 1 || state->max_threads >= 1024) errx(2, "invalid setting: " CFG_MAXTHREADS " (must be between 1 and 1024)"); if(state->timeout.tv_sec <= 0) errx(2, "invalid setting: " CFG_TIMEOUT); if(state->bounce == -1) errx(2, "invalid value for " CFG_BOUNCE); if(state->quarantine == -1) errx(2, "invalid value for " CFG_QUARANTINE); if(state->debug_files == -1) errx(2, "invalid value for " CFG_DEBUGFILES); /* This option has no default, but is required */ if(state->outname == NULL && !state->transparent) errx(2, "no " CFG_OUTADDR " specified."); /* Parse all the addresses */ if(state->outname != NULL) { if(state->transparent) warnx("the " CFG_OUTADDR " option will be ignored when " CFG_TRANSPARENT " is enabled"); else if(sock_any_pton(state->outname, &(state->outaddr), SANY_OPT_DEFPORT(25)) == -1) errx(2, "invalid " CFG_OUTADDR " socket name or ip: %s", state->outname); } if(sock_any_pton(state->listenname, &(state->listenaddr), SANY_OPT_DEFANY | SANY_OPT_DEFPORT(DEFAULT_PORT)) == -1) errx(2, "invalid " CFG_LISTENADDR " socket name or ip: %s", state->listenname); if(sock_any_pton(state->clamname, &(state->clamaddr), SANY_OPT_DEFLOCAL) == -1) errx(2, "invalid " CFG_CLAMADDR " socket name: %s", state->clamname); if(strlen(state->directory) == 0) errx(2, "invalid setting: " CFG_DIRECTORY); if(state->header) { /* * This is for when it comes from the command-line. * Once command line args are phased out this can be removed */ state->header = (const char*)trim_space((char*)state->header); if(strlen(state->header) == 0) state->header = NULL; } } void clstate_cleanup(clstate_t* state) { /* Close the mutex */ pthread_mutex_destroy(&(state->mutex)); pthread_mutexattr_destroy(&(state->_mtxattr)); if(state->_p) free(state->_p); memset(state, 0, sizeof(*state)); }