/* RootServ routines

   Cygnus IRC Services - Copyright (c) 2001-2002 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

#include "../inc/services.h"

int32 akillcnt = 0;
int32 akill_size = 0;
struct akill_ *akills = 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_rehash (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);
static void do_csop (User *u);
static void do_auth (User *u);
static void do_deauth (User *u);
static void do_hold (User *u);
static void do_mark (User *u);
#ifdef RAWINJECT
static void do_raw (User *u);
static void do_inject (User *u);
#endif
static void do_debug (User *u);
static void do_shutdown (User *u);
static void do_restart (User *u);
static void do_settings (User *u);
static void do_update (User *u);
static void do_resetnews (User *u);
static void do_search (User *u);
static void do_stats (User *u);
static void do_listnicks (User *u);
static void do_listchans (User *u);
static void do_cyclelogs (User *u);

/* Add a new trigger to the trigger list */
Trigger *new_trigger (const char *host, int 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 Cygnus IRC Services v%s",
	    version);
	return;
    }
}

/* Main RootServ routine. */
void rootserv (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_RootServ, u->nick, "PING %s", s);
	return;
    }
    else if (!stricmp (cmd, "\1VERSION\1"))
    {
	ctcpreply (s_RootServ, u->nick, "VERSION Cygnus IRC Services v%s",
	    version);
	return;
    }

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

    else if (!is_oper (u))
    {
	notice (s_RootServ, u->nick, RS_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},
	    {"TRIGGER",		H_IRCOP,	do_trigger},
	    {"EXCEPTION",	H_IRCOP,	do_exception},
	    {"STATS",		H_IRCOP,	do_stats},
	    {"AUTH",		H_IRCOP,	do_auth},
	    {"SEARCH",		H_CSOP,		do_search},
	    {"LISTNICKS",	H_CSOP,		do_listnicks},
	    {"LISTCHANS",	H_CSOP,		do_listchans},
	    {"DEAUTH",		H_SRA,		do_deauth},
	    {"HOLD",		H_SRA,		do_hold},
	    {"MARK",		H_SRA,		do_mark},
	    {"CSOP",		H_SRA,		do_csop},
	    {"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},
	    {"RESETNEWS",	H_SRA,		do_resetnews},
	    {"CYCLELOGS",	H_SRA,		do_cyclelogs},
	    {NULL}
	};
            
	if ((command = get_hash (s_RootServ, u, strupper (cmd),
				 hash_table)))
	{
	    /* Check if they want a disabled command. */
	    if ((command->process == do_global &&
		globalnoticer_on == FALSE) ||
		(command->process == do_clones && !maxclones) ||
		(command->process == do_trigger && !maxclones) ||
		(command->process == do_exception && !maxclones) ||
		(command->process == do_resetnews &&
		memoserv_on == FALSE))
	    {
		notice (s_RootServ, u->nick, RPL_UNKNOWN_COMMAND, strupper (cmd),
		    haveserv_on == TRUE ? "" : "MSG ", s_RootServ,
		    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 ("RS: <%s> %s", u->nick, orig);
		snoop (s_RootServ, "RS: <%s> %s", u->nick, orig);
	    }
	    else /* But we do want to snoop something. */
	    {
		log ("RS: <%s> AUTH ...", u->nick);
		snoop (s_RootServ, "RS: <%s> AUTH ...", u->nick);
	    }
	}
    }
}

