/* OperServ routines

   OperStats - Copyright (c) 2000-2003 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

#include "../inc/operstats.h"

int32 akillcnt = 0;
int32 akill_size = 0;
struct akill_ *akills = NULL;
MSG *msglist = NULL;
Trigger *triggerlist = NULL;
Exception *exceptionlist = NULL;

static void do_help (User *u);
static void do_global (User *u);
static void do_jupe (User *u);
static void do_clones (User *u);
static void do_akill (User *u);
static void do_stats (User *u);
static void do_logonmsg (User *u);
static void do_logonmsg_add (User *u);
static void do_logonmsg_del (User *u);
static void do_logonmsg_list (User *u);
static void do_trigger (User *u);
static void do_trigger_add (User *u);
static void do_trigger_del (User *u);
static void do_trigger_list (User *u);
static void do_exception (User *u);
static void do_exception_add (User *u);
static void do_exception_del (User *u);
static void do_exception_list (User *u);

/* Add a new logonmsg: This is also called when loading the db */
static MSG *new_message (const char *nick, time_t set, const char *text)
{
    MSG *msg;

    msg = scalloc (sizeof (MSG), 1);

    msg->text = sstrdup (text);
    msg->setter = sstrdup (nick);
    msg->set = set;

    msg->next = msglist;

    if (msg->next)
	msg->next->prev = msg;

    msglist = msg;

    return msg;
}

/* Add a new trigger to the trigger list */
Trigger *new_trigger (const char *host, uint8 limit)
{
    Trigger *trigger;
    char *username, *hostname;

    username = sstrdup (host);
    hostname = strchr (username, '@');

    if (!hostname)
	return NULL;

    *hostname++ = 0;

    if ((trigger = findtrig (username, hostname)))
    {
	free (username);
	return trigger;
    }

    trigger = scalloc (sizeof (Trigger), 1);

    strscpy (trigger->host, host, HOSTLEN);
    trigger->limit = limit;

    trigger->next = triggerlist;

    if (trigger->next)
        trigger->next->prev = trigger;

    triggerlist = trigger;

    free (username);

    return trigger;
}

/* Search for a trigger */
Trigger *findtrig (const char *user, const char *host)
{
    Trigger *trigger;
    char *mask;

    mask = smalloc (strlen (user) + strlen (host) + 2);
    sprintf (mask, "%s@%s", user, host);

    for (trigger = triggerlist; trigger; trigger = trigger->next)
	if (match_wild_nocase (trigger->host, mask))
	{
	    free (mask);
	    return trigger;
	}

    free (mask);
    return NULL;
}

/* Add a new exception to the exception list */
Exception *new_exception (const char *host)
{
    Exception *exception;
    char *username, *hostname;

    username = sstrdup (host);
    hostname = strchr (username, '@');

    if (!hostname)
	return NULL;

    *hostname++ = 0;

    if ((exception = findexcept (username, hostname)))
    {
	free (username);
	return exception;
    }

    exception = scalloc (sizeof (Exception), 1);

    strscpy (exception->host, host, HOSTLEN);

    exception->next = exceptionlist;

    if (exception->next)
	exception->next->prev = exception;

    exceptionlist = exception;

    free (username);

    return exception;
}

/* Search for an exception */
Exception *findexcept (const char *user, const char *host)
{
    Exception *exception;
    char *mask;

    mask = smalloc (strlen (user) + strlen (host) + 2);
    sprintf (mask, "%s@%s", user, host);

    for (exception = exceptionlist; exception; exception = exception->next)
	if (match_wild_nocase (exception->host, mask))
	{
	    free (mask);
	    return exception;
	}

    free (mask);

    return NULL;
}   

/* Global Noticer routine. This is simply to reply to CTCPs. */
void globalnoticer (User *u, char *buf)
{
    char *cmd, *s;

    cmd = strtok (buf, " ");

    if (!cmd)
	return;

    if (!stricmp (cmd, "\1PING"))
    {
	if (!(s = strtok (NULL, "")))
	    s = "0";
	strip (s);
	ctcpreply (s_GlobalNoticer, u->nick, "PING %s", s);
	return;
    }
    else if (!stricmp (cmd, "\1VERSION\1"))
    {
	ctcpreply (s_GlobalNoticer, u->nick, "VERSION OperStats v%s", version);
	return;
    }
}

/* Main OperServ routine. */
void operserv (User *u, char *buf)  
{
    char *cmd, *s;
    char orig[BUFSIZE];

    /* Make a copy of the original message. */ 
    strscpy (orig, buf, sizeof (orig));   

    cmd = strtok (buf, " ");

    if (!cmd)
	return;
    
    if (!stricmp (cmd, "\1PING"))
    {
	if (!(s = strtok (NULL, "")))
	    s = "0";
	strip (s);
	ctcpreply (s_OperServ, u->nick, "PING %s", s);
	return;
    }
    else if (!stricmp (cmd, "\1VERSION\1"))
    {
	ctcpreply (s_OperServ, u->nick, "VERSION OperStats v%s", version);
	return;
    }

    /* A CTCP we don't recognize.. Ignore it. */
    else if (*cmd == '\1')
	return;

    else if (!is_oper (u))
    {
	notice (s_OperServ, u->nick, OS_ACCESS_DENIED);
	return;
    }

    else
    {
	Hash *command, hash_table[] =
	{
	    {"HELP",		H_IRCOP,	do_help},
	    {"GLOBAL",		H_IRCOP,	do_global},
	    {"JUPE",		H_IRCOP,	do_jupe},
	    {"CLONES",		H_IRCOP,	do_clones},
	    {"AKILL",		H_IRCOP,	do_akill},
	    {"STATS",		H_IRCOP,	do_stats},
	    {"TRIGGER",		H_IRCOP,	do_trigger},
	    {"EXCEPTION",	H_IRCOP,	do_exception},
	    {"SEARCH",		H_IRCOP,	do_search},
	    {"AUTH",		H_IRCOP,	do_auth},
	    {"DEAUTH",		H_SRA,		do_deauth},
	    {"SHUTDOWN",	H_SRA,		do_shutdown},
	    {"RESTART",		H_SRA,		do_restart},
#ifdef RAWINJECT
	    {"RAW",		H_SRA,		do_raw},
	    {"INJECT",		H_SRA,		do_inject},
#endif
	    {"DEBUG",		H_SRA,		do_debug},
	    {"UPDATE",		H_SRA,		do_update},
	    {"REHASH",		H_SRA,		do_rehash},
	    {"SETTINGS",	H_SRA,		do_settings},
	    {"LOGONMSG",	H_SRA,		do_logonmsg},
	    {"CYCLELOGS",	H_SRA,		do_cyclelogs},
	    {NULL}
	};
            
	if ((command = get_hash (s_OperServ, u, strupper (cmd), hash_table)))
	{
	    /* Check if they want a disabled command. */
	    if ((command->process == do_global && globalnoticer_on == FALSE) ||
		(command->process == do_logonmsg && globalnoticer_on == FALSE) ||
		(command->process == do_stats && statserv_on == TRUE) ||
		(command->process == do_clones && !maxclones) ||
		(command->process == do_trigger && !maxclones) ||
		(command->process == do_exception && !maxclones))
	    {
		notice (s_OperServ, u->nick, RPL_UNKNOWN_COMMAND, strupper (cmd),
		    haveserv_on == TRUE ? "" : "MSG ", s_OperServ, haveserv_on == TRUE ?
		    "" : securitysetting == 1 ? "@" : "", haveserv_on == TRUE ? "" :
		    securitysetting == 1 ? me.name : "");

		return;
	    }

	    (*command->process) (u);

	    if (!(command->process == do_auth)) /* No pw snooping */
	    {
		log ("OperServ: <%s> %s", u->nick, orig);
		snoop (s_OperServ, "<%s> %s", u->nick, orig);
	    }
	    else /* But we do want to snoop something. */
	    {
		log ("OperServ: <%s> %s ...", u->nick, cmd);
		snoop (s_OperServ, "<%s> %s ...", u->nick, cmd);
	    }
	}
    }
}

