/*
 *	binkleyforce -- unix FTN mailer project
 *	
 *	Copyright (c) 1998-2000 Alexander Belkin, 2:5020/1398.11
 *	
 *	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.
 *	
 *	$Id: daemon.c,v 1.8 2002/11/21 21:22:55 kst Exp $
 */

#include "includes.h"
#include "confread.h"
#include "logger.h"
#include "util.h"
#include "nodelist.h"
#include "io.h"
#include "session.h"
#include "outbound.h"
#include "daemon.h"

/*
 * Maxumum number of simultaneously running clients
 */
int max_tcpip = 0;
int max_modem = 0;

/*
 * Current number of running clients
 */
int tcpip_clients = 0;
int modem_clients = 0;

/*
 * Write queue/demon status to the log every XX seconds
 */
#define DAEMON_ALIVE_TIMER 1200

/*
 * Time to sleep than all queues are in "DQ_Idle" state
 */
#define DAEMON_IDLE_SLEEP 5

/*
 * Table of handled signals
 */
struct {
	int signum;
	DM_State state;
	bool wait;
} daemon_signals[] = {
	{ SIGHUP,      DM_Restart,       FALSE },
	{ SIGINT,      DM_Shutdown,      FALSE },
	{ SIGTERM,     DM_Shutdown,      FALSE },
	{ SIGUSR1,     DM_Usr1,          FALSE },
	{ SIGUSR2,     DM_Usr2,          FALSE },
	{ 0,           0,                FALSE }
};

/*
 * Systems queue (with all adresses, flavors, etc.)
 */
s_sysqueue daemon_sys_queue;

/*
 * Outgoing calls queues
 */
s_daemon_queue daemon_queues[] = {
	{ &daemon_sys_queue, -1, 0, FALSE, DQ_Idle }, /* Modem */
	{ &daemon_sys_queue, -1, 0, TRUE,  DQ_Idle }, /* IP */
	{ NULL,              -1, 0, FALSE, DQ_Idle }
};

/*
 * Positions of the certain queues in the 'daemon_queues' array
 */
#define MODEM_QUEUE 0
#define TCPIP_QUEUE 1

static RETSIGTYPE daemon_sighandler_chld(int sig)
{
	int old_errno = errno;
	int rc, status;
	pid_t pid;

	while( (pid = waitpid(-1, &status, WNOHANG)) > 0 )
	{
		rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
		if( daemon_branch_exit(pid, rc) == -1 )
			log("wait got unexpected pid %d", (int)pid);
	}
	
	signal(SIGCHLD, daemon_sighandler_chld);
	errno = old_errno;
}

static RETSIGTYPE daemon_sighandler(int signum)
{
	int i;

	for( i = 0; daemon_signals[i].signum; i++ )
		if( daemon_signals[i].signum == signum )
		{
			daemon_signals[i].wait = TRUE;
			return;
		}

	log("daemon got unexpected signal %d", signum);
}

static int daemon_sysentry_init(s_sysentry *sysent)
{
	s_override *p;
	char msg[64] = "";
	char abuf[BF_MAXADDRSTR+1];
	
	/* Read calls/sessions statistic from sts file */
	(void)session_stat_get(&sysent->stat, &sysent->node.addr);
	
	/* Lookup node in nodelist */
	(void)nodelist_lookup(&sysent->node, sysent->node.addr);

	/* Set overrides */
	if( (p = conf_override(cf_override, sysent->node.addr)) )
	{
		sysent->overrides = p;
		sysent->lineptr = NULL;
		sysent->line = 0;
	}

	if( sysent->node.keyword == KEYWORD_HOLD )
	{
		strcpy(msg, "hold by nodelist");
		sysent->stat.undialable = TRUE;
	}
	else if( sysent->node.keyword == KEYWORD_DOWN )
	{
		strcpy(msg, "down by nodelist");
		sysent->stat.undialable = TRUE;
	}
	else if( sysent->stat.undialable )
		strcpy(msg, "undialable");
	else if( sysent->stat.hold_until )
	{
		int holdtime = sysent->stat.hold_until - time(0);

		if( holdtime > 0 )
			sprintf(msg, "calls holded for %d seconds", holdtime);
		else
			strcpy(msg, "calls holded");
	}
	else if( sysent->stat.hold_freqs )
	{
		int holdtime = sysent->stat.hold_freqs - time(0);

		if( holdtime > 0 )
			sprintf(msg, "freqs holded for %d seconds", holdtime);
		else
			strcpy(msg, "freqs holded");
	}

	if( msg && *msg )
		log("QUEUE: add %s (%s)", ftn_addrstr(abuf, sysent->node.addr), msg);
	else
		log("QUEUE: add %s", ftn_addrstr(abuf, sysent->node.addr));

	return 0;
}

