/* NickServ 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"

NickInfo *nicklist[HASHSIZE];
int sstamp = 0;

static void do_help (User *u);
static void do_register (User *u);
static void do_drop (User *u);
static void do_info (User *u);
static void do_identify (User *u);
static void do_set (User *u);
static void do_access (User *u);
static void do_recover (User *u);
static void do_link (User *u);
static void do_unlink (User *u);
static void do_links (User *u);
static void do_chans (User *u);
static void do_truth (User *u);
static void do_status (User *u);
static void do_getpass (User *u);
static void do_setpass (User *u);
static void do_sendpass (User *u);
static void do_freeze (User *u);
static void do_unfreeze (User *u);

static int is_on_access (User *u, NickInfo *ni);

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

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

    else
    {
	Hash *command, hash_table[] =
	{
	    {"HELP",		H_NONE,		do_help},
	    {"REGISTER",	H_NONE,		do_register},
	    {"DROP",		H_NONE,		do_drop},
	    {"INFO",		H_NONE,		do_info},
	    {"*IDENTIFY",	H_NONE,		do_identify},
	    {"ID",		H_NONE,		do_identify},
	    {"SET",		H_NONE,		do_set},
	    {"ACCESS",		H_NONE,		do_access},
	    {"RECOVER",		H_NONE,		do_recover},
	    {"LINK",		H_NONE,		do_link},
	    {"UNLINK",		H_NONE,		do_unlink},
	    {"LINKS",		H_NONE,		do_links},
	    {"CHANS",		H_NONE,		do_chans},
	    {"TRUTH",		H_NONE,		do_truth},
	    {"STATUS",		H_NONE,		do_status},
	    {"GETPASS",		H_CSOP,		do_getpass},
	    {"SETPASS",		H_CSOP,		do_setpass},
	    {"SENDPASS",	H_CSOP,		do_sendpass},
	    {"FREEZE",		H_CSOP,		do_freeze},
	    {"UNFREEZE",	H_CSOP,		do_unfreeze},
	    {NULL}
	};
            
	if ((command = get_hash (s_NickServ, u, strupper (cmd),
				 hash_table)))
	{
	    /* Check for disabled or otherwise inapplicable commands. */
	    if (((command->process == do_link || command->process == do_links ||
		   command->process == do_unlink) && !maxlinks) ||
		(command->process == do_sendpass && sendmail_on == FALSE) ||
		(command->process == do_chans && chanserv_on == FALSE))
		notice (s_NickServ, u->nick, RPL_UNKNOWN_COMMAND, strupper (cmd),
		    haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		    haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		    haveserv_on == TRUE ? "" : securitysetting == 1 ?
		    me.name : "");
	    else
		(*command->process) (u);
	}
    }
}

/* Return a help message. */
static void do_help (User *u) 
{
    char *cmd = strtok (NULL, "");
            
    if (!cmd)
	nickserv_help_index (u);
    else
    {
	Hash *command, hash_table[] =
	{
	    {"REGISTER",	H_NONE,		nickserv_help_register},
	    {"DROP",		H_NONE,		nickserv_help_drop},
	    {"INFO",		H_NONE,		nickserv_help_info},
	    {"IDENTIFY",	H_NONE,		nickserv_help_identify},
	    {"ACCESS",		H_NONE,		nickserv_help_access},
	    {"LINK",		H_NONE,		nickserv_help_link},
	    {"UNLINK",		H_NONE,		nickserv_help_unlink},
	    {"LINKS",		H_NONE,		nickserv_help_links},
	    {"CHANS",		H_NONE,		nickserv_help_chans},
	    {"STATUS",		H_NONE,		nickserv_help_status},
	    {"RECOVER",		H_NONE,		nickserv_help_recover},
	    {"SET",		H_NONE,		nickserv_help_set},
	    {"SET PASS",	H_NONE,		nickserv_help_set_pass},
	    {"SET EMAIL",	H_NONE,		nickserv_help_set_email},
	    {"SET URL",		H_NONE,		nickserv_help_set_url},
	    {"SET UIN",		H_NONE,		nickserv_help_set_uin},
	    {"SET NAME",	H_NONE,		nickserv_help_set_name},
	    {"SET AGE",		H_NONE,		nickserv_help_set_age},
	    {"SET SEX",		H_NONE,		nickserv_help_set_sex},
	    {"SET LOCATION",	H_NONE,		nickserv_help_set_location},
	    {"SET ENFORCE",	H_NONE,		nickserv_help_set_enforce},
	    {"SET SECURE",	H_NONE,		nickserv_help_set_secure},
	    {"SET SHOWMAIL",	H_NONE,		nickserv_help_set_showmail},
	    {"SET HIDEMAIL",	H_NONE,		nickserv_help_set_hidemail},
	    {"SET NEVEROP",	H_NONE,		nickserv_help_set_neverop},
	    {"SET NOOP",	H_NONE,		nickserv_help_set_noop},
	    {"SET NOSUCCESSOR",	H_NONE,		nickserv_help_set_nosuccessor},
	    {"SET PRIVMSG",	H_NONE,		nickserv_help_set_privmsg},
	    {"SET NOTICE",	H_NONE,		nickserv_help_set_notice},
	    {"SET PRIVATE",	H_NONE,		nickserv_help_set_private},
	    {"SET ZONE",	H_NONE,		nickserv_help_set_zone},
	    {"GETPASS",		H_CSOP,		nickserv_help_getpass},
	    {"SETPASS",		H_CSOP,		nickserv_help_setpass},
	    {"SENDPASS",	H_CSOP,		nickserv_help_sendpass},
	    {"FREEZE",		H_CSOP,		nickserv_help_freeze},
	    {"UNFREEZE",	H_CSOP,		nickserv_help_unfreeze},
	    {NULL}
	};

	if ((command = get_help_hash (s_NickServ, u, strupper (cmd),
				      hash_table)))
	    (*command->process) (u);
    }
}

/* Check if the given user has access to the nickname. If not, 
   tell them to identify and start collision timers.
 */