/* Return a help message. */
static void do_help (User *u) 
{
    char *cmd = strtok (NULL, " ");
            
    if (!cmd)
	operserv_help_index (u);
    else
    {
	Hash *command, hash_table[] =
	{
	    {"GLOBAL",		H_IRCOP,	operserv_help_global},
	    {"JUPE",		H_IRCOP,	operserv_help_jupe},
	    {"CLONES",		H_IRCOP,	operserv_help_clones},
	    {"AKILL",		H_IRCOP,	operserv_help_akill},
	    {"STATS",		H_IRCOP,	operserv_help_stats},
	    {"TRIGGER",		H_IRCOP,	operserv_help_trigger},
	    {"EXCEPTION",	H_IRCOP,	operserv_help_exception},
	    {"SEARCH",		H_IRCOP,	shared_help_search},
	    {"AUTH",		H_IRCOP,	shared_help_auth},
	    {"DEAUTH",		H_SRA,		shared_help_deauth},
	    {"SHUTDOWN",	H_SRA,		shared_help_shutdown},
	    {"RESTART",		H_SRA,		shared_help_restart},
#ifdef RAWINJECT
	    {"RAW",		H_SRA,		shared_help_raw},
	    {"INJECT",		H_SRA,		shared_help_inject},
#endif
	    {"DEBUG",		H_SRA,		shared_help_debug},
	    {"UPDATE",		H_SRA,		shared_help_update},
	    {"REHASH",		H_SRA,		shared_help_rehash},
	    {"SETTINGS",	H_SRA,		shared_help_settings},
	    {"LOGONMSG",	H_SRA,		operserv_help_logonmsg},
	    {"CYCLELOGS",	H_SRA,		shared_help_cyclelogs},
	    {NULL}
	};

	if ((command = get_help_hash (s_OperServ, u, strupper (cmd), hash_table)))
	{
	    /* Hide disabled or non-applicable commands. */
	    if (((command->process == operserv_help_global) && globalnoticer_on == FALSE) ||
		((command->process == operserv_help_logonmsg) && globalnoticer_on == FALSE) ||
		((command->process == operserv_help_trigger) && !maxclones) ||
		((command->process == operserv_help_exception) && !maxclones))
	    {
		notice (s_OperServ, u->nick, RPL_NO_HELP, strupper (cmd));
		return;
	    }

	    (*command->process) (u);
	}
    }
}

/* Jupiter a server or nickname. */
static void do_jupe (User *u)
{
    char *param = strtok (NULL, " ");
    char *reason = strtok (NULL, "");

    if (!param)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "JUPE Server|Nick [Reason]");
	errmoreinfo (s_OperServ, u->nick, "JUPE");
	return;
    }

    /* Sanity checks .. We could do these all in one shot, but the output
       (and resultant warnings) are more informative this way.
     */
    if (!stricmp (me.name, param))
    {
	notice (s_OperServ, u->nick, RPL_NOT_OPERSTATS);
	globops (s_OperServ, OS_JUPE_MY_SERVER, u->nick);
	return;
    }

    if (!stricmp (me.ruplink, param))
    {
	notice (s_OperServ, u->nick, RPL_NOT_OPERSTATS);
	globops (s_OperServ, OS_JUPE_MY_UPLINK, u->nick);
	return;
    }

    if (is_one_of_mine (param))
    {
	notice (s_OperServ, u->nick, RPL_NOT_OPERSTATS);
	globops (s_OperServ, OS_JUPE_MY_NICK, u->nick, param);
	return;
    }

    /* Check for a *. Rather than actually making a server called bob.* or
       soemthing, we'll bail out here. We could try matching against existing
       server names, but this is fairly 'dangerous', so we'll require a full
       server name.
     */
    if (strchr (param, '*'))
    {
	notice (s_OperServ, u->nick, RPL_BAD_SERVER);
	return;
    }

    /* Is it a server? */
    if (strchr (param, '.'))
    {
	Server *s;

	/* Does that server already exist? */
	s = servlist[SVHASH(param)];

	if (s)
	{
	    /* Yep. Now, we squit it from the network, and free the
	       memory for it.
	     */
	    send_cmd (me.name, "%s %s :Juped by %s: %s", me.token ? "-" : 
		"SQUIT", param, u->nick, reason ? reason : "No reason given");

	    delete_server (s);
	}

	/* Now the server is either gone, or was never there, so we bring
	   on the jupe.
	 */
	send_cmd (NULL, "%s %s 2 :%s - %s", me.token ? "'" : "SERVER",
	    param, reason ? reason : "Jupitered Server", u->nick);

	globops (s_OperServ, OS_JUPED, param, u->nick);
	notice (s_OperServ, u->nick, OS_JUPE_SUCCESSFUL, param);
    }

    /* Doesn't appear to be a server, treat it as a nickname. */
    else
    {
	char buf[256];

	/* Is the nick in use? */
	if (finduser (param))
	{
	    /* Yep. Now we kill the client before bringing on the jupe. */
	    sprintf (buf, "Jupitered by %s: %s", u->nick,
		reason ? reason : "No reason given");
	    kill_user (s_OperServ, param, buf);
	}

	/* Now the nick is gone, or was never there, so we bring on the
	   jupe.
	 */
	sprintf (buf, "Jupitered Nick - %s", u->nick);
	intro_user (param, "jupe", os.host, buf, "+");
	globops (s_OperServ, OS_JUPED, param, u->nick);
	notice (s_OperServ, u->nick, OS_JUPE_SUCCESSFUL, param);
    }
}

