/*
Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl
Copyright (c) 2005-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.
*/
/*
* serverchild.c
*
*
*
* function implementations of server children code (connection handling)
*/
#include "dbmail.h"
#define THIS_MODULE "serverchild"
volatile sig_atomic_t ChildStopRequested = 0;
volatile sig_atomic_t childSig = 0;
extern volatile sig_atomic_t alarm_occured;
static int connected = 0;
static int selfPipe[2];
volatile clientinfo_t client;
static void disconnect_all(void);
static int PerformChildTask(ChildInfo_t * info);
void client_close(void)
{
if (client.tx) {
fflush(client.tx);
fclose(client.tx); /* closes clientSocket as well */
client.tx = NULL;
}
if (client.rx) {
shutdown(fileno(client.rx), SHUT_RDWR);
fclose(client.rx);
client.rx = NULL;
}
}
void disconnect_all(void)
{
if (! connected)
return;
db_disconnect();
auth_disconnect();
connected = 0;
}
void noop_child_sig_handler(int sig, siginfo_t *info UNUSED, void *data UNUSED)
{
if (sig == SIGSEGV)
_exit(0);
}
void active_child_sig_handler(int sig, siginfo_t * info UNUSED, void *data UNUSED)
{
int saved_errno = errno;
/* Perform reinit at SIGHUP otherwise exit, but do nothing on
* SIGCHLD. Make absolutely sure that everything downwind of
* this function is signal-safe! Symptoms of signal-unsafe
* calls are random errors like this:
* *** glibc detected *** corrupted double-linked list: 0x0805f028 ***
* Right, so keep that in mind! */
if (selfPipe[1] > -1)
write(selfPipe[1], "S", 1);
switch (sig) {
case SIGCHLD:
break;
case SIGALRM:
alarm_occured = 1;
break;
case SIGPIPE:
break;
default:
ChildStopRequested = 1;
childSig = sig;
break;
}
errno = saved_errno;
}
/*
* SetChildSigHandler()
*
* sets the signal handler for a child proces
*/
int SetChildSigHandler()
{
struct sigaction act;
struct sigaction rstact;
memset(&act, 0, sizeof(act));
memset(&rstact, 0, sizeof(rstact));
act.sa_sigaction = active_child_sig_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
rstact.sa_sigaction = active_child_sig_handler;
sigemptyset(&rstact.sa_mask);
rstact.sa_flags = SA_SIGINFO | SA_RESETHAND;
sigaddset(&act.sa_mask, SIGINT);
sigaddset(&act.sa_mask, SIGQUIT);
sigaddset(&act.sa_mask, SIGILL);
sigaddset(&act.sa_mask, SIGBUS);
sigaddset(&act.sa_mask, SIGFPE);
sigaddset(&act.sa_mask, SIGSEGV);
sigaddset(&act.sa_mask, SIGTERM);
sigaddset(&act.sa_mask, SIGHUP);
sigaction(SIGINT, &rstact, 0);
sigaction(SIGQUIT, &rstact, 0);
sigaction(SIGILL, &rstact, 0);
sigaction(SIGBUS, &rstact, 0);
sigaction(SIGFPE, &rstact, 0);
sigaction(SIGSEGV, &rstact, 0);
sigaction(SIGTERM, &rstact, 0);
sigaction(SIGHUP, &rstact, 0);
sigaction(SIGPIPE, &rstact, 0);
sigaction(SIGALRM, &act, 0);
sigaction(SIGCHLD, &act, 0);
TRACE(TRACE_INFO, "signal handler placed");
return 0;
}
int DelChildSigHandler()
{
struct sigaction act;
/* init & install signal handlers */
memset(&act, 0, sizeof(act));
act.sa_sigaction = noop_child_sig_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
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);
sigaction(SIGALRM, &act, 0);
return 0;
}
/*
* CreateChild()
*
* creates a new child, returning only to the parent process
*/
pid_t CreateChild(ChildInfo_t * info)
{
extern int isGrandChildProcess;
pid_t pid;
pid = fork();
if (! pid) {
if ( child_register() == -1 ) {
TRACE(TRACE_FATAL, "child_register failed");
_exit(0);
}
isGrandChildProcess = 1;
ChildStopRequested = 0;
alarm_occured = 0;
childSig = 0;
if (pipe(selfPipe))
return -1;
fcntl(selfPipe[0], F_SETFL, O_NONBLOCK);
fcntl(selfPipe[1], F_SETFL, O_NONBLOCK);
SetChildSigHandler();
if (PerformChildTask(info) == -1) {
close(selfPipe[0]); selfPipe[0] = -1;
close(selfPipe[1]); selfPipe[1] = -1;
return -1;
}
child_unregister();
exit(0);
} else {
usleep(5000);
/* check for failed forkes */
if (waitpid(pid, NULL, WNOHANG|WUNTRACED) == pid)
return -1;
return pid;
}
}
int select_and_accept(ChildInfo_t * info, int * clientSocket, struct sockaddr * saClient)
{
fd_set rfds;
int ip, result, flags;
int active = 0, maxfd = 0;
socklen_t len;
/* This is adapted from man 2 select */
FD_ZERO(&rfds);
for (ip = 0; ip < info->numSockets; ip++) {
FD_SET(info->listenSockets[ip], &rfds);
maxfd = MAX(maxfd, info->listenSockets[ip]);
}
FD_SET(selfPipe[0], &rfds);
maxfd = MAX(maxfd, selfPipe[0]);
/* A null timeval means block indefinitely until there's activity. */
result = select(maxfd+1, &rfds, NULL, NULL, NULL);
if (result < 1)
return -1;
// Clear the self-pipe and return; we received a signal
// and we need to loop again upstream to handle it.
// See http://cr.yp.to/docs/selfpipe.html
if (FD_ISSET(selfPipe[0], &rfds)) {
char buf[1];
while (read(selfPipe[0], buf, 1) > 0)
;
return -1;
}
/* This is adapted from man 2 select */
for (ip = 0; ip < info->numSockets; ip++) {
if (FD_ISSET(info->listenSockets[ip], &rfds)) {
active = ip;
break;
}
}
/* accept the active fd */
len = sizeof(struct sockaddr_in);
// the listenSockets are set non-blocking in server.c,create_inet_socket
*clientSocket = accept(info->listenSockets[active], saClient, &len);
if (*clientSocket < 0)
return -1;
// the clientSocket *must* be blocking
flags = fcntl(*clientSocket, F_GETFL);
if (*clientSocket > 0)
fcntl(*clientSocket, F_SETFL, flags & ~ O_NONBLOCK);
TRACE(TRACE_INFO, "connection accepted");
return 0;
}
int PerformChildTask(ChildInfo_t * info)
{
int i, clientSocket, result;
struct sockaddr_in saClient;
struct hostent *clientHost;
if (!info) {
TRACE(TRACE_ERROR, "NULL info supplied");
return -1;
}
if (db_connect() != 0) {
TRACE(TRACE_ERROR, "could not connect to database");
return -1;
}
if (auth_connect() != 0) {
TRACE(TRACE_ERROR, "could not connect to authentication");
return -1;
}
srand((int) ((int) time(NULL) + (int) getpid()));
connected = 1;
for (i = 0; i < info->maxConnect && !ChildStopRequested; i++) {
if (db_check_connection()) {
TRACE(TRACE_ERROR, "database has gone away");
ChildStopRequested=1;
continue;
}
child_reg_disconnected();
/* wait for connect */
result = select_and_accept(info, &clientSocket, (struct sockaddr *) &saClient);
if (result != 0) {
i--; /* don't count this as a connect */
continue; /* accept failed, refuse connection & continue */
}
child_reg_connected();
memset((void *)&client, 0, sizeof(client)); /* zero-init */
client.timeout = info->timeout;
client.login_timeout = info->login_timeout;
strncpy((char *)client.ip_src, inet_ntoa(saClient.sin_addr), IPNUM_LEN);
client.clientname[0] = '\0';
if (info->resolveIP) {
clientHost = gethostbyaddr((char *) &saClient.sin_addr,
sizeof(saClient.sin_addr), saClient.sin_family);
if (clientHost && clientHost->h_name)
strncpy((char *)client.clientname, clientHost->h_name, FIELDSIZE);
TRACE(TRACE_MESSAGE, "incoming connection from [%s (%s)] by pid [%d]",
client.ip_src,
client.clientname[0] ? client.clientname : "Lookup failed", getpid());
} else {
TRACE(TRACE_MESSAGE, "incoming connection from [%s] by pid [%d]",
client.ip_src, getpid());
}
child_reg_connected_client((const char *)client.ip_src, (const char *)client.clientname);
/* make streams */
if (!(client.rx = fdopen(dup(clientSocket), "r"))) {
/* read-FILE opening failure */
TRACE(TRACE_ERROR, "error opening read file stream");
close(clientSocket);
continue;
}
if (!(client.tx = fdopen(clientSocket, "w"))) {
/* write-FILE opening failure */
TRACE(TRACE_ERROR, "error opening write file stream");
fclose(client.rx);
close(clientSocket);
memset((void *)&client, 0, sizeof(client));
continue;
}
setvbuf(client.tx, (char *) NULL, _IOLBF, 0);
setvbuf(client.rx, (char *) NULL, _IOLBF, 0);
TRACE(TRACE_DEBUG, "client info init complete, calling client handler");
/* streams are ready, perform handling */
info->ClientHandler((clientinfo_t *)&client);
TRACE(TRACE_DEBUG, "client handling complete, closing streams");
client_close();
TRACE(TRACE_INFO, "connection closed");
}
if (!ChildStopRequested)
TRACE(TRACE_ERROR, "maximum number of connections reached, stopping now");
else{
switch(childSig){
case SIGHUP:
case SIGTERM:
case SIGQUIT:
client_close();
disconnect_all();
child_unregister();
exit(1);
default:
child_unregister();
_exit(1);
}
TRACE(TRACE_ERROR, "stop requested");
}
child_reg_disconnected();
disconnect_all();
return 0;
}
int manage_start_cli_server(ChildInfo_t * info)
{
if (!info) {
TRACE(TRACE_ERROR, "NULL info supplied");
return -1;
}
if (db_connect() != 0) {
TRACE(TRACE_ERROR, "could not connect to database");
return -1;
}
if (auth_connect() != 0) {
TRACE(TRACE_ERROR, "could not connect to authentication");
return -1;
}
srand((int) ((int) time(NULL) + (int) getpid()));
connected = 1;
if (db_check_connection()) {
TRACE(TRACE_ERROR, "database has gone away");
return -1;
}
memset((void *)&client, 0, sizeof(client)); /* zero-init */
client.timeout = info->timeout;
client.login_timeout = info->login_timeout;
/* make streams */
client.rx = stdin;
client.tx = stdout;
setvbuf(client.tx, (char *) NULL, _IOLBF, 0);
setvbuf(client.rx, (char *) NULL, _IOLBF, 0);
TRACE(TRACE_DEBUG, "client info init complete, calling client handler");
/* streams are ready, perform handling */
info->ClientHandler((clientinfo_t *)&client);
TRACE(TRACE_DEBUG, "client handling complete, closing streams");
client_close();
TRACE(TRACE_INFO, "connection closed");
disconnect_all();
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1