/*
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"
#define THIS_MODULE "server"
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 mainStatus = 0;
volatile sig_atomic_t mainSig = 0;
volatile sig_atomic_t get_sigchld = 0;
volatile sig_atomic_t alarm_occured = 0;
int isChildProcess = 0;
int isGrandChildProcess = 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;
struct sigaction sact;
/* init & install signal handlers */
memset(&act, 0, sizeof(act));
memset(&sact, 0, sizeof(sact));
act.sa_sigaction = ParentSigHandler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sact.sa_sigaction = ParentSigHandler;
sigemptyset(&sact.sa_mask);
sact.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sact, 0);
sigaction(SIGINT, &sact, 0);
sigaction(SIGQUIT, &sact, 0);
sigaction(SIGILL, &sact, 0);
sigaction(SIGBUS, &sact, 0);
sigaction(SIGFPE, &sact, 0);
sigaction(SIGSEGV, &sact, 0);
sigaction(SIGTERM, &sact, 0);
sigaction(SIGHUP, &sact, 0);
sigaction(SIGUSR1, &sact, 0);
sigaction(SIGALRM, &act, 0);
return 0;
}
int server_setup(serverConfig_t *conf)
{
ParentPID = getpid();
Restart = 0;
GeneralStopRequested = 0;
get_sigchld = 0;
SetParentSigHandler();
childinfo.maxConnect = conf->childMaxConnect;
childinfo.listenSockets = g_memdup(conf->listenSockets, conf->ipcount * sizeof(int));
childinfo.numSockets = conf->ipcount;
childinfo.timeout = conf->timeout;
childinfo.login_timeout = conf->login_timeout;
childinfo.ClientHandler = conf->ClientHandler;
childinfo.resolveIP = conf->resolveIP;
return 0;
}
int StartCliServer(serverConfig_t * conf)
{
if (!conf)
TRACE(TRACE_FATAL, "NULL configuration");
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, "NULL configuration");
if (server_setup(conf))
return -1;
scoreboard_new(conf);
if (db_connect() != DM_SUCCESS)
TRACE(TRACE_FATAL, "Unable to connect to database.");
if (db_check_version() != 0) {
db_disconnect();
TRACE(TRACE_FATAL, "Unsupported database version.");
}
manage_start_children();
manage_spare_children();
TRACE(TRACE_DEBUG, "starting main service loop");
while (!GeneralStopRequested) {
if(get_sigchld){
get_sigchld = 0;
while((chpid = waitpid(-1,(int*)NULL,WNOHANG)) > 0)
scoreboard_release(chpid);
}
if (mainStatus) {
mainStatus = 0;
scoreboard_state();
}
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();
return Restart;
}
/* Should be called after a HUP to allow for log rotation,
* as the filesystem may want to give us new inodes and/or
* the user may have changed the log file configs. */
static void reopen_logs(serverConfig_t *conf)
{
int serr;
if (! (freopen(conf->log, "a", stdout))) {
serr = errno;
TRACE(TRACE_ERROR, "freopen failed on [%s] [%s]",
conf->log, strerror(serr));
}
if (! (freopen(conf->error_log, "a", stderr))) {
serr = errno;
TRACE(TRACE_ERROR, "freopen failed on [%s] [%s]",
conf->error_log, strerror(serr));
}
if (! (freopen("/dev/null", "r", stdin))) {
serr = errno;
TRACE(TRACE_ERROR, "freopen failed on stdin [%s]",
strerror(serr));
}
}
/* Should be called once to initially close the actual std{in,out,err}
* and open the redirection files. */
static void reopen_logs_fatal(serverConfig_t *conf)
{
int serr;
if (! (freopen(conf->log, "a", stdout))) {
serr = errno;
TRACE(TRACE_FATAL, "freopen failed on [%s] [%s]",
conf->log, strerror(serr));
}
if (! (freopen(conf->error_log, "a", stderr))) {
serr = errno;
TRACE(TRACE_FATAL, "freopen failed on [%s] [%s]",
conf->error_log, strerror(serr));
}
if (! (freopen("/dev/null", "r", stdin))) {
serr = errno;
TRACE(TRACE_FATAL, "freopen failed on stdin [%s]",
strerror(serr));
}
}
pid_t server_daemonize(serverConfig_t *conf)
{
assert(conf);
if (fork())
exit(0);
setsid();
if (fork())
exit(0);
chdir("/");
umask(0077);
reopen_logs_fatal(conf);
TRACE(TRACE_DEBUG, "sid: [%d]", getsid(0));
return getsid(0);
}
static void close_all_sockets(serverConfig_t *conf)
{
int i;
for (i = 0; i < conf->ipcount; i++) {
close(conf->listenSockets[i]);
}
}
int server_run(serverConfig_t *conf)
{
mainStop = 0;
mainRestart = 0;
mainStatus = 0;
mainSig = 0;
int serrno, status, result = 0;
pid_t pid = -1;
reopen_logs(conf);
CreateSocket(conf);
switch ((pid = fork())) {
case -1:
serrno = errno;
close_all_sockets(conf);
TRACE(TRACE_FATAL, "fork failed [%s]", strerror(serrno));
errno = serrno;
break;
case 0:
/* child process */
isChildProcess = 1;
if (drop_privileges(conf->serverUser, conf->serverGroup) < 0) {
mainStop = 1;
TRACE(TRACE_ERROR,"unable to drop privileges");
return 0;
}
result = StartServer(conf);
TRACE(TRACE_INFO, "server done, restart = [%d]",
result);
exit(result);
break;
default:
/* parent process, wait for child to exit */
while (waitpid(pid, &status, WNOHANG | WUNTRACED) == 0) {
if (mainStop || mainRestart || mainStatus){
TRACE(TRACE_DEBUG, "MainSigHandler(): got signal [%d]", mainSig);
if(mainStop) kill(pid, SIGTERM);
if(mainRestart) kill(pid, SIGHUP);
if(mainStatus) {
mainStatus = 0;
kill(pid, SIGUSR1);
}
}
sleep(2);
}
if (WIFEXITED(status)) {
/* child process terminated neatly */
result = WEXITSTATUS(status);
TRACE(TRACE_DEBUG, "server has exited, exit status [%d]",
result);
} else {
/* child stopped or signaled so make sure it is dead */
TRACE(TRACE_DEBUG, "server has not exited normally. Killing...");
kill(pid, SIGKILL);
result = 0;
}
if (strlen(conf->socket) > 0) {
if (unlink(conf->socket)) {
serrno = errno;
TRACE(TRACE_ERROR, "unlinking unix socket failed [%s]",
strerror(serrno));
errno = serrno;
}
}
break;
}
close_all_sockets(conf);
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;
case SIGUSR1:
mainStatus = 1;
break;
case SIGALRM:
alarm_occured = 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", strerror(err));
}
TRACE(TRACE_DEBUG, "done");
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, "failed");
return err;
}
if ((listen(sock, backlog)) == -1) {
err = errno;
TRACE(TRACE_DEBUG, "failed");
return err;
}
TRACE(TRACE_DEBUG, "done");
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, "creating socket on [%s] with backlog [%d]",
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, "Fatal error, could not bind to [%s] %s",
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, flags;
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, "creating socket on [%s:%d] with backlog [%d]",
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, "IP invalid [%s]", ip);
}
err = dm_bind_and_listen(sock, (struct sockaddr *)&saServer, sizeof(saServer), backlog);
if (err != 0) {
close(sock);
TRACE(TRACE_FATAL, "Fatal error, could not bind to [%s:%d] %s",
ip, port, strerror(err));
}
// man 2 accept says that if the connection disappears during the accept call
// accept will block forever unless it is set non-blocking with fcntl
flags = fcntl(sock, F_GETFL);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
return sock;
}
void CreateSocket(serverConfig_t * conf)
{
int i;
conf->listenSockets = g_new0(int, conf->ipcount);
if (strlen(conf->socket) > 0) {
conf->listenSockets[0] = create_unix_socket(conf);
} else {
for (i = 0; i < conf->ipcount; i++) {
conf->listenSockets[i] = create_inet_socket(conf->iplist[i], conf->port, conf->backlog);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1