/* Send a global notice */
static void do_global (User *u)
{
    char *dest = "", *tmp, *text = strtok (NULL, ""), msg[BUFSIZE], buf[BUFSIZE];
    uint8 i = 0;

    if (!text)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "GLOBAL [Destination] Message");
	errmoreinfo (s_OperServ, u->nick, "GLOBAL");
	return;
    }

    strscpy (buf, text, sizeof (buf));

    tmp = strtok (text, " ");

    if (!stricmp (tmp, "!S"))
    {
	dest = strtok (NULL, " ");

	if (!strlen (dest))
	{
	    notice (s_OperServ, u->nick, RPL_SYNTAX, "GLOBAL Destination Message");
	    errmoreinfo (s_OperServ, u->nick, "GLOBAL");
	    return;
	}

	i = 1;
    }

    if (!i)
	memcpy (msg, buf, sizeof (msg));
    else
    {
	text = strtok (NULL, "");

	if (text)
	    memcpy (msg, text, sizeof (msg));
	else
	{
	    notice (s_OperServ, u->nick, RPL_SYNTAX, "GLOBAL Destination Message");
	    errmoreinfo (s_OperServ, u->nick, "GLOBAL");
	    return;
	}
    }

    if (strlen (globalmsg))
    {
	*buf = 0;

	strcat (buf, globalmsg);
	strcat (buf, " ");
	strcat (buf, msg);

	if (strlen (dest))
	{
	    /* If we have a desintation, don't prefix the notice. */
	    noticeall (dest, msg);
	}
	else
	    noticeall (NULL, buf);

	globops (s_OperServ, OS_GLOBAL_NOTICE, u->nick);

	return;
    }

    if (strlen (dest))
	noticeall (dest, msg);
    else
	noticeall (NULL, msg);

    globops (s_OperServ, OS_GLOBAL_NOTICE, u->nick);
}