/* Return a help message. */
static void do_help (User *u) 
{
    char *cmd = strtok (NULL, " ");
            
    if (!cmd)
	rootserv_help_index (u);
    else
    {
	Hash *command, hash_table[] =
	{
	    {"GLOBAL",		H_IRCOP,	rootserv_help_global},
	    {"JUPE",		H_IRCOP,	rootserv_help_jupe},
	    {"CLONES",		H_IRCOP,	rootserv_help_clones},
	    {"AKILL",		H_IRCOP,	rootserv_help_akill},
	    {"TRIGGER",		H_IRCOP,	rootserv_help_trigger},
	    {"EXCEPTION",	H_IRCOP,	rootserv_help_exception},
	    {"STATS",		H_IRCOP,	rootserv_help_stats},
	    {"AUTH",		H_IRCOP,	rootserv_help_auth},
	    {"SEARCH",		H_CSOP,		rootserv_help_search},
	    {"LISTNICKS",	H_CSOP,		rootserv_help_listnicks},
	    {"LISTCHANS",	H_CSOP,		rootserv_help_listchans},
	    {"DEAUTH",		H_SRA,		rootserv_help_deauth},
	    {"SHUTDOWN",	H_SRA,		rootserv_help_shutdown},
	    {"HOLD",		H_SRA,		rootserv_help_hold},
	    {"MARK",		H_SRA,		rootserv_help_mark},
	    {"RESTART",		H_SRA,		rootserv_help_restart},
#ifdef RAWINJECT
	    {"RAW",		H_SRA,		rootserv_help_raw},
	    {"INJECT",		H_SRA,		rootserv_help_inject},
#endif
	    {"CSOP",		H_SRA,		rootserv_help_csop},
	    {"DEBUG",		H_SRA,		rootserv_help_debug},
	    {"UPDATE",		H_SRA,		rootserv_help_update},
	    {"REHASH",		H_SRA,		rootserv_help_rehash},
	    {"SETTINGS",	H_SRA,		rootserv_help_settings},
	    {"RESETNEWS",	H_SRA,		rootserv_help_resetnews},
	    {"CYCLELOGS",	H_SRA,		rootserv_help_cyclelogs},
	    {NULL}
	};

	if ((command = get_help_hash (s_RootServ, u, strupper (cmd),
				      hash_table)))
	{
	    /* Check if they want a disabled command. */
	    if (((command->process == rootserv_help_global) && globalnoticer_on == FALSE) ||
		((command->process == rootserv_help_resetnews) && memoserv_on == FALSE) ||
		((command->process == rootserv_help_clones) && !maxclones) ||
		((command->process == rootserv_help_trigger) && !maxclones) ||
		((command->process == rootserv_help_exception) && !maxclones))
	    {
		notice (s_RootServ, 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_RootServ, u->nick, RPL_SYNTAX, "JUPE Server|Nick [Reason]");
	errmoreinfo (s_RootServ, 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_RootServ, u->nick, RPL_NOT_SERVICES);
	globops (s_RootServ, RS_JUPE_MY_SERVER, u->nick);
	return;
    }

    if (!stricmp (me.ruplink, param))
    {
	notice (s_RootServ, u->nick, RPL_NOT_SERVICES);
	globops (s_RootServ, RS_JUPE_MY_UPLINK, u->nick);
	return;
    }

    if (is_one_of_mine (param))
    {
	notice (s_RootServ, u->nick, RPL_NOT_SERVICES);
	globops (s_RootServ, RS_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_RootServ, 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_RootServ, RS_JUPED, param, u->nick);
	notice (s_RootServ, u->nick, RS_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_RootServ, 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", rs.host, buf, "+");
	globops (s_RootServ, RS_JUPED, param, u->nick);
	notice (s_RootServ, u->nick, RS_JUPE_SUCCESSFUL, param);
    }
}

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

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

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

	snprintf (buf, sizeof (buf), "[\2%s\2] %s", globalmsg, text);

	noticeall (buf);

	globops (s_RootServ, RS_GLOBAL_NOTICE, u->nick);

	return;
    }

    noticeall (text);
    globops (s_RootServ, RS_GLOBAL_NOTICE, u->nick);
}

/* Reset NF_NEWS flags for all nicks. */
static void do_resetnews (User *u)
{
    NickInfo *ni;

    globops (s_RootServ, RS_RESETTING_NEWS, u->nick);

    /* Run through the registered nicks, resetting the flag for each. */
    for (ni = firstni (); ni; ni = nextni ())
	if (!(ni->flags & NF_LINKED))
	    ni->flags |= NF_NEWS;

    /* Now set the time of when we reset it. */
    news_time = time (NULL);

    notice (s_RootServ, u->nick, RS_NEWS_RESET);
}

/* Cycle the logfiles */
static void do_cyclelogs (User *u)
{
    /* Close them.. */
    close_log ();

    if (debuglevel > 0)
	close_debug ();

    /* Reopen them.. */
    open_log ();

    if (debuglevel > 0)
	open_debug ();

    log ("-----------------------------------------------------");

    if (debuglevel > 0)
	debug ("-----------------------------------------------------");

    /* That was easy :P */
    notice (s_RootServ, u->nick, RS_LOGS_CYCLED);
}

/* 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;
    int i, j = 0, nonwild = 0, realname = 0, perm = 0, expires;

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

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

	if (!param)
	{
	    notice (s_RootServ, u->nick, RPL_SYNTAX, "AKILL ADD Mask [Expiry] Reason");
	    errmoreinfo (s_RootServ, 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_RootServ, u->nick, RPL_SYNTAX, "AKILL ADD Mask [Expiry] Reason");
		errmoreinfo (s_RootServ, 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_RootServ, 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_RootServ, u->nick, RS_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_RootServ, u->nick, RPL_TOOMANYWILD, nonwildreq);
		    return;
		}
	    }

	    if (akillcnt >= 32767)
	    {
		notice (s_RootServ, 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_RootServ, u->nick, RS_AKILL_ADDED, mask);

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

	    /* Send it out to stats */
	    if (strlen (operstats_server))
		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_RootServ, 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_RootServ, time (NULL), akill->reason);
			else
			    send_cmd (me.name, "%s %s * :%s", me.token ? "V" :
				"AKILL", u->host, akill->reason);
		    }
		}
	    }
	}
	else
	{
	    notice (s_RootServ, u->nick, RPL_SYNTAX, "AKILL ADD Mask [Expiry] Reason");
	    errmoreinfo (s_RootServ, u->nick, "AKILL");
	    return;
	}
    }

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

	if (!param)
	{
	    notice (s_RootServ, u->nick, RPL_SYNTAX, "AKILL DEL Number|ALL");
	    errmoreinfo (s_RootServ, 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 stats */
		if (strlen (operstats_server))
		    svssend ("2 %s", akills[i].mask);

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

	    akillcnt = 0;

	    notice (s_RootServ, u->nick, RS_ALL_AKILL_DELETED, j);

	    if (globalonakill_on == TRUE)
		globops (s_RootServ, RS_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_RootServ, u->nick, RS_AKILL_DELETED, akills[i].mask);

		    if (globalonakill_on == TRUE)
			globops (s_RootServ, RS_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 stats */
		    if (strlen (operstats_server))
			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_RootServ, u->nick, RS_AKILL_NOT_FOUND, param);
	    return;
	}
    }

    /* LIST */
    if (!stricmp (option, "LIST"))
    {
	int 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_RootServ, u->nick, RPL_LIST_EMPTY, "The", "", "AKill");
	    return;
	}

	notice (s_RootServ, u->nick, RS_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_RootServ, u->nick, RS_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_RootServ, u->nick, RS_AKILL_LIST_MATCH, j,
			akills[i].mask, akills[i].setter, duration (expires, 1),
			"", showreasons ? akills[i].reason : "");
		else
		    notice (s_RootServ, u->nick, RS_AKILL_LIST_MATCH, j,
			akills[i].mask, akills[i].setter, time_ago (expires),
			" ago", showreasons ? akills[i].reason : "");
	    }
	}

	notice (s_RootServ, u->nick, RS_AKILL_LIST_END);
	return;
    }
}

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

    if (logupdates_on == TRUE)
	log ("RS: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 ("RS: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 stats */
		if (strlen (operstats_server))
		    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 ("RS: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)
 */
int check_akill (const char *nick, const char *user, const char *host,
		 const char *real)
{
    int 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_RootServ, time (NULL), akills[i].reason);
	    else
		send_cmd (me.name, "%s %s * :%s", me.token ? "V" : "AKILL",
		    host, akills[i].reason);

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

	    return 1;
	}
    }

    return 0;
}