static int daemon_sysentry_deinit(s_sysentry *sysent)
{
	char abuf[BF_MAXADDRSTR+1];
	
	log("QUEUE: remove %s", ftn_addrstr(abuf, sysent->node.addr));

	return 0;
}

static bool daemon_node_cancall_line(const s_node *node, const s_override *ovrd)
{
	bool good_phone = FALSE;
	bool good_host = FALSE;
	bool good_time = FALSE;
	time_t unixtime = time(NULL);
	struct tm *now = localtime(&unixtime);
	
	/*
	 * Check phone number
	 */
	if( ovrd && ovrd->sPhone && *ovrd->sPhone )
	{
		 if( modem_isgood_phone(ovrd->sPhone) )
			good_phone = TRUE;
	}
	else if( node && node->phone && *node->phone )
	{
		 if( modem_isgood_phone(node->phone) )
			good_phone = TRUE;
	}
	
	/*
	 * Check host name (IP address)
	 */
	if( ovrd && ovrd->sIpaddr && *ovrd->sIpaddr )
	{
		if( tcpip_isgood_host(ovrd->sIpaddr) )
			good_host = TRUE;
	}
	
	/*
	 * Check work time
	 */
	if( ovrd && timevec_isdefined(&ovrd->worktime) )
	{
		if( timevec_isnow(&ovrd->worktime, now) )
			good_time = TRUE;
	}
	else if( ovrd && ovrd->sFlags && !nodelist_checkflag(ovrd->sFlags, "CM") )
	{
		/*
		 * It is not an error. We check CM flag only
		 * if the work time was not specified in the override
		 */
		good_time = TRUE;
	}
	else if( node && timevec_isdefined(&node->worktime) )
	{
		if( timevec_isnow(&node->worktime, now) )
			good_time = TRUE;
	}
	
	if( good_time )
	{
		if( good_host )
			return 2;
		else if( good_phone )
			return 1;
	}
	
	return 0;
}

enum { UNHOLD_CALLS, UNHOLD_FREQS };

static void daemon_unhold_system(s_sysentry *syst, int mode)
{
	char abuf[BF_MAXADDRSTR+1];
	s_sess_stat stat_diff;
	
	memset(&stat_diff, '\0', sizeof(s_sess_stat));

	/*
	 * Reset try counters to the TRIES_RESET value
	 */
	session_stat_reset_counters(&stat_diff);
	
	if( mode == UNHOLD_FREQS )
		stat_diff.hold_freqs = HOLD_RESET;
	else
		stat_diff.hold_until = HOLD_RESET;
	
	/*
	 * Write new statistic to the sts file
	 */
	if( session_stat_apply_diff(syst->node.addr, stat_diff) == -1 )
	{
		log("cannot write statistic for address %s",
				ftn_addrstr(abuf, syst->node.addr));
	}

	/*
	 * Read new statistic from sts file, ignore errors
	 */
	(void)session_stat_get(&syst->stat, &syst->node.addr);
}