/* Handle an AKILL command */
static void do_akill (User *u)
{
    AutoKill *akill;
    User *utmp;
    char *option = strtok (NULL, " ");
    char *param, mask[BUFSIZE], reason[BUFSIZE], *tmp, orig[BUFSIZE], *username;
    char *hostname;
    uint8 nonwild = 0, realname = 0, perm = 0;
    uint32 i, j = 0, expires;

    if (!option)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "AKILL ADD|DEL|LIST [Mask|Number|Option]");
	errmoreinfo (s_OperServ, u->nick, "AKILL");
	return;
    }

    /* ADD */
    if (!stricmp (option, "ADD"))
    {
	param = strtok (NULL, " ");
	tmp = strtok (NULL, "");

	if (!param)
	{
	    notice (s_OperServ, u->nick, RPL_SYNTAX, "AKILL ADD Mask [Expiry] Reason");
	    errmoreinfo (s_OperServ, u->nick, "AKILL");
	    return;
	}

	if (!tmp)
	{
	    /* If no default AKill reason is specified, require them to give one */
	    if (!strlen (def_akill_reason))
	    {
		notice (s_OperServ, u->nick, RPL_SYNTAX, "AKILL ADD Mask [Expiry] Reason");
		errmoreinfo (s_OperServ, u->nick, "AKILL");
		return;
	    }
	    else
		memcpy (reason, def_akill_reason, sizeof (reason));
	}
	else
	    memcpy (reason, tmp, sizeof (reason));

	strscpy (orig, reason, sizeof (orig));

	/* If we don't find an expire time below, we'll use this default */
	expires = def_akill_time;

	tmp = strtok (reason, " ");

	if (!stricmp (tmp, "!P"))
	{
	    expires = 0;
	    j = 1;
	    perm = 1;

	    if (strstr (orig, "!R"))
		tmp = strtok (NULL, " ");
	}

	if (!stricmp (tmp, "!R"))
	{
	    realname = 1;
	    j = 1;

	    if (strstr (orig, "!T"))
		tmp = strtok (NULL, " ");
	}

	if (!stricmp (tmp, "!T"))
	{
	    tmp = strtok (NULL, " ");
	    expires = dotime (tmp);
	    j = 1;

	    if (expires <= 0)
		expires = def_akill_time;
	}

	if (!j)
	    memcpy (reason, orig, sizeof (reason));
	else
	{
	    tmp = strtok (NULL, "");

	    if (tmp)
		memcpy (reason, tmp, sizeof (reason));
	    else
		memcpy (reason, def_akill_reason, sizeof (reason));
	}

	/* Is the param a nick? If so, get the mask for it. But not if it's
	   a realname akill...
	 */
	if (!realname && (utmp = finduser (param)))
	    snprintf (mask, sizeof (mask), "*@%s", utmp->host);
	else
	    strscpy (mask, param, BUFSIZE);

	if (mask && reason)
	{
	    if (is_akilled (mask))
	    {
		notice (s_OperServ, u->nick, RPL_LIST_THERE, mask, "the", "AKill");
		return;
	    }

	    /* Sanity checks - These don't apply if it's a realname akill */
	    if ((strchr (mask, '!') && !realname) ||
		(!strchr (mask, '@') && !realname) ||
		(mask[strlen(mask)-1] == '@' && !realname))
	    {
		notice (s_OperServ, u->nick, OS_AKILL_FORMAT, "");
		return;
	    }

	    if (mask[0] == '@')
	    {
		char *mymask;
		mymask = smalloc (strlen (mask) + 2);
		strcpy (mymask, "*");
		strcat (mymask, mask);
		strcpy (mask, mymask);
		free (mymask);
	    }

	    /* Make sure theres a sufficient number of non-wildcard characters.
		 @ and . are counted as 'wildcards' .. SRAs and realname akills
		 are exempt from this.
	     */
	    if (!is_sra (u) && !realname)
	    {
		for (i = 0; i < strlen (mask); i++)
		    if (!(mask[i] == '*' || mask[i] == '?' || mask[i] == '@' ||
			    mask[i] == '.'))
		nonwild++;

		if (nonwild < nonwildreq)
		{
		    notice (s_OperServ, u->nick, RPL_TOOMANYWILD, nonwildreq);
		    return;
		}
	    }

	    if (akillcnt >= 32767)
	    {
		notice (s_OperServ, u->nick, RPL_LIST_FULL);
		return;
	    }

	    /* Passed all the checks, now add it to the AKill list */
	    if (akillcnt >= akill_size)
	    {
		if (akill_size < 8)
		    akill_size = 8;
		else if (akill_size >= 16384)
		    akill_size = 32767;
		else
		    akill_size *= 2;

		akills = srealloc (akills, sizeof (*akills) * akill_size);
	    }

	    akill = &akills[akillcnt];
	    akill->mask = sstrdup (mask);
	    akill->reason = sstrdup (reason);
	    akill->setter = sstrdup (u->nick);
	    akill->realname = realname;
	    akill->set = time (NULL);
	    akill->expires = expires;

	    akillcnt++;

	    notice (s_OperServ, u->nick, OS_AKILL_ADDED, mask);

	    if (globalonakill_on == TRUE)
		globops (s_OperServ, OS_AKILL_ADDED_GLOBOP, u->nick,
		    realname ? "Realname " : "", perm ? "P" : "", mask, reason);

	    /* Send it out to Services */
	    if (shareakills_on == TRUE)
		svssend ("1 %s %s %d %lu %lu %s", akill->mask, akill->setter,
		    akill->realname, akill->set, akill->expires, akill->reason);

	    if (!realname)
	    {
		username = sstrdup (akill->mask);
		hostname = strchr (username, '@');

		if (!hostname)
		    return;

		*hostname++ = 0;

		if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
		    send_cmd (me.name, "AKILL %s %s 0 %s %ld :%s", hostname,
			username, s_OperServ, time (NULL), akill->reason);
		else
		    send_cmd (me.name, "%s %s %s :%s", me.token ? "V" : "AKILL",
			hostname, username, akill->reason);

		free (username);
	    }
	    else
	    {
		/* Hunt down any users that match this AKill. */
		for (u = firstuser (); u; u = nextuser())
		{
		    if (u && match_wild_nocase (mask, u->real))
		    {
			if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
			    send_cmd (me.name, "AKILL %s * 0 %s %ld :%s", u->host,
				s_OperServ, time (NULL), akill->reason);
			else
			    send_cmd (me.name, "%s %s * :%s", me.token ? "V" :
				"AKILL", u->host, akill->reason);
		    }
		}
	    }
	}
	else
	{
	    notice (s_OperServ, u->nick, RPL_SYNTAX, "AKILL ADD Mask [Expiry] Reason");
	    errmoreinfo (s_OperServ, u->nick, "AKILL");
	    return;
	}
    }

    /* DEL */
    if (!stricmp (option, "DEL"))
    {
	param = strtok (NULL, " ");

	if (!param)
	{
	    notice (s_OperServ, u->nick, RPL_SYNTAX, "AKILL DEL Number|ALL");
	    errmoreinfo (s_OperServ, u->nick, "AKILL");
	    return;
	}

	if (!stricmp (param, "ALL"))
	{
	    for (i = 0; i < akillcnt; i++)
	    {
		j++;

		free (akills[i].setter);
		free (akills[i].reason);

		username = sstrdup (akills[i].mask);
		hostname = strchr (username, '@');

		if (hostname)
		{
		    *hostname++ = 0;

		    send_cmd (me.name, "%s %s %s", me.token ? "Y" : "RAKILL",
			hostname, username);
		}

		/* Send it out to Services */
		if (shareakills_on == TRUE)
		    svssend ("2 %s", akills[i].mask);

		free (username);
		free (akills[i].mask);
	    }

	    akillcnt = 0;

	    notice (s_OperServ, u->nick, OS_ALL_AKILL_DELETED, j);

	    if (globalonakill_on == TRUE)
		globops (s_OperServ, OS_ALL_AKILL_DEL_GLOBOP, u->nick, j);

	    return;
	}
	else
	{
	    for (i = 0; i < akillcnt; i++)
	    {
		j++;

		if (j == atoi (param) || !stricmp (param, akills[i].mask))
		{
		    notice (s_OperServ, u->nick, OS_AKILL_DELETED, akills[i].mask);

		    if (globalonakill_on == TRUE)
			globops (s_OperServ, OS_AKILL_DELETED_GLOBOP, u->nick,
			    akills[i].realname ? "Realname " : "",
			    akills[i].expires == 0 ? "P" : "", akills[i].mask);

		    free (akills[i].setter);
		    free (akills[i].reason);

		    username = sstrdup (akills[i].mask);
		    hostname = strchr (username, '@');

		    if (hostname) 
		    {
			*hostname++ = 0;

			send_cmd (me.name, "%s %s %s", me.token ? "Y" :
			    "RAKILL", hostname, username);
		    }

		    /* Send it out to Services */
		    if (shareakills_on == TRUE)
			svssend ("2 %s", akills[i].mask);

		    free (username);
		    free (akills[i].mask);

		    akillcnt--;

		    if (i < akillcnt)
			memmove (akills + i, akills + i + 1, sizeof (*akills) * (akillcnt - i));

		    return;
		}
	    }

	    notice (s_OperServ, u->nick, OS_AKILL_NOT_FOUND, param);
	    return;
	}
    }

    /* LIST */
    if (!stricmp (option, "LIST"))
    {
	uint8 showreasons = 0;
	char *param1 = strtok (NULL, " ");
	char *param2 = strtok (NULL, " ");

	/* We only show reasons if asked to. */
	if (param1 && !stricmp (param1, "FULL"))
	    showreasons = 1;

	/* Back out here if theres no AKills */
	if (!akillcnt)
	{
	    notice (s_OperServ, u->nick, RPL_LIST_EMPTY, "The", "", "AKill");
	    return;
	}

	notice (s_OperServ, u->nick, OS_AKILL_LIST,
	    showreasons ? " - With reasons" : "",
	    (showreasons && param2) ? " - Matching " :
	    (!showreasons && param1) ? " - Matching " : "",
	    (showreasons && param2) ? param2 : (!showreasons && param1) ?
		param1 : "");

	for (i = 0; i < akillcnt; i++)
	{
	    if (showreasons && param2)
		if (!match_wild_nocase (param2, akills[i].mask) &&
		    !match_wild_nocase (param2, akills[i].setter))
		    continue;

	    if (!showreasons && param1)
		if (!match_wild_nocase (param1, akills[i].mask) &&
		    !match_wild_nocase (param1, akills[i].setter))
		    continue;

	    j++;

	    if (akills[i].expires == 0)
		notice (s_OperServ, u->nick, OS_PAKILL_LIST_MATCH, j,
		    akills[i].mask, akills[i].setter,
		    showreasons ? akills[i].reason : "");
	    else
	    {
		expires = (akills[i].expires - (time (NULL) - akills[i].set));

		/* If the AKill is waiting to be expired, show how long ago
		   it should have expired, rather than a negative seconds value.
		 */
		if (expires > 0)
		    notice (s_OperServ, u->nick, OS_AKILL_LIST_MATCH, j,
			akills[i].mask, akills[i].setter, duration (expires, 1),
			"", showreasons ? akills[i].reason : "");
		else
		    notice (s_OperServ, u->nick, OS_AKILL_LIST_MATCH, j,
			akills[i].mask, akills[i].setter, time_ago (expires),
			" ago", showreasons ? akills[i].reason : "");
	    }
	}

	notice (s_OperServ, u->nick, OS_AKILL_LIST_END);
	return;
    }
}

