/*
 * POP3Lite - 3lite POP3 Daemon
 * Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu>
 *
 * This file is part of POP3Lite.
 *
 * POP3Lite 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.
 *
 * POP3Lite 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "main.h"

#include <pop3lite.h>
#include <ltdl.h>
#include <glib.h>

#ifdef HAVE_GETOPT_H
#	include <getopt.h>
#endif

#include <stdio.h>
#include <signal.h>
#include <syslog.h>
#include <stdlib.h>
#include <unistd.h>

#include "cfg.h"
#include "core.h"
#include "module.h"

#ifdef WITH_STANDALONE_SUPPORT
#	include <errno.h>
#	include <sys/types.h>
#	include <sys/wait.h>
#	include <sys/socket.h>
#	include <netinet/in.h>
#	include <netdb.h>
#	include "standalone.h"
int errno;
unsigned long child_cnt = 0;
char *pidfn = LOCALSTATEDIR "/run/pop3lite.pid";
pid_t main_pid;
#endif

#define LINELEN 512	/* RFC 2449 */

static const char rcsid[]="$Id: main.c,v 1.29.2.3 2001/05/30 12:38:57 algernon Exp $";

extern int opterr;

static gboolean logout_timer_reset = 1;
static unsigned long logout_iv;

static void sig_handler ( int number );
static void sigalrm_handler ( int sig );
static void setup_signal_handlers ( void );
static void setup_sigalrm_handler ( void );
static int auto_logout_handler ( P3LControl *control );
static void main_loop_body ( GHashTable *command_table );
static void exit_hook ( void );
static void print_version ( void );
static void pop3lite_main_loop ( P3LControl *control );

/**
 * control: the main P3LControl struct
 *
 * This is declared global, becuase sig_handler uses it.
 * BTW, this is the struct through which the whole daemon is
 * implemented.
 **/
static P3LControl *control;

/**
 * setup_sigalrm_handler: sets up SIGALRM hander
 *
 * This is used to setup/reset the SIGALRM handler
 *
 * Returns: nothing
 **/

static void
setup_sigalrm_handler ( void )
{
	/* This clears the handler, just in case... */
	signal ( SIGALRM, SIG_IGN );
	signal ( SIGALRM, &sigalrm_handler );
	alarm ( 0 );
	alarm ( SIGALRM_INTERVAL );
}

/**
 * sigchld_handler: SIGC(H)LD handler
 *
 * SIGC(H)LD handler
 *
 * Returns: nothing
 **/
#ifdef WITH_STANDALONE_SUPPORT
static void
sigchld_handler ( int signo )
{
	pid_t child;
	int status;

	while ( ( child = waitpid ( -1, &status, WNOHANG ) ) > (pid_t) 0 )
		child_cnt--;

	signal ( signo, &sigchld_handler );
}
#endif

/**
 * setup_signal_handlers: setup signal handlers
 *
 * This function sets up the signal handlers
 *
 * Returns: nothing
 **/

static void
setup_signal_handlers ( void )
{
	/*
	 * Is use signal here, because...
	 * well. sigaction wouldn't provide any
	 * benefit here.
	 */

	signal ( SIGHUP, sig_handler );
	signal ( SIGINT, sig_handler );
	signal ( SIGQUIT, sig_handler );
	signal ( SIGILL, sig_handler );
	signal ( SIGABRT, sig_handler );
	signal ( SIGFPE, sig_handler );
	signal ( SIGSEGV, sig_handler );
	signal ( SIGPIPE, SIG_IGN );
	signal ( SIGTERM, sig_handler );
	signal ( SIGUSR1, sig_handler );
	signal ( SIGUSR2, sig_handler );

	setup_sigalrm_handler ();
}

/**
 * sig_handler: signal handler
 * @number: signal number
 *
 * Handles signals gracefully.
 *
 * Returns: nothing. it exits.
 **/
static void
sig_handler ( int number )
{
	control->system->log ( control, LOG_CRIT, "Terminating on signal %d: %s",
			       number, g_strsignal ( number ) );
	if ( P3L_GET_DATA ( "USER" ) )
		control->send_response ( control, POP3_FATAL, "internal error" );

	exit(1);
}