static bool daemon_node_cancall(s_sysentry *syst, bool ign_wtime, bool tcpip)
{
	char abuf[BF_MAXADDRSTR+1];
	const s_override *ptrl, *lastptr;
	int line;
	int rc;
	
	/* Is this system allready active in an another queue? Skip. */
	if( syst->busy )
		return FALSE;
	
	/* Check for undialable flag */
	if( syst->stat.undialable )
		return FALSE;
	
	if( daemon_branch_exist(syst->node.addr) )
		return FALSE;

	/* Check for netmail presence, when MAILONLY */
	if( conf_options(cf_options) & OPTIONS_MAILONLY )
		if ( !( syst->types & TYPE_NETMAIL ) )
			return FALSE;

	/* Check system hold status */
	if( syst->stat.hold_until > 0 )
	{
		if( time(0) > syst->stat.hold_until )
		{
			log("unholding %s", ftn_addrstr(abuf, syst->node.addr));
			daemon_unhold_system(syst, UNHOLD_CALLS);
		}
		else
			return FALSE;
	}

	/* Check freq hold status */
	if( syst->stat.hold_freqs > 0 )
	{
		if( time(0) > syst->stat.hold_freqs )
		{
			log("unholding freqs for %s", ftn_addrstr(abuf, syst->node.addr));
			daemon_unhold_system(syst, UNHOLD_FREQS);
		}
		else if( !syst->flavors && (syst->types & TYPE_REQUEST) )
			return FALSE;
	}
	
	/* Make checks for all lines */
	if( syst->overrides )
	{
		if( syst->lineptr && syst->lineptr->hidden )
		{
			line = syst->line + 1;
			/* Check lines after current */
			for( ptrl = syst->lineptr->hidden; ptrl; ptrl = ptrl->hidden )
			{
				if( (rc = daemon_node_cancall_line(NULL, ptrl))
				 && (rc == (tcpip ? 2 : 1)) )
				{
					syst->line = line;
					syst->lineptr = ptrl;
					syst->tcpip = (rc == 2) ? TRUE : FALSE;
					return TRUE;
				}
				line++;
			}
		}
		
		if( syst->lineptr == NULL )
			{ lastptr = NULL; syst->line = 0; }
		else
			{ lastptr = syst->lineptr->hidden; }

		line = 0;
		/* Check lines before current */
		for( ptrl = syst->overrides; ptrl && ptrl != lastptr; ptrl = ptrl->hidden )
		{
			if( (rc = daemon_node_cancall_line(line ? NULL : &syst->node, ptrl))
			 && (rc == (tcpip ? 2 : 1)) )
			{
				syst->line = line;
				syst->lineptr = ptrl;
				syst->tcpip = (rc == 2) ? TRUE : FALSE;
				return TRUE;
			}
			line++;
		}
	}
	else /* Node without overriden parameters */
	{
		syst->line = line = 0;
		return daemon_node_cancall_line(&syst->node, NULL);
	}
	
	return FALSE;
}

static int daemon_getnext(const s_sysqueue *q, int current, bool tcpip)
{
	int i;
	int normal = -1;

	if( current < 0 )
		current = 0;

	/* Check systems AFTER the current */
	for( i = current + 1; i < q->sysnum; i++ )
	{
		if( q->systab[i].flavors & FLAVOR_IMMED ) {
			if( daemon_node_cancall(&q->systab[i], TRUE, tcpip) )
				return i;
		} else if( q->systab[i].flavors & FLAVOR_CRASH ) {
			if( daemon_node_cancall(&q->systab[i], FALSE, tcpip) )
				return i;
		} else if( q->systab[i].flavors & FLAVOR_DIRECT
		        || q->systab[i].flavors & FLAVOR_NORMAL ) {
			if( normal == -1 && daemon_node_cancall(&q->systab[i], FALSE, tcpip) )
				normal = i;
		}
	}

	/* Check systems BEFORE the current */
	for( i = 0; (i < current + 1) && (i < q->sysnum); i++ )
	{
		if( q->systab[i].flavors & FLAVOR_IMMED ) {
			if( daemon_node_cancall(&q->systab[i], TRUE, tcpip) )
				return i;
		} else if( q->systab[i].flavors & FLAVOR_CRASH ) {
			if( daemon_node_cancall(&q->systab[i], FALSE, tcpip) )
				return i;
		} else if( q->systab[i].flavors & FLAVOR_DIRECT
		        || q->systab[i].flavors & FLAVOR_NORMAL ) {
			if( normal == -1 && daemon_node_cancall(&q->systab[i], FALSE, tcpip) )
				normal = i;
		}
	}
	
	return normal;
}