/* Check all AKills and expire any nessecary */
void expire_akills ()
{
    char *user, *host;
    uint32 i, j = 0;

    if (logupdates_on == TRUE)
	log ("OS:SYNC: Checking expires...");

    for (i = 0; i < akillcnt; i++)
    {
	/* AKills with an expire time of 0 are permanent */
	if (akills[i].expires > 0)
	{
	    if ((akills[i].expires - (time (NULL) - akills[i].set)) < 0)
	    {
		log ("OS:SYNC:EXPIRE: %sAKill on %s by %s: %s",
		    akills[i].realname ? "Realname " : "",
		    akills[i].mask, akills[i].setter, akills[i].reason);

		user = sstrdup (akills[i].mask);
		host = strchr (user, '@');

		if (host)
		{
		    *host++ = 0;

		    send_cmd (me.name, "%s %s %s", me.token ? "Y" :
			"RAKILL", host, user);
		}

		/* Send it out to Services */
		if (shareakills_on == TRUE)
		    svssend ("2 %s", akills[i].mask);

		free (user);
		free (akills[i].setter);
		free (akills[i].reason);
		free (akills[i].mask);

		akillcnt--;

		if (i < akillcnt)
		    memmove (akills + i, akills + i + 1, sizeof (*akills) * (akillcnt - i));

		i--;
		j++;
	    }
	}
    }

    /* Only log this if we have something to report, or if we should
       log it regardless.
     */
    if (logupdates_on == TRUE || j)
	log ("OS:SYNC:EXPIRE: %d AKills expired.", j);
}

/* If the given information matches an AKill, kill that user and place an
   AKill on it. (If we see a user that's AKilled, then the AKill was
   probably rehashed out or the server restarted)
 */
uint8 check_akill (const char *nick, const char *user, const char *host, const char *real)
{
    uint32 i;
    char buf[512];

    strscpy (buf, user, sizeof (buf) -2);
    i = strlen (buf);
    buf[i++] = '@';
    strscpy (buf + i, host, sizeof (buf) -i);

    for (i = 0; i < akillcnt; i++)
    {
	if ((!akills[i].realname && match_wild_nocase (akills[i].mask, buf)) ||
	    (akills[i].realname && match_wild_nocase (akills[i].mask, real)))
	{
	    /* They're AKilled! For some reason the server hasn't disconnected
	       them yet. Perhaps our AKill was rehashed out. We'll re place it.
	       The server should disconnect them shortly after this. We'll send
	       out a kill, just in case....
	     */
	    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
		send_cmd (me.name, "AKILL %s * 0 %s %ld :%s", host,
		    s_OperServ, time (NULL), akills[i].reason);
	    else
		send_cmd (me.name, "%s %s * :%s", me.token ? "V" : "AKILL",
		    host, akills[i].reason);

	    kill_user (s_OperServ, nick, akills[i].reason);

	    return 1;
	}
    }

    return 0;
}

/* Show various services statistics */
static void do_stats (User *u)
{
    uint32 uptime = time (NULL) - me.since;

    notice (s_OperServ, u->nick, OS_STATS_START);
    notice (s_OperServ, u->nick, " ");
    notice (s_OperServ, u->nick, OS_STATS, "Users", userstat);
    notice (s_OperServ, u->nick, OS_STATS, "Channels", countchans (0));
    notice (s_OperServ, u->nick, OS_STATS, "Servers", servstat);
    notice (s_OperServ, u->nick, " ");
    notice (s_OperServ, u->nick, OS_STATS_UPTIME, "Uptime    ", duration (uptime, 2));

    /* Update MaxUptime as needed */
    if (uptime > maxuptime)
	maxuptime = uptime;

    notice (s_OperServ, u->nick, OS_STATS_UPTIME, "Max Uptime", duration (maxuptime, 2));
    notice (s_OperServ, u->nick, " ");
    notice (s_OperServ, u->nick, OS_STATS_END);
}

/* Handle a LOGONMSG command */
static void do_logonmsg (User *u)
{
    char *cmd = strtok (NULL, " ");

    if (!cmd)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX,
	    "LOGONMSG ADD|DEL|LIST [Text|Number]");
	errmoreinfo (s_OperServ, u->nick, "LOGONMSG");
    }
    else
    {
	Hash *command, hash_table[] =
	{
	    {"ADD",		H_SRA,		do_logonmsg_add},
	    {"DEL",		H_SRA,		do_logonmsg_del},
	    {"LIST",		H_SRA,		do_logonmsg_list},
	    {NULL}
	};

	if ((command = get_sub_hash (s_OperServ, u, strupper (cmd),
				 hash_table)))
	    (*command->process) (u);
	else 
	{
	    notice (s_OperServ, u->nick, RPL_SYNTAX,
		"LOGONMSG ADD|DEL|LIST [Text|Number]");
	    errmoreinfo (s_OperServ, u->nick, "LOGONMSG");
	}
    }   
}

/* Add a logonmsg to the list */
static void do_logonmsg_add (User *u)
{
    char *msg = strtok (NULL, "");
    char buf[512];

    if (!msg)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "LOGONMSG ADD Text");
	errmoreinfo (s_OperServ, u->nick, "LOGONMSG");
	return;
    }

    if (strlen (msg) > 256)
    {
	notice (s_OperServ, u->nick, RPL_MSG_LEN, strlen (msg));
	return;
    }

    if (stamplogons_on == TRUE)
    {
	time_t t;
	struct tm tm;

	time (&t);
	tm = *localtime (&t);
	strftime (buf, sizeof (buf) - 1, "[\2News\2 - %B %d, %Y] ", &tm);
	strcat (buf, msg);
    }
    else
	snprintf (buf, sizeof (buf), msg);

    noticeall (NULL, buf);
    new_message (u->nick, time (NULL), buf);
    notice (s_OperServ, u->nick, OS_LOGONMSG_ADDED);
}

/* Remove a logonmsg. */
static void do_logonmsg_del (User *u)
{
    MSG *msg;
    char *param = strtok (NULL, " ");
    uint32 i = 0;

    if (!param)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "LOGONMSG DEL Number");
	errmoreinfo (s_OperServ, u->nick, "LOGONMSG");
	return;
    }

    if (!stricmp (param, "ALL"))
    {
	for (msg = msglist; msg; msg = msg->next)
	{
	    i++;
	    free (msg->text);
	    free (msg->setter);

	    if (msg->prev)
		msg->prev->next = msg->next;
	    else
		msglist = msg->next;
	    if (msg->next)
		msg->next->prev = msg->prev;
	}

	free (msg);
	notice (s_OperServ, u->nick, OS_ALL_LOGONMSG_DELETED, i);
	return;
    }
    else
    {
	for (msg = msglist; msg; msg = msg->next)
	{
	    i++;
	    if (i == atoi (param))
	    {
		free (msg->text);
		free (msg->setter);

		if (msg->prev)
		    msg->prev->next = msg->next;
		else
		    msglist = msg->next;
		if (msg->next)
		    msg->next->prev = msg->prev;

		free (msg);
		notice (s_OperServ, u->nick, OS_LOGONMSG_DELETED, i);
		return;
	    }
	}
    }
    notice (s_OperServ, u->nick, OS_LOGONMSG_NOT_FOUND, param);
}