int validate_user (User *u)
{
    NickInfo *ni, *hni;
    int aclev;

    if (debuglevel > 1)
	debug ("validate_user(): %s", u->nick);

    /* Remove user modes here. We'll turn them back on further down if needed.
       This makes services do -ra+ra sometimes, but it's easier.
     */
    if ((u->mode & UMODE_r) || (u->mode & UMODE_a))
	send_cmd (s_NickServ, "%s %s -%s%s", me.token ? "n" : "SVSMODE", u->nick,
	    (u->mode & UMODE_r) ? "r" : "", (u->mode & UMODE_a) ? "a" : "");

    if (!(ni = findnick (u->nick)))
	return 0;

    /* If the nick is awaiting auth, bail out. */
    if (ni->flags & NF_WAITAUTH)
	return 0;

    /* If it's a linked nick, point to its host instead. */
    if (ni->host)
	hni = findnick (ni->host);
    else
	hni = ni;

    /* See if this nick is frozen (or linked to a frozen nick). Get them off of it if so. */
    if (hni->flags & NF_FROZEN)
    {
	notice (s_NickServ, u->nick, NS_INFO_FROZEN);

	if (hni->freezereason)
	    notice (s_NickServ, u->nick, NS_FROZEN_REASON, hni->freezereason);

	if (collidetype == 6 || (strlen (u->nick) == 30))
	    notice (s_NickServ, u->nick, NS_FROZEN_KILLED);
	else
	    notice (s_NickServ, u->nick, NS_FROZEN_CHANGED);

	collide (ni, 0, 0);

	return 0;
    }

    /* Check if they already have access to this nick */
    if (u->lastnick && !stricmp (u->lastnick, u->nick))
    {
	/* We still want to tell them about new memos, verifies
	   and news.
	 */
	if (chanserv_on == TRUE && is_csop (u))
	    check_verifies (u);

	if (memoserv_on == TRUE)
	{
	    check_memos (u);
	    check_news (u);
	}

	return 0;
    }

    /* Compare the server timestamp and servicesstamp. If these both match, it's as good 
       as an identify.
     */
    if ((hni->timestamp == u->timestamp) && (hni->sstamp == u->sstamp))
    {
	if (hni->usermask)
	    free (hni->usermask);

	hni->lastseen = time (NULL);
	hni->usermask = smalloc (strlen (u->nick) + strlen (u->user) + strlen (u->host) + 3);
	sprintf (hni->usermask, "%s!%s@%s", u->nick, u->user, u->host);

	if (u->lastnick)
	    free (u->lastnick);

	u->lastnick = sstrdup (hni->nick);

	if (hni->real)
	    free (hni->real);

	hni->real = sstrdup (u->real);

	/* Set the SRA flag if SecureSRA is off */
	if (securesra_on == FALSE)
	    if (can_sra (hni->nick))
	    {
		u->flags |= UF_SRA;

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

	/* CSOp stuff */
	if (is_csop (u))
	    send_cmd (s_NickServ, "%s %s +ra", me.token ? "n" : "SVSMODE", u->nick);
	else
	    send_cmd (s_NickServ, "%s %s +r-a", me.token ? "n" : "SVSMODE", u->nick);

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

	/* Check for waiting verifies */
	if (chanserv_on == TRUE && is_csop (u))
	    check_verifies (u);

	/* Tell them about new news and new memos */
	if (memoserv_on == TRUE)
	{
	    check_memos (u);
	    check_news (u);
	}

	return 1;
    }

    aclev = is_on_access (u, hni);

    if (!(hni->flags & NF_SECURE) && aclev)
    {
	hni->status |= NS_RECOGNIZED;
	hni->lastseen = time (NULL);

	if (hni->usermask)
	    free (hni->usermask);

	hni->usermask = smalloc (strlen (u->nick) + strlen (u->user) + strlen (u->host) + 3);
	sprintf (hni->usermask, "%s!%s@%s", u->nick, u->user, u->host);

	if (hni->real)
	    free (hni->real);

	hni->real = sstrdup (u->real);
	return 1;
    }

    notice (s_NickServ, u->nick, NS_NICK_OWNED);

    if (haveserv_on == TRUE)
	notice (s_NickServ, u->nick, NS_IDENTIFY, "", s_NickServ, "", "");
    else
	notice (s_NickServ, u->nick, NS_IDENTIFY, "MSG ", s_NickServ,
	securitysetting ? "@" : "", securitysetting ? me.name : "");

    if (((hni->flags & NF_ENFORCE) && !aclev) || (hni->flags & NF_SECURE))
    {
	notice (s_NickServ, u->nick, NS_60_SECONDS);
	add_ns_timeout (ni, TO_COLLIDE, 60);
	add_ns_timeout (ni, TO_MESS20, 40);
	add_ns_timeout (ni, TO_MESS40, 20);
    }

    return 0;
}

/* Is the given user's address on the given nick's access list? Return 1
   if so, 0 if not.
 */
static int is_on_access (User *u, NickInfo *ni)
{
    int i;
    char *buf;

    if (ni->accesscnt == 0)
	return 0;

    i = strlen (u->user);
    buf = smalloc (i + strlen (u->host) + 2);
    sprintf (buf, "%s@%s", u->user, u->host);
    strlower (buf + i + 1);

    for (i = 0; i < ni->accesscnt; i++)
    {
	if (match_wild_nocase (ni->access[i], buf))
	{
	    free (buf);
	    return 1;
	}
    }

    free (buf);
    return 0;
}

/* Release a held nickname from custody. */
void release (NickInfo *ni, int from_timeout)
{
    User *u;

    if (!from_timeout)
	del_ns_timeout (ni, TO_RELEASE, 1);

    /* Make sure this user isn't online. If someone's on with
       this nick, it isn't us, and some IRCds will generate an
       notice when they get the QUIT.
     */
    u = finduser (ni->nick);

    if (!u)
	send_cmd (ni->nick, "%s", me.token ? "," : "QUIT");

    ni->status &= ~NS_ENFORCED;
}

/* Collide a nickname. We simply change their nick to whatever the conf
   setting for collisions is. Or, depending on the conf setting, we may
   kill them regardless.
 */
void collide (NickInfo *ni, int from_timeout, int recovered)
{
    User *u;
    char guest[NICKLEN];
    int guestnick, success = 0, tries = 0;

    u = finduser (ni->nick);

    if (!from_timeout && !recovered)
	del_ns_timeout (ni, TO_COLLIDE, 1);

    if (collidetype == 6)
    {
	kill_user (s_NickServ, u->nick, NS_KILLED_ENFORCEMENT);

	if (!recovered)
	{
	    ni->status |= NS_ENFORCED;
	    u->flags |= UF_ENFORCED;
	}

	return;
    }

    while (success == 0)
    {
	/* Check if their nickname is at the max length. If it is, some collidetypes
	   will fail, so we'll just kill them.
	 */
	if (collidetype == 3 || collidetype == 4 || collidetype == 5)
	{
	    if (strlen (u->nick) == 30)
	    {
		kill_user (s_NickServ, u->nick, NS_KILLED_ENFORCEMENT);
		success = 1;
	    }
	}

	/* If we fail to get a usable nick 5 times in a row, kill them. */
	if (tries >= 5)
	{
	    kill_user (s_NickServ, u->nick, NS_KILLED_ENFORCEMENT);
	    success = 1;
	}

	if (collidetype == 1 || collidetype == 2 || collidetype == 3)
	{
	    /* Generate a random guest nick */
	    guestnick = 1+(int) (99999.0*random()/(RAND_MAX+10000.0));

	    /* If random() gives us a 4 digit number, add 10000 to it, bringing it into
	       the 5 digit range.
	     */
	    if (guestnick < 10000)
		guestnick += 10000;

	    if (collidetype == 1)
		snprintf (guest, sizeof (guest), "Guest%d", guestnick);
	    if (collidetype == 2)
		snprintf (guest, sizeof (guest), "User%d", guestnick);
	    if (collidetype == 3)
		snprintf (guest, sizeof (guest), "%s%d", u->nick, guestnick);
	}

	if (collidetype == 4)
	    snprintf (guest, sizeof (guest), "%s_", u->nick);

	if (collidetype == 5)
	    snprintf (guest, sizeof (guest), "%s-", u->nick);

	if (!finduser (guest))
	    success = 1;

	tries++;
    }

    /* If we killed them above, there's no sense sending this to them. */
    if (tries < 5)
    {
	if (from_timeout)
	{
	    notice (s_NickServ, u->nick, NS_NICK_CHANGED);

	    if (strlen (collision_url))
	    {
		notice (s_NickServ, u->nick, NS_COLLISION_INFO);
		notice (s_NickServ, u->nick, collision_url);
	    }
	}

	send_cmd (s_NickServ, "%s %s %s :%ld", me.token ? "e" : "SVSNICK", u->nick, guest, time (NULL));
    }

    if (!recovered)
    {
	ni->status |= NS_ENFORCED;
	u->flags |= UF_ENFORCED;
    }
}

/* Cancel validation flags for a nick (i.e. when the user with that nick
   signs off or changes nicks). Also cancels any impending collides.
 */
void cancel_user (User *u)
{
    NickInfo *ni = findnick (u->nick);

    /* Delete all pending timers for this nickname. */
    del_ns_timeout (ni, -1, 1);

    if (ni)
	ni->status &= ~NS_RECOGNIZED;

    if (u->flags & UF_ENFORCED)
    {
	intro_user (u->nick, "enforcer", ns.host, "NickName Enforcement", "+i");
	u->flags &= ~UF_ENFORCED;

	if (ni)
	{
	    ni->status |= NS_ENFORCED;
	    add_ns_timeout (ni, TO_RELEASE, 60);
	}
    }
}

/* Delete a registered nickname. */
int delnick (NickInfo *ni)
{
    NickInfo *tni;
    MemoInfo *mi;
    ChanInfo *ci;
    ChanAccess *ca;
    char **chans, mbuf[BUFSIZE];
    int i;

    if (debuglevel > 1)
	debug ("delnick(): %s", ni->nick);

    if (finduser (ni->nick))
	send_cmd (s_NickServ, "%s %s -r%s", me.token ? "n" : "SVSMODE", ni->nick,
	    (ni->flags & NF_CSOP) ? "a" : "");

    /* Delete any pending timeouts for this nick */
    del_ns_timeout (ni, -1, 1);

    /* Delete linked nicks */
    if (ni->links)
    {
	/* Go through all of the linked nicks and call delnick for each. */
	for (i = 0; i < ni->linkcnt; i++)
	{
	    if (ni->links[i])
	    {
		/* We store a list of the nicks linked to this one,
		   so we don't have to waste cpu hunting them down.
		   now we'll go through that list, find the nicks
		   linked to this one, and delete them.
		 */
		tni = findnick (ni->links[i]);

		/* This should NEVER fail. */
		if (tni)
		    delnick (tni);
	    }
	}

	/* Free links list. */
	if (ni->links)
	{
	    for (i = 0; i < ni->linkcnt; i++)
		if (ni->links[i])
		    free (ni->links[i]);

	    free (ni->links);
	}
    }

    /* Free host */
    if (ni->host)
	free (ni->host);

    /* Free last usermask */
    if (ni->usermask)
	free (ni->usermask);

    /* Free realname */
    if (ni->real)
	free (ni->real);

    /* Free email */
    if (ni->email)
	free (ni->email);

    /* Free url */
    if (ni->url)
	free (ni->url);

    /* Free name */
    if (ni->name)
	free (ni->name);

    /* Free sex */
    if (ni->sex)
	free (ni->sex);

    /* Free location */
    if (ni->location)
	free (ni->location);

    /* Temporary E-mail */
    if (ni->temp)
	free (ni->temp);
	
    /* Free access list */
    if (ni->access)
    {
	for (i = 0; i < ni->accesscnt; i++)
	{
	    if (ni->access[i])
		free (ni->access[i]);
	}

	free (ni->access);
    }

    /* Free memos */
    mi = &ni->memos;

    if (mi->memocnt)
    {
	for (i = 0; i < mi->memocnt; i++)
	    free (mi->memos[i].text);

	free (mi->memos);
	mi->memos = NULL;
	mi->memocnt = 0;
    }

    /* Check for any channels this nick is the successor for. Send a memo to the
       founder.
     */
    if (ni->successor)
    {
	for (i = 0; i < ni->successorcnt; i++)
	{
	    if (ni->successor[i])
	    {
		/* We store a list of channels we're the successor for so that we can
		   notify the current founder of the channel when the successor's nickname
		   expires. May seem like a waste, but the alternative is searching every
		   registered channel to get this information, or not notifying them at all.
		 */
		ci = cs_findchan (ni->successor[i]);

		/* This shouldn't fail. */
		if (ci)
		{
		    NickInfo *fni = findnick (ci->founder);

		    snprintf (mbuf, sizeof (mbuf), CS_SUCCESSOR_GONE_MEMO, ci->name, ni->nick);
		    send_memo (fni, s_ChanServ, mbuf);

		    if (ci->successor)
		    {
			free (ci->successor);
			ci->successor = NULL;
		    }
		}
	    }
	}

	/* Now free the successor list. */
	for (i = 0; i < ni->successorcnt; i++)
	{
	    if (ni->successor[i])
		free (ni->successor[i]);
	}

	free (ni->successor);
    }

    /* Check for successors to any owned channels. */
    for (chans = ni->chans, i = 0; i < ni->chancnt; chans++, i++)
    {
	int delete = 1;

	/* Like links, we store a list of channels this nickname
	   owns on the nick to save time. Since this nick is being
	   dropped, we'll delete those channels. If they have a
	   successor set, we'll check if that nick is registered and
	   under any channel limits, if everything checks out, we'll
	   transfer it to them.
	 */
	ci = cs_findchan (*chans);

	/* This shouldn't fail. */
	if (ci)
	{
	    if (ci->successor)
	    {
		NickInfo *sni = findnick (ci->successor);
		long newpass = 0;

		if (sni)
		{
		    /* Woo, the successor's nick is still registered, but do
		       they have enough channel spots left?
		     */
		    if (sni->chancnt < chanlimit)
		    {
			/* Kickass. Transfer it to them. */
			strscpy (ci->founder, sni->nick, NICKLEN);

			/* We don't want to give away the old founders password,
			   but the new founder needs to know the pass.
			 */
			newpass = makekey ();
			strscpy (ci->pass, itoa (newpass), PASSWDLEN);

			/* We don't want to delete this channel, now */
			delete = 0;

			/* No more successor */
			free (ci->successor);
			ci->successor = NULL;

			/* Add it to their channel list */
			++sni->chancnt;
			sni->chans = srealloc (sni->chans, sizeof (char *) *sni->chancnt);
			sni->chans[sni->chancnt - 1] = sstrdup (ci->name);

			/* And finally, tell the new owner about it. */
			snprintf (mbuf, sizeof (mbuf), CS_SUCCESSOR_MEMO, ci->name, ci->pass);
			send_memo (sni, s_ChanServ, mbuf);
		    }
		}
	    }
	}

	if (delete)
	    delchan (ci, 1);
    }

    /* Free chans list */
    if (ni->chans)
    {
	for (i = 0; i < ni->chancnt; i++)
	{
	    if (ni->chans[i])
		free (ni->chans[i]);
	}

	free (ni->chans);
    }

    /* Go through each channel's access list, and remove this person
       from them as needed.
     */
    for (ci = firstci (); ci; ci = nextci ())
    {
	for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	{
	    if (!ca->level)
		continue;

	    if (!stricmp (ni->nick, ca->mask))
	    {
		if (ca->mask)
		    free (ca->mask);

		ca->level = 0;
	    }
	}
    }

    if (ni->prev)
	ni->prev->next = ni->next;
    else
	nicklist[NSHASH(ni->nick)] = ni->next;

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

    free (ni);

    return 1;
}

/* Check all nicknames and expire any nessecary */
void expire_nicks ()
{
    User *u;
    NickInfo *ni, *next;
    int j = 0;

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

    for (ni = firstni (); ni; ni = next)
    {
	next = nextni ();

	/* While we're here, check for any nicknames that are online.
	   if they are, update their lastseen time. This stops them from
	   being expired in error if they've been online longer than
	   nick_expire.
	 */
	if ((u = finduser (ni->nick)))
	    if (u && u->lastnick && !stricmp (u->lastnick, ni->nick))
		ni->lastseen = time (NULL);

	/* Check if this nickname has a Temp Email older than 24 hours.
	   Remove it if so.
	 */
	if (ni->temp)
	{
	    /* Ok, Old temp emails won't have a timestamp.. So if the
	       nickname's lastused time is older than 24 hours, we'll
	       kill ni->temp, but only if the nick isn't frozen, since
	       we use ni->temp for that. Also remove it if it's the same
	       as the email itself.
	     */
	    if ((ni->temptime && (time (NULL) >= ni->temptime + 86400)) ||
		(!(ni->flags & NF_FROZEN) && (time (NULL) >= ni->lastseen + 86400)) ||
		!stricmp (ni->email, ni->temp))
	    {
		free (ni->temp);
		ni->temp = NULL;
		ni->temptime = 0;
		ni->key = 0;
	    }
	}

	/* Only non-linked nicks and non-held nicks */
	if (!(ni->flags & NF_LINKED) && !(ni->flags & NF_HELD))
	{
	    if ((time (NULL) >= ni->lastseen + nick_expire) ||
		(ni->flags & NF_WAITAUTH && time (NULL) >=
		ni->registered + tempexpire))
	    {
		if (ni->linkcnt)
		    log ("NS:SYNC:EXPIRE: %s (%d)", ni->nick,
			ni->linkcnt);
		else
		    log ("NS:SYNC:EXPIRE: %s", ni->nick);

		delnick (ni);

		j++;
	    }
	}
    }

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

/* Register a nickname. */
static void do_register (User *u)
{
    NickInfo *ni, *tni, **list;
    FILE *f;
    char *param1 = strtok (NULL, " ");
    char *param2 = strtok (NULL, " ");
    char *param3 = strtok (NULL, " ");
    int found = 0, reason = 0;
    time_t now = time (NULL);

    /* Check here if they're completing registration from E-Mail AUTH */
    if (param1 && param2 && (nsregistertype == 1 || nsregistertype == 3 ||
	nsregistertype == 6))
    {
	ni = findnick (param1);

	/* Is this nick waiting for completion? */
	if (ni && (ni->flags & NF_WAITAUTH))
	{
	    if (ni->key && ni->key == atol (param2))
	    {
		notice (s_NickServ, u->nick, NS_REGISTERED);
		notice (s_NickServ, u->nick, RPL_PASSWORD_SET, ni->pass);

		if (strlen (nsregister_url))
		{
		    notice (s_NickServ, u->nick, NS_REGISTER_INFO);
		    notice (s_NickServ, u->nick, nsregister_url);
		}

		/* Tell them about new news.. This is a new nick, we'll assume
		   they haven't read the news. If we find the newsfile, we'll
		   turn the flag on for them.
		 */
		f = fopen (NEWSFILE, "r");

		if (f)
		{
		    /* News found, tell them about it */
		    notice (s_MemoServ, u->nick, MS_NEW_NEWS);
		    notice (s_MemoServ, u->nick, MS_TO_READ_NEWS,
			haveserv_on == TRUE ? "" : "MSG ", s_MemoServ,
			haveserv_on == TRUE ? "" : securitysetting == 1 ?
			"@" : "", haveserv_on == TRUE ? "" :
			securitysetting == 1 ? me.name : "");
		    fclose (f);

		    /* Now turn the NF_NEWS flag on for them */
		    ni->flags |= NF_NEWS;
		}

		/* Turn the key off */
		ni->key = 0;

		/* No longer waiting */
		ni->flags &= ~NF_WAITAUTH;

		/* Identified */
		if (u->lastnick)
		    free (u->lastnick);

		u->lastnick = sstrdup (ni->nick);

		/* Set the SRA flag if SecureSRA is off */
		if (securesra_on == FALSE)
		{
		    if (can_sra (ni->nick))
		    {
			u->flags |= UF_SRA;

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

		    if (is_sra (u))
		    {
			send_cmd (s_NickServ, "%s %s +ra", me.token ? "n" : "SVSMODE", u->nick);

			if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
			    send_cmd (s_NickServ,
				"%s %s :is a Services Root Admin", me.token ? "BA" : "SWHOIS", u->nick);
		    }
		    else
			send_cmd (s_NickServ, "%s %s +r", me.token ? "n" : "SVSMODE", u->nick);
		}

		return;
	    }
	}
    }

    /* Check here first if this nick is a Guest or User nick. We don't want
       people registering those. Also check it's not a services nick.
     */
    if (match_wild_nocase ("Guest*", u->nick) ||
	match_wild_nocase ("User*", u->nick) ||
	is_one_of_mine (u->nick))
    {
	notice (s_NickServ, u->nick, RPL_NO_REGISTER);
	return;
    }

    /* Check that we have all needed parameters and that they're valid. */
    if (!nsregistertype && !param1)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "REGISTER Password");
	errmoreinfo (s_NickServ, u->nick, "REGISTER");
	return;
    }

    /* REGISTER E-Mail */
    if (nsregistertype == 1)
    {
	if (!param1)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX, "REGISTER E-Mail");
	    errmoreinfo (s_NickServ, u->nick, "REGISTER");
	    return;
	}

	if (!validemail (param1, u->nick))
	{
	    notice (s_NickServ, u->nick, RPL_INVALID, "E-Mail", "");
	    return;
	}
    }

    /* REGISTER Pass E-Mail */
    if (nsregistertype == 2 || nsregistertype == 3)
    {
	if (!param1 || !param2)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX, "REGISTER Password E-Mail");
	    errmoreinfo (s_NickServ, u->nick, "REGISTER");
	    return;
	}

	if (!validemail (param2, u->nick))
	{
	    notice (s_NickServ, u->nick, RPL_INVALID, "E-Mail", "");
	    return;
	}
    }

    /* REGISTER Pass Pass */
    if (nsregistertype == 4)
    {
	if (!param1 || !param2)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX, "REGISTER Password Password");
	    errmoreinfo (s_NickServ, u->nick, "REGISTER");
	    return;
	}

	if (strcmp (param1, param2))
	{
	    notice (s_NickServ, u->nick, RPL_PASS_DONT_MATCH);
	    return;
	}
    }

    /* REGISTER Pass Pass E-Mail */
    if (nsregistertype == 5 || nsregistertype == 6)
    {

	if (!param1 || !param2 || !param3)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX,
		"REGISTER Password Password E-Mail");
	    errmoreinfo (s_NickServ, u->nick, "REGISTER");
	    return;
	}

	if (strcmp (param1, param2))
	{
	    notice (s_NickServ, u->nick, RPL_PASS_DONT_MATCH);
	    return;
	}

	if (!validemail (param3, u->nick))
	{
	    notice (s_NickServ, u->nick, RPL_INVALID, "E-Mail", "");
	    return;
	}
    }

    /* No register floods! */
    if (nick_delay && (now - u->lastnreg < nick_delay) && !is_oper (u))
    {
	notice (s_NickServ, u->nick, NS_WAIT_REG,
	    duration (u->lastnreg - now + nick_delay, 2));

	/* Pimp nick linking to them */
	if (maxlinks)
	{
	    notice (s_NickServ, u->nick, NS_TRY_LINK);
	    notice (s_NickServ, u->nick, NS_HELP_LINK,
		haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	}

	return;
    }

    /* Is it a Services nick? */
    if (is_one_of_mine (u->nick))
    {
	notice (s_NickServ, u->nick, NS_NOT_SERVICES_NICK);
	return;
    }

    /* Is it already registered? */
    if ((ni = findnick (u->nick)))
    {
	notice (s_NickServ, u->nick, NS_ALREADY_REGISTERED);
	return;
    }

    /* Password sanity checks */
    if (nsregistertype != 1)
    {
	if (strlen (param1) < 5)
	    reason = 1;
	if (strlen (param1) > PASSWDLEN)
	    reason = 2;
	if (!stricmp (u->nick, param1))
	    reason = 3;
	if (!stricmp ("password", param1))
	    reason = 4;

	if (reason)
	{
	    notice (s_NickServ, u->nick, RPL_BETTER_PASS);

	    if (reason == 1)
		notice (s_NickServ, u->nick, RPL_PASS_REJECTED_SHORT);
	    if (reason == 2)
		notice (s_NickServ, u->nick, RPL_PASS_REJECTED_LONG);
	    if (reason == 3)
		notice (s_NickServ, u->nick, RPL_PASS_REJECTED_NICK);
	    if (reason == 4)
		notice (s_NickServ, u->nick, RPL_PASS_REJECTED_WEAK);

	    errmoreinfo (s_NickServ, u->nick, "REGISTER");
	    return;
	}
    }

    /* Check for an email limit */
    if (emaillimit && !(!nsregistertype || nsregistertype == 4) && !is_sra (u))
    {
	char *email = "";

	if (nsregistertype == 1)
	    email = param1;
	if (nsregistertype == 2 || nsregistertype == 3)
	    email = param2;
	if (nsregistertype == 5 || nsregistertype == 6)
	    email = param3;

	for (tni = firstni (); tni; tni = nextni ())
	    if (tni->email && !stricmp (tni->email, email))
		++found;

	if (found && found >= emaillimit)
	{
	    notice (s_NickServ, u->nick, NS_TOO_MANY_EMAILS, email);
	    return;
	}
    }

    /* Register the NickName. */
    ni = scalloc (sizeof (NickInfo), 1);
    strscpy (ni->nick, u->nick, NICKLEN);

    if (nsregistertype != 1)
	strscpy (ni->pass, param1, PASSWDLEN);

    /* We'll make a pw */
    if (nsregistertype == 1 || nsregistertype == 3 || nsregistertype == 6)
	ni->key = makekey ();

    /* They don't have a password. We'll use the key as their pw. */
    if (nsregistertype == 1)
	strscpy (ni->pass, itoa (ni->key), PASSWDLEN);

    ni->registered = ni->lastseen = now;
    ni->memos.memolimit = memobox_size;
    ni->real = sstrdup (u->real);
    ni->usermask = smalloc (strlen (u->nick) + strlen (u->user) + strlen (u->host) + 3);
    sprintf (ni->usermask, "%s!%s@%s", u->nick, u->user, u->host);
    ni->accesscnt = 1;
    ni->access = smalloc (sizeof (char *));
    ni->access[0] = sstrdup (create_mask (u));

    if (defnickflags)
	ni->flags = defnickflags;

    if (nsregistertype == 1)
	ni->email = sstrdup (param1);

    if (nsregistertype == 2 || nsregistertype == 3)
	ni->email = sstrdup (param2);

    if (nsregistertype == 5 || nsregistertype == 6)
	ni->email = sstrdup (param3);

    if (u->lastnick)
	free (u->lastnick);

    u->lastnick = sstrdup (ni->nick);
    u->lastnreg = now;

    list = &nicklist[NSHASH(ni->nick)];
    ni->next = *list;
    if (*list)
	(*list)->prev = ni;
    *list = ni;

    /* Make note of the occurance. */
    log ("NS:REGISTER: %s!%s@%s", u->nick, u->user, u->host);
    snoop (s_NickServ, "NS:REGISTER: %s!%s@%s", u->nick, u->user,
	u->host);

    /* If this was an E-Mail auth register, we'll tell them to check their
       email, email the instructions, and bail. We don't need to go beyond
       this point.
     */
    if (nsregistertype == 1 || nsregistertype == 3 || nsregistertype == 6)
    {
	/* Is this email being abused? */
	if (check_email (ni->email))
	{
	    notice (s_NickServ, u->nick, RPL_EMAIL_USED);
	    delnick (ni);
	    return;
	}

	sendemail (ni->nick, itoa (ni->key), 1);

	notice (s_NickServ, u->nick, NS_REGISTERED_TEMP);
	notice (s_NickServ, u->nick, RPL_EMAIL_INSTRUCTIONS);
	notice (s_NickServ, u->nick, NS_TEMP_EXPIRE, duration (tempexpire, 2));

	/* Set a flag so we know we're waiting for them */
	ni->flags |= NF_WAITAUTH;

	return;
    }

    /* Set the SRA flag if SecureSRA is off */
    if (securesra_on == FALSE)
    {
	if (can_sra (ni->nick))
	{
	    u->flags |= UF_SRA;

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

	if (is_sra (u))
	{
	    send_cmd (s_NickServ, "%s %s +ra", me.token ? "n" : "SVSMODE", u->nick);

	    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
		send_cmd (s_NickServ,
		    "%s %s :is a Services Root Admin", me.token ? "BA" : "SWHOIS", u->nick);
	}
	else
	    send_cmd (s_NickServ, "%s %s +r", me.token ? "n" : "SVSMODE", u->nick);
    }

    /* Tell them we were successful. */
    notice (s_NickServ, u->nick, NS_REGISTERED);
    notice (s_NickServ, u->nick, RPL_PASSWORD_SET, ni->pass);

    if (strlen (nsregister_url))
    {
	notice (s_NickServ, u->nick, NS_REGISTER_INFO);
	notice (s_NickServ, u->nick, nsregister_url);
    }

    /* Tell them about new news.. This is a new nick, we'll assume they
       haven't read the news. If we find the newsfile, we'll turn the flag
       on for them.
     */
    f = fopen (NEWSFILE, "r");

    if (f)
    {
	/* News found, tell them about it */
	notice (s_MemoServ, u->nick, MS_NEW_NEWS);
	notice (s_MemoServ, u->nick, MS_TO_READ_NEWS,
	    haveserv_on == TRUE ? "" : "MSG ", s_MemoServ,
	    haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	    haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	fclose (f);

	/* Now turn the NF_NEWS flag on for them */
	ni->flags |= NF_NEWS;
    }
}

/* Drop a registered nickname */
static void do_drop (User *u)
{
    NickInfo *ni, *hni;
    char *nick = strtok (NULL, " ");
    char *pass = strtok (NULL, " ");
    int csop = is_csop (u);

    if (!nick || !pass)
    {
	if (!csop)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX, "DROP NickName Password");
	    errmoreinfo (s_NickServ, u->nick, "DROP");
	    return;
	}
	else
	{
	    if (!nick)
	    {
		notice (s_NickServ, u->nick, RPL_SYNTAX, "DROP NickName [Password]");
		errmoreinfo (s_NickServ, u->nick, "DROP");
		return;
	    }
	}
    }

    ni = findnick (nick);

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

    if (ni->flags & NF_FROZEN && !csop)
    {
	notice (s_NickServ, u->nick, NS_NICK_FROZEN, ni->nick);
	return;
    }

    if (!csop)
	if (!check_ns_auth (s_NickServ, u->nick, ni))
	    return;

    /* Linked nicks don't have a password. Point to host instead */
    if (ni->host)
	hni = findnick (ni->host);
    else
	hni = ni;

    if (csop || !strcmp (pass, hni->pass))
    {
	if (ni->linkcnt)
	{
	    log ("NS:DROP: %s %s!%s@%s (%d)", ni->nick, u->nick, u->user, u->host,
		ni->linkcnt);
	    snoop (s_NickServ, "NS:DROP: %s %s!%s@%s (%d)", ni->nick, u->nick,
		u->user, u->host, ni->linkcnt);
	}
	else
	{
	    log ("NS:DROP: %s %s!%s@%s", ni->nick, u->nick, u->user, u->host);
	    snoop (s_NickServ, "NS:DROP: %s %s!%s@%s", ni->nick, u->nick, u->user,
		u->host);
	}

	/* If this is a linked nick, we need to remove it from the hosts links
	   list here, since delnick doesn't do this.
	 */
	if (ni->host)
	{
	    char **links;
	    int i;

	    hni = findnick (ni->host);

	    if (hni)
	    {
		for (links = hni->links, i = 0; i < hni->linkcnt; links++, i++)
		    if (!stricmp (*links, ni->nick))
			break;

		if (*links)
		    free (*links);

		--hni->linkcnt;

		if (i < hni->linkcnt)
		    bcopy (links + 1, links, (hni->linkcnt - i) * sizeof (char *));

		if (hni->linkcnt)
		    hni->links = srealloc (hni->links, hni->linkcnt * sizeof (char *));
		else
		{
		    if (hni->links)
			free (hni->links);

		    hni->links = NULL;
		}
	    }
	}

	delnick (ni);

	notice (s_NickServ, u->nick, NS_DROPPED, ni->nick);
    }
    else
    {
	passfail (s_NickServ, ni->nick, "NS:DROP:BP:", u);
	return;
    }
}