static int daemon_getnext2(const s_sysqueue *q, int current, bool tcpip)
{
	int next;

	/* Unlock previous system if available */
	if( current >= 0 )
		q->systab[current].busy = FALSE;

	next = daemon_getnext(q, current, tcpip);
	
	/* Lock new system */
	if( next >= 0 )
		q->systab[next].busy = TRUE;

	return next;
}

static void daemon_do_tries_action(s_sysqueue *q, int pos, s_tries *act, const char *msg)
{
	s_sess_stat stat_diff;
	char abuf[BF_MAXADDRSTR+1];
	
	memset(&stat_diff, '\0', sizeof(s_sess_stat));
	
	if( act->action == TRIES_ACTION_UNDIALABLE )
	{
		log("set %s undialable: %s",
			ftn_addrstr(abuf, q->systab[pos].node.addr), msg);
		stat_diff.undialable = TRUE;
	}
	else if( act->action == TRIES_ACTION_HOLDSYSTEM )
	{
		log("hold %s for %d seconds: %s",
			ftn_addrstr(abuf, q->systab[pos].node.addr), act->arg, msg);
		stat_diff.hold_until = time(NULL) + act->arg;
	}
	else if( act->action == TRIES_ACTION_HOLDALL )
	{
		log("hold all systems for %d seconds: %s", act->arg, msg);
		q->holduntil = time(NULL) + act->arg;
	}

	/*
	 * Write new statistic to the sts file
	 */
	if( session_stat_apply_diff(q->systab[pos].node.addr, stat_diff) == -1 )
	{
		log("cannot write statistic for address %s",
				ftn_addrstr(abuf, q->systab[pos].node.addr));
	}

	/*
	 * Read new statistic from sts file, ignore errors
	 */
	(void)session_stat_get(&q->systab[pos].stat,
			&q->systab[pos].node.addr);
}