/* List the current logonmsgs */
static void do_logonmsg_list (User *u)
{
    MSG *msg;
    uint32 i = 0;

    notice (s_OperServ, u->nick, OS_LOGONMSG_LIST_START);

    for (msg = msglist; msg; msg = msg->next)
    {
	i++;
	notice (s_OperServ, u->nick, OS_LOGONMSG_LIST, i, msg->setter,
	    time_ago (msg->set));
	notice (s_OperServ, u->nick, msg->text);
    }

    notice (s_OperServ, u->nick, OS_LOGONMSG_LIST_END);
}

/* Display the list of clones on the network */
static void do_clones (User *u)
{
    Clone *clone;
    User *user;
    char *param = strtok (NULL, " ");
    char *limit = strtok (NULL, " ");
    uint8 max = 250;
    uint32 i, j, shown = 0, count = 0;

    if (!param)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "CLONES Pattern [Limit]");
	errmoreinfo (s_OperServ, u->nick, "CLONES");
	return;
    }

    if (limit)
    {
	max = atoi (limit);

	if (max > 250)
	    max = 250;
    }

    notice (s_OperServ, u->nick, OS_CLONES_START, max);
    notice (s_OperServ, u->nick, OS_CLONES_HEADER);

    for (i = 0; i < HASHSIZE; i++)
    {
	for (clone = clonelist[i]; clone; clone = clone->next)
	{
	    uint8 shost = 0;

	    if (!match_wild_nocase (param, clone->host))
		continue;

	    if (clone->cnt < 2)
		continue;

	    if (count >= max)
		break;

	    count++;

	    for (j = 0; j < HASHSIZE; j++)
	    {
		for (user = userlist[j]; user; user = user->next)
		{
		    if (stricmp (clone->host, user->host))
			continue;

		    /* Very few hosts will go over this.. */
		    notice (s_OperServ, u->nick, "%s%-44s %s%s%s%s%s",
			shost ? "" : "\2", shost ? "" : clone->host,
			user->nick, shost ? "" : " (",
			shost ? "" : itoa (clone->cnt), shost ? "" : ")",
			shost ? "" : "\2");
		    shost = 1;
		    shown++;
		}
	    }
	}
    }

    notice (s_OperServ, u->nick, OS_CLONES_END, shown);
}

/* Handle a TRIGGER command */
static void do_trigger (User *u)
{
    char *cmd = strtok (NULL, " ");

    if (!cmd)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX,
	    "TRIGGER ADD|DEL|LIST [Host|Number] [Limit]");
	errmoreinfo (s_OperServ, u->nick, "TRIGGER");
    }

    else
    {
	Hash *command, hash_table[] =
	{
	    {"ADD",		H_IRCOP,	do_trigger_add},
	    {"DEL",		H_IRCOP,	do_trigger_del},
	    {"LIST",		H_IRCOP,	do_trigger_list},
	    {NULL}
	};

	if ((command = get_sub_hash (s_OperServ, u, strupper (cmd),
				 hash_table)))
	    (*command->process) (u);
	else
	{
	    notice (s_OperServ, u->nick, RPL_SYNTAX,
		"TRIGGER ADD|DEL|LIST [Host|Number] [Limit]");
	    errmoreinfo (s_OperServ, u->nick, "TRIGGER");
	}
    }
}

/* Add a trigger to the list */
static void do_trigger_add (User *u)
{
    Trigger *trigger;
    char *mask = strtok (NULL, " "), *limit = strtok (NULL, " ");
    char *username, *hostname;

    if (!mask || !limit)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "TRIGGER ADD Host Limit");
	errmoreinfo (s_OperServ, u->nick, "TRIGGER");
	return;
    }

    if (!strchr (mask, '@'))
    {
	notice (s_OperServ, u->nick, OS_TRIGGER_FORMAT);
	return;
    }

    if (!is_sra (u))
    {
	uint8 nonwild = 0;
	uint32 i;

	for (i = 0; i < strlen (mask); i++)
	    if (!(mask[i] == '*' || mask[i] == '?' || mask[i] == '@' ||
		mask[i] == '.'))
		nonwild++;

	if (nonwild < nonwildreq)
	{
	    notice (s_OperServ, u->nick, RPL_TOOMANYWILD, nonwildreq);
	    return;
	}
    }

    username = sstrdup (mask);
    hostname = strchr (username, '@');

    if (!hostname)
	return;

    *hostname++ = 0;

    if ((trigger = findtrig (username, hostname)))
    {
	trigger->limit = atoi (limit);
	notice (s_OperServ, u->nick, OS_RETRIGGER, mask, atoi (limit));
	free (username);
	return;
    }

    free (username);

    new_trigger (mask, atoi (limit));
    notice (s_OperServ, u->nick, OS_TRIGGER_ADDED, mask);
}

/* Remove a trigger. */
static void do_trigger_del (User *u)
{
    Trigger *trigger;
    char *param = strtok (NULL, " ");
    uint32 i = 0;

    if (!param)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "TRIGGER DEL Number|Host|ALL");
	errmoreinfo (s_OperServ, u->nick, "TRIGGER");
	return;
    }

    if (!stricmp (param, "ALL"))
    {
	for (trigger = triggerlist; trigger; trigger = trigger->next)
	{
	    i++;

	    /* Notice them up here so we can show the host */
	    notice (s_OperServ, u->nick, OS_ALL_TRIGGER_DELETED, trigger->host);

	    if (trigger->prev)
		trigger->prev->next = trigger->next;
	    else
		triggerlist = trigger->next;
	    if (trigger->next)
		trigger->next->prev = trigger->prev;
	}

	free (trigger);
	return;
    }
    else
    {
	for (trigger = triggerlist; trigger; trigger = trigger->next)
	{
	    i++;
	    if (!stricmp (param, trigger->host) || i == atoi (param))
	    {
		if (trigger->prev)
		    trigger->prev->next = trigger->next;
		else
		    triggerlist = trigger->next;
		if (trigger->next)
		    trigger->next->prev = trigger->prev;

		free (trigger);
		if (atoi (param) > 0)
		    notice (s_OperServ, u->nick, OS_TRIGGER_DELETED, "#", param);
		else
		    notice (s_OperServ, u->nick, OS_TRIGGER_DELETED, "on", param);
		return;
	    }
	}
    }
    notice (s_OperServ, u->nick, OS_TRIGGER_NOT_FOUND, param);
}