/* Link a nickname to another nickname */
static void do_link (User *u)
{
    NickInfo *ni, *lni, **list;
    char *nick = strtok (NULL, " ");
    char *pass = strtok (NULL, " ");
    time_t now = time (NULL);

    /* Do we have all needed info? */
    if (!nick)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "LINK NickName [Password]");
	errmoreinfo (s_NickServ, u->nick, "LINK");
	return;
    }

    /* Check here first if this nick is a Guest or User nick. We don't want
       people registering those. Also check that it's not a services nick.
     */
    if (match_wild_nocase ("Guest*", u->nick) ||
        match_wild_nocase ("User*", u->nick) ||
	match_wild_nocase ("Guest*", nick) ||
	match_wild_nocase ("User*", nick) ||
	is_one_of_mine (u->nick) ||
	is_one_of_mine (nick))
    {
	notice (s_NickServ, u->nick, RPL_NO_REGISTER);
	return;
    }

    /* Bail out if the specified nick is in use. This stops people from
       linking people's away nicks to recover them.
     */
    if (finduser (nick))
    {
	notice (s_NickServ, u->nick, RPL_IN_USE, nick);
	return;
    }

    /* No register floods! */
    if (nick_delay && (now - u->lastnreg < nick_delay) && !is_oper (u))
    {
	notice (s_NickServ, u->nick, NS_WAIT_REG,
	    duration (u->lastnreg - now + nick_delay, 2));
	return;
    }

    lni = findnick (nick);
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_NickServ, u->nick, RPL_NEED_IDENT, "LINK");
	return;
    }

    /* Check if these nicks are already linked. */
    if (lni)
	if (islinked (nick, ni) || islinked (ni->nick, lni))
	{
	    notice (s_NickServ, u->nick, NS_LINK_SELF);
	    return;
	}

    /* lni->nick and ni->nick might be the same. We don't want to link
       a nick to itself. Since they might be authed for the specified
       nick and using an unregistered nick, we'll set ni to u->nick.
     */
    if ((ni && lni && !stricmp (lni->nick, ni->nick)))
    {
	ni = findnick (u->nick);

	/* Is it STILL the same nick? */
	if ((ni && lni && !stricmp (lni->nick, ni->nick)))
	{
	    notice (s_NickServ, u->nick, NS_LINK_SELF);
	    return;
	}
    }

    /* Neither nickname is registered. */ 
    if (!lni && !ni)
    {
	notice (s_NickServ, u->nick, NS_IDENTIFY_ONE);
	return;
    }

    /* The specified nickname isn't registered, but the u->nick one is. */
    if (!lni && ni)
    {
	/* Since they want to link an unregistered nickname to their
	   current nickname, we'll skip the password and just make
	   sure they're identified.
	 */
	if (!u->lastnick || stricmp (ni->nick, u->lastnick))
	{
	    notice (s_NickServ, u->nick, RPL_NEED_IDENT, "LINK");
	    return;
	}

	/* Make sure we're not being asked to link to a linked nick */
	if (ni->flags & NF_LINKED)
	{
	    /* Beh. It's linked. Grab the host nick instead. */
	    ni = findnick (ni->host);
	}

	/* Do they have too many? */
	if (ni->linkcnt >= maxlinks && !is_csop (u))
	{
	    notice (s_NickServ, u->nick, NS_MAX_LINKS, maxlinks);
	    return;
	}

	/* Make note of it on the host nick */
	++ni->linkcnt;
	u->lastnreg = now;
	ni->links = srealloc (ni->links, sizeof (char *) *ni->linkcnt);
	ni->links[ni->linkcnt - 1] = sstrdup (nick);

	/* Linked nicknames are normally registered nicks, except they have
	   the LINKED flag, and have the host nickname stored. Thus, we
	   have to register it here.
	 */
	lni = scalloc (sizeof (NickInfo), 1);
	strscpy (lni->nick, nick, NICKLEN);
	lni->host = sstrdup (ni->nick);
	lni->flags = NF_LINKED;

	list = &nicklist[NSHASH(lni->nick)];
	lni->next = *list;
	if (*list)
	    (*list)->prev = lni;
	*list = lni;

	notice (s_NickServ, u->nick, NS_NICKS_LINKED, lni->nick, ni->nick);
	log ("NS:LINK: %s %s %s!%s@%s", lni->nick, ni->nick,
	    u->nick, u->user, u->host);
	snoop (s_NickServ, "NS:LINK: %s %s %s!%s@%s", lni->nick,
	    ni->nick, u->nick, u->user, u->host);
	return;
    }

    /* The u->nick nickname isn't registered, but the specified one is. */
    if (lni && !ni)
    {
	if (!pass)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX, "LINK NickName Password");
	    errmoreinfo (s_NickServ, u->nick, "LINK");
	    return;
	}

	/* Check the passwords */
	if (strcmp (pass, lni->pass))
	{
	    passfail (s_NickServ, lni->nick, "NS:LINK:BP:", u);
	    return;
	}

	/* Make sure we're not being asked to link to a linked nick */
	if (lni->flags & NF_LINKED)
	{
	    /* Beh. It's linked. Grab the host nick instead. */
	    lni = findnick (lni->host);
	}

	/* Do they have too many? */
	if (lni->linkcnt >= maxlinks)
	{
	    notice (s_NickServ, u->nick, NS_MAX_LINKS_OTHER, lni->nick, maxlinks);
	    return;
	}

	if (!check_ns_auth (s_NickServ, u->nick, lni))
	    return;

	++lni->linkcnt;
	u->lastnreg = now;
	lni->links = srealloc (lni->links, sizeof (char *) *lni->linkcnt);
	lni->links[lni->linkcnt - 1] = sstrdup (u->nick);

	ni = scalloc (sizeof (NickInfo), 1);
	strscpy (ni->nick, u->nick, NICKLEN);
	ni->host = sstrdup (lni->nick);
	ni->flags = NF_LINKED;

	list = &nicklist[NSHASH(ni->nick)];
	ni->next = *list;
	if (*list)
	    (*list)->prev = ni;
	*list = ni;

	/* They've essentially identified for this nickname, so we'll
	   consider them as such.
	 */
	if (u->lastnick)
	    free (u->lastnick);

	u->lastnick = sstrdup (ni->nick);

	notice (s_NickServ, u->nick, NS_NICKS_LINKED, ni->nick, lni->nick);
	log ("NS:LINK: %s %s %s!%s@%s", ni->nick, lni->nick,
	    u->nick, u->user, u->host);
	snoop (s_NickServ, "NS:LINK: %s %s %s!%s@%s", ni->nick,
	    lni->nick, u->nick, u->user, u->host);
	return;
    }

    /* Both nicknames are registered. We'll merge them. If the user read
       the help files (which they should have) they'll know their current
       nickname is considered the host.
     */
    if (lni && ni)
    {
	char tnick[NICKLEN];

	/* Make sure they're identified for their current nick */
	ni = get_nick (u);

	if (!ni)
	{
	    notice (s_NickServ, u->nick, RPL_NEED_IDENT, "LINK");
	    return;
	}

	if (!stricmp (ni->nick, lni->nick))
	{
	    notice (s_NickServ, u->nick, NS_LINK_SELF);
	    return;
	}

	if (!pass)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX, "LINK NickName Password");
	    errmoreinfo (s_NickServ, u->nick, "LINK");
	    return;
	}

	/* Check the passwords */
	if (strcmp (pass, lni->pass))
	{
	    passfail (s_NickServ, lni->nick, "NS:LINK:BP:", u);
	    return;
	}

	/* Make sure we're not being asked to link to a linked nick */
	if (lni->flags & NF_LINKED)
	{
	    /* Beh. It's linked. Grab the host nick instead. */
	    ni = findnick (ni->host);
	}

	/* Do they have too many? */
	if (lni->linkcnt >= maxlinks)
	{
	    notice (s_NickServ, u->nick, NS_MAX_LINKS_OTHER, lni->nick, maxlinks);
	    return;
	}

	if (!check_ns_auth (s_NickServ, u->nick, lni))
	    return;

	++ni->linkcnt;
	u->lastnreg = now;
	ni->links = srealloc (ni->links, sizeof (char *) *ni->linkcnt);
	ni->links[ni->linkcnt - 1] = sstrdup (lni->nick);

	if (email_on == TRUE && lni->email && !ni->email)
	    ni->email = sstrdup (lni->email);

	if (url_on == TRUE && lni->url && !ni->url)
	    ni->url = sstrdup (lni->url);

	if (uin_on == TRUE && lni->uin && !ni->uin)
	    ni->uin = lni->uin;

	if (sex_on == TRUE && lni->sex && !ni->sex)
	    ni->sex = sstrdup (lni->sex);

	if (age_on == TRUE && lni->age && !ni->age)
	    ni->age = lni->age;

	if (location_on == TRUE && lni->location && !ni->location)
	    ni->location = sstrdup (lni->location);

	if (name_on == TRUE && lni->name && !ni->name)
	    ni->name = sstrdup (lni->name);

	/* Combine the nick flags. */
	ni->flags |= lni->flags;
	ni->flags &= ~NF_LINKED;

	/* Make note of the nickname before we free it */
	strscpy (tnick, lni->nick, NICKLEN);

	/* Done merging .. Drop the nick. */
	delnick (lni);

	/* Now re-register it, blank, and set it up as a linked nick.
	   We use tni, since the linking nickname doesn't nessecarily
	   have to be u->nick.
	 */
	lni = scalloc (sizeof (NickInfo), 1);
	strscpy (lni->nick, tnick, NICKLEN);
	lni->host = sstrdup (ni->nick);
	lni->flags = NF_LINKED;

	list = &nicklist[NSHASH(lni->nick)];
	lni->next = *list;
	if (*list)
	    (*list)->prev = lni;
	*list = lni;

	notice (s_NickServ, u->nick, NS_NICKS_MERGED, tnick, ni->nick);
	log ("NS:LINK: %s %s %s!%s@%s", tnick, ni->nick,
	    u->nick, u->user, u->host);
	snoop (s_NickServ, "NS:LINK: %s %s %s!%s@%s", tnick,
	    ni->nick, u->nick, u->user, u->host);
    }
}