static void daemon_process_rc(s_sysqueue *q, s_faddr addr, int rc)
{
	int i;
	s_tries *maxtries        = NULL;
	s_tries *maxtries_noansw = NULL;
	s_tries *maxtries_noconn = NULL;
	s_tries *maxtries_nodial = NULL;
	s_tries *maxtries_hshake = NULL;
	s_tries *maxtries_sessns = NULL;
	char abuf[BF_MAXADDRSTR+1];
	
	for( i = 0; i < q->sysnum; i++ )
		if( ftn_addrcomp(q->systab[i].node.addr, addr) == 0 )
			break;

	if( i < q->sysnum )
	{
		/* Set last call finish time */
		q->systab[i].lastcall = time(0);
	
		/* Temporary set `state' structure to
		   make dynamical configuration work! */
		state.valid  = TRUE;
		state.node   = q->systab[i].node;
		state.listed = q->systab[i].node.listed;
			
		maxtries        = conf_tries(cf_maxtries);
		maxtries_noansw = conf_tries(cf_maxtries_noansw);
		maxtries_noconn = conf_tries(cf_maxtries_noconn);
		maxtries_nodial = conf_tries(cf_maxtries_nodial);
		maxtries_hshake = conf_tries(cf_maxtries_hshake);
		maxtries_sessns = conf_tries(cf_maxtries_sessions);
			
		/* Reset `state' structure */
		init_state(&state);
			
		/* Update statistic, ignore errors */
		session_stat_get(&q->systab[i].stat, &addr);
			
		log("%s: rc = %d [%d/%d/%d/%d/%d/%d]",
			ftn_addrstr(abuf, addr), rc,
			q->systab[i].stat.tries,
			q->systab[i].stat.tries_noansw,
			q->systab[i].stat.tries_noconn,
			q->systab[i].stat.tries_nodial,
			q->systab[i].stat.tries_hshake,
			q->systab[i].stat.tries_sessns);
			
		DEB((D_DAEMON, "daemon_process_rc: address %s: [%d/%d/%d/%d/%d/%d]",
			ftn_addrstr(abuf, addr),
			q->systab[i].stat.tries,
			q->systab[i].stat.tries_noansw,
			q->systab[i].stat.tries_noconn,
			q->systab[i].stat.tries_nodial,
			q->systab[i].stat.tries_hshake,
			q->systab[i].stat.tries_sessns));
			
		if( maxtries && q->systab[i].stat.tries >= maxtries->tries )
		{
			daemon_do_tries_action(q, i, maxtries, "reached maximal tries count");
		}
		else if( maxtries_noansw && q->systab[i].stat.tries_noansw >= maxtries_noansw->tries )
		{
			daemon_do_tries_action(q, i, maxtries_noansw, "reached maximal no answers count");
		}
		else if( maxtries_noconn && q->systab[i].stat.tries_noconn >= maxtries_noconn->tries )
		{
			daemon_do_tries_action(q, i, maxtries_noconn, "reached maximal connect failures count");
		}
		else if( maxtries_nodial && q->systab[i].stat.tries_nodial >= maxtries_nodial->tries )
		{
			daemon_do_tries_action(q, i, maxtries_nodial, "reached maximal no dialtone count");
		}
		else if( maxtries_hshake && q->systab[i].stat.tries_hshake >= maxtries_hshake->tries )
		{
			daemon_do_tries_action(q, i, maxtries_hshake, "reached maximal handshake failures count");
		}
		else if( maxtries_sessns && q->systab[i].stat.tries_sessns >= maxtries_sessns->tries )
		{
			daemon_do_tries_action(q, i, maxtries_sessns, "reached maximal session failures count");
		}
	}
	else
	{
		/* No entry in the systems queue
		 * TODO: load statistic and check tries? */
		log("%s: rc = %d",
			ftn_addrstr(abuf, addr), rc);
		DEB((D_DAEMON, "daemon_process_rc: address %s: [%d/%d/%d/%d/%d]",
			ftn_addrstr(abuf, addr)));
	}
}

static void daemon_alive_message(s_sysqueue *q)
{
	int i, holded_num = 0;
	
	for( i = 0; i < q->sysnum; i++ )
		if( q->systab[i].stat.hold_until
		 || q->systab[i].stat.hold_freqs )
			holded_num++;
	
	log("still alive (%d systems, %d holded, %d branches)",
		q->sysnum, holded_num, daemon_branch_number());
}

static void daemon_queue_do(s_daemon_queue *dq)
{
	bool select_next_address = FALSE;
	s_sysqueue *q = dq->q;
#ifdef DEBUG
	char abuf[BF_MAXADDRSTR+1];
#endif
	
	if( dq->current >= 0 )
	{
		if( (q->systab[dq->current].lastcall == 0)
		 || (q->systab[dq->current].lastcall + dq->circle < time(0)) )
		{
			if( !daemon_branch_exist(q->systab[dq->current].node.addr) )
			{
				DEB((D_DAEMON, "daemon_queue: calling %s",
					ftn_addrstr(abuf, q->systab[dq->current].node.addr)));
				if( !daemon_call(&q->systab[dq->current]) )
					select_next_address = TRUE;
			}
			else
				select_next_address = TRUE;
		}
	}
	else
		select_next_address = TRUE;

	if( select_next_address )
	{
		int next = daemon_getnext2(q, dq->current, dq->tcpip);
		DEB((D_DAEMON, "daemon_queue: next = %d, current = %d",
			next, dq->current));
		
		if( next >= 0 )
		{
			dq->current = next;

			/*
			 * Temporary set `state' structure to
			 * make dynamical configuration work!
			 */
			state.valid  = TRUE;
			state.node   = q->systab[dq->current].node;
			state.listed = q->systab[dq->current].node.listed;
			state.inet   = dq->tcpip;
			
			if( (q->systab[dq->current].flavors & FLAVOR_IMMED) )
				dq->circle = conf_number(cf_daemon_circle_immed);
			else if( (q->systab[dq->current].flavors & FLAVOR_CRASH) )
				dq->circle = conf_number(cf_daemon_circle_crash);
			else if( (q->systab[dq->current].flavors & FLAVOR_DIRECT) )
				dq->circle = conf_number(cf_daemon_circle_direct);
			else
				dq->circle = conf_number(cf_daemon_circle_normal);
			
			init_state(&state);
		}
		else
			dq->current = -1;
	}
}