/**
 * sigalrm_handler: SIGALRM handler
 * @sig: signal number, unused
 *
 * This is the master SIGALRM handler. It increments
 * the tick counter of all registered handlers, and
 * runs those that reach their time.
 *
 * Returns: nothing
 **/

static void
sigalrm_handler ( int sig )
{
	unsigned long i;
	HandlerInfo *hinfo;
	GList *handlers = p3l_get_alarm_hooks ();

	for ( i = 0; i < g_list_length ( handlers ); i++ )
	{
		hinfo = (HandlerInfo *) g_list_nth_data ( handlers, i );
		hinfo->counter++;
		if ( hinfo->counter >= hinfo->tick_max )
		{
			hinfo->counter=0;
			(* (hinfo->hook) ) ( control );
		}
	}
	setup_sigalrm_handler ();
}

/**
 * auto_logout_handler: auto-logout SIGALRM handler
 * @control: the main control struct
 *
 * This function is called whenever auto-logout time is
 * reached.
 *
 * Returns: 0
 **/

static int
auto_logout_handler ( P3LControl *control )
{
	if ( logout_timer_reset == 0 )
	{
		control->system->log ( control, LOG_NOTICE, "auto-logout time elapsed (%s)",
				       P3L_GET_DATA ( "USER" ) );
		control->send_response ( control, POP3_FATAL, "auto-logout time elapsed" );
		exit ( 1 );
		return 0;
	}
	if ( logout_timer_reset == 1 )
		logout_timer_reset = 0;
	return 0;
}

/**
 * main_loop_body: main loop body
 * @command_table: table to search for commands
 *
 * This one is called in the main loops, this reads
 * input and executes commands.
 *
 * Returns: nothing.
 **/

static void
main_loop_body ( GHashTable *command_table )
{
	char *line;
	char **cmdarg;
	unsigned int c, len;
	p3l_pop3_command cmd_ptr;
	CommandResponse *resp;

	/*
	 * Read input, max LINELEN chars. Ended with \n or \r
	 */
	line = (char *) g_malloc ( LINELEN + 2 );
	len = 0;

	logout_timer_reset = 0;
	while ( ( c = fgetc ( stdin ) ) != '\r' && c != '\n' && len < LINELEN )
	{
		if ( c != '\r' && c != '\n' )
			line[len++] = c;
		else
			len++;
	}
	logout_timer_reset = 2;

	/*
	 * If the first character is 0xff, ignore it, because
	 * under heavy load, that gets prepended to some commands.
	 * FIXME: find the REAL reason for this!
	 */

	if ( line[0] == (char) 0xff )
	{
		line = &line[1];
		len--;
	}

	/*
	 * Is it too long ?
	 */ 

	if ( len >= LINELEN )
	{
		control->send_response ( control, POP3_ERR, "command too long");
		exit ( 1 );
	}

	/*
	 * Split into command + arg, and execute.
	 */

	line[len] = '\0';
	line = g_strdup ( g_strstrip ( line ) );
	cmdarg = g_strsplit ( line, " ", 1 );
	if ( cmdarg[0] != NULL )
	{
		g_strup ( cmdarg[0] );
		cmd_ptr = (p3l_pop3_command) g_hash_table_lookup ( command_table, cmdarg[0] );
		if (  cmd_ptr == NULL )
			control->send_response ( control, POP3_ERR, "Invalid command" );
		else
		{
			resp = (*cmd_ptr) ( control, cmdarg[1] );
			control->send_response ( control, resp->code, resp->message );
			g_free ( resp );
		}
	}
	g_strfreev ( cmdarg );
	g_free ( line );
	logout_timer_reset = 1;
}

/**
 * exit_hook: g_atexit hook
 *
 * Called when the program terminates.
 *
 * Returns: nothing.
 **/

static void
exit_hook ( void )
{
	if ( p3l_is_enabled ( P3L_GET_FIRST_OPTION ( "QUIT_ON_ERROR" ) ) &&
	     P3L_GET_DATA ( "USER" ) )
		control->update ( control );

#ifndef WITH_STANDALONE_SUPPORT
	free_modules ( control );
#endif

	control->system->closelog ( control );

#ifdef WITH_STANDALONE_SUPPORT
	if ( getpid () == main_pid )
	{
		unlink ( pidfn );
		free_modules ( control );
	}
#endif
}