/* Unlink two nicks. */
static void do_unlink (User *u)
{
    NickInfo *ni, *lni, **list;
    char *nick = strtok (NULL, " "), **acclist, **links, tmp[NICKLEN];
    int i;

    if (!nick)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "UNLINK NickName");
	return;
    }

    ni = get_nick (u);

    if (!ni)
    {
	notice (s_NickServ, u->nick, RPL_NEED_IDENT, "UNLINK");
	return;
    }

    lni = findnick (nick);

    if (!lni)
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	return;
    }

    if (!check_ns_auth (s_NickServ, u->nick, lni))
	return;

    /* Are they actually linked? */
    if (!lni->host || stricmp (ni->nick, lni->host))
    {
	notice (s_NickServ, u->nick, NS_NOT_LINKED, lni->nick, ni->nick);
	return;
    }

    /* Ok. By this point, we know they're authed for the host nick, and
       we know the given nick is linked to the host nick. Now we seperate
       the two. In most services that I'm aware, unlinking two nicks drops
       the previously linked nick. Not here. When two nicks are unlinked,
       they become standalone nicknames. If someone wants to get rid of a
       linked nick, they can use the drop command, before or after unlinking.

       To seperate two nicknames, we'll delete it, then 'register' it, copying
       all the hosts info into it.
     */
    strscpy (tmp, lni->nick, NICKLEN);

    /* We have to remove it from the hosts links list, since delnick doesnt. */
    for (links = ni->links, i = 0; i < ni->linkcnt; links++, i++)
	if (!stricmp (*links, lni->nick))
	    break;

    if (*links)
	free (*links);

    --ni->linkcnt;

    if (i < ni->linkcnt)
	bcopy (links + 1, links, (ni->linkcnt - i) * sizeof (char *));

    if (ni->linkcnt)
	ni->links = srealloc (ni->links, ni->linkcnt * sizeof (char *));
    else
    {
	if (ni->links)
	    free (ni->links);

	ni->links = NULL;
    }

    delnick (lni);

    /* Now fill the fields. */
    lni = scalloc (sizeof (NickInfo), 1);
    strscpy (lni->nick, tmp, NICKLEN);
    strscpy (lni->pass, ni->pass, PASSWDLEN);
    lni->registered = ni->registered;
    lni->lastseen = ni->lastseen;
    lni->timestamp = ni->timestamp;
    lni->sstamp = ni->sstamp;
    lni->memos.memolimit = ni->memos.memolimit;
    lni->flags = ni->flags;
    lni->usermask = sstrdup (ni->usermask);
    lni->real = sstrdup (ni->real);

    if (ni->email)
	lni->email = sstrdup (ni->email);

    if (ni->url)
	lni->url = sstrdup (ni->url);

    if (ni->uin)
	lni->uin = ni->uin;

    if (ni->name)
	lni->name = sstrdup (ni->name);

    if (ni->age)
	lni->age = ni->age;

    if (ni->sex)
	lni->sex = sstrdup (ni->sex);

    if (ni->location)
	lni->location = sstrdup (ni->location);

    if (ni->forward)
	lni->forward = sstrdup (ni->forward);

    for (acclist = ni->access, i = 0; i < ni->accesscnt; acclist++, i++)
    {
	++lni->accesscnt;
	lni->access = srealloc (lni->access, sizeof (char *) *lni->accesscnt);
	lni->access[lni->accesscnt - 1] = sstrdup (ni->access[i]);
    }

    list = &nicklist[NSHASH(lni->nick)];
    lni->next = *list;
    if (*list)
	(*list)->prev = lni;
    *list = lni;

    notice (s_NickServ, u->nick, NS_NICKS_UNLINKED, lni->nick, ni->nick);
}

/* List the links for a nickname. */
static void do_links (User *u)
{
    NickInfo *ni;
    char buf[BUFSIZE], **links, *nick = strtok (NULL, " ");
    int i;

    /* If they didn't specify a nick, check if they've identified for a
       nick. If not, then we'll go for their current nick.
     */
    if (nick)
	ni = findnick (nick);
    else
	ni = get_nick (u);

    if (!ni)
    {
	if (nick)
	    notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	else
	    notice (s_NickServ, u->nick, RPL_NEED_IDENT, "LINKS");

	return;
    }

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

    /* Go for the host */
    if (ni->host)
	ni = findnick (ni->host);

    /* Bail out here if they don't have any */
    if (!ni->linkcnt)
    {
	notice (s_NickServ, u->nick, NS_NO_LINKS, ni->nick);
	return;
    }

    *buf = 0;

    /* We store the list of linked nicks for a host on the host
       nick to save cpu time searching for them. Thus, we assume
       the list on the host is always correct (bad things happen
       if its not.)
     */
    for (links = ni->links, i = 0; i < ni->linkcnt; links++, i++)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, *links);
    }

    if (*buf)
    {
	notice (s_NickServ, u->nick, NS_LINKS_LIST, ni->nick, buf);
	return;
    }
    else
    {
 	notice (s_NickServ, u->nick, NS_NO_LINKS, ni->nick);
	return;
    }
}

/* List the channels for a nickname. */
static void do_chans (User *u)
{
    NickInfo *ni;
    ChanInfo *ci;
    char buf[BUFSIZE], **chans, *nick = strtok (NULL, " ");
    int i;

    /* If they didn't specify a nick, check if they've identified for a
       nick. If not, then we'll go for their current nick.
     */
    if (nick)
	ni = findnick (nick);
    else
	ni = get_nick (u);

    if (!ni)
    {
	if (nick)
	    notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	else
	    notice (s_NickServ, u->nick, RPL_NEED_IDENT, "CHANS");

	return;
    }

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

    /* Go for the host */
    if (ni->host)
	ni = findnick (ni->host);

    /* Bail out here if they don't have any */
    if (!ni->chancnt)
    {
	notice (s_NickServ, u->nick, NS_NO_CHANS, ni->nick);
	return;
    }

    *buf = 0;

    /* We store the list of channels for a host on the host
       nick to save cpu time searching for them. This list may not
       always be correct, though. So we'll check each channel,
       and remove it from the list if it's not registered. This
       is still much faster than checking the founder of every
       channel, though.
     */
    for (chans = ni->chans, i = 0; i < ni->chancnt; chans++, i++)
    {
	ci = cs_findchan (*chans);

	if (!ci)
	{
	    /* This channel isn't registered anymore. Take it out
	       of the chans list, and continue on.
	     */
	    free (*chans);

	    --ni->chancnt;

	    if (i < ni->chancnt)
		bcopy (chans + 1, chans, (ni->chancnt - i) * sizeof (char *));

	    if (ni->chancnt)
		ni->chans = srealloc (ni->chans, ni->chancnt * sizeof (char *));
	    else
	    {
		if (ni->chans)
		    free (ni->chans);

		ni->chans = NULL;
	    }

	    continue;
	}

	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, *chans);
    }

    if (*buf)
    {
	notice (s_NickServ, u->nick, NS_CHANS_LIST, ni->nick, buf);
	return;
    }
    else
    {
 	notice (s_NickServ, u->nick, NS_NO_CHANS, ni->nick);
	return;
    }
}

/* I guess I should have at least ONE of these things :) */
static void do_truth (User *u)
{
    notice (s_NickServ, u->nick, "YOU CAN'T HANDLE THE TRUTH");
}

/* Show the users status to services. */
static void do_status (User *u)
{
    NickInfo *ni;
    User *utmp;
    char buf[256], *param = strtok (NULL, " ");
    int hasstatus = 0, isother = 0;

    if (is_csop (u) && param)
    {
	utmp = finduser (param);

	if (!utmp)
	{
	    notice (s_NickServ, u->nick, RPL_NOT_ONLINE, param);
	    return;
	}

	isother = 1;
    }
    else
	utmp = u;

    ni = get_nick (utmp);

    if (ni)
    {
	hasstatus = 1;

	if (ni->flags & NF_WAITAUTH)
	    sprintf (buf, "Unvalidated (%s)", ni->nick);
	else
	    sprintf (buf, "Identified (%s)", ni->nick);

	if (is_oper (utmp))
	    strcat (buf, ", IRCOp");

	if (is_csop (utmp))
	{
	    if (*buf)
		strcat (buf, ", ");

	    strcat (buf, "CSOp");
	}

	if (is_sra (utmp))
	{
	    if (*buf)
		strcat (buf, ", ");

	    strcat (buf, "SRA");
	}

	if (isother)
	    notice (s_NickServ, u->nick, NS_STATUS_OTHER, utmp->nick, buf);
	else
	    notice (s_NickServ, u->nick, NS_STATUS, buf);
    }

    if (!ni)
	ni = findnick (utmp->nick);

    if (!hasstatus && ni && (ni->status & NS_RECOGNIZED))
    {
	hasstatus = 1;

	if (ni->flags & NF_WAITAUTH)
	    sprintf (buf, "Unvalidated (%s)", ni->nick);
	else
	    sprintf (buf, "Recognized");

	if (is_oper (utmp))
	    strcat (buf, ", IRCOp");

	if (is_csop (utmp))
	{
	    if (*buf)
		strcat (buf, ", ");

	    strcat (buf, "CSOp");
	}

	if (is_sra (utmp))
	{
	    if (*buf)
		strcat (buf, ", ");

	    strcat (buf, "SRA");
	}

	if (isother)
	    notice (s_NickServ, u->nick, NS_STATUS_OTHER, utmp->nick, buf);
	else
	    notice (s_NickServ, u->nick, NS_STATUS, buf);
    }

    if (utmp->idchancnt)
    {
	char **chans;
	int i;

	*buf = 0;

	for (chans = utmp->idchans, i = 0; i < utmp->idchancnt; chans++, i++)
	{
	    /* If they identified for a channel that was then dropped,
	       take that channel out of their idchans list.
	     */
	    if (!cs_findchan (*chans))
	    {
		if (*chans)
		    free (*chans);

		--utmp->idchancnt;

		if (i < utmp->idchancnt)
		    bcopy (chans + 1, chans, (utmp->idchancnt - i) * sizeof (char *));

		if (utmp->idchancnt)
		    utmp->idchans = srealloc (utmp->idchans, utmp->idchancnt * sizeof (char *));
		else
		{
		    if (utmp->idchans)
			free (utmp->idchans);
		    utmp->idchans = NULL;
		}

		continue;
	    }

	    if (*buf)
		strcat (buf, ", ");

	    strcat (buf, *chans);
	}

	if (*buf)
	{
	    hasstatus = 1;

	    if (isother)
		notice (s_NickServ, u->nick, NS_STATUS_CHANS_OTHER, utmp->nick, buf);
	    else
		notice (s_NickServ, u->nick, NS_STATUS_CHANS, buf);
	}
    }

    if (!hasstatus)
    {
	if (isother)
	    notice (s_NickServ, u->nick, NS_STATUS_NONE_OTHER, utmp->nick);
	else
	    notice (s_NickServ, u->nick, NS_STATUS_NONE);
    }
}

