/*
 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.
*/

/* $Id: pop3d.c 1712 2005-03-26 20:23:18Z aaron $
*
* pop3d.c
*
* main prg for pop3 daemon
*/

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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include "dm_getopt.h"
#include "server.h"
#include "debug.h"
#include "misc.h"
#include "pidfile.h"
#include "dbmail.h"
#include "clientinfo.h"
#include "pool.h"
#include "pop3.h"
#ifdef PROC_TITLES
#include "proctitleutils.h"
#endif


#define PNAME "dbmail/pop3d"

/* server timeout error */
#define POP_TIMEOUT_MSG "-ERR I'm leaving, you're tooo slow\r\n"

char *pidFile = DEFAULT_PID_DIR "dbmail-pop3d" DEFAULT_PID_EXT;
char *configFile = DEFAULT_CONFIG_FILE;

/* set up database login data */
extern db_param_t _db_params;

static void SetConfigItems(serverConfig_t * config);
static void Daemonize(void);
static int SetMainSigHandler(void);
static void MainSigHandler(int sig, siginfo_t * info, void *data);

/* also used in pop3.c */
int pop_before_smtp = 0;

static int mainRestart = 0;
static int mainStop = 0;

int do_showhelp(void) {
	printf("*** dbmail-pop3d ***\n");

	printf("This daemon provides Post Office Protocol v3 services.\n");
	printf("See the man page for more info.\n");

        printf("\nCommon options for all DBMail daemons:\n");
	printf("     -f file   specify an alternative config file\n");
	printf("     -p file   specify an alternative runtime pidfile\n");
	printf("     -n        do not daemonize (no children are forked)\n");
	printf("     -v        log to the console (only useful with -n)\n");
	printf("     -V        show the version\n");
	printf("     -h        show this help message\n");

	return 0;
}


#ifdef PROC_TITLES
int main(int argc, char *argv[], char **envp)
#else
int main(int argc, char *argv[])
#endif
{
	serverConfig_t config;
	int result, status, no_daemonize = 0;
	pid_t pid;
	int opt;

	openlog(PNAME, LOG_PID, LOG_MAIL);

	/* get command-line options */
	dm_opterr = 0;		/* suppress error message from getopt() */
	while ((opt = dm_getopt(argc, argv, "vVhqnf:p:")) != -1) {
		switch (opt) {
		case 'v':
			/* TODO: Perhaps verbose should log to the console with -n? */
			break;
		case 'V':
			printf("DBMail: dbmail-pop3d\n"
			       "Version: %s\n"
			       "$Revision: 1712 $\n"
			       "Copyright: %s\n", VERSION, COPYRIGHT);
			return 0;
		case 'n':
			/* TODO: We should also prevent children from forking,
			 * but for now we'll just set a flag and skip Daemonize. */
			no_daemonize = 1;
			break;
		case 'h':
			do_showhelp();
			return 0;
		case 'p':
			if (dm_optarg && strlen(dm_optarg) > 0)
				pidFile = dm_optarg;
			else {
				fprintf(stderr,
					"dbmail-pop3d: -p requires a filename "
					"argument\n\n");
				return 1;
			}
			break;
		case 'f':
			if (dm_optarg && strlen(dm_optarg) > 0)
				configFile = dm_optarg;
			else {
				fprintf(stderr,
					"dbmail-pop3d: -f requires a filename "
					"argument\n\n");
				return 1;
			}
			break;

		default:
			break;
		}
	}

	SetMainSigHandler();
	
	/* TODO: don't spawn children, either. this is at least a good start. */
	if (!no_daemonize)
		Daemonize();

	/* We write the pidFile after Daemonize because
	 * we may actually be a child of the original process. */
	pidfile_create(pidFile, getpid());

	result = 0;
	do {
		mainStop = 0;
		mainRestart = 0;

		trace(TRACE_DEBUG, "main(): reading config");
#ifdef PROC_TITLES
		init_set_proc_title(argc, argv, envp, PNAME);
		set_proc_title("%s", "Idle");
#endif

		ReadConfig("POP", configFile);
		ReadConfig("DBMAIL", configFile);
		SetConfigItems(&config);
		SetTraceLevel("POP");
		GetDBParams(&_db_params);

		config.ClientHandler = pop3_handle_connection;
		config.timeoutMsg = POP_TIMEOUT_MSG;

		CreateSocket(&config);

		switch ((pid = fork())) {
		case -1:
			close(config.listenSocket);
			trace(TRACE_FATAL, "main(): fork failed [%s]",
			      strerror(errno));

		case 0:
			/* child process */
			drop_privileges(config.serverUser,
					config.serverGroup);
			result = StartServer(&config);

			trace(TRACE_INFO, "main(): server done, exit.");
			exit(result);

		default:
			/* parent process, wait for child to exit */
			while (waitpid(pid, &status, WNOHANG | WUNTRACED)
			       == 0) {
				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,
				      "main(): server has exited, exit status [%d]",
				      result);
			} else {
				/* child stopped or signaled, don't like */
				/* make sure it is dead */
				trace(TRACE_DEBUG,
				      "main(): server has not exited normally. Killing..");

				kill(pid, SIGKILL);
				result = 0;
			}
		}
		
		close(config.listenSocket);
		config_free();
	} while (result == 1 && !mainStop);	/* 1 means reread-config and restart */
	
	trace(TRACE_INFO, "main(): exit");
	return 0;
}