void daemon_queues_update_current(s_daemon_queue dqs[], int pos)
{
	int i;

	for( i = 0; dqs[i].q; i++ )
	{
		if( dqs[i].current < 0 )
			continue;
		
		if( dqs[i].current == pos )
			dqs[i].current = -1;
		else if( dqs[i].current > pos )
			dqs[i].current--;
	}
}

int daemon_rescan_sysqueue(s_sysqueue *q, s_daemon_queue dqs[])
{
	int i;
	s_outbound_callback_data ocb;
	
	out_reset_sysqueue(q);
	
	memset(&ocb, '\0', sizeof(s_outbound_callback_data));
	ocb.callback = out_handle_sysqueue;
	ocb.dest = (void *)q;
	if( out_scan(&ocb, NULL) )
	{
		log("error scanning outbound");
		return -1;
	}

	/*
	 * Remove empty entries from sysqueue (e.g. the mail was
	 * removed by the sysop or by an another program)
	 */
	for( i = 0; i < q->sysnum; i++ )
		if( !q->systab[i].types && !q->systab[i].flavors )
		{
			out_remove_from_sysqueue(q, i);
			daemon_queues_update_current(dqs, i);
		}
	
#ifdef DEBUG
	log_sysqueue(q);
#endif

	return 0;
}

int daemon_remove_waiting_branches(s_sysqueue *q, s_daemon_queue dqs[])
{
	int i;
	int j;
	s_daemon_branch *bptr;
#ifdef DEBUG
	char abuf[BF_MAXADDRSTR+1];
#endif
	
	while( (i = daemon_branch_get_first_waiting()) >= 0 )
	{
		bptr = daemon_branch_get_pointer(i);
		if( !bptr )
		{
			log("internal error: cannot get branch pointer!");
			return -1;
		}
		
		DEB((D_DAEMON, "daemon: process branch information for %s, rc = %d",
			ftn_addrstr(abuf, bptr->addr), bptr->rc));
		
		if( bptr->tcpip )
			--tcpip_clients;
		else
			--modem_clients;
		
		daemon_process_rc(q, bptr->addr, bptr->rc);
		
		if( bptr->rc == BFERR_NOERROR )
		{
			/* Remove system from queue */
			for( j = 0; j < q->sysnum; j++ )
				if( !ftn_addrcomp(bptr->addr, q->systab[j].node.addr) )
				{
					out_remove_from_sysqueue(q, j);
					daemon_queues_update_current(dqs, j);
				}	
		}
	
		/* Hold modem line for some time */
		if( !bptr->tcpip && bptr->portname )
		{
			daemon_line_hold(bptr->portname,
				conf_number(cf_daemon_circle_modem));
		}
		
		/* Remove branch entry */
		daemon_branch_remove(i);
	}

	return 0;
}

#define	PIDFILE_CREATE	1
#define PIDFILE_DELETE	2
#define PIDFILE_TERMINATE 3