/* Show information on a registered nickname. */
static void do_info (User *u)
{
    NickInfo *ni, *hni, *sni = get_nick (u);
    char *nick = strtok (NULL, " ");
    char buf[BUFSIZE];

    if (!nick)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "INFO NickName");
	errmoreinfo (s_NickServ, u->nick, "INFO");
	return;
    }

    if (!(ni = findnick (nick)))
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	return;
    }

    /* We'll reference hni from here on in */
    if (ni->host)
	hni = findnick (ni->host);
    else
	hni = ni;

    notice (s_NickServ, u->nick, NS_INFO_START, ni->nick, hni->real);

    if (ni->status & NS_RECOGNIZED)
	notice (s_NickServ, u->nick, NS_INFO_RECOGNIZED);

    if (ni->flags & NF_WAITAUTH)
	notice (s_NickServ, u->nick, NS_INFO_WAITING);

    if (hni->flags & NF_FROZEN)
    {
	notice (s_NickServ, u->nick, NS_INFO_FROZEN);

	/* Hide the setter from non ircops to avoid evil retribution */
	if (is_oper (u))
	    notice (s_NickServ, u->nick, NS_INFO_FROZEN_BY, hni->temp);

	if (hni->freezereason)
	    notice (s_NickServ, u->nick, NS_INFO_FROZEN_WHY, hni->freezereason);

	notice (s_NickServ, u->nick, NS_INFO_FROZEN_TIME, sni ? zone_time
	    (sni, hni->lastseen, 4) : get_time (hni->lastseen, 4, 0, "GMT"));
    }

    if (ni->host)
	notice (s_NickServ, u->nick, NS_INFO_HOST, ni->host);

    notice (s_NickServ, u->nick, NS_INFO_REGISTERED, sni ? zone_time
	(sni, hni->registered, 4) : get_time (hni->registered, 4, 0, "GMT"),
	time_ago (hni->registered));

    if (!finduser (hni->nick) && (!(hni->flags & NF_PRIVATE) || is_csop (u)))
    {
	notice (s_NickServ, u->nick, NS_INFO_LAST_USED, sni ? zone_time
	    (sni, hni->lastseen, 4) : get_time (hni->lastseen, 4, 0, "GMT"),
	    time_ago (hni->lastseen));

	notice (s_NickServ, u->nick, NS_INFO_LAST_MASK, hni->usermask);
    }

    if (ni->zone)
    {
	Zone *zone = findzone (ni->zone);

	/* Better safe than sorry.. */
	if (zone)
	    notice (s_NickServ, u->nick, NS_INFO_ZONE, zone->name, zone->noffset, zone->desc);
    }

    if (showmail_on == TRUE && email_on == TRUE)
	if (hni->email && ((hni->flags & NF_SHOWMAIL) || is_csop (u))) 
	    notice (s_NickServ, u->nick, NS_INFO_EMAIL, hni->email);

    if (showmail_on == FALSE && email_on == TRUE)
	if (hni->email && (!(hni->flags & NF_HIDEMAIL) || is_csop (u)))
	    notice (s_NickServ, u->nick, NS_INFO_EMAIL, hni->email);

    if (ni->linkcnt)
	notice (s_NickServ, u->nick, NS_INFO_LINKS, ni->linkcnt);

    if (url_on == TRUE)
	if (hni->url)
	    notice (s_NickServ, u->nick, NS_INFO_URL, hni->url);

    if (uin_on == TRUE)
	if (hni->uin)
	    notice (s_NickServ, u->nick, NS_INFO_UIN, hni->uin);

    if (name_on == TRUE)
	if (hni->name)
	    notice (s_NickServ, u->nick, NS_INFO_NAME, hni->name);

    if (age_on == TRUE)
	if (hni->age)
	    notice (s_NickServ, u->nick, NS_INFO_AGE, hni->age);

    if (sex_on == TRUE)
	if (hni->sex)
	    notice (s_NickServ, u->nick, NS_INFO_SEX, hni->sex);

    if (location_on == TRUE)
	if (hni->location)
	    notice (s_NickServ, u->nick, NS_INFO_LOCATION, hni->location);

    if (hni->forward)
	notice (s_NickServ, u->nick, NS_INFO_FORWARD, hni->forward);

    *buf = 0;

    if (hni->flags & NF_ENFORCE)
	strcat (buf, FLAG_ENFORCE);

    if (hni->flags & NF_NEVEROP)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_NEVEROP);
    }

    if (hni->flags & NF_NOOP)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_NOOP);
    }

    if (hni->flags & NF_NOSUCCESSOR)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_NOSUCCESSOR);
    }

    if (hni->flags & NF_PRIVATE)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_PRIVATE);
    }

    if (hni->flags & NF_SECURE)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_SECURE);
    }

    if (hni->flags & NF_MEMOMAIL)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_MEMOMAIL);
    }

    if (hni->flags & NF_RECEIPTS)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_RECEIPTS);
    }

    if (hni->flags & NF_NOMEMO)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_NOMEMO);
    }

    if (hni->flags & NF_HELD)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_HELD);
    }

    if (hni->flags & NF_MARKED)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_MARKED);
    }

    if (can_csop (hni->nick))
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_CSOP);
    }

    if (can_sra (hni->nick))
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_SRA);
    }

    if (*buf)
	notice (s_NickServ, u->nick, NS_INFO_FLAGS, buf);

    notice (s_NickServ, u->nick, NS_INFO_END);
}

/* Identify to a nickname. */
static void do_identify (User *u)
{
    NickInfo *ni, *hni;
    struct u_chanlist *c;
    struct c_userlist *cu;
    char *param1 = strtok (NULL, " ");
    char *param2 = strtok (NULL, " ");
    char *pass;

    if (!param1)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "IDENTIFY [NickName] Password");
	errmoreinfo (s_NickServ, u->nick, "IDENTIFY");
        return;
    }

    /* Do the password shuffle! */
    if (param2)
    {
	ni = findnick (param1);
	pass = param2;
    }
    else
    {
	ni = findnick (u->nick);
	pass = param1;
    }

    /* Is the nick registered? */
    if (!ni)
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED,
	    param2 ? param1 : u->nick);
	return;
    }

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

    /* We'll reference hni from here on in */
    if (ni->host)
	hni = findnick (ni->host);
    else
	hni = ni;

    /* Check the given password against the stored one */
    if (!strcmp (pass, hni->pass))
    {
	/* Passwords match, Update flags, times, and other information */
	if (hni->usermask)
	    free (hni->usermask);

	/* Delete all pending NS timers */
	del_ns_timeout (ni, -1, 1);

	hni->lastseen = time (NULL);
	hni->usermask = smalloc (strlen (u->nick) + strlen (u->user) + strlen (u->host) + 3);
	hni->timestamp = u->timestamp;
	hni->sstamp = u->sstamp;
	hni->status &= ~NS_RECOGNIZED;

	sprintf (hni->usermask, "%s!%s@%s", u->nick, u->user, u->host);

	if (hni->real)
	    free (hni->real);

	hni->real = sstrdup (u->real);

	if (u->lastnick)
	    free (u->lastnick);

	u->lastnick = sstrdup (hni->nick);

	/* Reset the passfail count. */
	u->passfail = 0;

	/* Set the SRA flag if SecureSRA is off */
	if (securesra_on == FALSE)
	    if (can_sra (hni->nick))
	    {
		u->flags |= UF_SRA;

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

	/* SRA-specific stuff */
	if (is_csop (u) && is_oper (u))
	{
	    send_cmd (s_NickServ, "%s %s +ra", me.token ? "n" : "SVSMODE", u->nick);
	    u->mode |= (UMODE_r | UMODE_a);
	}
	else
	{
	    send_cmd (s_NickServ, "%s %s +r", me.token ? "n" : "SVSMODE", u->nick);
	    u->mode |= UMODE_r;
	}

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

	notice (s_NickServ, u->nick, RPL_PASS_ACCEPTED, ni->nick);

	snoop (s_NickServ, "NS:IDENTIFY: %s %s!%s@%s", ni->nick, u->nick, 
	    u->user, u->host);

	/* Check for waiting verifies */
	if (is_csop (u))
	    check_verifies (u);

	/* Tell them about new news or memos */
	if (memoserv_on == TRUE)
	{
	    check_memos (u);
	    check_news (u);
	}

	/* Lastly, go through their channels and op them in any they have access
	   in, unless NEVEROP is on. This isn't really NESSECARY, but it's one of
	   those nice little touches that makes everyone happy ;)
	 */
	if (!(ni->flags & NF_NEVEROP))
	{
	    ChanInfo *ci;
	    int ulev = 0;

	    for (c = u->chans; c; c = c->next)
		for (cu = c->chan->users; cu; cu = cu->next)
		    if (!stricmp (cu->user->nick, u->nick))
		    {
			ci = cs_findchan (c->chan->name);

			if (ci)
			    ulev = get_access (u, ci);
			else
			    continue;

			/* VOP? */
			if (ulev == VOP && !(cu->mode & CMODE_v))
			{
			    send_cmode (s_ChanServ, c->chan->name, "+v", u->nick);
			    cu->mode |= CMODE_v;
			}

			if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
			{
			    /* HOP? */
			    if (ulev == HOP && !(cu->mode & CMODE_h))
			    {
				send_cmode (s_ChanServ, c->chan->name, "+h", u->nick);
				cu->mode |= CMODE_h;
			    }
			}

		 	/* AOP or higher? */
			if (ulev >= AOP && !(cu->mode & CMODE_o))
			{
			    send_cmode (s_ChanServ, c->chan->name, "+o", u->nick);
			    cu->mode |= CMODE_o;
			}
		    }
	}
    }
    else
	passfail (s_NickServ, ni->nick, "NS:IDENTIFY:BP:", u);
}