void MainSigHandler(int sig, siginfo_t * info UNUSED, void *data UNUSED)
{
	trace(TRACE_DEBUG, "MainSigHandler(): got signal [%d]", sig);

	if (sig == SIGHUP)
		mainRestart = 1;
	else
		mainStop = 1;
}


void Daemonize()
{
	if (fork())
		exit(0);
	setsid();

	if (fork())
		exit(0);
}


int SetMainSigHandler()
{
	struct sigaction act;

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

	act.sa_sigaction = MainSigHandler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;

	sigaction(SIGINT, &act, 0);
	sigaction(SIGQUIT, &act, 0);
	sigaction(SIGTERM, &act, 0);
	sigaction(SIGHUP, &act, 0);

	return 0;
}


void SetConfigItems(serverConfig_t * config)
{
	field_t val;

	/* read items: NCHILDREN */
	GetConfigValue("NCHILDREN", "POP", val);
	if (strlen(val) == 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): no value for NCHILDREN in config file");

	if ((config->startChildren = atoi(val)) <= 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): value for NCHILDREN is invalid: [%d]",
		      config->startChildren);

	trace(TRACE_DEBUG,
	      "SetConfigItems(): server will create  [%d] children",
	      config->startChildren);


	/* read items: MAXCONNECTS */
	GetConfigValue("MAXCONNECTS", "POP", val);
	if (strlen(val) == 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): no value for MAXCONNECTS in config file");

	if ((config->childMaxConnect = atoi(val)) <= 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): value for MAXCONNECTS is invalid: [%d]",
		      config->childMaxConnect);

	trace(TRACE_DEBUG,
	      "SetConfigItems(): children will make max. [%d] connections",
	      config->childMaxConnect);
	
	/* read items: MINSPARECHILDREN */
	GetConfigValue("MINSPARECHILDREN", "POP", val);
	if (strlen(val) == 0) {
		trace(TRACE_WARNING, 
			"SetConfigItems(): no value for MINSPARECHILDREN in config file. Using default value [2].");
		config->minSpareChildren = 2;
	} else if ( (config->minSpareChildren = atoi(val)) <= 0) {
		trace(TRACE_WARNING, 
			"SetConfigItems(): value for MINSPARECHILDREN is invalid: [%d]. Using default value [4].", 
			config->minSpareChildren);
		config->minSpareChildren = 4;
	}
 
	trace(TRACE_DEBUG, 
		"SetConfigItems(): will maintain minimum of [%d] spare children in reserve", 
		config->minSpareChildren);
   
   
	/* read items: MAXSPARECHILDREN */
	GetConfigValue("MAXSPARECHILDREN", "POP", val);
	if (strlen(val) == 0) {
		trace(TRACE_WARNING, 
			"SetConfigItems(): no value for MAXSPARECHILDREN in config file. Using default value [4].");
		config->maxSpareChildren = 4;
	} else if ( (config->maxSpareChildren = atoi(val)) <= 0) {
		trace(TRACE_WARNING, 
			"SetConfigItems(): value for MAXSPARECHILDREN is invalid: [%d]. Using default value [4].", 
			config->maxSpareChildren);
		config->maxSpareChildren = 4;
	}
 
	trace(TRACE_DEBUG, 
		"SetConfigItems(): will maintain maximum of [%d] spare children in reserve", 
		config->maxSpareChildren);
 
   
	/* read items: MAXCHILDREN */
	GetConfigValue("MAXCHILDREN", "POP", val);
	if (strlen(val) == 0) {
		trace(TRACE_WARNING, 
			"SetConfigItems(): no value for MAXCHILDREN in config file. Using defaults.");
		config->maxChildren = HARD_MAX_CHILDREN;
	} else if ( (config->maxChildren = atoi(val)) <= 0) {
		trace(TRACE_WARNING, 
			"SetConfigItems(): value for MAXCHILDREN is invalid: [%d]. Using defaults.", 
			config->maxSpareChildren);
		config->maxChildren = HARD_MAX_CHILDREN;
	} 
 
	trace(TRACE_DEBUG, 
		"SetConfigItems(): will allow maximum of [%d] children", 
		config->maxChildren);

	/* read items: TIMEOUT */
	GetConfigValue("TIMEOUT", "POP", val);
	if (strlen(val) == 0) {
		trace(TRACE_DEBUG,
		      "SetConfigItems(): no value for TIMEOUT in config file");
		config->timeout = 0;
	} else if ((config->timeout = atoi(val)) <= 30)
		trace(TRACE_FATAL,
		      "SetConfigItems(): value for TIMEOUT is invalid: [%d]",
		      config->timeout);

	trace(TRACE_DEBUG, "SetConfigItems(): timeout [%d] seconds",
	      config->timeout);


	/* read items: PORT */
	GetConfigValue("PORT", "POP", val);
	if (strlen(val) == 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): no value for PORT in config file");

	if ((config->port = atoi(val)) <= 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): value for PORT is invalid: [%d]",
		      config->port);

	trace(TRACE_DEBUG, "SetConfigItems(): binding to PORT [%d]",
	      config->port);


	/* read items: BINDIP */
	GetConfigValue("BINDIP", "POP", val);
	if (strlen(val) == 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): no value for BINDIP in config file");

	strncpy(config->ip, val, IPLEN);
	config->ip[IPLEN - 1] = '\0';

	trace(TRACE_DEBUG, "SetConfigItems(): binding to IP [%s]",
	      config->ip);


	/* read items: RESOLVE_IP */
	GetConfigValue("RESOLVE_IP", "POP", val);
	if (strlen(val) == 0)
		trace(TRACE_DEBUG,
		      "SetConfigItems(): no value for RESOLVE_IP in config file");

	config->resolveIP = (strcasecmp(val, "yes") == 0);

	trace(TRACE_DEBUG, "SetConfigItems(): %sresolving client IP",
	      config->resolveIP ? "" : "not ");


	/* read items: IMAP-BEFORE-SMTP */
	GetConfigValue("POP_BEFORE_SMTP", "POP", val);
	if (strlen(val) == 0)
		trace(TRACE_DEBUG,
		      "SetConfigItems(): no value for POP_BEFORE_SMTP  in config file");

	pop_before_smtp = (strcasecmp(val, "yes") == 0);

	trace(TRACE_DEBUG, "SetConfigItems(): %s POP-before-SMTP",
	      pop_before_smtp ? "Enabling" : "Disabling");


	/* read items: EFFECTIVE-USER */
	GetConfigValue("EFFECTIVE_USER", "POP", val);
	if (strlen(val) == 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): no value for EFFECTIVE_USER in config file");

	strncpy(config->serverUser, val, FIELDSIZE);
	config->serverUser[FIELDSIZE - 1] = '\0';

	trace(TRACE_DEBUG,
	      "SetConfigItems(): effective user shall be [%s]",
	      config->serverUser);


	/* read items: EFFECTIVE-GROUP */
	GetConfigValue("EFFECTIVE_GROUP", "POP", val);
	if (strlen(val) == 0)
		trace(TRACE_FATAL,
		      "SetConfigItems(): no value for EFFECTIVE_GROUP in config file");

	strncpy(config->serverGroup, val, FIELDSIZE);
	config->serverGroup[FIELDSIZE - 1] = '\0';

	trace(TRACE_DEBUG,
	      "SetConfigItems(): effective group shall be [%s]",
	      config->serverGroup);
}


syntax highlighted by Code2HTML, v. 0.9.1