int daemon_pidfile(int cmd)
{
	char *pidfile	    = conf_string(cf_daemon_pid_file);
	pid_t hispid, mypid = getpid();
	FILE *pf;
	struct stat sb;

	if (pidfile == NULL)
		return 0;
	
	switch (cmd) {
		case PIDFILE_CREATE:
			if (stat(pidfile, &sb) == 0) {
				pf = fopen(pidfile, "r");
				if (pf == NULL) {
					log("daemon_pidfile: cannot open %s for reading", pidfile);
					return (-1);
				}	
				
				fscanf(pf, "%d", &hispid);
				fclose(pf);

				if (hispid != 0) {
					if (kill(hispid, 0) == 0) {
						log("daemon_pidfile: another daemon exist. pid=%d", hispid);
						return (-1);
					} else if(errno != ESRCH) {
						log("daemon_pidfile: error sending signal. pid=%d, errno=%d", hispid, errno);
						return (-1);
					}	
				}	
			} else if (errno != ENOENT) {
				log("daemon_pidfile: error stat(%s). errno=%d", pidfile, errno);
				return (-1);
			}	
			
			pf = fopen(pidfile, "w");
			if (pf == NULL) {
				log("daemon_pidfile: cannot open %s for writing", pidfile);
				return (-1);
			}	
			
			fprintf(pf, "%d", mypid);
			fclose(pf);
			break;
			
		case PIDFILE_DELETE:
			if (stat(pidfile, &sb) == 0) {
				pf = fopen(pidfile, "r");
				if (pf == NULL) {
					log("daemon_pidfile: cannot open %s for reading", pidfile);
					return (-1);
				}
				
				fscanf(pf, "%d", &hispid);
				fclose(pf);
				
				if ((hispid == 0) || (hispid == mypid)) {
					unlink(pidfile);
					return 0;
				} else {
					log("daemon_pidfile: oops! mypid=%d, hispid=%d", mypid, hispid);
					return (-1);
				}
			} else {
				log("daemon_pidfile: error stat(%s). errno=%d", pidfile, errno);
				return (-1);
			}
			break;
		
		case PIDFILE_TERMINATE:
			if (stat(pidfile, &sb) == 0) {
				pf = fopen(pidfile, "r");
				if (pf == NULL) {
					log("daemon_pidfile: cannot open %s for reading", pidfile);
					return (-1);
				}
				
				fscanf(pf, "%d", &hispid);
				fclose(pf);
				unlink(pidfile);
				
				if (hispid != 0)
					kill(hispid, 15);

			} else {
				log("daemon_pidfile: error stat(%s). errno=%d", pidfile, errno);
				return (-1);
			}
			break;
		
		default:
			log("daemon_pidfile: undefined cmd = %d", cmd);
			return (-1);
	}

	return 0;
}