/* Toggle various NickServ settings */
static void do_set (User *u)
{
    NickInfo *ni;
    char *option = strtok (NULL, " ");
    char *param = strtok (NULL, "");

    /* Do we have all needed info? */
    if (!option || !param)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "SET Option Parameters");
	errmoreinfo (s_NickServ, u->nick, "SET");
	return;
    }

    /* Make sure they're authed for a nick */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_NickServ, u->nick, RPL_NEED_IDENT, "SET");
	return;
    }

    if (ni->flags & NF_FROZEN)
    {
	notice (s_NickServ, u->nick, NS_NICK_FROZEN, ni->nick);
	return;
    }

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

    /* Toggle ENFORCE? */
    if (!stricmp (option, "ENFORCE"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ni->flags & NF_ENFORCE)
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "Enforce", "on");
		return;
	    }

	    ni->flags |= NF_ENFORCE;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "Enforce", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ni->flags & NF_ENFORCE))
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "Enforce", "off");
		return;
	    }

	    ni->flags &= ~NF_ENFORCE;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "Enforce", "off");
	    return;
	}

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

    /* Toggle SECURE? */
    if (!stricmp (option, "SECURE"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ni->flags & NF_SECURE)
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "Secure", "on");
		return;
	    }

	    ni->flags |= NF_SECURE;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "Secure", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ni->flags & NF_SECURE))
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "Secure", "off");
		return;
	    }

	    ni->flags &= ~NF_SECURE;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "Secure", "off");
	    return;
	}

	else
	{
	    notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    if (showmail_on == FALSE && email_on == TRUE)
    {
	/* Toggle HIDEMAIL? */
	if (!stricmp (option, "HIDEMAIL"))
	{
	    if (!stricmp (param, "ON"))
	    {
		if (ni->flags & NF_HIDEMAIL)
		{
		    notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "HideMail", "on");
		    return;
		}

		ni->flags &= ~NF_SHOWMAIL;
		ni->flags |= NF_HIDEMAIL;
		notice (s_NickServ, u->nick, NS_OPTION_NOW, "HideMail", "on");
		return;
	    }

	    if (!stricmp (param, "OFF"))
	    {
		if (!(ni->flags & NF_HIDEMAIL))
		{
		    notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "HideMail", "off");
		    return;
		}

		ni->flags &= ~NF_HIDEMAIL;
		notice (s_NickServ, u->nick, NS_OPTION_NOW, "HideMail", "off");
		return;
	    }

	    else
	    {
		notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		    haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		    haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		    haveserv_on == TRUE ? "" : securitysetting == 1 ?
		    me.name : "");
		return;
	    }
	}
    }

    if (showmail_on == TRUE && email_on == TRUE)
    {
	/* Toggle SHOWMAIL? */
	if (!stricmp (option, "SHOWMAIL"))
	{
	    if (!stricmp (param, "ON"))
	    {
		if (ni->flags & NF_SHOWMAIL)
		{
		    notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "ShowMail", "on");
		    return;
		}

		ni->flags &= ~NF_HIDEMAIL;
		ni->flags |= NF_SHOWMAIL;
		notice (s_NickServ, u->nick, NS_OPTION_NOW, "ShowMail", "on");
		return;
	    }

	    if (!stricmp (param, "OFF"))
	    {
		if (!(ni->flags & NF_SHOWMAIL))
		{
		    notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "ShowMail", "off");
		    return;
		}

		ni->flags &= ~NF_SHOWMAIL;
		notice (s_NickServ, u->nick, NS_OPTION_NOW, "ShowMail", "off");
		return;
	    }

	    else
	    {
		notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		    haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		    haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		    haveserv_on == TRUE ? "" : securitysetting == 1 ?
		    me.name : "");
		return;
	    }
	}
    }

    /* Toggle NEVEROP? */
    if (!stricmp (option, "NEVEROP"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ni->flags & NF_NEVEROP)
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "NeverOp", "on");
		return;
	    }

	    ni->flags |= NF_NEVEROP;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "NeverOp", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ni->flags & NF_NEVEROP))
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "NeverOp", "off");
		return;
	    }

	    ni->flags &= ~NF_NEVEROP;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "NeverOp", "off");
	    return;
	}

	else
	{
	    notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    /* Toggle NOOP? */
    if (!stricmp (option, "NOOP"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ni->flags & NF_NOOP)
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "NoOp", "on");
		return;
	    }

	    ni->flags |= NF_NOOP;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "NoOp", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ni->flags & NF_NOOP))
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "NoOp", "off");
		return;
	    }

	    ni->flags &= ~NF_NOOP;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "NoOp", "off");
	    return;
	}

	else
	{
	    notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
 		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    /* Toggle NOSUCCESSOR? */
    if (!stricmp (option, "NOSUCCESSOR"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ni->flags & NF_NOSUCCESSOR)
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "NoSuccessor", "on");
		return;
	    }

	    ni->flags |= NF_NOSUCCESSOR;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "NoSuccessor", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ni->flags & NF_NOSUCCESSOR))
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "NoSuccessor", "off");
		return;
	    }

	    ni->flags &= ~NF_NOSUCCESSOR;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "NoSuccessor", "off");
	    return;
	}

	else
	{
	    notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    if (privmsg_on == FALSE)
    {
	/* Toggle PRIVMSG? */
	if (!stricmp (option, "PRIVMSG"))
	{
	    if (!stricmp (param, "ON"))
	    {
		if (ni->flags & NF_PRIVMSG)
		{
		    notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "PrivMSG", "on");
		    return;
		}

		ni->flags &= ~NF_NOTICE;
		ni->flags |= NF_PRIVMSG;
		notice (s_NickServ, u->nick, NS_OPTION_NOW, "PrivMSG", "on");
		return;
	    }

	    if (!stricmp (param, "OFF"))
	    {
		if (!(ni->flags & NF_PRIVMSG))
		{
		    notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "PrivMSG", "off");
		    return;
		}

		ni->flags &= ~NF_PRIVMSG;
		notice (s_NickServ, u->nick, NS_OPTION_NOW, "PrivMSG", "off");
		return;
	    }

	    else
	    {
		notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		    haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		    haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		    haveserv_on == TRUE ? "" : securitysetting == 1 ?
		    me.name : "");
		return;
	    }
	}
    }

    if (privmsg_on == TRUE)
    {
	/* Toggle NOTICE? */
	if (!stricmp (option, "NOTICE"))
	{
	    if (!stricmp (param, "ON"))
	    {
		if (ni->flags & NF_NOTICE)
		{
		    notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "Notice", "on");
		    return;
		}

		ni->flags &= ~NF_PRIVMSG;
		ni->flags |= NF_NOTICE;
		notice (s_NickServ, u->nick, NS_OPTION_NOW, "Notice", "on");
		return;
	    }

	    if (!stricmp (param, "OFF"))
	    {
		if (!(ni->flags & NF_NOTICE))
		{
		    notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "Notice", "off");
		    return;
		}

		ni->flags &= ~NF_NOTICE;
		notice (s_NickServ, u->nick, NS_OPTION_NOW, "Notice", "off");
		return;
	    }

	    else
	    {
		notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		    haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		    haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		    haveserv_on == TRUE ? "" : securitysetting == 1 ?
		    me.name : "");
		return;
	    }
	}
    }

    /* Toggle PRIVATE? */
    if (!stricmp (option, "PRIVATE"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ni->flags & NF_PRIVATE)
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "Private", "on");
		return;
	    }

	    ni->flags |= NF_PRIVATE;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "Private", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ni->flags & NF_PRIVATE))
	    {
		notice (s_NickServ, u->nick, NS_OPTION_ALREADY, "Private", "off");
		return;
	    }

	    ni->flags &= ~NF_PRIVATE;
	    notice (s_NickServ, u->nick, NS_OPTION_NOW, "Private", "off");
	    return;
	}

	else
	{
	    notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    /* Change password? */
    if (!stricmp (option, "PASS") || !stricmp (option, "PASSWD") ||
	!stricmp (option, "PASSWORD"))
    {
	int reason = 0;
	char *pass1 = strtok (param, " ");
	char *pass2 = strtok (NULL, " ");

	if (!pass2)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX,
		"SET PASS Password Password");
	    errmoreinfo (s_NickServ, u->nick, "SET PASS");
	    return;
	}

	/* Password sanity checks */
	if (strlen (pass1) < 5)
	    reason = 1;
	if (strlen (pass1) > PASSWDLEN)
	    reason = 2;
	if (!stricmp (u->nick, pass1))
	    reason = 3;
	if (!stricmp ("password", pass1))
	    reason = 4;
	if (strcmp (pass1, pass2))
	    reason = 5;

	if (reason)
	{
	    notice (s_NickServ, u->nick, RPL_BETTER_PASS);

	    if (reason == 1)
		notice (s_NickServ, u->nick, RPL_PASS_REJECTED_SHORT);
	    if (reason == 2)
		notice (s_NickServ, u->nick, RPL_PASS_REJECTED_LONG);
	    if (reason == 3)
		notice (s_NickServ, u->nick, RPL_PASS_REJECTED_NICK);
	    if (reason == 4)
		notice (s_NickServ, u->nick, RPL_PASS_REJECTED_WEAK);
	    if (reason == 5)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_DIFF);

	    errmoreinfo (s_NickServ, u->nick, "SET PASS");
	    return;
	}

	/* Passed checks, set it */
	strscpy (ni->pass, pass1, PASSWDLEN);
	notice (s_NickServ, u->nick, RPL_PASSWORD_SET, ni->pass);

	/* Log this. This might be an attempt to steal a nick.. */
	log ("NS:SET:PASS: %s %s!%s@%s", ni->nick, u->nick, u->user, u->host);
	return;
    }

    /* Change E-Mail? */
    if (email_on == TRUE)
    {
	if (!stricmp (option, "EMAIL"))
	{
	    if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	    {
		if (ni->email)
		    free (ni->email);

		ni->email = NULL;

		notice (s_NickServ, u->nick, RPL_REMOVED, "Your \2E-Mail\2");
		return;
	    }
	    else
	    {
		/* Check if we're waiting for confirmation on an E-Mail change. */
		if (ni->temp && ni->key && (nsregistertype == 1 || nsregistertype == 3 ||
		    nsregistertype == 6))
		{
		    if (atoi (param) == ni->key)
		    {
			free (ni->email);
			ni->email = sstrdup (ni->temp);
			free (ni->temp);
			ni->temp = NULL;
			ni->temptime = 0;
			ni->key = 0;

			notice (s_NickServ, u->nick, RPL_ADDED, "Your \2E-Mail\2", ni->email);

			return;
		    }
		}

		if (validemail (param, u->nick))
		{
		    /* If we're using E-Mail AUTH register types, we'll confirm
		       this new address with an email. If we get the right code,
		       we'll change their E-Mail.
		    */
		    if (nsregistertype == 1 || nsregistertype == 3 ||
			nsregistertype == 6)
		    {
			/* Is this email being abused? */
			if (check_email (param))
			{
			    notice (s_NickServ, u->nick, RPL_EMAIL_USED);
			    return;
			}

			/* Make a key */
			ni->key = makekey ();

			/* Put the E-Mail they want into temp */
			if (ni->temp)
			    free (ni->temp);

			ni->temp = sstrdup (param);
			ni->temptime = time (NULL);

			/* Send the email */
			sendemail (ni->nick, itoa (ni->key), 6);

			/* Tell them to expect an email .. */
			notice (s_NickServ, u->nick, NS_CONFIRM_EMAIL, ni->temp);
			notice (s_NickServ, u->nick, NS_CONFIRM_CHANGE);
			notice (s_NickServ, u->nick, NS_CONFIRM_TIMEOUT,
			    duration (tempexpire, 2));
		    }
		    else
		    {
			if (ni->email)
			    free (ni->email);

			ni->email = sstrdup (param);

			notice (s_NickServ, u->nick, RPL_ADDED, "Your \2E-Mail\2", param);
		    }
		}
		else
		    notice (s_NickServ, u->nick, RPL_INVALID, "E-Mail", "");

		return;
	    }
	}
    }

    /* Change URL? */
    if (url_on == TRUE)
    {
	if (!stricmp (option, "URL"))
	{
	    if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	    {
		if (ni->url)
		    free (ni->url);
		ni->url = NULL;
		notice (s_NickServ, u->nick, RPL_REMOVED, "Your \2URL\2");
		return;
	    }
	    else
	    {
		if (strstr (param, "://"))
		{
		    if (ni->url)
			free (ni->url);
		    ni->url = sstrdup (param);
		    notice (s_NickServ, u->nick, RPL_ADDED, "Your \2URL\2", param);
		}
		else
		    notice (s_NickServ, u->nick, RPL_INVALID, "URL", "");

		return;
	    }
	}
    }

    /* Change UIN? */
    if (uin_on == TRUE)
    {
	if (!stricmp (option, "UIN"))
	{
	    if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	    {
		ni->uin = 0;
		notice (s_NickServ, u->nick, RPL_REMOVED, "Your \2UIN\2");
		return;
	    }
	    else
	    {
		if (atoi (param) > 1001 && atoi (param) < 2147483646)
		{
		    ni->uin = atoi (param);
		    notice (s_NickServ, u->nick, RPL_ADDED, "Your \2UIN\2", param);
		}
		else
		    notice (s_NickServ, u->nick, RPL_INVALID, "UIN", "");

		return;
	    }
	}
    }

    /* Change Zone? */
    if (!stricmp (option, "ZONE"))
    {
	if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	{
	    ni->zone = 0;
	    notice (s_NickServ, u->nick, RPL_REMOVED, "Your \2ZONE\2");
	    return;
	}
	else
	{
	    Zone *zone;
	    int zones = 0;

	    /* Are they requesting a specific timezone by number? */
	    if ((zone = findzone (atoi (param))))
	    {
		ni->zone = zone->zindex;

		notice (s_NickServ, u->nick, RPL_ADDED, "Your \2ZONE\2", zone->desc);
		return;
	    }

	    /* Search through the zones and see how many match their query. */
	    for (zone = zonelist; zone; zone = zone->next)
		if (!stricmp (zone->name, param))
		    zones++;

	    if (!zones)
	    {
		notice (s_NickServ, u->nick, RPL_INVALID, "TimeZone", "");
		return;
	    }

	    /* If we only got one match, set it to that one, otherwise, tell them the
	       numbers of the ones that match so they can get a specific one.
	     */
	    if (zones > 1)
	    {
		notice (s_NickServ, u->nick, NS_ZONE_MULTIPLE, strupper (param));

		for (zone = zonelist; zone; zone = zone->next)
		    if (!stricmp (zone->name, param))
			notice (s_NickServ, u->nick, NS_ZONE_LIST, zone->zindex,
			    zone->name, zone->noffset, zone->desc);

		notice (s_NickServ, u->nick, NS_ZONE_SET, haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
		    haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		    haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");

		return;
	    }

	    /* Go through again and set it. */
	    for (zone = zonelist; zone; zone = zone->next)
		if (!stricmp (zone->name, param))
		{
		    notice (s_NickServ, u->nick, RPL_ADDED, "Your \2ZONE\2", zone->desc);
		    ni->zone = zone->zindex;
		    break;
		}

	    return;
	}
    }

    /* Change Name? */
    if (name_on == TRUE)
    {
	/* Change NAME? */
	if (!stricmp (option, "NAME"))
	{
	    if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	    {
		free (ni->name);
		ni->name = NULL;
		notice (s_NickServ, u->nick, RPL_REMOVED, "Your \2Name\2");
		return;
	    }
	    else
	    {
		if (strlen (param) > 100)
		    notice (s_NickServ, u->nick, RPL_LENGTH, "name", 100);
		else
		{
		    free (ni->name);
		    ni->name = sstrdup (param);
		    notice (s_NickServ, u->nick, RPL_ADDED, "Your \2Name\2",
			param);
		}

		return;
	    }
	}
    }

    /* Change Location? */
    if (location_on == TRUE)
    {
	/* Change LOCATION? */
	if (!stricmp (option, "LOCATION"))
	{
	    if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	    {
		free (ni->location);
		ni->location = NULL;
		notice (s_NickServ, u->nick, RPL_REMOVED, "Your \2Location\2");
		return;
	    }
	    else
	    {
		if (strlen (param) > 100)
		    notice (s_NickServ, u->nick, RPL_LENGTH, "location", 100);
		else
		{
		    free (ni->location);
		    ni->location = sstrdup (param);
		    notice (s_NickServ, u->nick, RPL_ADDED, "Your \2Location\2",
			param);
		}

		return;
	    }
	}
    }

    /* Change Sex? (No comment ..) */
    if (sex_on == TRUE)
    {
	/* Change SEX? */
	if (!stricmp (option, "SEX"))
	{
	    if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	    {
		free (ni->sex);
		ni->sex = NULL;
		notice (s_NickServ, u->nick, RPL_REMOVED, "Your \2Sex\2");
		return;
	    }

	    else if (!stricmp (param, "MALE"))
	    {
		free (ni->sex);
		ni->sex = sstrdup ("Male");
		notice (s_NickServ, u->nick, RPL_ADDED, "Your \2Sex\2", "Male");
		return;
	    }

	    else if (!stricmp (param, "FEMALE"))
	    {
		free (ni->sex);
		ni->sex = sstrdup ("Female");
		notice (s_NickServ, u->nick, RPL_ADDED, "Your \2Sex\2",
		    "Female");
		return;
	    }
	    else
	    {
		notice (s_NickServ, u->nick, RPL_INVALID, "Sex",
		    " SEX must be either MALE or FEMALE.");
		return;
	    }
	}
    }

    /* Change Age? */
    if (age_on == TRUE)
    {
	/* Change AGE? */
	if (!stricmp (option, "AGE"))
	{
	    if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	    {
		ni->age = 0;
		notice (s_NickServ, u->nick, RPL_REMOVED, "Your \2Age\2");
		return;
	    }
	    else
	    {
		if (atoi (param) >= 5 && atoi (param) <= 100)
		{
		    ni->age = atoi (param);
		    notice (s_NickServ, u->nick, RPL_ADDED, "Your \2Age\2",
			param);
		}
		else
		    notice (s_NickServ, u->nick, RPL_INVALID, "Age", "");

		return;
	    }
	}
    }

    /* Don't know what they want .. */
    notice (s_NickServ, u->nick, RPL_UNKNOWN_OPTION, strupper (option),
	haveserv_on == TRUE ? "" : "MSG ", s_NickServ,
	haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
}

/* Add/remove/list access list entries */
static void do_access (User *u)
{
    NickInfo *ni;
    char **acclist;
    int i, nonwild = 0;
    char *option = strtok (NULL, " ");
    char *param = strtok (NULL, "");

    /* Do we have all needed info? */
    if (!option)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "ACCESS ADD|DEL|LIST|CURRENT [Mask]");
	errmoreinfo (s_NickServ, u->nick, "ACCESS");
	return;
    }

    /* Make sure they're authed for a nick */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_NickServ, u->nick, RPL_NEED_IDENT, "ACCESS");
	return;
    }

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

    /* Add? */
    if (!stricmp (option, "ADD"))
    {
	if (!param)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX, "ACCESS ADD Mask");
	    errmoreinfo (s_NickServ, u->nick, "ACCESS");
	    return;
	}

	if (!strchr (param, '@'))
	{
	    notice (s_NickServ, u->nick, RPL_FORMATTING, "user@host");
	    errmoreinfo (s_NickServ, u->nick, "ACCESS");
	    return;
	}

	for (i = 0; i < strlen (param); i++)
	    if (!(param[i] == '*' || param[i] == '?' || param[i] == '@' || param[i] == '.'))
		nonwild++;
                
	if (nonwild < nonwildreq)
	{
	    notice (s_NickServ, u->nick, RPL_TOOMANYWILD, nonwildreq);
	    return;
	}

	for (acclist = ni->access, i = 0; i < ni->accesscnt; acclist++, i++)
	{
	    if (match_wild_nocase (param, *acclist))
	    {
		notice (s_NickServ, u->nick, RPL_LIST_THERE, param,
		    "your", "\2ACCESS\2");
		return;
	    }
	}

	++ni->accesscnt;
	ni->access = srealloc (ni->access, sizeof (char *) *ni->accesscnt);
	ni->access[ni->accesscnt - 1] = sstrdup (param);

	notice (s_NickServ, u->nick, NS_ACCESS_ADDED, param);
	return;
    }

    /* Del? */
    if (!stricmp (option, "DEL"))
    {
	if (!param)
	{
	    notice (s_NickServ, u->nick, RPL_SYNTAX, "ACCESS DEL Mask|Num");
	    errmoreinfo (s_NickServ, u->nick, "ACCESS");
	    return;
	}

	if (strspn (param, "1234567890") == strlen (param) &&
	    (i = atoi (param)) > 0 && i <= ni->accesscnt)
	{
	    --i;
	    acclist = &ni->access[i];
	}
	else
	{
	    for (acclist = ni->access, i = 0; i < ni->accesscnt; ++acclist, ++i)
		if (!strcmp (*acclist, param))
		    break;

	    if (i == ni->accesscnt)
		for (acclist = ni->access, i = 0; i < ni->accesscnt;
		    ++acclist, ++i)
		    if (!stricmp (*acclist, param))
			break;

	    if (i == ni->accesscnt)
            {
		notice (s_NickServ, u->nick, RPL_LIST_NOT_THERE, param,
		    "your \2ACCESS\2");
		return;
	    }
	}

	notice (s_NickServ, u->nick, NS_ACCESS_REMOVED, *acclist);

	if (*acclist)
	    free (*acclist);

	--ni->accesscnt;

	if (i < ni->accesscnt)
	    bcopy (acclist + 1, acclist, (ni->accesscnt - i) * sizeof (char *));

	if (ni->accesscnt)
	    ni->access = srealloc (ni->access, ni->accesscnt * sizeof (char *));
	else
	{
	    if (ni->access)
		free (ni->access);
	    ni->access = NULL;
	}

	return;
    }

    /* Current? */
    if (!stricmp (option, "CURRENT"))
    {
	char *current = create_mask (u);

	for (acclist = ni->access, i = 0; i < ni->accesscnt; acclist++, i++)
	{
	    if (match_wild_nocase (current, *acclist))
	    {
		notice (s_NickServ, u->nick, RPL_LIST_THERE, current,
		    "your", "\2ACCESS\2");
		return;
	    }
	}

	++ni->accesscnt;
	ni->access = srealloc (ni->access, sizeof (char *) *ni->accesscnt);
	ni->access[ni->accesscnt - 1] = sstrdup (current);
	notice (s_NickServ, u->nick, NS_ACCESS_ADDED, current);
	return;
    }

    /* List? */
    if (!stricmp (option, "LIST"))
    {
	if (!ni->accesscnt)
	{
	    notice (s_NickServ, u->nick, RPL_LIST_EMPTY, "Your", "", "ACCESS");
	    return;
	}

	notice (s_NickServ, u->nick, NS_ACCESS_LIST, ni->nick);
	notice (s_NickServ, u->nick, NS_ACCESS_LIST_HEADER);

	for (acclist = ni->access, i = 0; i < ni->accesscnt; acclist++, i++)
	{
	    if (param && !match_wild_nocase (param, *acclist))
		continue;

	    notice (s_NickServ, u->nick, "%-3d %s", i + 1, *acclist);
	}

	notice (s_NickServ, u->nick, NS_ACCESS_END, i);
	return;
    }

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

