/*
 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
 *
 * $Id: serverchild.c 2199 2006-07-18 11:07:53Z paul $
 * 
 * function implementations of server children code (connection handling)
 */

#include "dbmail.h"

volatile sig_atomic_t ChildStopRequested = 0;
volatile sig_atomic_t childSig = 0;
volatile sig_atomic_t alarm_occured = 0;

int connected = 0;
volatile clientinfo_t client;

static void disconnect_all(void);

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! */
	switch (sig) {
	case SIGCHLD:
		break;
	case SIGALRM:
		alarm_occured = 1;
		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(SIGALRM,	&act, 0);
	sigaction(SIGCHLD,	&act, 0);
	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)
{
	pid_t pid = fork();

	if (! pid) {
		if (child_register() == -1) {
			trace(TRACE_FATAL, "%s,%s: child_register failed", 
				__FILE__, __func__);
			_exit(0);
		}
	
 		ChildStopRequested = 0;
		alarm_occured = 0;
		childSig = 0;
 		SetChildSigHandler();
		
 		trace(TRACE_INFO, "%s,%s: signal handler placed, going to perform task now",
			__FILE__, __func__);
 		
		if (PerformChildTask(info) == -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 PerformChildTask(ChildInfo_t * info)
{
	int i, len, serr, clientSocket;
	struct sockaddr_in saClient;
	struct hostent *clientHost;

	if (!info) {
		trace(TRACE_ERROR, "%s,%s: NULL info supplied", 
				__FILE__, __func__);
		return -1;
	}

	if (db_connect() != 0) {
		trace(TRACE_ERROR, "%s,%s: could not connect to database", 
				__FILE__, __func__);
		return -1;
	}

	if (auth_connect() != 0) {
		trace(TRACE_ERROR, "%s,%s: could not connect to authentication", 
				__FILE__, __func__);
		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, "%s,%s: database has gone away", 
					__FILE__, __func__);
			ChildStopRequested=1;
			continue;
		}

		trace(TRACE_INFO, "%s,%s: waiting for connection", 
				__FILE__, __func__);

		child_reg_disconnected();

		/* wait for connect */
		len = sizeof(saClient);
		clientSocket = accept(info->listenSocket, (struct sockaddr *) &saClient, (socklen_t *)&len);
		if (clientSocket == -1) {
			serr = errno;
			i--;	/* don't count this as a connect */
			trace(TRACE_INFO, "%s,%s: accept failed [%s]", 
					__FILE__, __func__, strerror(serr));
			errno = serr;
			continue;	/* accept failed, refuse connection & continue */
		}

		child_reg_connected();
		
		memset((void *)&client, 0, sizeof(client));	/* zero-init */

		client.timeoutMsg = info->timeoutMsg;
		client.timeout = info->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, "%s,%s: incoming connection from [%s (%s)] by pid [%d]",
					__FILE__, __func__,
			      client.ip_src,
			      client.clientname[0] ? client.clientname : "Lookup failed", getpid());
		} else {
			trace(TRACE_MESSAGE, "%s,%s: incoming connection from [%s] by pid [%d]", 
					__FILE__, __func__,
			      client.ip_src, getpid());
		}
		
		/* make streams */
		if (!(client.rx = fdopen(dup(clientSocket), "r"))) {
			/* read-FILE opening failure */
			trace(TRACE_ERROR, "%s,%s: error opening read file stream", 
					__FILE__, __func__);
			close(clientSocket);
			continue;
		}

		if (!(client.tx = fdopen(clientSocket, "w"))) {
			/* write-FILE opening failure */
			trace(TRACE_ERROR, "%s,%s: error opening write file stream", 
					__FILE__, __func__);
			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, "%s,%s: client info init complete, calling client handler",
				__FILE__, __func__);

		/* streams are ready, perform handling */
		info->ClientHandler((clientinfo_t *)&client);

		trace(TRACE_DEBUG, "%s,%s: client handling complete, closing streams",
				__FILE__, __func__);
		client_close();
		trace(TRACE_INFO, "%s,%s: connection closed", 
				__FILE__, __func__);
	}

	if (!ChildStopRequested)
		trace(TRACE_ERROR, "%s,%s: maximum number of connections reached, stopping now", 
				__FILE__, __func__);
	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, "%s,%s: stop requested", 
				__FILE__, __func__);
	}

	child_reg_disconnected();
	disconnect_all();
	
	return 0;
}

int manage_start_cli_server(ChildInfo_t * info)
{
	if (!info) {
		trace(TRACE_ERROR, "%s,%s: NULL info supplied", 
				__FILE__, __func__);
		return -1;
	}

	if (db_connect() != 0) {
		trace(TRACE_ERROR, "%s,%s: could not connect to database", 
				__FILE__, __func__);
		return -1;
	}

	if (auth_connect() != 0) {
		trace(TRACE_ERROR, "%s,%s: could not connect to authentication", 
				__FILE__, __func__);
		return -1;
	}

	srand((int) ((int) time(NULL) + (int) getpid()));
	connected = 1;

	if (db_check_connection()) {
		trace(TRACE_ERROR, "%s,%s: database has gone away", 
				__FILE__, __func__);
		return -1;
	}

		
	memset((void *)&client, 0, sizeof(client));	/* zero-init */

	client.timeoutMsg = info->timeoutMsg;
	client.timeout = info->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,
	      "%s,%s: client info init complete, calling client handler", __FILE__, __func__);

	/* streams are ready, perform handling */
	info->ClientHandler((clientinfo_t *)&client);

	trace(TRACE_DEBUG,
	      "%s,%s: client handling complete, closing streams", __FILE__, __func__);
	client_close();
	trace(TRACE_INFO, "%s,%s: connection closed", __FILE__, __func__);
	
	disconnect_all();
	
	return 0;
}




syntax highlighted by Code2HTML, v. 0.9.1