/* 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, " ");
    int i, max = 250, shown = 0, count = 0;

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

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

	if (max > 250)
	    max = 250;
    }

    notice (s_RootServ, u->nick, RS_CLONES_START, max, max == 1 ? "" : "es");
    notice (s_RootServ, u->nick, RS_CLONES_HEADER);

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

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

	    if (clone->cnt < 2)
		continue;

	    if (count >= max)
		break;

	    count++;

	    for (user = firstuser (); user; user = nextuser ())
	    {
		if (stricmp (clone->host, user->host))
		    continue;

		/* Very few hosts will go over this.. */
		notice (s_RootServ, 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_RootServ, u->nick, RS_CLONES_END, shown, shown == 1 ? "" : "es");
}

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

    if (!cmd)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX,
	    "TRIGGER ADD|DEL|LIST [Host|Number] [Limit]");
	errmoreinfo (s_RootServ, 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_RootServ, u, strupper (cmd),
				 hash_table)))
	    (*command->process) (u);
	else
	{
	    notice (s_RootServ, u->nick, RPL_SYNTAX,
		"TRIGGER ADD|DEL|LIST [Host|Number] [Limit]");
	    errmoreinfo (s_RootServ, 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_RootServ, u->nick, RPL_SYNTAX, "TRIGGER ADD Host Limit");
	errmoreinfo (s_RootServ, u->nick, "TRIGGER");
	return;
    }

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

    if (!is_sra (u))
    {
	int i, nonwild = 0;

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

	if (nonwild < nonwildreq)
	{
	    notice (s_RootServ, 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_RootServ, u->nick, RS_RETRIGGER, mask, atoi (limit));
	free (username);
	return;
    }

    free (username);

    new_trigger (mask, atoi (limit));
    notice (s_RootServ, u->nick, RS_TRIGGER_ADDED, mask);
}

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

    if (!param)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "TRIGGER DEL Number|Host|ALL");
	errmoreinfo (s_RootServ, 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_RootServ, u->nick, RS_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_RootServ, u->nick, RS_TRIGGER_DELETED, "#", param);
		else
		    notice (s_RootServ, u->nick, RS_TRIGGER_DELETED, "on",
			param);
		return;
	    }
	}
    }

    notice (s_RootServ, u->nick, RS_TRIGGER_NOT_FOUND, param);
}

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

    notice (s_RootServ, u->nick, RS_LIST_START, "Trigger");

    for (trigger = triggerlist; trigger; trigger = trigger->next)
    {
	i++;

	notice (s_RootServ, u->nick, RS_TRIGGER_LIST, i, trigger->host,
	    trigger->limit);
    }

    notice (s_RootServ, u->nick, RS_LIST_END, "Triggers");
}

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

    if (!cmd)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX,
	    "EXCEPTION ADD|DEL|LIST [Host|Number]");
	errmoreinfo (s_RootServ, 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_RootServ, u, strupper (cmd),
				 hash_table)))
	    (*command->process) (u);
	else
	{
	    notice (s_RootServ, u->nick, RPL_SYNTAX,
		"EXCEPTION ADD|DEL|LIST [Host|Number]");
	    errmoreinfo (s_RootServ, 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_RootServ, u->nick, RPL_SYNTAX, "EXCEPTION ADD Host");
	errmoreinfo (s_RootServ, u->nick, "EXCEPTION");
	return;
    }

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

    if (!is_sra (u))
    {
	int i, nonwild = 0;

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

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

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

    if (!hostname)
	return;

    *hostname++ = 0;

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

    free (username);

    new_exception (mask);
    notice (s_RootServ, u->nick, RS_EXCEPTION_ADDED, mask);
}

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

    if (!param)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX,
	    "EXCEPTION DEL Number|Host|ALL");
	errmoreinfo (s_RootServ, 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_RootServ, u->nick, RS_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_RootServ, u->nick, RS_EXCEPTION_DELETED, "#",
			param);
		else
		    notice (s_RootServ, u->nick, RS_EXCEPTION_DELETED, "on",
			param);
		return;
	    }
	}
    }

    notice (s_RootServ, u->nick, RS_EXCEPTION_NOT_FOUND, param);
}

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

    notice (s_RootServ, u->nick, RS_LIST_START, "Exception");

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

    notice (s_RootServ, u->nick, RS_LIST_END, "Exceptions");
}

/* Handle a CSOP command */
static void do_csop (User *u)
{
    NickInfo *ni;
    User *utmp;
    char *param, *max, *option = strtok (NULL, " ");
    int i = 0;

    if (!option)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "CSOP ADD|DEL|LIST [Nickname|Number|Pattern] [Max]");
	errmoreinfo (s_RootServ, u->nick, "CSOP");
	return;
    }

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

	if (!param)
	{
	    notice (s_RootServ, u->nick, RPL_SYNTAX, "CSOP ADD NickName");
	    errmoreinfo (s_RootServ, u->nick, "CSOP");
	    return;
	}

	ni = findnick (param);

	if (!ni)
	{
	    notice (s_RootServ, u->nick, NS_NOT_REGISTERED, param);
	    return;
	}

	if (ni->flags & NF_WAITAUTH)
	{
	    notice (s_RootServ, u->nick, NS_NOT_AUTHED, ni->nick);
	    return;
	}

	if (ni->flags & NF_CSOP)
	{
	    notice (s_RootServ, u->nick, RPL_LIST_THERE, ni->nick, "the", "CSOp");
	    return;
	}
	else
	{
	    ni->flags |= NF_CSOP;
	    notice (s_RootServ, u->nick, RS_CSOP_ADDDEL, ni->nick, "w");

	    /* Set the +a flag if this person is online and identified */
	    if ((utmp = finduser (ni->nick)) && (u->lastnick == ni->nick))
		send_cmd (s_RootServ, "%s %s +a", me.token ? "n" : "SVSMODE", utmp->nick);

	    return;
	}
    }

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

	if (!param)
	{
	    notice (s_RootServ, u->nick, RPL_SYNTAX, "CSOP DEL NickName|Number");
	    errmoreinfo (s_RootServ, u->nick, "CSOP");
	    return;
	}

	ni = findnick (param);

	if (!ni)
	{
	    /* It might be a number.. */
	    for (ni = firstni (); ni; ni = nextni ())
	    {
		if (ni->flags & NF_CSOP)
		{
		    i++;

		    if (i == atoi (param))
		    {
			ni->flags &= ~NF_CSOP;
			notice (s_RootServ, u->nick, RS_CSOP_ADDDEL, ni->nick, " longer");

			/* Remove the +a flag if this person is online. */
			if ((utmp = finduser (ni->nick)))
			    send_cmd (s_RootServ, "%s %s -a", me.token ? "n" : "SVSMODE", utmp->nick);

			return;
		    }
		}
	    }

	    notice (s_RootServ, u->nick, NS_NOT_REGISTERED, param);
	    return;
	}

	if (!(ni->flags & NF_CSOP))
	{
	    notice (s_RootServ, u->nick, RPL_LIST_NOT_THERE, ni->nick, "the CSOp");
	    return;
	}
	else
	{
	    ni->flags &= ~NF_CSOP;
	    notice (s_RootServ, u->nick, RS_CSOP_ADDDEL, ni->nick, " longer");

	    /* Set the +a flag if this person is online and identified */
	    if ((utmp = finduser (ni->nick)) && (u->lastnick == ni->nick))
		send_cmd (s_RootServ, "%s %s -a", me.token ? "n" : "SVSMODE", utmp->nick);

	    return;
	}
    }

    /* LIST */
    if (!stricmp (option, "LIST"))
    {
	int nmax = 250;

	param = strtok (NULL, " ");
	max = strtok (NULL, " ");

	if (max)
	    nmax = atoi (max);
	if (nmax > 250)
	    nmax = 250;

	notice (s_RootServ, u->nick, RS_LIST_START, "CSOp");

	if (!param)
	    param = "*";

	for (ni = firstni (); ni; ni = nextni ())
	{
	    if (ni->flags & NF_CSOP)
	    {
		if (match_wild_nocase (param, ni->nick) ||
		    match_wild_nocase (param, ni->usermask))
		{
		    i++;

		    notice (s_RootServ, u->nick, RS_CSOP_LIST, i, ni->nick, ni->usermask);

		    if (i >= nmax)
			break;
		}
	    }
	}

	notice (s_RootServ, u->nick, RS_LIST_END_MATCHES, "CSOp", i, i == 1 ? "" : "es");
	return;
    }

    notice (s_RootServ, u->nick, RPL_UNKNOWN_OPTION, strupper (option),
	haveserv_on == TRUE ? "" : "MSG ", s_RootServ,
	haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
}

