/*
$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);
}
syntax highlighted by Code2HTML, v. 0.9.1