/**
 * print_version: print version information
 *
 * Prints something like this:
 *	POP3Lite version 0.1.4-devel (1.107) [i386-pc-gnu]
 *	Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu>
 *
 * Returns: nothing
 **/

static void
print_version ( void )
{
	printf ( "POP3Lite version %s%s [%s]\n",
		 POP3LITE_VERSION, EXTRA_VERSION, __MACHINE__ );
	printf ( "%s\n", "Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu>" );
}

/**
 * pop3lite_main_loop: main loop
 * @control: the main control struct
 *
 * This is the main loop. In standalone mode, it is the only job
 * of the child process.
 *
 * Returns: nothing
 **/

static void
pop3lite_main_loop ( P3LControl *control )
{
	CommandResponse *res;
	char *alt;

	setup_signal_handlers ();

	/*
	 * Greeting
	 */

	res = control->greeting ( control );
	control->send_response ( control, res->code, res->message );
	g_free ( res );

	/*
	 * Setup auto-logout handler
	 */
	alt = P3L_GET_FIRST_OPTION ( "AUTO_LOGOUT_TIME" );
	if ( alt == NULL )
		alt = "10";
	logout_iv = strtoul ( alt, (char **) NULL, 10 ) * 60;
	p3l_register_alarm ( auto_logout_handler, logout_iv );

	/*
	 * AUTHENTICATION state
	 */

	while ( control->state == POP3_STATE_AUTH )
		main_loop_body ( control->auth_commands );

	/*
	 * If we're authenticated, we MUST get rid of all
	 * our special privileges
	 */
	control->system->drop_privileges ( control );

	/*
	 * If we're still root, log a warning and exit
	 * if ALLOW_ROOT is off
	 */
	if ( getuid () == 0 )
	{
		control->system->log ( control, LOG_ALERT, "Still running as root!" );
		if ( ! p3l_is_enabled ( P3L_GET_FIRST_OPTION ( "ALLOW_ROOT" ) ) )
		{
			control->send_response ( control, POP3_FATAL, "Internal error" );
			exit ( 1 ); /* In case send_response didn't do it... */
		}
	}

	/*
	 * Get into transaction state
	 */

	res = control->trans_init ( control );
	control->send_response ( control, res->code, res->message );
	g_free ( res );

	/*
	 * TRANSACTION state
	 */

	while ( control->state == POP3_STATE_TRANS )
		main_loop_body ( control->trans_commands );

	/*
	 * Do update
	 */

	res = control->update ( control );
	control->send_response ( control, res->code, res->message );
	g_free ( res );

	/*
	 * Exit
	 */

	exit ( 0 );
}

/**
 * main: CC hack
 * @argc: argument count
 * @argv: argument vector
 *
 * This is here, because stupid C compilers want to do
 * nasty things with main. ;>
 *
 * Returns: bogus numbers
 **/