/* Load the RootServ DB from disk */
void load_rs_dbase ()
{
    FILE *f = fopen (ROOTSERV_DB, "r");
    char *item, *s, dBuf[2048], backup [2048];
    int dolog = 0, ain = 0, tin = 0, ein = 0, convert = 1, dbend = 0, dbver = 0;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

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

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (2, "No %s found! ", ROOTSERV_DB);
#else
	log ("RS:DB: No %s found!", ROOTSERV_DB);
#endif

	dolog = 1;

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

	if ((f = fopen (backup, "r")))
	{
#ifdef HAVE_GETTIMEOFDAY
	    log_sameline (0, "Recovering %s.save.", ROOTSERV_DB);
#else
	    log ("RS:DB: Recovering %s.save.", ROOTSERV_DB);
#endif
	}
	else
	{
	    log_sameline (0, "");
	    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, "RV"))
	{
	    if ((s = strtok (NULL, "\n"))) { dbver = atoi (s); }

	    if (dbver < rs.version)
		convert = 1;
	    else
		convert = 0;
	}

	/* 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;
	}

	/* Trigger */
	if (!strcmp (item, "TR"))
	{
	    char *host;
	    int 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;
	}

	/* Network News Timestamp */
	if (!strcmp (item, "NW"))
	{
	    news_time = atol (strtok (NULL, "\n"));
	    continue;
	}

	/* General stats */
	if (dbver > 2 && !strcmp (item, "RS"))
	{
	    maxuptime = atol (strtok (NULL, "\n"));
	    continue;
	}

	/* End of DB */
	if (!strcmp (item, "DE") || (dbver < 3 && !strcmp (item, "RS")))
	{
	    int 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) != 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, "RS:DB: Loading... ");

	    dbend = 1;
	}
    }

    fclose (f);

    if (!dbend && rs.version > 1 && !convert && !force)
    {
	/* We need a newline first. */
	log_sameline (0, "");
	log ("Incomplete rootserv.db detected. Only found %d akills, %d triggers and %d exceptions.", ain, tin, ein);
	fatal (1, "Please restore from backup or run Cygnus 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 Trigger%s, %d Exception%s)",
	    tv2ms (&tmp), ain, ain == 1 ? "" : "s", tin, tin == 1 ? "" : "s", ein, ein == 1 ? "" : "s");
    }
#endif
}

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

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

    f = fopen (ROOTSERV_DB, "w");

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

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

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

	return;
    }

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

    /* Write the version */
    fprintf (f, "RV %d\n", rs.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 %lu %s\n", akills[i].mask, akills[i].setter,
	    akills[i].realname, akills[i].set, akills[i].expires, akills[i].reason);

	aout++;
    }

    /* 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++;
    }

    /* Time of last NEWS Update */
    fprintf (f, "NW %lu\n", news_time);

    /* Update MaxUptime */
    if ((time (NULL) - me.since) > maxuptime)
	maxuptime = (time (NULL) - me.since);

    /* General stats */
    fprintf (f, "RS %d\n", maxuptime);

    /* Write some stats. This helps us determine if a DB has been truncated as well. */
    fprintf (f, "DE %d %d %d\n", aout, 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 triggers, %d exceptions)", tv2ms (&tmp), aout, tout, eout);
    }
#endif
}

/* Authenticate for SRA access. */
static void do_auth (User *u)
{
    char *nick = strtok (NULL, " ");
    char *pass = strtok (NULL, " ");
    SRA *sra;
         
    if (!nick || !pass)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "AUTH NickName Password");
	errmoreinfo (s_RootServ, u->nick, "AUTH");
	return;
    }

    sra = findsra (nick);

    if (!u)
	return;

    if (!sra)
    {
	notice (s_RootServ, u->nick, RPL_NOT_SRA);
	return;
    }

    /* Avoid excessive logging. */
    if (is_sra (u))
    {
	notice (s_RootServ, u->nick, RPL_ALREADY_SRA);
	return;
    }

    if (stricmp (sra->nick, nick) || strcmp (sra->pass, pass))
    {
	passfail (s_RootServ, sra->nick, "RS:AUTH:BP:", u);
	globops (s_RootServ, RS_FAILED_AUTH, u->nick, u->user, u->host);
	return;
    }
    else
    {
	u->flags |= UF_SRA;

	if (globalonauth_on == TRUE)
	    globops (s_RootServ, RS_NOW_SRA_GLOBOP, u->nick, u->user, u->host);

	/* Send it out to stats */
	if (strlen (operstats_server))
	    svssend ("3 %s", u->nick);

	notice (s_RootServ, u->nick, RS_NOW_SRA);

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	    send_cmd (s_RootServ, "%s %s :is a Services Root Admin", me.token ?
		"BA" : "SWHOIS", u->nick);

	return;
    }
}

/* De-Authenticate from SRA access. */
static void do_deauth (User *u)
{
    if (u->flags & UF_SRA)
    {
	u->flags &= ~UF_SRA;
	notice (s_RootServ, u->nick, RS_NO_LONGER_SRA);

	/* Send it out to Services */
	if (strlen (operstats_server))
	    svssend ("4 %s", u->nick);

	return;
    }

    /* We shouldn't get here, but just in case. */
    notice (s_RootServ, u->nick, RPL_NOT_SRA);
}