int daemon_run(const char *confname, const char *incname, bool quit)
{
	DM_State dmstate    = DM_Start;
	time_t timer_rescan = 0;
	time_t timer_alive  = 0;
	bool started        = FALSE;
	int circle_rescan   = 0;
	int rc              = 0;
	int i;

	if ( quit ) {
		daemon_pidfile(PIDFILE_TERMINATE);
		exit(0);
	}
	
	/* Initialisation */
	memset(&daemon_sys_queue, '\0', sizeof(s_sysqueue));
	
	/* Install signal handlers */
	signal(SIGCHLD, daemon_sighandler_chld);
	signal(SIGINT,  daemon_sighandler);
	signal(SIGTERM, daemon_sighandler);
	signal(SIGHUP,  daemon_sighandler);
	signal(SIGUSR1, daemon_sighandler);
	signal(SIGUSR2, daemon_sighandler);
	
	while(1)
	{
		switch(dmstate) {
		case DM_Start:
			DEB((D_DAEMON, "daemon: entering state DM_Start"));
			
			if (daemon_pidfile(PIDFILE_CREATE) == -1)
				exit(0);
			
			circle_rescan = conf_number(cf_daemon_circle_rescan);
			max_tcpip     = conf_number(cf_daemon_maxclients_tcpip);
			max_modem     = conf_number(cf_daemon_maxclients_modem);
			
			/* Set default values */
			if( !circle_rescan ) circle_rescan = 60;
						
			/* Disable counting of files/mail size */
			sysqueue_dont_count_sizes = TRUE;
			
			/* Setup sysqueue scanner callbacks */
			sysqueue_add_callback = daemon_sysentry_init;
			sysqueue_rem_callback = daemon_sysentry_deinit;

			/* Reopen log and debug files */
			if( log_isopened() )
				log_close();
#ifdef DEBUG
			if( debug_isopened() )
				debug_close();
#endif
			if( log_open(log_getfilename(LOG_FILE_DAEMON), NULL, NULL) )
			{
				log("can't continue without logging");
				return BFERR_FATALERROR;
			}

#ifdef DEBUG
			(void)debug_setfilename(log_getfilename(LOG_FILE_DEBUG));
#endif

			log("%sstarting daemon (%s)",
				started ? "re" : "", BF_VERSION);

			started = TRUE;
			dmstate = DM_ProcessQueues;
			break;

		case DM_Restart:
			DEB((D_DAEMON, "daemon: entering state DM_Restart"));

			/* Release memory */
			deinit_sysqueue(&daemon_sys_queue);
			deinit_conf();

			/* Reset queues */
			daemon_queues[MODEM_QUEUE].current =
				daemon_queues[TCPIP_QUEUE].current = -1;
			daemon_queues[MODEM_QUEUE].circle =
				daemon_queues[TCPIP_QUEUE].circle = 0;
			daemon_queues[MODEM_QUEUE].state =
				daemon_queues[TCPIP_QUEUE].state = DQ_Idle;
			
			/* Read primary config file */
			if( confname && *confname )
				rc = conf_readconf(confname, 0);
			else
				rc = conf_readconf(conf_getconfname(), 0);
			
			if( rc )
				return(BFERR_FATALERROR);
			
			/* Read additional config file (manual include) */
			if( incname && *incname && conf_readconf(incname, 1) )
				log("cannot read additional config (ignore)");
			
			dmstate = DM_Start;
			break;

		case DM_ProcessQueues:
			DEB((D_DAEMON, "daemon: entering state DM_ProcessQueues"));
			
			/*
			 * Handle received signals
			 */
			for( i = 0; daemon_signals[i].signum; i++ )
				if( daemon_signals[i].wait )
					break;

			if( daemon_signals[i].signum )
			{
				daemon_signals[i].wait = FALSE;
				dmstate = daemon_signals[i].state;
				break;
			}

			/*
			 * Process finished branches
			 */
			(void)daemon_remove_waiting_branches(&daemon_sys_queue,
					daemon_queues);
			
			/*
			 * Check rescan timer
			 */
			if( !timer_running(timer_rescan) || timer_expired(timer_rescan) )
			{
				(void)daemon_rescan_sysqueue(&daemon_sys_queue,
						daemon_queues);
				timer_set(&timer_rescan, circle_rescan);
			}
			
			/*
			 * Check alive timer
			 */
			if( !timer_running(timer_alive) || timer_expired(timer_alive) )
			{
				daemon_alive_message(&daemon_sys_queue);
				timer_set(&timer_alive, DAEMON_ALIVE_TIMER);
			}
			
			if( max_modem > 0 )
				daemon_queue_do(&daemon_queues[MODEM_QUEUE]);
			if( max_tcpip > 0 )
				daemon_queue_do(&daemon_queues[TCPIP_QUEUE]);
			
			(void)sleep(DAEMON_IDLE_SLEEP);
			break;

		case DM_Usr1:
			DEB((D_DAEMON, "daemon: entering state DM_Usr1"));

			log("signal USR1 has no effect now. Have any ideas?");
			dmstate = DM_ProcessQueues;
			
			break;
		
		case DM_Usr2:
			DEB((D_DAEMON, "daemon: entering state DM_Usr2"));

			log("signal USR2 has no effect now. Have any ideas?");
			dmstate = DM_ProcessQueues;
			
			break;
			
		case DM_Shutdown:
			DEB((D_DAEMON, "daemon: entering state DM_Shutdown"));
			
			log("stopping daemon");
			daemon_pidfile(PIDFILE_DELETE);
			
			/* Release memory */
			deinit_sysqueue(&daemon_sys_queue);
			daemon_branch_deinit();
			daemon_lines_deinit();
			deinit_conf();
			
			exit(0);
		}
	}
}