/* Recover a nickname. */
static void do_recover (User *u)
{
    NickInfo *ni, *hni;
    char *nick = strtok (NULL, " ");
    char *pass = strtok (NULL, " ");

    /* Do we have all needed info? */
    if (!nick || !pass)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "RECOVER Nickname Password");
	errmoreinfo (s_NickServ, u->nick, "RECOVER");
	return;
    }

    /* Don't let them do it on themselves. */
    if (!stricmp (nick, u->nick))
    {
	notice (s_NickServ, u->nick, NS_NOT_SELF, "RECOVER");
	return;
    }

    /* Is the nick registered? */
    if (!(ni = findnick (nick)))
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	return;
    }

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

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

    /* Check the passwords. */
    if (strcmp (pass, hni->pass))
    {
	passfail (s_NickServ, ni->nick, "NS:RECOVER:BP:", u);
	return;
    }

    /* Passwords check out, now either remove an enforcer or kill a user
       using the nick. */
    if (ni->status & NS_ENFORCED)
    {
	release (ni, 0);

	snoop (s_NickServ, "NS:RECOVER: %s %s!%s@%s", ni->nick, u->nick,
	    u->user, u->host);

	notice (s_NickServ, u->nick, NS_RECOVERED, ni->nick);
	return;
    }

    /* Not an enforcer.. is it a user thats online? */
    if (finduser (nick))
    {
	/* KILL them if RecoverKill is on. */
	if (recoverkill_on == TRUE)
	    kill_user (s_NickServ, ni->nick, NS_KILLED_ENFORCEMENT);
	else
	    collide (ni, 0, 1);

	snoop (s_NickServ, "NS:RECOVER: %s %s!%s@%s", ni->nick, u->nick,
	    u->user, u->host);

	notice (s_NickServ, u->nick, NS_RECOVERED, ni->nick);
	return;
    }
 
    /* If we get here, we found neither an enforcer or a user. Oh well. */
    notice (s_NickServ, u->nick, RPL_NOT_ONLINE, ni->nick);
}

/* Retrieve the password for a nickname */
static void do_getpass (User *u)
{
    NickInfo *ni, *hni;
    char *nick = strtok (NULL, " ");

    /* Do we have all needed info? */
    if (!nick)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "GETPASS Nickname");
	errmoreinfo (s_NickServ, u->nick, "GETPASS");
	return;
    }

    /* Is the nick registered? */
    if (!(ni = findnick (nick)))
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	return;
    }

    if (ni->flags & NF_WAITAUTH)
    {
	/* If this nick is awaiting auth, get the key. */
	notice (s_NickServ, u->nick, RPL_GETPASS, "key", ni->nick, "", "", "",
	    itoa (ni->key));

	globops (s_NickServ, RPL_PASS_GLOBAL, u->nick, "GETPASS", ni->nick,
	    "", "", "");

	snoop (s_NickServ, "NS:GETPASS: %s %s%s%s%s", u->nick, ni->nick,
	    "", "", "");

	log ("NS:GETPASS: %s %s%s%s%s", u->nick, ni->nick, "", "", "");

	return;
    }   

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

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

    /* Check if it's marked */
    if (hni->flags & NF_MARKED && !is_sra (u))
    {
	notice (s_NickServ, u->nick, NS_NICK_MARKED, hni->nick, "GET");
	return;
    }

    notice (s_NickServ, u->nick, RPL_GETPASS, "password", ni->nick, ni->host ?
	" (" : "", ni->host ? ni->host : "", ni->host ? ")" : "", ni->host ?
	hni->pass : ni->pass);

    globops (s_NickServ, RPL_PASS_GLOBAL, u->nick, "GETPASS", ni->nick,
	ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");

    snoop (s_NickServ, "NS:GETPASS: %s %s%s%s%s", u->nick, ni->nick,
	ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");

    log ("NS:GETPASS: %s %s%s%s%s", u->nick, ni->nick,
        ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");
}

/* Set the password for a nickname */
static void do_setpass (User *u)
{
    NickInfo *ni, *hni;
    char *nick = strtok (NULL, " ");
    char *pass1 = strtok (NULL, " ");
    char *pass2 = strtok (NULL, " ");

    /* Do we have all needed info? */
    if (!nick || !pass1 || !pass2)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX,
	    "SETPASS Nickname NewPass NewPass");
	errmoreinfo (s_NickServ, u->nick, "SETPASS");
	return;
    }

    /* Is the nick registered? */
    if (!(ni = findnick (nick)))
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	return;
    }

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

    if (strcmp (pass1, pass2))
    {
	notice (s_NickServ, u->nick, RPL_PASS_DONT_MATCH);
	return;
    }

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

	/* Check if it's marked */
	if (hni->flags & NF_MARKED && !is_sra (u))
	{
	    notice (s_NickServ, u->nick, NS_NICK_MARKED, hni->nick, "SET");
	    return;
 	}

	strscpy (hni->pass, pass1, sizeof (hni->pass));
    }
    else
    {
	hni = ni;

	/* Check if it's marked */
	if (hni->flags & NF_MARKED && !is_sra (u))
	{
	    notice (s_NickServ, u->nick, NS_NICK_MARKED, hni->nick, "SET");
	    return;
	}

	strscpy (ni->pass, pass1, sizeof (ni->pass));
    }

    notice (s_NickServ, u->nick, RPL_SETPASS, ni->nick, ni->host ? " (" : "",
	ni->host ? ni->host : "", ni->host ? ")" : "", ni->host ? hni->pass :
	ni->pass);

    globops (s_NickServ, RPL_PASS_GLOBAL, u->nick, "SETPASS", ni->nick,
	ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");

    snoop (s_NickServ, "NS:SETPASS: %s %s%s%s%s", u->nick, ni->nick,
	ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");

    log ("NS:SETPASS: %s %s%s%s%s", u->nick, ni->nick,
        ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");
}

/* Email the password for a nickname */
static void do_sendpass (User *u)
{
    NickInfo *ni, *hni;
    char *nick = strtok (NULL, " ");

    /* Do we have all needed info? */
    if (!nick)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "SENDPASS Nickname");
	errmoreinfo (s_NickServ, u->nick, "SENDPASS");
	return;
    }

    /* Is the nick registered? */
    if (!(ni = findnick (nick)))
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	return;
    }

    if (ni->flags & NF_WAITAUTH)
    {
	/* If this nick is awaiting auth, generate a new key and re issue it. */
	ni->key = makekey ();
	sendemail (ni->nick, itoa (ni->key), 1);

	notice (s_NickServ, u->nick, RPL_KEY_RESENT, ni->nick);

	globops (s_NickServ, RPL_PASS_GLOBAL, u->nick, "SENDPASS", ni->nick,
	    ni->host ? " (" : "", ni->host ? ni->host : "", ni->host ? ")" : "");

	snoop (s_NickServ, "NS:SENDPASS: %s %s%s%s%s", u->nick, ni->nick,
	    ni->host ? " (" : "", ni->host ? ni->host : "", ni->host ? ")" : "");

	log ("NS:SENDPASS: %s %s%s%s%s", u->nick, ni->nick,
	    ni->host ? " (" : "", ni->host ? ni->host : "", ni->host ? ")" : "");

	return;
    }

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

	/* Check if it's marked */
	if (hni->flags & NF_MARKED && !is_sra (u))
	{
	    notice (s_NickServ, u->nick, NS_NICK_MARKED, hni->nick, "SEND");
	    return;
	}

	if (!hni->email)
	{
	    notice (s_NickServ, u->nick, NS_CANT_SENDPASS, hni->nick);
	    return;
	}
    }
    else
    {
	hni = ni;

	/* Check if it's marked */
	if (hni->flags & NF_MARKED && !is_sra (u))
	{
	    notice (s_NickServ, u->nick, NS_NICK_MARKED, hni->nick, "SEND");
	    return;
	}

	if (!ni->email)
	{
	    notice (s_NickServ, u->nick, NS_CANT_SENDPASS, ni->nick);
	    return;
	}
    }

    notice (s_NickServ, u->nick, RPL_SENDPASS, ni->nick, ni->host ? " (" : "",
	ni->host ? ni->host : "", ni->host ? ")" : "", ni->host ? hni->email :
	ni->email);

    globops (s_NickServ, RPL_PASS_GLOBAL, u->nick, "SENDPASS", ni->nick,
	ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");

    snoop (s_NickServ, "NS:SENDPASS: %s %s%s%s%s", u->nick, ni->nick,
	ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");

    log ("NS:SENDPASS: %s %s%s%s%s", u->nick, ni->nick,
        ni->host ? " (" : "", ni->host ? hni->nick : "", ni->host ? ")" : "");

    sendemail (ni->host ? hni->nick : ni->nick, ni->email, 2);
}

/* Freeze a nickname */
static void do_freeze (User *u)
{
    NickInfo *ni, *hni, *tni;
    User *fu;
    char *nick = strtok (NULL, " ");
    char *reason = strtok (NULL, "");
    char **links;
    int i;

    /* Do we have all needed info? */
    if (!nick)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "FREEZE NickName [Reason]");
	errmoreinfo (s_NickServ, u->nick, "FREEZE");
	return;
    }

    /* Is the nick registered? */
    if (!(ni = findnick (nick)))
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	return;
    }

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

    /* Freeze is effective on the host nick, not just one linked nick. */
    if (ni->host)
	hni = findnick (ni->host);
    else
	hni = ni;

    /* Is it already frozen? */
    if (hni->flags & NF_FROZEN)
    {
	notice (s_NickServ, u->nick, NS_ALREADY_FROZEN, hni->nick);
	return;
    }

    hni->flags |= NF_FROZEN;

    /* We'll use the lastseen timestamp for a freeze time. */
    hni->lastseen = time (NULL);

    /* Use temp field for freeze setter. This nick won't be used
       when frozen, so this is safe.
     */
    if (hni->temp)
	free (hni->temp);

    hni->temp = sstrdup (u->nick);

    if (reason)
    {
	if (hni->freezereason)
	    free (hni->freezereason);

	hni->freezereason = sstrdup (reason);
    }

    /* See if this nick is in use. Deal with it if it is. */
    fu = finduser (hni->nick);

    if (fu)
    {
	notice (s_NickServ, fu->nick, NS_INFO_FROZEN);

	if (hni->freezereason)
	    notice (s_NickServ, fu->nick, NS_FROZEN_REASON, hni->freezereason);

	if (collidetype != 6)
	    notice (s_NickServ, fu->nick, NS_FROZEN_CHANGED);
	else
	    notice (s_NickServ, fu->nick, NS_FROZEN_KILLED);

	collide (hni, 0, 0);
    }

    /* Go through the nicks linked to this host and deal with any
       which are in use.
     */
    for (links = hni->links, i = 0; i < hni->linkcnt; links++, i++)
    {
	fu = finduser (*links);

	if (fu)
	{
	    notice (s_NickServ, fu->nick, NS_INFO_FROZEN);

	    if (hni->freezereason)
		notice (s_NickServ, fu->nick, NS_FROZEN_REASON, hni->freezereason);

	    if (collidetype != 6)
		notice (s_NickServ, fu->nick, NS_FROZEN_CHANGED);
	    else
		notice (s_NickServ, fu->nick, NS_FROZEN_KILLED);

	    tni = findnick (fu->nick);

	    collide (tni, 0, 0);
	}
    }

    notice (s_NickServ, u->nick, NS_FROZEN, hni->nick);
    log ("NS:FREEZE: %s %s", u->nick, hni->nick);
    snoop (s_NickServ, "NS:FREEZE: %s froze %s", u->nick, hni->nick);
}

/* Undo a freeze on a nickname */
static void do_unfreeze (User *u)
{
    NickInfo *ni, *hni;
    char *nick = strtok (NULL, " ");

    /* Do we have all needed info? */
    if (!nick)
    {
	notice (s_NickServ, u->nick, RPL_SYNTAX, "UNFREEZE NickName");
	errmoreinfo (s_NickServ, u->nick, "UNFREEZE");
	return;
    }

    /* Is the nick registered? */
    if (!(ni = findnick (nick)))
    {
	notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	return;
    }

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

    /* Freeze is effective on the host nick, not just one linked nick. */
    if (ni->host)
    {
	hni = findnick (ni->host);

	/* Is it frozen? */
	if (!(hni->flags & NF_FROZEN))
	{
	    notice (s_NickServ, u->nick, NS_NOT_FROZEN, hni->nick);
	    return;
	}

	hni->flags &= ~NF_FROZEN;

	free (hni->temp);
	hni->temp = NULL;

	/* Set the last used timestamp to now. We don't want nicks expiring because
	   they've been frozen for more than expiretime..
	 */
	hni->lastseen = time (NULL);

	if (hni->freezereason)
	{
	    free (hni->freezereason);

	    hni->freezereason = NULL;
	}

	notice (s_NickServ, u->nick, NS_UNFROZEN_HOST, ni->nick, hni->nick);
	log ("NS:UNFREEZE: %s %s (%s)", u->nick, hni->nick, ni->nick);
	snoop (s_NickServ, "NS:UNFREEZE: %s unfroze %s (%s)", u->nick, hni->nick, ni->nick);
    }
    else
    {
	/* Is it frozen? */
	if (!(ni->flags & NF_FROZEN))
	{
	    notice (s_NickServ, u->nick, NS_NOT_FROZEN, ni->nick);
	    return;
	}

	ni->flags &= ~NF_FROZEN;

	free (ni->temp);
	ni->temp = NULL;

	/* Set the last used timestamp to now. We don't want nicks expiring because
	   they've been frozen for more than expiretime..
	 */
	ni->lastseen = time (NULL);

	if (ni->freezereason)
	{
	    free (ni->freezereason);

	    ni->freezereason = NULL;
	}

	notice (s_NickServ, u->nick, NS_UNFROZEN, ni->nick);
	log ("NS:UNFREEZE: %s %s", u->nick, ni->nick);
	snoop (s_NickServ, "NS:UNFREEZE: %s unfroze %s", u->nick, ni->nick);
    }
}