int
main ( int argc, char **argv )
{
	struct option long_options[] = {
		{"config", required_argument, 0, 'c'},
		{"define", required_argument, 0, 'D'},
		{"help", no_argument, 0, 'h'},
		{"undefine", required_argument, 0, 'U'},
		{"version", no_argument, 0, 'v'},
#ifdef WITH_STANDALONE_SUPPORT
		{"inetd", no_argument, 0, 'i'},
		{"pidfile", required_argument, 0, 'p'},
#endif
		{0, 0, 0, 0}
	};
	int optc;
	char *cfg_file = NULL;
	GList *cmd_def = NULL;
	GList *cmd_udef= NULL;
#ifdef WITH_STANDALONE_SUPPORT
	FILE *pidfile;
	int sd;
	pid_t child, self;
	char *host, *port;
	gboolean from_inetd = FALSE;
	char *getopt_list = "c:hvD:U:ip:";
	unsigned long MAX_CHILDREN = 20;
#else
	char *getopt_list = "c:hvD:U:";
#endif

	opterr = 0;

	/*
	 * Argument parsing
	 */
	while ( ( optc = getopt_long ( argc, argv, getopt_list, long_options, 0 ) ) != - 1 )
	{
		switch ( optc )
		{
			case 0:
				break;
			case 'c':
				cfg_file = optarg;
				break;
			case 'D':
				cmd_def = g_list_append ( cmd_def, g_strdup ( optarg ) );
				break;
			case 'U':
				cmd_udef = g_list_append ( cmd_udef, g_strdup ( optarg ) );
				break;
			case 'h':
				print_version ();
				printf ( "%s", "\nOptions:\n"
					 "\t--config, -c [file]           use [file] as the "
					 "configuration file.\n"
					 "\t--define, -D [field]=[value]  define [field] as [value].\n" );
#ifdef WITH_STANDALONE_SUPPORT
				printf ( "%s", "\t--inetd, -i                   run from inetd.\n" );
#endif
				printf ( "%s", "\t--help, -h                    this help screen.\n" );
#ifdef WITH_STANDALONE_SUPPORT
				printf ( "%s", "\t--pidfile, -p [file]          write the pid to [file] in daemon mode.\n" );
#endif
				printf ( "%s", "\t--undefine, -U [field]        undefine [field].\n"
					 "\t--version, -v                 print version information.\n"
					 "\nReport bugs to <8@free.bsd.hu>\n" );
				exit ( 0 );
				break;
			case 'v':
				print_version ();
				printf ( "%s", "\nPOP3Lite comes with ABSOLUTELY NO WARRANTY.\n"
					 "You may redistribute copies of POP3Lite under the terms of the GNU\n"
					 "General Public License, either version two, or any later version.\n" );
#ifdef DEBIAN
				printf ( "%s", "For more information see /usr/share/common-licenses/GPL.\n" );
#else
				printf ( "%s", "For more information about these matters, see the file named COPYING.\n" );
#endif
				exit ( 0 );
				break;
#ifdef WITH_STANDALONE_SUPPORT
			case 'i':
				from_inetd=TRUE;
				break;
			case 'p':
				pidfn = g_strdup ( optarg );
				break;
#endif
			default:
				break;
		}
	}

	/*
	 * Control structure initialisation
	 */

	control = (P3LControl *) g_malloc ( sizeof ( P3LControl ) );
	control->greeting = NULL;
	control->send_response = NULL;
	control->send_raw = NULL;
	control->trans_init = NULL;
	control->state = POP3_STATE_AUTH;
	control->msg_info=NULL;
	control->auth_commands = g_hash_table_new ( (GHashFunc) g_str_hash,
						    (GCompareFunc) g_str_equal );
	control->trans_commands = g_hash_table_new ( (GHashFunc) g_str_hash,
						     (GCompareFunc) g_str_equal );
	control->data = g_hash_table_new ( (GHashFunc) g_str_hash,
					   (GCompareFunc) g_str_equal );
	control->config = g_hash_table_new ( (GHashFunc) g_str_hash,
					     (GCompareFunc) g_str_equal );
	control->capabilities = g_hash_table_new ( (GHashFunc) g_str_hash,
						   (GCompareFunc) g_str_equal );

	control->system = (SysControl *) g_malloc ( sizeof ( SysControl ) );
	control->system->getuinam = NULL;
	control->system->authenticate = NULL;
	control->system->drop_privileges = NULL;
	control->system->openlog = NULL;
	control->system->log = NULL;
	control->system->closelog = NULL;
	control->system->users = NULL;
	control->system->data = g_hash_table_new ( (GHashFunc) g_str_hash,
						   (GCompareFunc) g_str_equal );
	control->hooks = g_hash_table_new ( (GHashFunc) g_str_hash,
					    (GCompareFunc) g_str_equal );
	control->modules = g_hash_table_new ( (GHashFunc) g_str_hash,
					      (GCompareFunc) g_str_equal );

	alarm_handlers = NULL;

	/*
	 * Load default module
	 */

	core_init ( control );

	/*
	 * Setup signal handlers.
	 */ 

	setup_signal_handlers ();

	control->system->openlog ( control );

	/*
	 * AtExit magic. If we must exit, do it in an elegant way.
	 */

	if ( g_ATEXIT ( exit_hook ) < 0 )
	{
		control->system->log ( control, LOG_ERR, "g_ATEXIT failed" );
		exit(1);
	}

	/*
	 * Load configuration
	 */

	p3l_load_config ( control, cfg_file, control->config );

	/*
	 * Setup configuration specified on the commandline
	 */
	for ( optc = 0; optc < g_list_length ( cmd_def ); optc++ )
		cfg_parse_line ( control, g_list_nth_data ( cmd_def, optc ),
				 0, NULL, control->config );

	/*
	 * Undefine options specified with --undefine
	 */
	for ( optc = 0; optc < g_list_length ( cmd_udef ); optc++ )
		g_hash_table_remove ( control->config,
			g_list_nth_data ( cmd_udef, optc ) );

	/*
	 * Reinit core, in case the configuration has changed something
	 */
	core_reinit ( control );

	/*
	 * Load modules
	 */

	load_modules ( control );

	/*
	 * Main loop
	 */
#ifdef WITH_STANDALONE_SUPPORT
	if ( P3L_GET_FIRST_OPTION ( "MAX_CHILDREN" ) )
		strtoul ( P3L_GET_FIRST_OPTION ( "MAX_CHILDREN" ), (char **) NULL, 10 );

	if ( from_inetd || p3l_get_peer_name () != NULL )
	{
		main_pid = getpid ();
		pop3lite_main_loop ( control );
	}
	else if ( ( self = fork () ) == 0 )
	{
		close ( 0 );
		close ( 1 );
		close ( 2 );
		close ( 3 );

		host = P3L_GET_FIRST_OPTION ( "LISTEN.HOST" );
		port = P3L_GET_FIRST_OPTION ( "LISTEN.PORT" );

		if ( port == NULL )
			port = "110";

		if ( ( sd = tcp_listen ( host, port ) ) < 0 )
		{
			if ( host == NULL )
				control->system->log ( control, LOG_ERR,
					g_strdup_printf ( "Cannot listen on port %s", port ) );
			else
				control->system->log ( control, LOG_ERR,
					g_strdup_printf ( "Cannot listen on %s:%s", host, port ) );
			exit ( 1 );
		}

#ifdef DEBUG
		if ( host == NULL )
			control->system->log ( control, LOG_DEBUG,
				g_strdup_printf ( "Listening on port %s", port ) );
		else
			control->system->log ( control, LOG_DEBUG,
				g_strdup_printf ( "Listening on %s:%s", host, port ) );
#endif

		pidfile = fopen ( pidfn, "w" );
		if ( pidfile != NULL )
		{
			fprintf ( pidfile, "%ld\n", (long) getpid () );
			fclose ( pidfile );
		}
		else
			control->system->log ( control, LOG_WARNING,
				g_strdup_printf ( "Cannot write to pidfile: %s", pidfn ) );

		main_pid = getpid ();

#if defined(SIGCHLD)
		signal ( SIGCHLD, &sigchld_handler );
#elif defined(SIGCLD)
		signal ( SIGCLD, &sigcld_handler );
#endif

		while ( 1 )
		{
			struct sockaddr_in them;
			socklen_t len = sizeof them;
			int ns = accept ( sd, (struct sockaddr*) &them, &len );

			if ( ns < 0 )
			{
				if ( errno == EINTR || errno == ECONNRESET )
					continue;
				control->system->log ( control, LOG_ERR,
					g_strdup_printf ( "Accept failed: %s", g_strerror ( errno ) ) );
				exit ( 1 );
			}

			if ( MAX_CHILDREN == 0 || child_cnt <= MAX_CHILDREN )
			{
				switch ( child = fork() )
				{
					case -1:
						control->system->log ( control, LOG_ERR,
							g_strdup_printf ( "Cannot fork child: %s",
								g_strerror ( errno ) ) );
						break;
					case 0:
						close ( sd );
						dup2 ( ns, 0 );
						dup2 ( ns, 1 );
						setsid ();
						if ( p3l_get_peer_name () )
							pop3lite_main_loop ( control );
						else
						{
							control->system->log ( control, LOG_WARNING,
								"Remote end not connected." );
							return 1;
						}
						break;
					default:
						child_cnt++;
						close ( ns );
						break;
				}
			}
			else
			{
				control->system->log ( control, LOG_ERR, "Too many open connections!" );
				close ( ns );
			}
		}
	}
	else if ( self == -1 )
		control->system->log ( control, LOG_ERR, "Cannot fork main daemon" );
#else
	pop3lite_main_loop ( control );
#endif

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1