/* List the current triggers */
static void do_trigger_list (User *u)
{
    Trigger *trigger;
    uint32 i = 0;

    notice (s_OperServ, u->nick, OS_TRIGGER_LIST_START);

    for (trigger = triggerlist; trigger; trigger = trigger->next)
    {
	i++;
	notice (s_OperServ, u->nick, OS_TRIGGER_LIST, i, trigger->host,
	    trigger->limit);
    }

    notice (s_OperServ, u->nick, OS_TRIGGER_LIST_END);
}

/* Handle a EXCEPTION command */
static void do_exception (User *u)
{
    char *cmd = strtok (NULL, " ");

    if (!cmd)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX,
	    "EXCEPTION ADD|DEL|LIST [Host|Number]");
	errmoreinfo (s_OperServ, u->nick, "EXCEPTION");
    }

    else
    {
	Hash *command, hash_table[] =
	{
	    {"ADD",		H_IRCOP,	do_exception_add},
	    {"DEL",		H_IRCOP,	do_exception_del},
	    {"LIST",		H_IRCOP,	do_exception_list},
	    {NULL}
	};

	if ((command = get_sub_hash (s_OperServ, u, strupper (cmd),
				 hash_table)))
	    (*command->process) (u);
	else
	{
	    notice (s_OperServ, u->nick, RPL_SYNTAX,
		"EXCEPTION ADD|DEL|LIST [Host|Number]");
	    errmoreinfo (s_OperServ, u->nick, "EXCEPTION");
	}
    }
}

/* Add a exception to the list */
static void do_exception_add (User *u)
{
    Exception *exception;
    char *mask = strtok (NULL, " ");
    char *username, *hostname;

    if (!mask)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "EXCEPTION ADD Host");
	errmoreinfo (s_OperServ, u->nick, "EXCEPTION");
	return;
    }

    if (!strchr (mask, '@'))
    {
	notice (s_OperServ, u->nick, OS_EXCEPTION_FORMAT);
	return;
    }

    if (!is_sra (u))
    {
	uint8 nonwild = 0;
	uint32 i;

	for (i = 0; i < strlen (mask); i++)
	    if (!(mask[i] == '*' || mask[i] == '?' || mask[i] == '@' ||
		mask[i] == '.'))
		nonwild++;

	if (nonwild < nonwildreq)
	{
	    notice (s_OperServ, u->nick, RPL_TOOMANYWILD, nonwildreq);
	    return;
	}
    }

    username = sstrdup (mask);
    hostname = strchr (username, '@');

    if (!hostname)
	return;

    *hostname++ = 0;

    if ((exception = findexcept (username, hostname)))
    {
	notice (s_OperServ, u->nick, OS_ALREADY_EXCEPT, mask);
	free (username);
	return;
    }

    free (username);

    new_exception (mask);
    notice (s_OperServ, u->nick, OS_EXCEPTION_ADDED, mask);
}

/* Remove a exception. */
static void do_exception_del (User *u)
{
    Exception *exception;
    char *param = strtok (NULL, " ");
    uint32 i = 0;

    if (!param)
    {
	notice (s_OperServ, u->nick, RPL_SYNTAX, "EXCEPTION DEL Number|Host|ALL");
	errmoreinfo (s_OperServ, u->nick, "EXCEPTION");
	return;
    }

    if (!stricmp (param, "ALL"))
    {
	for (exception = exceptionlist; exception; exception = exception->next)
	{
	    i++;

	    /* Notice them up here so we can show the host */
	    notice (s_OperServ, u->nick, OS_EXCEPTIONS_DELETED,
		exception->host);

	    if (exception->prev)
		exception->prev->next = exception->next;
	    else
		exceptionlist = exception->next;
	    if (exception->next)
		exception->next->prev = exception->prev;
	}

	free (exception);
	return;
    }
    else
    {
	for (exception = exceptionlist; exception; exception = exception->next)
	{
	    i++;
	    if (!stricmp (param, exception->host) || i == atoi (param))
	    {
		if (exception->prev)
		    exception->prev->next = exception->next;
		else
		    exceptionlist = exception->next;
		if (exception->next)
		    exception->next->prev = exception->prev;

		free (exception);
		if (atoi (param) > 0)
		    notice (s_OperServ, u->nick, OS_EXCEPTION_DELETED, "#", param);
		else
		    notice (s_OperServ, u->nick, OS_EXCEPTION_DELETED, "on", param);
		return;
	    }
	}
    }
    notice (s_OperServ, u->nick, OS_EXCEPTION_NOT_FOUND, param);
}

/* List the current exceptions */
static void do_exception_list (User *u)
{
    Exception *exception;
    uint32 i = 0;

    notice (s_OperServ, u->nick, OS_EXCEPTION_LIST_START);

    for (exception = exceptionlist; exception; exception = exception->next)
    {
	i++;
	notice (s_OperServ, u->nick, OS_EXCEPTION_LIST, i, exception->host);
    }

    notice (s_OperServ, u->nick, OS_EXCEPTION_LIST_END);
}