/* Toggle HOLD on a nickname or channel */
static void do_hold (User *u)
{
    NickInfo *ni, *hni;
    ChanInfo *ci;
    char *what = strtok (NULL, " ");
    char *param = strtok (NULL, " ");

    if (!what || !param)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "HOLD Nickname|Channel ON|OFF");
	errmoreinfo (s_RootServ, u->nick, "HOLD");
	return;
    }

    /* Channel? */
    if (strchr (what, '#'))
    {
	ci = cs_findchan (what);

	if (ci)
	{
	    if (!check_cs_auth (s_RootServ, u->nick, ci))
		return;

	    if (!stricmp (param, "ON"))
	    {
		if (ci->flags & CF_HELD)
		{
		    notice (s_RootServ, u->nick, RPL_IS_ALREADY, ci->name, "held");
		    return;
		}

		ci->flags |= CF_HELD;

		notice (s_RootServ, u->nick, RPL_TOGGLE, "HOLD", ci->name, "ON");
		log ("RS:HOLD:ON %s %s (%s)", u->nick, ci->name, ci->founder);
		snoop (s_RootServ, "RS:HOLD:ON %s held %s (%s)", u->nick, ci->name, ci->founder);
		return;
	    }
	    else
	    {
		if (!(ci->flags & CF_HELD))
		{
		    notice (s_RootServ, u->nick, RPL_IS_NOT, ci->name, "held");
		    return;
		}

		ci->flags &= ~CF_HELD;

		notice (s_RootServ, u->nick, RPL_TOGGLE, "HOLD", ci->name, "OFF");
		log ("RS:HOLD:OFF %s %s (%s)", u->nick, ci->name, ci->founder);
		snoop (s_RootServ, "RS:HOLD:OFF %s held %s (%s)", u->nick, ci->name, ci->founder);
		return;
	    }
	}
	else
	{
	    notice (s_RootServ, u->nick, CS_NOT_REGISTERED, what);
	    return;
	}
    }

    /* Nickname... */
    else
    {
	ni = findnick (what);

	if (ni)
	{
	    if (!check_ns_auth (s_RootServ, u->nick, ni))
		return;

	    if (ni->host)
		hni = findnick (ni->host);
	    else
		hni = ni;

	    if (!stricmp (param, "ON"))
	    {
		if (hni->flags & NF_HELD)
		{
		    notice (s_RootServ, u->nick, RPL_IS_ALREADY, hni->nick, "held");
		    return;
		}

		hni->flags |= NF_HELD;

		notice (s_RootServ, u->nick, RPL_TOGGLE, "HOLD", hni->nick, "ON");
		log ("RS:HOLD:ON %s %s", u->nick, hni->nick);
		snoop (s_RootServ, "RS:HOLD:ON %s held %s", u->nick, hni->nick);
		return;
	    }
	    else
	    {
		if (!(hni->flags & NF_HELD))
		{
		    notice (s_RootServ, u->nick, RPL_IS_NOT, hni->nick, "held");
		    return;
		}

		hni->flags &= ~NF_HELD;

		notice (s_RootServ, u->nick, RPL_TOGGLE, "HOLD", hni->nick, "OFF");
		log ("RS:HOLD:OFF %s %s", u->nick, hni->nick);
		snoop (s_RootServ, "RS:HOLD:OFF %s unheld %s", u->nick, hni->nick);
		return;
	    }
	}
	else
	{
	    notice (s_RootServ, u->nick, NS_NOT_REGISTERED, what);
	    return;
	}
    }
}

/* Toggle MARK on a nickname or channel */
static void do_mark (User *u)
{
    NickInfo *ni, *hni;
    ChanInfo *ci;
    char *what = strtok (NULL, " ");
    char *param = strtok (NULL, " ");

    if (!what || !param)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "MARK Nickname|Channel ON|OFF");
	errmoreinfo (s_RootServ, u->nick, "MARK");
	return;
    }

    /* Channel? */
    if (strchr (what, '#'))
    {
	ci = cs_findchan (what);

	if (ci)
	{
	    if (!check_cs_auth (s_RootServ, u->nick, ci))
		return;

	    if (!stricmp (param, "ON"))
	    {
		if (ci->flags & CF_MARKED)
		{
		    notice (s_RootServ, u->nick, RPL_IS_ALREADY, ci->name, "marked");
		    return;
		}

		ci->flags |= CF_MARKED;

		notice (s_RootServ, u->nick, RPL_TOGGLE, "MARK", ci->name, "ON");
		log ("RS:MARK:ON %s %s (%s)", u->nick, ci->name, ci->founder);
		snoop (s_RootServ, "RS:MARK:ON %s marked %s (%s)", u->nick, ci->name, ci->founder);
		return;
	    }
	    else
	    {
		if (!(ci->flags & CF_MARKED))
		{
		    notice (s_RootServ, u->nick, RPL_IS_NOT, ci->name, "marked");
		    return;
		}

		ci->flags &= ~CF_MARKED;

		notice (s_RootServ, u->nick, RPL_TOGGLE, "MARK", ci->name, "OFF");
		log ("RS:MARK:OFF %s %s (%s)", u->nick, ci->name, ci->founder);
		snoop (s_RootServ, "RS:MARK:OFF %s unmarked %s (%s)", u->nick, ci->name, ci->founder);
		return;
	    }
	}
	else
	{
	    notice (s_RootServ, u->nick, CS_NOT_REGISTERED, what);
	    return;
	}
    }

    /* Nickname... */
    else
    {
	ni = findnick (what);

	if (ni)
	{
	    if (!check_ns_auth (s_RootServ, u->nick, ni))
		return;

	    if (ni->host)
		hni = findnick (ni->host);
	    else
		hni = ni;

	    if (!stricmp (param, "ON"))
	    {
		if (hni->flags & NF_MARKED)
		{
		    notice (s_RootServ, u->nick, RPL_IS_ALREADY, hni->nick, "marked");
		    return;
		}

		hni->flags |= NF_MARKED;

		notice (s_RootServ, u->nick, RPL_TOGGLE, "MARK", hni->nick, "ON");
		log ("RS:MARK:ON %s %s", u->nick, hni->nick);
		snoop (s_RootServ, "RS:MARK:ON %s marked %s", u->nick, hni->nick);
		return;
	    }
	    else
	    {
		if (!(hni->flags & NF_MARKED))
		{
		    notice (s_RootServ, u->nick, RPL_IS_NOT, hni->nick, "marked");
		    return;
		}

		hni->flags &= ~NF_MARKED;

		notice (s_RootServ, u->nick, RPL_TOGGLE, "MARK", hni->nick, "OFF");
		log ("RS:MARK:OFF %s %s", u->nick, hni->nick);
		snoop (s_RootServ, "RS:MARK:OFF %s unmarked %s", u->nick, hni->nick);
		return;
	    }
	}
	else
	{
	    notice (s_RootServ, u->nick, NS_NOT_REGISTERED, what);
	    return;
	}
    }
}

#ifdef RAWINJECT
/* Send raw info to the uplink */
void do_raw (User *u)
{
    char *text = strtok (NULL, "");

    if (!text)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "RAW Command");
	errmoreinfo (s_RootServ, u->nick, "RAW");
    }
    else
	send_cmd (NULL, text);
}

/* Throw something into the inbuf and process it. */
void do_inject (User *u)
{
    char *text = strtok (NULL, "");

    if (!text)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "INJECT Data");
	errmoreinfo (s_RootServ, u->nick, "INJECT");
    }
    else
    {
	strscpy (inbuf, text, BUFSIZE);
	process();
    }
}
#endif /* RAWINJECT */

/* Toggle debug mode. */
static void do_debug (User *u)
{
    char *param = strtok (NULL, " ");
        
    if (!param)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "DEBUG Level");
	errmoreinfo (s_RootServ, u->nick, "DEBUG");
	return;
    }

    if (atoi (param) > 0)
    {
	debuglevel = atoi (param);
	open_debug();
	debug ("-----------------------------------------------------");
	notice (s_RootServ, u->nick, RS_DEBUG_LEVEL, atoi (param),
	    debuglevel == 1 ? "Server<->Server traffic" :
	    debuglevel == 2 ? "Some function calls" :
	    debuglevel == 3 ? "Server<->Server traffic and some function calls"
	    : "Unknown");
    }
    else
    {
	notice (s_RootServ, u->nick, RS_DEBUG_LEVEL, atoi (param), "Off");
	debug ("%s turned debug mode off.", u->nick);
	debuglevel = 0;
	close_debug();
    }
}

