/*
  $Id: serverchild.c 2087 2006-04-28 14:26:28Z paul $
 Copyright (C) 1999-2004 IC & S  dbmail@ic-s.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)
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "debug.h"
#include "serverchild.h"
#include "db.h"
#include "auth.h"
#include "clientinfo.h"
#include "pool.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#ifdef PROC_TITLES
#include "proctitleutils.h"
#endif

int ChildStopRequested = 0;
int connected = 0;
clientinfo_t client;

int PerformChildTask(ChildInfo_t * info);

static void client_close(void);
static void disconnect_all(void);

void client_close(void)
{
	if (client.tx) {
		trace(TRACE_DEBUG,"%s,%s: closing write stream", __FILE__,__func__);
		fflush(client.tx);
		fclose(client.tx);	/* closes clientSocket as well */
		client.tx = NULL;
	}
	if (client.rx) {
		trace(TRACE_DEBUG,"%s,%s: closing read stream", __FILE__,__func__);
		shutdown(fileno(client.rx), SHUT_RDWR);
		fclose(client.rx);
		client.rx = NULL;
	}
}

void disconnect_all(void)
{
	if (! connected)
		return;
	
	trace(TRACE_DEBUG,
		"%s,%s: database connection still open, closing",
		__FILE__,__func__);
	db_disconnect();
	auth_disconnect();
	connected = 0;		/* FIXME a signal between this line and the previous one 
				 * would screw things up. Would like to have all this in
				 * db_disconnect() making 'connected' obsolete
				 */


}

void noop_child_sig_handler(int sig UNUSED, siginfo_t *info UNUSED, void *data UNUSED)
{
	return;
}

void active_child_sig_handler(int sig, siginfo_t * info UNUSED, void *data UNUSED)
{
	int saved_errno = errno;
	
	static int triedDisconnect = 0;

	/* perform reinit at SIGHUP otherwise exit, but do nothing on
	 *  SIGCHLD*/
	switch (sig) {
	case SIGCHLD:
		break;
	case SIGALRM:
		client_close();
		break;

	case SIGHUP:
	case SIGTERM:
	case SIGQUIT:
	case SIGSTOP:
		if (ChildStopRequested) {
			/* already caught this signal, exit the hard way now */
			client_close();
			disconnect_all();
			child_unregister();
			exit(1);
		}
		DelChildSigHandler();
	 	ChildStopRequested = 1;
		break;
	default:
		/* bad shtuff, exit */

		/*
		 * For some reason i have not yet determined the process starts eating up
		 * all CPU time when trying to disconnect.
		 * For now: just bail out :-)
		 */
		child_unregister();
		_exit(1);

		if (!triedDisconnect) {
			triedDisconnect = 1;
			client_close();
			disconnect_all();
		}

		child_unregister();
		exit(1);
	}

	errno = saved_errno;
}



/*
 * SetChildSigHandler()
 * 
 * sets the signal handler for a child proces
 */
int SetChildSigHandler()
{
	struct sigaction act;

	/* init & install signal handlers */
	memset(&act, 0, sizeof(act));

	act.sa_sigaction = active_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(SIGPIPE, &act, 0);
	sigaction(SIGFPE, &act, 0);
	sigaction(SIGSEGV, &act, 0);
	sigaction(SIGTERM, &act, 0);
	sigaction(SIGHUP, &act, 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(SIGPIPE, &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;
 		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, clientSocket;
	socklen_t len;
	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 (db_check_version() != 0) 
		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__);
			return -1;
		}
	
		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, &len);

	
		if (clientSocket == -1) {
			i--;	/* don't count this as a connect */
			trace(TRACE_INFO, "%s,%s: accept failed",
					__FILE__, __func__);
			continue;	/* accept failed, refuse connection & continue */
		}

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

		client.timeoutMsg = info->timeoutMsg;
		client.timeout = info->timeout;
		strncpy(client.ip, 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(client.clientname, clientHost->h_name, FIELDSIZE);

			trace(TRACE_MESSAGE, "%s,%s: incoming connection from [%s (%s)]",
					__FILE__, __func__,
			      client.ip,
			      client.clientname[0] ? client.
			      clientname : "Lookup failed");
		} else {
			trace(TRACE_MESSAGE, "%s,%s: incoming connection from [%s]",
					__FILE__, __func__, client.ip);
		}

		/* 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(&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(&client);
#ifdef PROC_TITLES
		set_proc_title("%s", "Idle");
#endif

		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
		trace(TRACE_ERROR, "%s,%s: stop requested",
				__FILE__, __func__);

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


syntax highlighted by Code2HTML, v. 0.9.1