/* Load the OperServ DB from disk */
void load_os_dbase ()
{
    FILE *f = fopen (OPERSERV_DB, "r");
    char *item, *s, dBuf[2048], backup[2048];
    uint8 dbver = 0, dolog = 0, dbend = 0;
    uint32 ain = 0, lin = 0, tin = 0, ein = 0;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

#ifdef HAVE_GETTIMEOFDAY
    log_sameline (1, "OS:DB: Loading... ");
#else
    log ("OS:DB: Loading...");
#endif

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (0, "No %s found!", OPERSERV_DB);
#else
	log ("OS:DB: No %s found!", OPERSERV_DB);
#endif

	dolog = 1;

	/* Check for a db.save */
	strcpy (backup, OPERSERV_DB);
	strcat (backup, ".save");

	if (rename (backup, OPERSERV_DB) > 0)
	{
#ifdef HAVE_GETTIMEOFDAY
	    log_sameline (0, "Recovering %s.save", OPERSERV_DB);
#else
	    log ("OS:DB: Recovering %s.save", OPERSERV_DB);
#endif
	    f = fopen (OPERSERV_DB, "r");
	}
	else
	    return;
    }

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    while (fgets (dBuf, 2047, f))
    {
	item = strtok (dBuf, " ");

	/* Ignore certain things.. */
	if (*item == '#' || *item == '\n' || *item == '\t' || *item == ' ' ||
	    *item == '\0' || *item == '\r' || !*item)
	    continue;

	if (!strcmp (item, "OV"))
	    if ((s = strtok (NULL, "\n"))) { dbver = atoi (s); }

	/* AKill */
	if (!strcmp (item, "AK"))
	{
	    AutoKill *akill;
	    char *mask, *setter, *realname, *set, *expires, *reason;

	    mask = strtok (NULL, " ");
	    setter = strtok (NULL, " ");
	    realname = strtok (NULL, " ");
	    set = strtok (NULL, " ");
	    expires = strtok (NULL, " ");
	    reason = strtok (NULL, "\n");

	    if (!mask || !setter || !realname || !set || !expires || !reason)
		continue;

	    if (akillcnt >= akill_size)
	    {
		if (akill_size < 8)
		    akill_size = 8;
		else if (akill_size >= 16384)
		    akill_size = 32767;
		else
		    akill_size *= 2;

		akills = srealloc (akills, sizeof (*akills) * akill_size);
	    }

	    akill = &akills[akillcnt];
	    akill->mask = sstrdup (mask);
	    akill->setter = sstrdup (setter);
	    akill->realname = atoi (realname);
	    akill->set = atol (set);
	    akill->expires = atol (expires);
	    akill->reason = sstrdup (reason);

	    akillcnt++;
	    ain++;

	    continue;
	}

	/* Logon Message */
	if (!strcmp (item, "LM"))
	{
	    char *setter, *text;
	    time_t set;

	    setter = strtok (NULL, " ");
	    set = atol (strtok (NULL, " "));
	    text = strtok (NULL, "\n");

	    if (setter && set && text)
	    {
		new_message (setter, set, text);
		lin++;
	    }

	    continue;
	}

	/* Trigger */
	if (!strcmp (item, "TR"))
	{
	    char *host;
	    uint8 limit;

	    host = strtok (NULL, " ");
	    limit = atoi (strtok (NULL, "\n"));

	    if (host && limit)
	    {
		new_trigger (host, limit);
		tin++;
	    }

	    continue;
	}

	/* Exception */
	if (!strcmp (item, "EX"))
	{
	    char *host;

	    host = strtok (NULL, "\n");

	    if (host)
	    {
		new_exception (host);
		ein++;
	    }

	    continue;
	}

	/* End of DB */
	if (!strcmp (item, "DE"))
	{
	    uint8 newline = 0;

	    if ((s = strtok (NULL, " ")))
	    {
		if (atoi (s) != ain)
		{
		    log_sameline (0, "");
		    log ("Warning: Expected %s AKills, got %d.", s, ain);
		    newline = 1;
		}
	    }

	    if ((s = strtok (NULL, " ")))
	    {
		if (atoi (s) != lin)
		{
		    if (!newline)
		    {
		        log_sameline (0, "");
			newline = 1;
		    }

		    log ("Warning: Expected %s Logon Messages, got %d.", s, lin);
		}
	    }

	    if ((s = strtok (NULL, " ")))
	    {
		if (atoi (s) != tin)
		{
		    if (!newline)
		    {
		        log_sameline (0, "");
			newline = 1;
		    }

		    log ("Warning: Expected %s Triggers, got %d.", s, tin);
		}
	    }

	    if ((s = strtok (NULL, "\n")))
	    {
		if (atoi (s) != ein)
		{
		    if (!newline)
		    {
			log_sameline (0, "");
			newline = 1;
		    }

		    log ("Warning: Expected %s Exceptions, got %d.", s, ein);
		}
	    }

	    if (newline)
		log_sameline (1, "OS:DB: Loading... ");

	    dbend = 1;
	}
    }

    fclose (f);

    if (!dbend && dbver > 1 && !force)
    {
	/* We need a newline first. */
	log_sameline (0, "");
	log ("Incomplete operserv.db detected. Only found %d akills, %d logon messages, %d triggers and %d exceptions.", ain, lin, tin, ein);
	fatal (1, "Please restore from backup or run OperStats with -force\n");
    }

#ifdef HAVE_GETTIMEOFDAY
    if (!dolog)
    {
	gettimeofday (&now, NULL);
	timersub (&now, &start, &tmp);
	log_sameline (0, "finished in %d ms. (%d AKill%s, %d Logon Message%s, %d Trigger%s, %d Exception%s)",
	    tv2ms (&tmp), ain, ain == 1 ? "" : "s", lin, lin == 1 ? "" : "s", tin, tin == 1 ? "" : "s",
	    ein, ein == 1 ? "" : "s");
    }
#endif
}

/* Write the operserv db to disk. */
void save_os_dbase ()
{
    FILE *f;
    MSG *msg;
    Trigger *trigger;
    Exception *exception;
    char backup[2048];
    uint8 dolog = 0;
    uint32 i, aout = 0, lout = 0, tout = 0, eout = 0;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

    if (logupdates_on == TRUE)
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (1, "OS:DB: Saving... ");
#else
	log ("OS:DB: Saving...");
#endif

    f = fopen (OPERSERV_DB, "w");

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (0, "Can't write to %s!", OPERSERV_DB);
#else
	log ("OS:DB: Can't write to %s!", OPERSERV_DB);
#endif
	dolog = 1;

	strcpy (backup, OPERSERV_DB);
	strcat (backup, ".save");

	if (rename (backup, OPERSERV_DB) < 0)
	    fatal (0, "OS:DB: Can't restore backup of %s!", OPERSERV_DB);

	return;
    }

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    /* Write the version */
    fprintf (f, "OV %d\n", os.version);

    /* Write any AKills: One line per AKill. Format:
       AK mask setter realname? set expires reason
     */
    for (i = 0; i < akillcnt; i++)
    {
	fprintf (f, "AK %s %s %d %lu %d %s\n", akills[i].mask, akills[i].setter,
	    akills[i].realname, akills[i].set, akills[i].expires, akills[i].reason);

	aout++;
    }

    /* Write any LogonMSGs: One line per message. Format:
       LM setter set text
     */
    for (msg = msglist; msg; msg = msg->next)
    {
	fprintf (f, "LM %s %lu %s\n", msg->setter, msg->set, msg->text);

	lout++;
    }

    /* Write any Triggers: One line per trigger. Format:
       TR host limit
     */
    for (trigger = triggerlist; trigger; trigger = trigger->next)
    {
	fprintf (f, "TR %s %d\n", trigger->host, trigger->limit);

	tout++;
    }

    /* Write any Exceptions: One line per exception. Format:
       EX host
     */
    for (exception = exceptionlist; exception; exception = exception->next)
    {
	fprintf (f, "EX %s\n", exception->host);

	eout++;
    }

    /* Write some stats. This helps us determine if a DB has been truncated as well. */
    fprintf (f, "DE %d %d %d %d\n", aout, lout, tout, eout);

    fclose (f);

#ifdef HAVE_GETTIMEOFDAY
    if (logupdates_on == TRUE && !dolog)
    {
	gettimeofday (&now, NULL);
	timersub (&now, &start, &tmp);
	log_sameline (0, "finished in %d ms (%d akills, %d logon messages, %d triggers, %d exceptions)", tv2ms (&tmp), aout, lout, tout, eout);
    }
#endif
}