/* Terminate Services permanently. */
static void do_shutdown (User *u)
{
    char reason[BUFSIZE], orig[BUFSIZE], *param;

    param = strtok (NULL, "");

    if (!param)
	memcpy (reason, "No reason given", sizeof (reason));
    else
	memcpy (reason, param, sizeof (reason));

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

    param = strtok (reason, " ");

    /* Should we skip the DB save? */
    if (!stricmp (param, "NOSAVE"))
    {
	/* Yep. */
	param = strtok (NULL, "");

	if (param)
	    snprintf (quitmsg, BUFSIZE, "%s", param);
	else
	    snprintf (quitmsg, BUFSIZE, "No reason given");

	globops (s_RootServ, RS_SHUTTING_DOWN, u->nick, quitmsg);
	runflags |= RUN_SHUTDOWN;
    }
    else
    {
	/* Nope. Save the DBs. */
	snprintf (quitmsg, BUFSIZE, "%s", orig);

	/* Backup the DBs */
	db_backup (0);

	expire_emails ();
	expire_akills ();
	expire_nicks ();
	expire_chans ();

	save_rs_dbase ();

	if (nickserv_on == TRUE)
	    save_ns_dbase ();

	if (chanserv_on == TRUE)
	    save_cs_dbase ();

	if (webint_on == TRUE)
	    save_web_dbase ();

	/* Remove the DB backups */
	db_backup (1);

	globops (s_RootServ, RS_SHUTTING_DOWN, u->nick, quitmsg);
	runflags |= RUN_SHUTDOWN;
    }
}

/* Restart Services. */
static void do_restart (User *u)
{
    char reason[BUFSIZE], orig[BUFSIZE], *param;

    param = strtok (NULL, "");

    if (!param)
	memcpy (reason, "No reason given", sizeof (reason));
    else
	memcpy (reason, param, sizeof (reason));

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

    param = strtok (reason, " ");

    /* If wait_restart isn't set, Services won't restart. But if we're
       using this function, we want them to restart, so we set
       wait_restart to 5 seconds here. This change is wiped out when
       they restart.
     */
    wait_restart = 5;

    /* Should we skip the DB save? */
    if (!stricmp (param, "NOSAVE"))
    {
	/* Yep. */
	param = strtok (NULL, "");
	snprintf (quitmsg, BUFSIZE, "%s", param);

	if (!quitmsg)
	    snprintf (quitmsg, BUFSIZE, "No reason given");

	globops (s_RootServ, RS_RESTARTING, duration (wait_restart, 1), u->nick, quitmsg);
	runflags |= RUN_RESTART;
    }
    else
    {
	/* Nope. Save the DBs. */
	snprintf (quitmsg, BUFSIZE, "%s", orig);

	/* Back up dbs */
	db_backup (0);

	expire_emails ();
	expire_akills ();
	expire_nicks ();
	expire_chans ();

	save_rs_dbase ();

	if (nickserv_on == TRUE)
	    save_ns_dbase ();

	if (chanserv_on == TRUE)
	    save_cs_dbase ();

	if (webint_on == TRUE)
	    save_web_dbase ();

	/* Remove the backup */
	db_backup (1);

	globops (s_RootServ, RS_RESTARTING, duration (wait_restart, 1), u->nick, quitmsg);
	runflags |= RUN_RESTART;
    }
}

