/* Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl Copyright (C) 2004-2006 NFG Net Facilities Group BV support@nfg.nl Copyright (C) 2006 Aaron Stone aaron@serendipity.cx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * * serverparent.c * * Main program for all daemons. */ #include "dbmail.h" #define THIS_MODULE "serverparent" /* set up database login data */ extern db_param_t _db_params; static char *configFile = DEFAULT_CONFIG_FILE; extern volatile sig_atomic_t mainRestart; extern volatile sig_atomic_t mainStatus; extern volatile sig_atomic_t mainStop; extern volatile sig_atomic_t mainSig; /* Not used, but required to link with libdbmail.so */ int verbose = 0; int no_to_all = 0; int yes_to_all = 0; int reallyquiet = 0; int quiet = 0; static int SetMainSigHandler(void); static void MainSigHandler(int sig, siginfo_t * info, void *data); static void ClearConfig(serverConfig_t * conf); static void DoConfig(serverConfig_t * conf, const char * const service); static void LoadServerConfig(serverConfig_t * config, const char * const service); /* Valid chars used by LMTP, POP3 and Tim's Sieve. */ const char ValidNetworkChars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ",'\"?_.!|@#$%^&*()-+=~[]{}<>:;\\/ '"; void serverparent_showhelp(const char *name, const char *greeting) { printf("*** %s ***\n", name); printf("%s\n", greeting); printf("See the man page for more info.\n"); printf("\nCommon options for all DBMail daemons:\n"); printf(" -f file specify an alternative config file\n"); printf(" -p file specify an alternative runtime pidfile\n"); printf(" -s file specify an alternative runtime statefile\n"); printf(" -n do not daemonize (no children are forked)\n"); printf(" -v verbose logging to syslog and stderr\n"); printf(" -V show the version\n"); printf(" -h show this help message\n"); } /* Return values: * -1 just quit * 0 all is well, config is updated * 1 help must be shown, then quit */ int serverparent_getopt(serverConfig_t *config, const char *service, int argc, char *argv[]) { int opt; configFile = g_strdup(DEFAULT_CONFIG_FILE); ClearConfig(config); TRACE(TRACE_DEBUG, "checking command line options"); /* get command-line options */ opterr = 0; /* suppress error message from getopt() */ while ((opt = getopt(argc, argv, "vVhqnf:p:s:")) != -1) { switch (opt) { case 'v': config->log_verbose = 1; break; case 'V': PRINTF_THIS_IS_DBMAIL; return -1; case 'n': config->no_daemonize = 1; break; case 'h': return 1; case 'p': if (optarg && strlen(optarg) > 0) config->pidFile = g_strdup(optarg); else { fprintf(stderr, "%s: -p requires a filename argument\n\n", argv[0]); return 1; } break; case 's': if (optarg && strlen(optarg) > 0) config->stateFile = g_strdup(optarg); else { fprintf(stderr, "%s: -s requires a filename argument\n\n", argv[0]); return 1; } break; case 'f': if (optarg && strlen(optarg) > 0) { g_free(configFile); configFile = g_strdup(optarg); } else { fprintf(stderr, "%s: -f requires a filename argument\n\n", argv[0]); return 1; } break; default: fprintf(stderr, "%s: unrecognized option: %s\n\n", argv[0], argv[optind]); return 1; } } if (optind < argc) { fprintf(stderr, "%s: unrecognized options: ", argv[0]); while (optind < argc) fprintf(stderr, "%s ", argv[optind++]); fprintf(stderr, "\n\n"); return 1; } DoConfig(config, service); return 0; } int serverparent_mainloop(serverConfig_t *config, const char *service, const char *servicename) { SetMainSigHandler(); if (config->no_daemonize) { StartCliServer(config); TRACE(TRACE_INFO, "exiting cli server"); return 0; } server_daemonize(config); /* We write the pidFile after daemonize because * we may actually be a child of the original process. */ if (! config->pidFile) config->pidFile = config_get_pidfile(config, servicename); pidfile_create(config->pidFile, getpid()); if (! config->stateFile) config->stateFile = config_get_statefile(config, servicename); statefile_create(config->stateFile); /* This is the actual main loop. */ while (!mainStop && server_run(config)) { /* Reread the config file and restart the services, * e.g. on SIGHUP or other graceful restart condition. */ DoConfig(config, service); sleep(2); } ClearConfig(config); TRACE(TRACE_INFO, "leaving main loop"); return 0; } void MainSigHandler(int sig, siginfo_t * info UNUSED, void *data UNUSED) { mainSig = sig; if (sig == SIGHUP) mainRestart = 1; else if (sig == SIGUSR1) mainStatus = 1; else mainStop = 1; } int SetMainSigHandler() { struct sigaction act; /* init & install signal handlers */ memset(&act, 0, sizeof(act)); act.sa_sigaction = MainSigHandler; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; sigaction(SIGINT, &act, 0); sigaction(SIGQUIT, &act, 0); sigaction(SIGTERM, &act, 0); sigaction(SIGHUP, &act, 0); sigaction(SIGUSR1, &act, 0); return 0; } void ClearConfig(serverConfig_t * config) { assert(config); g_strfreev(config->iplist); g_free(config->listenSockets); config->listenSockets = NULL; config->iplist = NULL; memset(config, 0, sizeof(serverConfig_t)); } void DoConfig(serverConfig_t * config, const char * const service) { TRACE(TRACE_DEBUG, "reading config [%s]", configFile); config_free(); config_read(configFile); SetTraceLevel(service); /* Override SetTraceLevel. */ if (config->log_verbose) { configure_debug(5,5); } LoadServerConfig(config, service); GetDBParams(&_db_params); } void LoadServerConfig(serverConfig_t * config, const char * const service) { field_t val; config_get_logfiles(config); /* read items: NCHILDREN */ config_get_value("NCHILDREN", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for NCHILDREN in config file"); if ((config->startChildren = atoi(val)) <= 0) TRACE(TRACE_FATAL, "value for NCHILDREN is invalid: [%d]", config->startChildren); TRACE(TRACE_DEBUG, "server will create [%d] children", config->startChildren); /* read items: MAXCONNECTS */ config_get_value("MAXCONNECTS", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for MAXCONNECTS in config file"); if ((config->childMaxConnect = atoi(val)) <= 0) TRACE(TRACE_FATAL, "value for MAXCONNECTS is invalid: [%d]", config->childMaxConnect); TRACE(TRACE_DEBUG, "children will make max. [%d] connections", config->childMaxConnect); /* read items: TIMEOUT */ config_get_value("TIMEOUT", service, val); if (strlen(val) == 0) { TRACE(TRACE_DEBUG, "no value for TIMEOUT in config file"); config->timeout = 0; } else if ((config->timeout = atoi(val)) <= 30) TRACE(TRACE_FATAL, "value for TIMEOUT is invalid: [%d]", config->timeout); TRACE(TRACE_DEBUG, "timeout [%d] seconds", config->timeout); /* read items: LOGIN_TIMEOUT */ config_get_value("LOGIN_TIMEOUT", service, val); if (strlen(val) == 0) { TRACE(TRACE_DEBUG, "no value for TIMEOUT in config file"); config->login_timeout = 60; } else if ((config->login_timeout = atoi(val)) <= 10) TRACE(TRACE_FATAL, "value for TIMEOUT is invalid: [%d]", config->login_timeout); TRACE(TRACE_DEBUG, "login_timeout [%d] seconds", config->login_timeout); /* SOCKET */ config_get_value("SOCKET", service, val); if (strlen(val) == 0) TRACE(TRACE_DEBUG, "no value for SOCKET in config file"); strncpy(config->socket, val, FIELDSIZE); TRACE(TRACE_DEBUG, "socket [%s]", config->socket); /* read items: PORT */ config_get_value("PORT", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for PORT in config file"); if ((config->port = atoi(val)) <= 0) TRACE(TRACE_FATAL, "value for PORT is invalid: [%d]", config->port); TRACE(TRACE_DEBUG, "binding to PORT [%d]", config->port); /* read items: BINDIP */ config_get_value("BINDIP", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for BINDIP in config file"); // If there was a SIGHUP, then we're resetting an active config. g_strfreev(config->iplist); g_free(config->listenSockets); // Allowed list separators are ' ' and ','. config->iplist = g_strsplit_set(val, " ,", 0); config->ipcount = g_strv_length(config->iplist); if (config->ipcount < 1) { TRACE(TRACE_FATAL, "no value for BINDIP in config file"); } int ip; for (ip = 0; ip < config->ipcount; ip++) { // Remove whitespace from each list entry, then log it. g_strstrip(config->iplist[ip]); TRACE(TRACE_DEBUG, "binding to IP [%s]", config->iplist[ip]); } /* read items: BACKLOG */ config_get_value("BACKLOG", service, val); if (strlen(val) == 0) { TRACE(TRACE_DEBUG, "no value for BACKLOG in config file. Using default value [%d]", BACKLOG); config->backlog = BACKLOG; } else if ((config->backlog = atoi(val)) <= 0) TRACE(TRACE_FATAL, "value for BACKLOG is invalid: [%d]", config->backlog); /* read items: RESOLVE_IP */ config_get_value("RESOLVE_IP", service, val); if (strlen(val) == 0) TRACE(TRACE_DEBUG, "no value for RESOLVE_IP in config file"); config->resolveIP = (strcasecmp(val, "yes") == 0); TRACE(TRACE_DEBUG, "%sresolving client IP", config->resolveIP ? "" : "not "); /* read items: service-BEFORE-SMTP */ char *service_before_smtp = g_strconcat(service, "_BEFORE_SMTP", NULL); config_get_value(service_before_smtp, service, val); g_free(service_before_smtp); if (strlen(val) == 0) TRACE(TRACE_DEBUG, "no value for %s_BEFORE_SMTP in config file", service); config->service_before_smtp = (strcasecmp(val, "yes") == 0); TRACE(TRACE_DEBUG, "%s %s-before-SMTP", config->service_before_smtp ? "Enabling" : "Disabling", service); /* read items: EFFECTIVE-USER */ config_get_value("EFFECTIVE_USER", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for EFFECTIVE_USER in config file"); strncpy(config->serverUser, val, FIELDSIZE); config->serverUser[FIELDSIZE - 1] = '\0'; TRACE(TRACE_DEBUG, "effective user shall be [%s]", config->serverUser); /* read items: EFFECTIVE-GROUP */ config_get_value("EFFECTIVE_GROUP", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for EFFECTIVE_GROUP in config file"); strncpy(config->serverGroup, val, FIELDSIZE); config->serverGroup[FIELDSIZE - 1] = '\0'; TRACE(TRACE_DEBUG, "effective group shall be [%s]", config->serverGroup); /* read items: MINSPARECHILDREN */ config_get_value("MINSPARECHILDREN", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for MINSPARECHILDREN in config file"); if ( (config->minSpareChildren = atoi(val)) < 0) TRACE(TRACE_FATAL, "value for MINSPARECHILDREN is invalid: [%d]", config->minSpareChildren); TRACE(TRACE_DEBUG, "will maintain minimum of [%d] spare children in reserve", config->minSpareChildren); /* read items: MAXSPARECHILDREN */ config_get_value("MAXSPARECHILDREN", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for MAXSPARECHILDREN in config file"); if ( (config->maxSpareChildren = atoi(val)) <= 0) TRACE(TRACE_FATAL, "value for MAXSPARECHILDREN is invalid: [%d]", config->maxSpareChildren); TRACE(TRACE_DEBUG, "will maintain maximum of [%d] spare children in reserve", config->maxSpareChildren); /* read items: MAXCHILDREN */ config_get_value("MAXCHILDREN", service, val); if (strlen(val) == 0) TRACE(TRACE_FATAL, "no value for MAXCHILDREN in config file"); if ( (config->maxChildren = atoi(val)) <= 0) TRACE(TRACE_FATAL, "value for MAXCHILDREN is invalid: [%d]", config->maxSpareChildren); TRACE(TRACE_DEBUG, "will allow maximum of [%d] children", config->maxChildren); }