/* $Id: server.c 2199 2006-07-18 11:07:53Z paul $ Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl Copyright (c) 2004-2006 NFG Net Facilities Group BV support@nfg.nl 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. */ /* * server.c * * code to implement a network server */ #include "dbmail.h" volatile sig_atomic_t GeneralStopRequested = 0; volatile sig_atomic_t Restart = 0; volatile sig_atomic_t mainStop = 0; volatile sig_atomic_t mainRestart = 0; volatile sig_atomic_t mainSig = 0; volatile sig_atomic_t get_sigchld = 0; pid_t ParentPID = 0; ChildInfo_t childinfo; /* some extra prototypes (defintions are below) */ static void ParentSigHandler(int sig, siginfo_t * info, void *data); static int SetParentSigHandler(void); static int server_setup(serverConfig_t *conf); int SetParentSigHandler() { struct sigaction act; /* init & install signal handlers */ memset(&act, 0, sizeof(act)); act.sa_sigaction = ParentSigHandler; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP; sigaction(SIGCHLD, &act, 0); sigaction(SIGINT, &act, 0); sigaction(SIGQUIT, &act, 0); sigaction(SIGILL, &act, 0); sigaction(SIGBUS, &act, 0); sigaction(SIGFPE, &act, 0); sigaction(SIGSEGV, &act, 0); sigaction(SIGTERM, &act, 0); sigaction(SIGHUP, &act, 0); return 0; } int server_setup(serverConfig_t *conf) { if (db_connect() != 0) return -1; if (db_check_version() != 0) { db_disconnect(); return -1; } db_disconnect(); ParentPID = getpid(); Restart = 0; GeneralStopRequested = 0; get_sigchld = 0; SetParentSigHandler(); childinfo.maxConnect = conf->childMaxConnect; childinfo.listenSocket = conf->listenSocket; childinfo.timeout = conf->timeout; childinfo.ClientHandler = conf->ClientHandler; childinfo.timeoutMsg = conf->timeoutMsg; childinfo.resolveIP = conf->resolveIP; return 0; } int StartCliServer(serverConfig_t * conf) { if (!conf) trace(TRACE_FATAL, "%s,%s: NULL configuration", __FILE__, __func__); if (server_setup(conf)) return -1; manage_start_cli_server(&childinfo); return 0; } int StartServer(serverConfig_t * conf) { int stopped = 0; pid_t chpid; if (!conf) trace(TRACE_FATAL, "%s,%s: NULL configuration", __FILE__, __func__); if (server_setup(conf)) return -1; scoreboard_new(conf); if (db_connect() != DM_SUCCESS) trace(TRACE_FATAL, "%s,%s: unable to connect to sql storage", __FILE__, __func__); manage_start_children(); manage_spare_children(); trace(TRACE_DEBUG, "%s,%s: starting main service loop", __FILE__, __func__); while (!GeneralStopRequested) { if(get_sigchld){ get_sigchld = 0; while((chpid = waitpid(-1,(int*)NULL,WNOHANG)) > 0) scoreboard_release(chpid); } if (db_check_connection() != 0) { if (! stopped) manage_stop_children(); stopped=1; sleep(10); } else { if (stopped) { manage_start_children(); stopped=0; } manage_spare_children(); sleep(1); } } manage_stop_children(); scoreboard_delete(); return Restart; } pid_t server_daemonize(serverConfig_t *conf) { int serr; assert(conf); if (fork()) exit(0); setsid(); if (fork()) exit(0); chdir("/"); umask(0); if (! (freopen(conf->log,"a",stdout))) { serr = errno; trace(TRACE_FATAL,"%s,%s: freopen failed on [%s] [%s]", __FILE__, __func__, conf->log, strerror(serr)); } if (! (freopen(conf->error_log,"a",stderr))) { serr = errno; trace(TRACE_FATAL,"%s,%s: freopen failed on [%s] [%s]", __FILE__, __func__, conf->error_log, strerror(serr)); } if (! (freopen("/dev/null","r",stdin))) { serr = errno; trace(TRACE_FATAL,"%s,%s: freopen failed on stdin [%s]", __FILE__, __func__, strerror(serr)); } trace(TRACE_DEBUG,"%s,%s: sid: [%d]", __FILE__, __func__, getsid(0)); return getsid(0); } int server_run(serverConfig_t *conf) { mainStop = 0; mainRestart = 0; mainSig = 0; int serrno, status, result = 0; pid_t pid = -1; CreateSocket(conf); switch ((pid = fork())) { case -1: serrno = errno; close(conf->listenSocket); trace(TRACE_FATAL, "%s,%s: fork failed [%s]", __FILE__, __func__, strerror(serrno)); errno = serrno; break; case 0: /* child process */ drop_privileges(conf->serverUser, conf->serverGroup); result = StartServer(conf); trace(TRACE_INFO, "%s,%s: server done, restart = [%d]", __FILE__, __func__, result); exit(result); break; default: /* parent process, wait for child to exit */ while (waitpid(pid, &status, WNOHANG | WUNTRACED) == 0) { if (mainStop || mainRestart){ trace(TRACE_DEBUG, "MainSigHandler(): got signal [%d]", mainSig); if(mainStop) kill(pid, SIGTERM); if(mainRestart) kill(pid, SIGHUP); } sleep(2); } if (WIFEXITED(status)) { /* child process terminated neatly */ result = WEXITSTATUS(status); trace(TRACE_DEBUG, "%s,%s: server has exited, exit status [%d]", __FILE__, __func__, result); } else { /* child stopped or signaled so make sure it is dead */ trace(TRACE_DEBUG, "%s,%s: server has not exited normally. Killing..", __FILE__, __func__); kill(pid, SIGKILL); result = 0; } if (strlen(conf->socket) > 0) { if (unlink(conf->socket)) { serrno = errno; trace(TRACE_ERROR, "%s,%s: unlinking unix socket failed [%s]", __FILE__, __func__, strerror(serrno)); errno = serrno; } } break; } close(conf->listenSocket); return result; } void ParentSigHandler(int sig, siginfo_t * info UNUSED, void *data UNUSED) { int saved_errno = errno; Restart = 0; /* this call is for a child but it's handler is not yet installed */ /* if (ParentPID != getpid()) active_child_sig_handler(sig, info, data); */ switch (sig) { case SIGCHLD: /* ignore, wait for child in main loop */ /* but we need to catch zombie */ get_sigchld = 1; break; case SIGSEGV: sleep(60); _exit(1); break; case SIGHUP: Restart = 1; GeneralStopRequested = 1; break; default: GeneralStopRequested = 1; break; } errno = saved_errno; } static int dm_socket(int domain) { int sock, err; if ((sock = socket(domain, SOCK_STREAM, 0)) == -1) { err = errno; trace(TRACE_FATAL, "%s,%s: %s", __FILE__, __func__, strerror(err)); } trace(TRACE_DEBUG, "%s,%s: done", __FILE__, __func__); return sock; } static int dm_bind_and_listen(int sock, struct sockaddr *saddr, socklen_t len, int backlog) { int err; /* bind the address */ if ((bind(sock, saddr, len)) == -1) { err = errno; trace(TRACE_DEBUG, "%s,%s: failed", __FILE__, __func__); return err; } if ((listen(sock, backlog)) == -1) { err = errno; trace(TRACE_DEBUG, "%s,%s: failed", __FILE__, __func__); return err; } trace(TRACE_DEBUG, "%s,%s: done", __FILE__, __func__); return 0; } static int create_unix_socket(serverConfig_t * conf) { int sock, err; struct sockaddr_un saServer; conf->resolveIP=0; sock = dm_socket(PF_UNIX); /* setup sockaddr_un */ memset(&saServer, 0, sizeof(saServer)); saServer.sun_family = AF_UNIX; strncpy(saServer.sun_path,conf->socket, sizeof(saServer.sun_path)); trace(TRACE_DEBUG, "%s,%s: creating socket on [%s] with backlog [%d]", __FILE__, __func__, conf->socket, conf->backlog); err = dm_bind_and_listen(sock, (struct sockaddr *)&saServer, sizeof(saServer), conf->backlog); if (err != 0) { close(sock); trace(TRACE_FATAL, "%s,%s: Fatal error, could not bind to [%s] %s", __FILE__, __func__, conf->socket, strerror(err)); } chmod(conf->socket, 02777); return sock; } static int create_inet_socket(const char * const ip, int port, int backlog) { int sock, err; struct sockaddr_in saServer; int so_reuseaddress = 1; sock = dm_socket(PF_INET); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddress, sizeof(so_reuseaddress)); /* setup sockaddr_in */ memset(&saServer, 0, sizeof(saServer)); saServer.sin_family = AF_INET; saServer.sin_port = htons(port); trace(TRACE_DEBUG, "%s,%s: creating socket on [%s:%d] with backlog [%d]", __FILE__, __func__, ip, port, backlog); if (ip[0] == '*') { saServer.sin_addr.s_addr = htonl(INADDR_ANY); } else if (! (inet_aton(ip, &saServer.sin_addr))) { close(sock); trace(TRACE_FATAL, "%s,%s: IP invalid [%s]", __FILE__, __func__, ip); } err = dm_bind_and_listen(sock, (struct sockaddr *)&saServer, sizeof(saServer), backlog); if (err != 0) { close(sock); trace(TRACE_FATAL, "%s,%s: Fatal error, could not bind to [%s:%d] %s", __FILE__, __func__, ip, port, strerror(err)); } return sock; } void CreateSocket(serverConfig_t * conf) { if (strlen(conf->socket) > 0) conf->listenSocket = create_unix_socket(conf); else conf->listenSocket = create_inet_socket(conf->ip, conf->port, conf->backlog); } void ClearConfig(serverConfig_t * conf) { memset(conf, 0, sizeof(serverConfig_t)); } 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, "%s,%s: no value for NCHILDREN in config file", __FILE__, __func__); if ((config->startChildren = atoi(val)) <= 0) trace(TRACE_FATAL, "%s,%s: value for NCHILDREN is invalid: [%d]", __FILE__, __func__, config->startChildren); trace(TRACE_DEBUG, "%s,%s: server will create [%d] children", __FILE__, __func__, config->startChildren); /* read items: MAXCONNECTS */ config_get_value("MAXCONNECTS", service, val); if (strlen(val) == 0) trace(TRACE_FATAL, "%s,%s: no value for MAXCONNECTS in config file", __FILE__, __func__); if ((config->childMaxConnect = atoi(val)) <= 0) trace(TRACE_FATAL, "%s,%s: value for MAXCONNECTS is invalid: [%d]", __FILE__, __func__, config->childMaxConnect); trace(TRACE_DEBUG, "%s,%s: children will make max. [%d] connections", __FILE__, __func__, config->childMaxConnect); /* read items: TIMEOUT */ config_get_value("TIMEOUT", service, val); if (strlen(val) == 0) { trace(TRACE_DEBUG, "%s,%s: no value for TIMEOUT in config file", __FILE__, __func__); config->timeout = 0; } else if ((config->timeout = atoi(val)) <= 30) trace(TRACE_FATAL, "%s,%s: value for TIMEOUT is invalid: [%d]", __FILE__, __func__, config->timeout); trace(TRACE_DEBUG, "%s,%s: timeout [%d] seconds", __FILE__, __func__, config->timeout); /* SOCKET */ config_get_value("SOCKET", service, val); if (strlen(val) == 0) trace(TRACE_DEBUG,"%s,%s: no value for SOCKET in config file", __FILE__, __func__); strncpy(config->socket, val, FIELDSIZE); trace(TRACE_DEBUG, "%s,%s: socket %s", __FILE__, __func__, config->socket); /* read items: PORT */ config_get_value("PORT", service, val); if (strlen(val) == 0) trace(TRACE_FATAL, "%s,%s: no value for PORT in config file", __FILE__, __func__); if ((config->port = atoi(val)) <= 0) trace(TRACE_FATAL, "%s,%s: value for PORT is invalid: [%d]", __FILE__, __func__, config->port); trace(TRACE_DEBUG, "%s,%s: binding to PORT [%d]", __FILE__, __func__, config->port); /* read items: BINDIP */ config_get_value("BINDIP", service, val); if (strlen(val) == 0) trace(TRACE_FATAL, "%s,%s: no value for BINDIP in config file", __FILE__, __func__); strncpy(config->ip, val, IPLEN); config->ip[IPLEN - 1] = '\0'; trace(TRACE_DEBUG, "%s,%s: binding to IP [%s]", __FILE__, __func__, config->ip); /* read items: BACKLOG */ config_get_value("BACKLOG", service, val); if (strlen(val) == 0) { trace(TRACE_DEBUG, "%s,%s: no value for BACKLOG in config file. Using default value [%d]", __FILE__, __func__, BACKLOG); config->backlog = BACKLOG; } else if ((config->backlog = atoi(val)) <= 0) trace(TRACE_FATAL, "%s,%s: value for BACKLOG is invalid: [%d]", __FILE__, __func__, config->backlog); /* read items: RESOLVE_IP */ config_get_value("RESOLVE_IP", service, val); if (strlen(val) == 0) trace(TRACE_DEBUG, "%s,%s: no value for RESOLVE_IP in config file", __FILE__, __func__); config->resolveIP = (strcasecmp(val, "yes") == 0); trace(TRACE_DEBUG, "%s,%s: %sresolving client IP", __FILE__, __func__, 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, "%s,%s: no value for %s_BEFORE_SMTP in config file", __FILE__, __func__, service); config->service_before_smtp = (strcasecmp(val, "yes") == 0); trace(TRACE_DEBUG, "%s,%s: %s %s-before-SMTP", __FILE__, __func__, 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, "%s,%s: no value for EFFECTIVE_USER in config file", __FILE__, __func__); strncpy(config->serverUser, val, FIELDSIZE); config->serverUser[FIELDSIZE - 1] = '\0'; trace(TRACE_DEBUG, "%s,%s: effective user shall be [%s]", __FILE__, __func__, config->serverUser); /* read items: EFFECTIVE-GROUP */ config_get_value("EFFECTIVE_GROUP", service, val); if (strlen(val) == 0) trace(TRACE_FATAL, "%s,%s: no value for EFFECTIVE_GROUP in config file", __FILE__, __func__); strncpy(config->serverGroup, val, FIELDSIZE); config->serverGroup[FIELDSIZE - 1] = '\0'; trace(TRACE_DEBUG, "%s,%s: effective group shall be [%s]", __FILE__, __func__, config->serverGroup); /* read items: MINSPARECHILDREN */ config_get_value("MINSPARECHILDREN", service, val); if (strlen(val) == 0) trace(TRACE_FATAL, "%s,%s: no value for MINSPARECHILDREN in config file", __FILE__, __func__); if ( (config->minSpareChildren = atoi(val)) <= 0) trace(TRACE_FATAL, "%s,%s: value for MINSPARECHILDREN is invalid: [%d]", __FILE__, __func__, config->minSpareChildren); trace(TRACE_DEBUG, "%s,%s: will maintain minimum of [%d] spare children in reserve", __FILE__, __func__, config->minSpareChildren); /* read items: MAXSPARECHILDREN */ config_get_value("MAXSPARECHILDREN", service, val); if (strlen(val) == 0) trace(TRACE_FATAL, "%s,%s: no value for MAXSPARECHILDREN in config file", __FILE__, __func__); if ( (config->maxSpareChildren = atoi(val)) <= 0) trace(TRACE_FATAL, "%s,%s: value for MAXSPARECHILDREN is invalid: [%d]", __FILE__, __func__, config->maxSpareChildren); trace(TRACE_DEBUG, "%s,%s: will maintain maximum of [%d] spare children in reserve", __FILE__, __func__, config->maxSpareChildren); /* read items: MAXCHILDREN */ config_get_value("MAXCHILDREN", service, val); if (strlen(val) == 0) trace(TRACE_FATAL, "%s,%s: no value for MAXCHILDREN in config file", __FILE__, __func__); if ( (config->maxChildren = atoi(val)) <= 0) trace(TRACE_FATAL, "%s,%s: value for MAXCHILDREN is invalid: [%d]", __FILE__, __func__, config->maxSpareChildren); trace(TRACE_DEBUG, "%s,%s: will allow maximum of [%d] children", __FILE__, __func__, config->maxChildren); }