/* Show various Services settings */
static void do_settings (User *u)
{
    notice (s_RootServ, u->nick, RS_SETTINGS_START);
    notice (s_RootServ, u->nick, " ");

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	notice (s_RootServ, u->nick, RS_SETTINGS, "Unreal Numeric", itoa (unreal_numeric));

    notice (s_RootServ, u->nick, RS_SETTINGS, "Network Name", network_name);

    if (sendmail_on == TRUE)
	notice (s_RootServ, u->nick, RS_SETTINGS, "Sendmail Path", sendmail_path);

    notice (s_RootServ, u->nick, RS_SETTINGS, "Database Sync Delay", duration (update_timeout, 1));
    notice (s_RootServ, u->nick, RS_SETTINGS, "Auth Timeout Expiry", duration (tempexpire, 1));
    notice (s_RootServ, u->nick, RS_SETTINGS, "Maximum Password Failures", itoa (passfails));
    notice (s_RootServ, u->nick, RS_SETTINGS, "Use /Services Commands", haveserv_on == TRUE ? "On" : "Off");
    notice (s_RootServ, u->nick, RS_SETTINGS, "Helpfile Index Style", helpindex == 1 ? "Short" :
	helpindex == 2 ? "Medium" : helpindex == 3 ? "Long" : "Unknown");

    if (floodmsgs)
    {
	notice (s_RootServ, u->nick, RS_SETTINGS_MULTI, "Flood Trigger", itoa (floodmsgs),
	    " messages in ", duration (floodtime, 1));
	notice (s_RootServ, u->nick, RS_SETTINGS, "GlobOp On Flood", noisyflood_on == TRUE ?
	    "On" : "Off");
    }

    if (strlen (snoopchan))
	notice (s_RootServ, u->nick, RS_SETTINGS, "Snoop Channel", snoopchan);

    if (strlen (joinchan))
	notice (s_RootServ, u->nick, RS_SETTINGS, "Join Channel", joinchan);

    if (globalnoticer_on == TRUE)
	notice (s_RootServ, u->nick, RS_SETTINGS, "Top-Level Domains", tlds);

    if (nickserv_on == TRUE)
    {
	notice (s_RootServ, u->nick, RS_SETTINGS, "NickName Registration Type",
	    nsregistertype == 0 ? "Normal" : nsregistertype == 1 ? "EMailAuth" :
	    nsregistertype == 2 ? "EMailNoAuth" : nsregistertype == 3 ? "PassAuth" :
	    nsregistertype == 4 ? "Verify" : nsregistertype == 5 ? "EMailVerify" :
	    "AuthVerify");
	notice (s_RootServ, u->nick, RS_SETTINGS, "NickName Registration Delay", duration (nick_delay, 1));
	notice (s_RootServ, u->nick, RS_SETTINGS, "Maximum Links Per Host", itoa (maxlinks));
	notice (s_RootServ, u->nick, RS_SETTINGS, "E-Mail Limit", itoa (emaillimit));
	notice (s_RootServ, u->nick, RS_SETTINGS, "NickName Expiry", duration (nick_expire, 1));
	notice (s_RootServ, u->nick, RS_SETTINGS, "NickName Collision Type",
	    collidetype == 1 ? "Guest" : collidetype == 2 ? "User" :
	    collidetype == 3 ? "Nick" : collidetype == 4 ? "Underscore" :
	    collidetype == 5 ? "Hyphen" : "Kill");

	if (strlen (nsregister_url))
	    notice (s_RootServ, u->nick, RS_SETTINGS, "NickName Registration URL", nsregister_url);

	if (strlen (collision_url))
	    notice (s_RootServ, u->nick, RS_SETTINGS, "NickName Collision URL", collision_url);
    }

    if (memoserv_on == TRUE)
    {
	notice (s_RootServ, u->nick, RS_SETTINGS, "Maximum MemoMails", itoa (memomailmax));
	notice (s_RootServ, u->nick, RS_SETTINGS, "Maximum Memos From User", itoa (memosfromuser));
	notice (s_RootServ, u->nick, RS_SETTINGS, "Memo Send Delay", duration (memo_delay, 1));
    }

    if (chanserv_on == TRUE)
    {
	notice (s_RootServ, u->nick, RS_SETTINGS, "Channel Registration Type",
	    csregistertype == 0 ? "Normal" : csregistertype == 1 ? "EMailAuth" :
	    csregistertype == 2 ? "Verify" : csregistertype == 3 ? "AuthVerify" :
	    "CSOpVerify");
	notice (s_RootServ, u->nick, RS_SETTINGS, "Channels Per User", itoa (chanlimit));
	notice (s_RootServ, u->nick, RS_SETTINGS, "Channel Registration Delay", duration (chan_delay, 1));
	notice (s_RootServ, u->nick, RS_SETTINGS, "Channel Expiry", duration (chan_expire, 1));

	if (strlen (csregister_url))
	    notice (s_RootServ, u->nick, RS_SETTINGS, "Channel Registration URL", csregister_url);
    }

    if (rootserv_on == TRUE)
    {
	notice (s_RootServ, u->nick, RS_SETTINGS, "Secure SRA Access", securesra_on == TRUE ? "On" : "Off");
	notice (s_RootServ, u->nick, RS_SETTINGS, "Default AKill Expiry", duration (def_akill_time, 1));
	notice (s_RootServ, u->nick, RS_SETTINGS, "Default AKill Reason", strlen (def_akill_reason) ?
	    def_akill_reason : "None");
	notice (s_RootServ, u->nick, RS_SETTINGS, "GlobOp On AKill", globalonakill_on == TRUE ? "On" : "Off");
	notice (s_RootServ, u->nick, RS_SETTINGS, "Share AKills", shareakills_on == TRUE ? "On" : "Off");
	notice (s_RootServ, u->nick, RS_SETTINGS, "Share SRAs", sharesras_on == TRUE ? "On" : "Off");
	notice (s_RootServ, u->nick, RS_SETTINGS, "Non-Wild Chars Required", itoa (nonwildreq));
	notice (s_RootServ, u->nick, RS_SETTINGS, "GlobOp On AUTH", globalonauth_on == TRUE ? "On" : "Off");
    }

    if (globalnoticer_on == TRUE)
	if (strlen (globalmsg))
	    notice (s_RootServ, u->nick, RS_SETTINGS, "Global Notice Prefix", globalmsg);

    notice (s_RootServ, u->nick, RS_SETTINGS, "Log Updates", logupdates_on == TRUE ? "On" : "Off");
    notice (s_RootServ, u->nick, RS_SETTINGS, "Stauts Globals", statglobal_on == TRUE ? "On" : "Off");
    notice (s_RootServ, u->nick, RS_SETTINGS, "Maximum Connections Per User", maxclones ? itoa (maxclones)
	: "Unlimited");
    notice (s_RootServ, u->nick, RS_SETTINGS, "Maximum Clone Kills Per Host", maxclonekills ?
	itoa (maxclonekills) : "Unlimited");
    notice (s_RootServ, u->nick, RS_SETTINGS, "Restart Delay", duration (wait_restart, 1));
    notice (s_RootServ, u->nick, RS_SETTINGS, "Debug Logging Level", debuglevel ? itoa (debuglevel) :
	"Off");
    notice (s_RootServ, u->nick, " ");
    notice (s_RootServ, u->nick, RS_SETTINGS_END);
}

/* Update the DBs. */
static void do_update (User *u)
{
    globops (s_RootServ, RS_FORCE_UPDATE, u->nick);
    runflags |= RUN_SAVEDBS;
}

/* Call rehash() with the nick of the user doing the command */
void do_rehash (User *u)
{
    rehash (u->nick);
}

/* Re-read services.conf */
void rehash (const char *source)
{
    /* Just in case. */
    globops (s_RootServ, RS_FORCE_UPDATE, source);
    runflags |= RUN_SAVEDBS;

    globops (s_RootServ, RS_DOING_REHASH, CONFIGFILE, source);

    /* Remove all clients from the JoinChan. We do this here
       before we turn the service bots off. */
    if (strlen (joinchan))
	joinchan_part ();

    /* Turn these off. They'll be re-enabled if they're
       turned on in the conf.
     */
    rootserv_on = FALSE;
    nickserv_on = FALSE;
    chanserv_on = FALSE;
    memoserv_on = FALSE;
    globalnoticer_on = FALSE;
    globalonakill_on = FALSE;
    logupdates_on = FALSE;
    age_on = FALSE;
    sex_on = FALSE;
    location_on = FALSE;
    shareakills_on = FALSE;
    sharesras_on = FALSE;
    name_on = FALSE;
    privmsg_on = FALSE;
    showmail_on = FALSE;
    fakehost_on = FALSE;
    webint_on = FALSE;
    sendmail_on = FALSE;
    email_on = FALSE;
    url_on = FALSE;
    memomail_on = FALSE;
    securevop_on = FALSE;
    securesra_on = FALSE;
    globalonauth_on = FALSE;
    haveserv_on = FALSE;
    statglobal_on = FALSE;
    noisyflood_on = FALSE;
    recoverkill_on = FALSE;
    *joinchan = 0;
    maxlinks = 0;

    /* Flush SRAs and TimeZones (some might be removed) */
    flushlists ();

    /* Now re-read. */
    load_conf ();
    
    /* Now check it */
    check_conf ();

    /* Reload Zones */
    if (nickserv_on == TRUE)
	load_zones ();

    /* Remove any disabled pseudoclients */
    if (rs.isonline && rootserv_on == FALSE)
    {
	send_cmd (s_RootServ, "%s :Client disabled", me.token ? "," : "QUIT");
	--userstat;
	rs.isonline = 0;
    }

    if (ns.isonline && nickserv_on == FALSE)
    {
	send_cmd (s_NickServ, "%s :Client disabled", me.token ? "," : "QUIT");
	--userstat;
	ns.isonline = 0;
    }

    if (cs.isonline && chanserv_on == FALSE)
    {
	send_cmd (s_ChanServ, "%s :Client disabled", me.token ? "," : "QUIT");
	--userstat;
	cs.isonline = 0;
    }

    if (ms.isonline && memoserv_on == FALSE)
    {
	send_cmd (s_MemoServ, "%s :Client disabled", me.token ? "," : "QUIT");
	--userstat;
	ms.isonline = 0;
    }

    if (gn.isonline && globalnoticer_on == FALSE)
    {
	send_cmd (s_GlobalNoticer, "%s :Client disabled", me.token ? "," :
	    "QUIT");
	--userstat;
	gn.isonline = 0;
    }

    /* Put all clients back in the JoinChan */
    if (strlen (joinchan))
	joinchan_join ();
}