/* Load the NickServ DB from disk */
void load_ns_dbase ()
{
    FILE *f = fopen (NICKSERV_DB, "r");
    NickInfo *ni = NULL;
    MemoInfo *mi;
    Memo *m;
    char *item, *s, dBuf[2048], backup[2048];
    int dolog = 0, nin = 0, min = 0, lin = 0, convert = 1, dbend = 0;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

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

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (2, "No %s found! ", NICKSERV_DB);
#else
	log ("NS:DB: No %s found!", NICKSERV_DB);
#endif
	dolog = 1;

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

	if ((f = fopen (backup, "r")))
	{
#ifdef HAVE_GETTIMEOFDAY
	    log_sameline (0, "Recovering %s.save.", NICKSERV_DB);
#else
	    log ("NS:DB: Recovering %s.save.", NICKSERV_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, "NV"))
	{
	    int dbver = 0;

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

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

	/* Services Stamp */
	if (!strcmp (item, "SS"))
	{
	    /* Add 1000 to the stamp each time we load. This helps to
	       prevent duplicate stamps if theres any rollback on the
	       dbs.
	     */
	    if ((s = strtok (NULL, "\n"))) { sstamp = atoi (s) + 1000; }
	    continue;
	}

	/* NickInfo */
	if (!strcmp (item, "NI"))
	{
	    NickInfo **list;

	    ni = scalloc (sizeof (NickInfo), 1);
	    mi = &ni->memos;

	    if ((s = strtok (NULL, " "))) { strscpy (ni->nick, s, NICKLEN); } else { continue; }
	    if ((s = strtok (NULL, " "))) { strscpy (ni->pass, s, PASSWDLEN); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ni->registered = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ni->lastseen = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ni->timestamp = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ni->sstamp = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ni->flags = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { mi->memolimit = atoi (s); } else { continue; }

	    if (ns.version > 2 && !convert)
	    {
		if ((s = strtok (NULL, " "))) { ni->zone = atoi (s); } else { continue; }
	    }

	    if ((s = strtok (NULL, " "))) { ni->key = atoi (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ni->usermask = sstrdup (s); } else { continue; }
	    if ((s = strtok (NULL, "\n"))) { ni->real = sstrdup (s); }

	    list = &nicklist[NSHASH(ni->nick)];
	    ni->next = *list;
	    if (*list)
		(*list)->prev = ni;
	    *list = ni;

	    nin++;

	    continue;
	}

	/* Freeze Reason */
	if (!strcmp (item, "FR"))
	{
	    if ((s = strtok (NULL, "\n"))) { ni->freezereason = sstrdup (s); }
	    continue;
	}

	/* E-Mail */
	if (email_on == TRUE)
	    if (!strcmp (item, "EM"))
	    {
		if ((s = strtok (NULL, "\n"))) { ni->email = sstrdup (s); }
		continue;
	    }

	/* Temp E-Mail */
	if (!strcmp (item, "TE"))
	{
	    if ((s = strtok (NULL, "\n"))) { ni->temp = sstrdup (s); }
	    continue;
	}

	/* Temp TimeStamp */
	if (!strcmp (item, "TT"))
	{
	    if ((s = strtok (NULL, "\n"))) { ni->temptime = atol (s); }
	    continue;
	}

	/* URL */
	if (url_on == TRUE)
	    if (!strcmp (item, "UR"))
	    {
		if ((s = strtok (NULL, "\n"))) { ni->url = sstrdup (s); }
		continue;
	    }

	/* UIN */
	if (uin_on == TRUE)
	    if (!strcmp (item, "UN"))
	    {
		if ((s = strtok (NULL, "\n"))) { ni->uin = atoi (s); }
		continue;
	    }

	/* Name */
	if (name_on == TRUE)
	    if (!strcmp (item, "NA"))
	    {
		if ((s = strtok (NULL, "\n"))) { ni->name = sstrdup (s); }
		continue;
	    }

	/* Age */
	if (age_on == TRUE)
	    if (!strcmp (item, "AG"))
	    {
		if ((s = strtok (NULL, "\n"))) { ni->age = atoi (s); }
		continue;
	    }

	/* Sex */
	if (sex_on == TRUE)
	    if (!strcmp (item, "SX"))
	    {
		if ((s = strtok (NULL, "\n"))) { ni->sex = sstrdup (s); }
		continue;
	    }

	/* Location */
	if (location_on == TRUE)
	    if (!strcmp (item, "LO"))
	    {
		if ((s = strtok (NULL, "\n"))) { ni->location = sstrdup (s); }
		continue;
	    }

	/* Access List */
	if (!strcmp (item, "AC"))
	{
	    char *acctmp;

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

	    if (!acctmp)
	    {
		if (debuglevel > 1)
		    debug ("Dropping invalid access list entry for %s", ni->nick);

		continue;
	    }

	    ++ni->accesscnt;
	    ni->access = srealloc (ni->access, sizeof (char *) *ni->accesscnt);
	    ni->access[ni->accesscnt - 1] = sstrdup (acctmp);

	    continue;
	}

	/* Links List */
	if (!strcmp (item, "LN"))
	{
	    NickInfo **list, *lni;
	    char *tmpchr;

	    if (ns.version > 1 && !convert)
	    {
		/* In the new format, links are listed on one line. We need to
		   go through this line one param at a time.
		 */
		while ((tmpchr = strtok (NULL, " ")))
		{
		    strip (tmpchr);

		    /* A bug in an earlier version allowed LINK to be used over and over.
		       Filter out any linked nicknames which are already registered.
		     */
		    if (!findnick (tmpchr))
		    {
			++ni->linkcnt;
			ni->links = srealloc (ni->links, sizeof (char *) *ni->linkcnt);
			ni->links[ni->linkcnt - 1] = sstrdup (tmpchr);

			lni = scalloc (sizeof (NickInfo), 1);

			strscpy (lni->nick, tmpchr, NICKLEN);

			lni->host = sstrdup (ni->nick);
			lni->flags = NF_LINKED;

			list = &nicklist[NSHASH(lni->nick)];
			lni->next = *list;

			if (*list)
			    (*list)->prev = lni;
			*list = lni;

			lin++;
		    }
		}

		continue;
	    }
	    else
	    {
		tmpchr = strtok (NULL, "\n");

		if (!tmpchr)
		{
		    if (debuglevel > 1)
			debug ("Dropping invalid linked nickname for %s", ni->nick);

		    continue;
		}

		++ni->linkcnt;
		ni->links = srealloc (ni->links, sizeof (char *) *ni->linkcnt);
		ni->links[ni->linkcnt - 1] = sstrdup (tmpchr);

		lni = scalloc (sizeof (NickInfo), 1);

		strscpy (lni->nick, tmpchr, NICKLEN);
		lni->host = sstrdup (ni->nick);
		lni->flags = NF_LINKED;

		list = &nicklist[NSHASH(lni->nick)];
		lni->next = *list;
		if (*list)
		    (*list)->prev = lni;
		*list = lni;

		lin++;

		continue;
	    }
	}

	/* Memos */
	if (!strcmp (item, "MO"))
	{
	    mi = &ni->memos;

	    mi->memocnt++;
	    mi->memos = srealloc (mi->memos, sizeof (Memo) * mi->memocnt);
	    m = &mi->memos[mi->memocnt - 1];

	    if ((s = strtok (NULL, " "))) { m->number = atoi (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { m->flags = atoi (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { m->time = atoi (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { strscpy (m->sender, s, NICKLEN); } else { continue; }
	    if ((s = strtok (NULL, "\n"))) { m->text = sstrdup (s); }

	    min++;

	    continue;
	}

	/* Memo forward nick */
	if (!strcmp (item, "FW"))
	{
	    if ((s = strtok (NULL, "\n"))) { ni->forward = sstrdup (s); }
	    continue;
	}

	/* End of DB */
	if (!strcmp (item, "DE") || (ns.version < 3 && !strcmp (item, "NS")))
	{
	    int newline = 0;

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

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

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

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

		    log ("Warning: Expected %s memos, got %d.", s, min);
		}
	    }

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

	    dbend = 1;
	}
    }

    fclose (f);

    if (!dbend && ns.version > 1 && !convert && !force)
    {
	/* We need a newline first. */
	log_sameline (0, "");
	log ("Incomplete nickserv.db detected. Only found %d host nicks, %d linked nicks, and %d memos.",
	    nin, lin, min);
	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 nicknames (%d links), %d memos)", tv2ms (&tmp), nin, lin, min);
    }
#endif
}

/* Write the NickServ db to disk. */
void save_ns_dbase ()
{
    FILE *f;
    NickInfo *ni;
    MemoInfo *mi;
    Memo *m;
    int i, dolog = 0, nout = 0, lout = 0, mout = 0;
    char backup[2048], **acclist, **links;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

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

    f = fopen (NICKSERV_DB, "w");

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

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

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

	return;
    }

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

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

    /* Write the sstamp. */
    fprintf (f, "SS %d\n", sstamp);

    /* Write each nicknames info */
    for (ni = firstni (); ni; ni = nextni ())
    {
	/* Find their memos here. This lets us write memo things to NickInfo. */
	mi = &ni->memos;

	/* If MemoServ is disabled, and they have memos, delete them here. */
	if (memoserv_on == FALSE && mi)
	{
	    for (i = 0; i < mi->memocnt; i++)
		free (mi->memos[i].text);

	    free (mi->memos);
	    mi->memos = NULL;
	    mi->memocnt = 0;
	}

	/* Ditto for linked nicks. */
	if (!maxlinks && ni->linkcnt)
	{
	    NickInfo *lni;

	    for (i = 0; i < ni->linkcnt; i++)
	    {
		lni = findnick (ni->links[i]);
		delnick (lni);
		free (ni->links[i]);
	    }

	    free (ni->links);
	    ni->links = NULL;
	    ni->linkcnt = 0;
	}

	/* NickInfo - Only non linked nicks */
	if (ni->flags & NF_LINKED)
	    continue;

	fprintf (f, "NI %s %s %lu %lu %lu %d %d %d %d %lu %s %s\n",
	    ni->nick, ni->pass, ni->registered, ni->lastseen,
	    ni->timestamp, ni->sstamp, ni->flags, mi->memolimit,
	    ni->zone, ni->key, ni->usermask, ni->real);

	nout++;

	/* ACCESS list */
	if (ni->accesscnt)
	    for (acclist = ni->access, i = 0; i < ni->accesscnt; acclist++, i++)
	    {
		if (strlen (*acclist) && strchr (*acclist, '@') && strchr (*acclist, '.'))
		    fprintf (f, "AC %s\n", *acclist);
	    }

	/* LINKS list. Stack the nicks onto one line. */
	if (ni->linkcnt)
	{
	    fprintf (f, "LN");

	    for (links = ni->links, i = 0; i < ni->linkcnt; links++, i++)
	    {
		if (strlen (*links))
		{
		    fprintf (f, " %s", *links);
		    lout++;
		}
	    }

	    fprintf (f, "\n");
	}

	/* Memos */
	if (mi->memocnt)
	{
	    for (i = 0; i < mi->memocnt; i++)
	    {
		m = &mi->memos[i];

		fprintf (f, "MO %d %d %lu %s %s\n", m->number, m->flags,
		    m->time, m->sender, m->text);

		mout++;
	    }
	}

	/* Memo forward nick */
	if (ni->forward && memoserv_on == TRUE)
	{
	    /* Make sure it's still registered. */
	    if (findnick (ni->forward))
		fprintf (f, "FW %s\n", ni->forward);
	    else
	    {
		free (ni->forward);
		ni->forward = NULL;
	    }
	}

	/* Freeze reason */
	if (ni->freezereason)
	    fprintf (f, "FR %s\n", ni->freezereason);

	/* E-Mail */
	if (ni->email)
	{
	    if (email_on == TRUE)
		fprintf (f, "EM %s\n", ni->email);
	    else
	    {
		free (ni->email);
		ni->email = NULL;
	    }
	}

	/* Temporary E-Mail */
	if (ni->temp)
	{
	    fprintf (f, "TE %s\n", ni->temp);

	    /* Only write the timestamp if temp isn't null */
	    if (ni->temptime)
		fprintf (f, "TT %li\n", ni->temptime);
	}

	/* URL */
	if (ni->url)
	{
	    if (url_on == TRUE)
		fprintf (f, "UR %s\n", ni->url);
	    else
	    {
		free (ni->url);
		ni->url = NULL;
	    }
	}

	/* UIN */
	if (ni->uin)
	{
	    if (uin_on == TRUE)
		fprintf (f, "UN %d\n", ni->uin);
	    else
		ni->uin = 0;
	}

	/* Real Name */
	if (ni->name)
	{
	    if (name_on == TRUE)
		fprintf (f, "NA %s\n", ni->name);
	    else
	    {
		free (ni->name);
		ni->name = NULL;
	    }
	}

	/* Age */
	if (ni->age)
	{
	    if (age_on == TRUE)
		fprintf (f, "AG %d\n", ni->age);
	    else
		ni->age = 0;
	}

	/* Sex */
	if (ni->sex)
	{
	    if (sex_on == TRUE)
		fprintf (f, "SX %s\n", ni->sex);
	    else
	    {
		free (ni->sex);
		ni->sex = NULL;
	    }
	}

	/* Location */
	if (ni->location)
	{
	    if (location_on == TRUE)
		fprintf (f, "LO %s\n", ni->location);
	    else
	    {
		free (ni->location);
		ni->location = NULL;
	    }
	}
    }

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

    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 Nickname%s (%d Link%s), %d Memo%s)",
	    tv2ms (&tmp), nout, nout == 1 ? "" : "s", lout, lout == 1 ? "" : "s", mout, mout == 1 ? "" : "s");
    }
#endif
}

/* Return 1 if the first nickname is linked to the specified one, 0 otherwise. */
int islinked (const char *nick, NickInfo *ni)
{
    NickInfo *lni = findnick (nick);

    if (lni->host && !stricmp (lni->host, ni->nick))
	return 1;

    return 0;
}