/* Search the log file for the specified text. Return Num matching lines. */
void do_search (User *u)
{
    FILE *logfile = fopen (LOGFILE, "r");
    char *match = strtok (NULL, " ");
    char *param = strtok (NULL, " ");
    char tmp[BUFSIZE];
    char dBuf[2048];
    int max, found = 0;

    if (!match)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "SEARCH MatchText [Num]");
	errmoreinfo (s_RootServ, u->nick, "SEARCH");
	return;
    }

    if (!param || (param && atoi (param) > 250))
	max = 50;
    else
	max = atoi (param);

    if (!logfile)
    {
	notice (s_RootServ, u->nick, RPL_CANT_OPEN, LOGFILE);
	return; 
    }

    notice (s_RootServ, u->nick, RS_LOG_START, max, max == 1 ? "" : "es");

    snprintf (tmp, sizeof (tmp), "*%s*", match);

    while (fgets (dBuf, 2047, logfile))
    {
	if (found >= max)
	    break;

	if (match_wild_nocase (tmp, dBuf))
	{
	    ++found;
	    notice (s_RootServ, u->nick, dBuf);
	}
    }

    notice (s_RootServ, u->nick, RS_LOG_END, found, found == 1 ? "" : "es");

    fclose (logfile);
}

/* Show various services statistics */
static void do_stats (User *u)
{
    NickInfo *ni;
    MemoInfo *mi;
    ChanInfo *ci;
    int nicks = 0, hosts = 0, links = 0, chans = 0, memos = 0;
    int uptime = time (NULL) - me.since;

    notice (s_RootServ, u->nick, RS_STATS_START);
    notice (s_RootServ, u->nick, " ");
    notice (s_RootServ, u->nick, RS_STATS_NETWORK);
    notice (s_RootServ, u->nick, " ");
    notice (s_RootServ, u->nick, RS_STATS, "Users", userstat);
    notice (s_RootServ, u->nick, RS_STATS, "Channels", chancnt);
    notice (s_RootServ, u->nick, RS_STATS, "Servers", servcnt + 1);
    notice (s_RootServ, u->nick, RS_STATS, "SStamp", sstamp);
    notice (s_RootServ, u->nick, " ");
    notice (s_RootServ, u->nick, RS_STATS_SERVICES);
    notice (s_RootServ, u->nick, " ");

    for (ni = firstni (); ni; ni = nextni ())
    {
	if (ni->flags & NF_LINKED)
	    links++;
	else
	    hosts++;

	nicks++;

	mi = &ni->memos;

	if (mi->memocnt)
	    memos += mi->memocnt;
    }

    notice (s_RootServ, u->nick, RS_STATS, "Nicknames", nicks);
    notice (s_RootServ, u->nick, RS_STATS, "    Hosts", hosts);
    notice (s_RootServ, u->nick, RS_STATS, "    Links", links);
    notice (s_RootServ, u->nick, RS_STATS, "Memos", memos);

    for (ci = firstci (); ci; ci = nextci ())
	chans++;

    notice (s_RootServ, u->nick, RS_STATS, "Channels", chans);

    notice (s_RootServ, u->nick, RS_STATS, "AutoKills", akillcnt);

    notice (s_RootServ, u->nick, RS_STATS_UPTIME, "Uptime    ", uptime/86400,
	(uptime/3600) % 24, (uptime/60) % 60, uptime % 60);

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

    notice (s_RootServ, u->nick, RS_STATS_UPTIME, "Max Uptime", maxuptime/86400,
	(maxuptime/3600) % 24, (maxuptime/60) % 60, maxuptime % 60);

    notice (s_RootServ, u->nick, " ");
    notice (s_RootServ, u->nick, RS_STATS_END);
}

/* Search registered nicknames */
static void do_listnicks (User *u)
{
    NickInfo *ni;
    char *pattern = strtok (NULL, " ");
    char *param = strtok (NULL, " ");
    int max, found = 0;

    if (!pattern)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "LISTNICKS Pattern [Max]");
	errmoreinfo (s_RootServ, u->nick, "LISTNICKS");
	return;
    }

    if (!param || (param && atoi (param) > 250))
	max = 50;
    else
	max = atoi (param);

    notice (s_RootServ, u->nick, RS_LIST_START, "Nickname");
    notice (s_RootServ, u->nick, RS_LISTNICKS_HEADER);

    for (ni = firstni (); ni; ni = nextni ())
    {
	if (found >= max)
	    break;

	if (match_wild_nocase (pattern, ni->nick) || (ni->usermask &&
	    match_wild_nocase (pattern, ni->usermask)) || (ni->email &&
	    match_wild_nocase (pattern, ni->email)) || (ni->url &&
	    match_wild_nocase (pattern, ni->url)) || (ni->host &&
	    match_wild_nocase (pattern, ni->host)) || (ni->name &&
	    match_wild_nocase (pattern, ni->name)) || (ni->sex &&
	    match_wild_nocase (pattern, ni->sex)))
	{
	    ++found;
	    notice (s_RootServ, u->nick, RS_LISTNICKS_LIST, ni->nick,
		ni->host ? ni->host : ni->usermask);
	}
    }

    notice (s_RootServ, u->nick, RS_LIST_END_MATCHES, "Nicknames", found, found == 1 ? "" : "es");
}

/* Search registered channels */
static void do_listchans (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    char *pattern = strtok (NULL, " ");
    char *param = strtok (NULL, " ");
    int max, found = 0;

    if (!pattern)
    {
	notice (s_RootServ, u->nick, RPL_SYNTAX, "LISTCHANS Pattern [Max]");
	errmoreinfo (s_RootServ, u->nick, "LISTCHANS");
	return;
    }

    if (!param || (param && atoi (param) > 250))
	max = 50;
    else
	max = atoi (param);

    notice (s_RootServ, u->nick, RS_LIST_START, "Channel");
    notice (s_RootServ, u->nick, RS_LISTCHANS_HEADER);

    for (ci = firstci (); ci; ci = nextci ())
    {
	if (found >= max)
	    break;

	ni = findnick (ci->founder);

	if (match_wild_nocase (pattern, ci->name) ||
	    match_wild_nocase (pattern, ci->founder) || (ci->successor &&
	    match_wild_nocase (pattern, ci->successor)) || (ci->url &&
	    match_wild_nocase (pattern, ci->url)) || (ni &&
	    match_wild_nocase (pattern, ni->usermask)))
	{
	    ++found;
	    notice (s_RootServ, u->nick, RS_LISTCHANS_LIST, ci->name, ci->founder);
	}
    }

    notice (s_RootServ, u->nick, RS_LIST_END_MATCHES, "Channels", found, found == 1 ? "" : "es");
}
