
/* NickServ functions.
 *
 * (C) 2003-2005 Anope Team
 * Contact us at info@anope.org
 *
 * Please read COPYING and README for further details.
 *
 * Based on the original code of Epona by Lara.
 * Based on the original code of Services by Andy Church. 
 * 
 * $Id: nickserv.c 813 2005-05-25 15:25:30Z geniusdex $
 *
 */

#include "services.h"
#include "pseudo.h"

/*************************************************************************/

#define HASH(nick)	((tolower((nick)[0])&31)<<5 | (tolower((nick)[1])&31))

NickAlias *nalists[1024];
NickCore *nclists[1024];
NickRequest *nrlists[1024];

unsigned int guestnum;          /* Current guest number */

#define TO_COLLIDE   0          /* Collide the user with this nick */
#define TO_RELEASE   1          /* Release a collided nick */

/*************************************************************************/

extern char *getvHost(char *nick);

void alpha_insert_alias(NickAlias * na);
void insert_core(NickCore * nc);
void insert_requestnick(NickRequest * nr);
void change_core_display(NickCore * nc, char *newdisplay);

void collide(NickAlias * na, int from_timeout);
void release(NickAlias * na, int from_timeout);
static void add_ns_timeout(NickAlias * na, int type, time_t delay);
void del_ns_timeout(NickAlias * na, int type);
int delnickrequest(NickRequest * nr);
NickRequest *findrequestnick(const char *nick);

int do_setmodes(User * u);

/*************************************************************************/
/* *INDENT-OFF* */
void moduleAddNickServCmds(void) {
    modules_core_init(NickServCoreNumber, NickServCoreModules);
}
/* *INDENT-ON* */
/*************************************************************************/

/* Display total number of registered nicks and info about each; or, if
 * a specific nick is given, display information about that nick (like
 * /msg NickServ INFO <nick>).  If count_only != 0, then only display the
 * number of registered nicks (the nick parameter is ignored).
 */

void listnicks(int count_only, const char *nick)
{
    int count = 0;
    NickAlias *na;
    int i;
    char *end;

    if (count_only) {

        for (i = 0; i < 1024; i++) {
            for (na = nalists[i]; na; na = na->next)
                count++;
        }
        printf("%d nicknames registered.\n", count);

    } else if (nick) {

        struct tm *tm;
        char buf[512];
        static const char commastr[] = ", ";
        int need_comma = 0;

        if (!(na = findnick(nick))) {
            printf("%s not registered.\n", nick);
            return;
        } else if (na->status & NS_VERBOTEN) {
            printf("%s is FORBIDden.\n", nick);
            return;
        }
        printf("%s is %s\n", nick, na->last_realname);
        printf("Last seen address: %s\n", na->last_usermask);
        tm = localtime(&na->time_registered);
        strftime(buf, sizeof(buf),
                 getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
        printf("  Time registered: %s\n", buf);
        tm = localtime(&na->last_seen);
        strftime(buf, sizeof(buf),
                 getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
        printf("   Last seen time: %s\n", buf);
        if (na->nc->url)
            printf("              URL: %s\n", na->nc->url);
        if (na->nc->email)
            printf("   E-mail address: %s\n", na->nc->email);
        if (na->nc->icq)
            printf("            ICQ #: %d\n", na->nc->icq);
        if (na->nc->greet)
            printf("            Greet: %s\n", na->nc->greet);
        *buf = 0;
        end = buf;
        if (na->nc->flags & NI_KILLPROTECT) {
            end +=
                snprintf(end, sizeof(buf) - (end - buf),
                         "Kill protection");
            need_comma = 1;
        }
        if (na->nc->flags & NI_SECURE) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%sSecurity",
                            need_comma ? commastr : "");
            need_comma = 1;
        }
        if (na->nc->flags & NI_PRIVATE) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%sPrivate",
                            need_comma ? commastr : "");
            need_comma = 1;
        }
        if (na->status & NS_NO_EXPIRE) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%sNo Expire",
                            need_comma ? commastr : "");
            need_comma = 1;
        }
        printf("          Options: %s\n", *buf ? buf : "None");

    } else {

        for (i = 0; i < 1024; i++) {
            for (na = nalists[i]; na; na = na->next) {
                printf("    %s %-20s  %s\n",
                       na->status & NS_NO_EXPIRE ? "!" : " ",
                       na->nick, na->status & NS_VERBOTEN ?
                       "Disallowed (FORBID)" : na->last_usermask);
                count++;
            }
        }
        printf("%d nicknames registered.\n", count);

    }
}

/*************************************************************************/

/* Return information on memory use.  Assumes pointers are valid. */

void get_aliases_stats(long *nrec, long *memuse)
{
    long count = 0, mem = 0;
    int i;
    NickAlias *na;

    for (i = 0; i < 1024; i++) {
        for (na = nalists[i]; na; na = na->next) {
            count++;
            mem += sizeof(*na);
            if (na->nick)
                mem += strlen(na->nick) + 1;
            if (na->last_usermask)
                mem += strlen(na->last_usermask) + 1;
            if (na->last_realname)
                mem += strlen(na->last_realname) + 1;
            if (na->last_quit)
                mem += strlen(na->last_quit) + 1;
        }
    }
    *nrec = count;
    *memuse = mem;
}

/*************************************************************************/

/* Return information on memory use.  Assumes pointers are valid. */

void get_core_stats(long *nrec, long *memuse)
{
    long count = 0, mem = 0;
    int i, j;
    NickCore *nc;
    char **accptr;

    for (i = 0; i < 1024; i++) {
        for (nc = nclists[i]; nc; nc = nc->next) {
            count++;
            mem += sizeof(*nc);

            if (nc->display)
                mem += strlen(nc->display) + 1;
            if (nc->pass)
                mem += strlen(nc->pass) + 1;

            if (nc->url)
                mem += strlen(nc->url) + 1;
            if (nc->email)
                mem += strlen(nc->email) + 1;
            if (nc->greet)
                mem += strlen(nc->greet) + 1;

            mem += sizeof(char *) * nc->accesscount;
            for (accptr = nc->access, j = 0; j < nc->accesscount;
                 accptr++, j++) {
                if (*accptr)
                    mem += strlen(*accptr) + 1;
            }

            mem += nc->memos.memocount * sizeof(Memo);
            for (j = 0; j < nc->memos.memocount; j++) {
                if (nc->memos.memos[j].text)
                    mem += strlen(nc->memos.memos[j].text) + 1;
            }

            mem += sizeof(void *) * nc->aliases.count;
        }
    }
    *nrec = count;
    *memuse = mem;
}

/*************************************************************************/
/*************************************************************************/

/* NickServ initialization. */

void ns_init(void)
{
    moduleAddNickServCmds();
    guestnum = time(NULL);
    while (guestnum > 9999999)
        guestnum -= 10000000;
}

/*************************************************************************/

/* Main NickServ routine. */

void nickserv(User * u, char *buf)
{
    char *cmd, *s;

    cmd = strtok(buf, " ");

    if (!cmd) {
        return;
    } else if (stricmp(cmd, "\1PING") == 0) {
        if (!(s = strtok(NULL, ""))) {
            s = "";
        }
        anope_cmd_ctcp(s_NickServ, u->nick, "PING %s", s);
    } else if (skeleton) {
        notice_lang(s_NickServ, u, SERVICE_OFFLINE, s_NickServ);
    } else {
        mod_run_cmd(s_NickServ, u, NICKSERV, cmd);
    }

}

/*************************************************************************/

/* Load/save data files. */


#define SAFE(x) do {					\
    if ((x) < 0) {					\
	if (!forceload)					\
	    fatal("Read error on %s", NickDBName);	\
	failed = 1;					\
	break;						\
    }							\
} while (0)

/* Loads NickServ database versions 5 to 11 (<= 4 is not supported) */

void load_old_ns_dbase(void)
{
    dbFILE *f;
    int ver, i, j, c;
    NickAlias *na, *na2, *next;
    NickCore *nc;
    int failed = 0;

    uint16 tmp16;
    uint32 tmp32;

    char bufn[NICKMAX], bufp[PASSMAX];
    char *email, *greet, *url, *forbidby, *forbidreason;
    uint32 icq;

    if (!(f = open_db(s_NickServ, NickDBName, "r", NICK_VERSION)))
        return;

    ver = get_file_version(f);
    if (ver <= 4) {
        fatal("Unsupported version number (%d) on %s", ver, NickDBName);
        close_db(f);
        return;
    }

    for (i = 0; i < 256 && !failed; i++) {
        while ((c = getc_db(f)) == 1) {
            if (c != 1)
                fatal("Invalid format in %s", NickDBName);

            na = scalloc(sizeof(NickAlias), 1);

            SAFE(read_buffer(bufn, f));
            na->nick = sstrdup(bufn);
            SAFE(read_buffer(bufp, f)); /* Will be used later if needed */

            SAFE(read_string(&url, f));
            SAFE(read_string(&email, f));
            if (ver >= 10)
                SAFE(read_int32(&icq, f));
            else
                icq = 0;
            if (ver >= 9)
                SAFE(read_string(&greet, f));
            else
                greet = NULL;

            SAFE(read_string(&na->last_usermask, f));
            SAFE(read_string(&na->last_realname, f));
            SAFE(read_string(&na->last_quit, f));

            SAFE(read_int32(&tmp32, f));
            na->time_registered = tmp32;
            SAFE(read_int32(&tmp32, f));
            na->last_seen = tmp32;

            SAFE(read_int16(&na->status, f));
            na->status &= ~NS_TEMPORARY;
#ifdef USE_ENCRYPTION
            if (!(na->status & (NS_OLD_ENCRYPTEDPW | NS_VERBOTEN))) {
                if (debug)
                    alog("debug: %s: encrypting password for `%s' on load",
                         s_NickServ, na->nick);
                if (encrypt_in_place(bufp, PASSMAX) < 0)
                    fatal("%s: Can't encrypt `%s' nickname password!",
                          s_NickServ, na->nick);
                na->status |= NS_OLD_ENCRYPTEDPW;
            }
#else
            if (na->status & NS_OLD_ENCRYPTEDPW) {
                /* Bail: it makes no sense to continue with encrypted
                 * passwords, since we won't be able to verify them */
                fatal
                    ("%s: load database: password for %s encrypted but encryption disabled, aborting",
                     s_NickServ, na->nick);
            }
#endif
            if (ver >= 9) {
                SAFE(read_string(&forbidby, f));
                SAFE(read_string(&forbidreason, f));
                /* Cleanup */
                if (forbidby && *forbidby == '@') {
                    free(forbidby);
                    forbidby = NULL;
                }
                if (forbidreason && *forbidreason == 0) {
                    free(forbidreason);
                    forbidreason = NULL;
                }
            } else {
                forbidby = NULL;
                forbidreason = NULL;
            }

            if (na->status & NS_VERBOTEN) {
                if (na->last_usermask)
                    free(na->last_usermask);
                if (na->last_realname)
                    free(na->last_realname);

                na->last_usermask = forbidby;
                na->last_realname = forbidreason;
            } else {
                if (!na->last_usermask)
                    na->last_usermask = sstrdup("");
                if (!na->last_realname)
                    na->last_realname = sstrdup("");
            }

            /* Store the reference for later resolving */
            SAFE(read_string((char **) &na->nc, f));
            SAFE(read_int16(&tmp16, f));        /* Was linkcount */

            if (na->nc) {
                SAFE(read_int16(&tmp16, f));    /* Was channelcount */
            } else {
                /* This nick was a master nick, so it also has all the
                 * core info! =)
                 */
                nc = scalloc(1, sizeof(NickCore));
                slist_init(&nc->aliases);

                /* The initial display is what used to be the master nick */
                nc->display = sstrdup(na->nick);

                /* We grabbed info before; fill the appropriate fields now */
                if (*bufp)
                    nc->pass = sstrdup(bufp);
                else
                    nc->pass = NULL;    /* Which may be the case for forbidden nicks .. */

                nc->email = email;
                nc->greet = greet;
                nc->icq = icq;
                nc->url = url;

                /* We check whether the e-mail is valid because it was not tested
                 * in older versions.
                 */
                if (ver <= 10 && nc->email && !MailValidate(nc->email)) {
                    free(nc->email);
                    nc->email = NULL;
                }

                SAFE(read_int32(&nc->flags, f));
                if (!NSAllowKillImmed)
                    nc->flags &= ~NI_KILL_IMMED;

                /* Status flags cleanup */
                if (na->status & NS_OLD_ENCRYPTEDPW) {
                    nc->flags |= NI_ENCRYPTEDPW;
                    na->status &= ~NS_OLD_ENCRYPTEDPW;
                }

                /* Add services opers and admins to the appropriate list, but
                   only if the database version is equal to or more than 10. */
                if (ver >= 10) {
                    if (nc->flags & NI_SERVICES_ADMIN)
                        slist_add(&servadmins, nc);
                    if (nc->flags & NI_SERVICES_OPER)
                        slist_add(&servopers, nc);
                }

                /* Add the Services root flag if needed. */
                if (nc)
                    for (j = 0; j < RootNumber; j++)
                        if (!stricmp(ServicesRoots[j], na->nick))
                            nc->flags |= NI_SERVICES_ROOT;

                SAFE(read_int16(&nc->accesscount, f));
                if (nc->accesscount) {
                    char **access;
                    access = scalloc(sizeof(char *) * nc->accesscount, 1);
                    nc->access = access;
                    for (j = 0; j < nc->accesscount; j++, access++)
                        SAFE(read_string(access, f));
                }

                SAFE(read_int16(&tmp16, f));
                nc->memos.memocount = (int16) tmp16;
                SAFE(read_int16(&tmp16, f));
                nc->memos.memomax = (int16) tmp16;
                if (nc->memos.memocount) {
                    Memo *memos;
                    memos = scalloc(sizeof(Memo) * nc->memos.memocount, 1);
                    nc->memos.memos = memos;

                    for (j = 0; j < nc->memos.memocount; j++, memos++) {
                        SAFE(read_int32(&memos->number, f));
                        SAFE(read_int16(&memos->flags, f));
                        SAFE(read_int32(&tmp32, f));
                        memos->time = tmp32;
                        SAFE(read_buffer(memos->sender, f));
                        SAFE(read_string(&memos->text, f));
                        memos->moduleData = NULL;
                    }
                }

                /* We read the channel count, but don't take care of it.
                   load_cs_dbase will regenerate it correctly. */
                SAFE(read_int16(&tmp16, f));
                SAFE(read_int16(&nc->channelmax, f));
                if (ver == 5)
                    nc->channelmax = CSMaxReg;

                SAFE(read_int16(&nc->language, f));

                if (ver >= 11 && ver < 13) {
                    char *s;

                    SAFE(read_int16(&tmp16, f));
                    SAFE(read_int32(&tmp32, f));
                    SAFE(read_int16(&tmp16, f));
                    SAFE(read_string(&s, f));
                }

                /* Set us as being a master nick; fill the nc field also.
                   The NS_MASTER flag will not be cleared in this function. */
                na->status |= NS_MASTER;
                na->nc = nc;
                slist_add(&nc->aliases, na);

                /* Insert our new core in the core list */
                insert_core(nc);
            }

            alpha_insert_alias(na);

        }                       /* while (getc_db(f) != 0) */
    }                           /* for (i) */

    /* Now resolve what were called links */
    for (i = 0; i < 1024; i++) {
        for (na = nalists[i]; na; na = next) {
            next = na->next;

            /* Master nicks are already resolved */
            if (na->status & NS_MASTER)
                continue;

            na2 = na;
            /* While the reference resolves and it's not a master nick */
            while ((na2 = findnick((char *) na2->nc))
                   && !(na2->status & NS_MASTER));

            /* It didn't resolve. This is problematic since there is no core. :/
               We delete the nick. */
            if (!na2) {
                alog("%s: while loading database: %s was linked to inexistant %s", s_NickServ, na->nick, (char *) na->nc);
                delnick(na);
                continue;
            }

            /* OK we have information on the core. We mark the current alias
               as a master nick because it now contains a valid core. */
            na->nc = na2->nc;
            na->status |= NS_MASTER;
            slist_add(&na->nc->aliases, na);
        }
    }

    close_db(f);
}

void load_ns_req_db(void)
{
    dbFILE *f;
    int i, c, ver;
    NickRequest *nr;
    uint32 tmp32;
    int failed = 0;

    if (!(f = open_db(s_NickServ, PreNickDBName, "r", PRE_NICK_VERSION)))
        return;
    ver = get_file_version(f);
    for (i = 0; i < 1024 && !failed; i++) {
        while ((c = getc_db(f)) == 1) {
            if (c != 1)
                fatal("Invalid format in %s", PreNickDBName);
            nr = scalloc(1, sizeof(NickRequest));
            SAFE(read_string(&nr->nick, f));
            SAFE(read_string(&nr->passcode, f));
            SAFE(read_string(&nr->password, f));
            SAFE(read_string(&nr->email, f));
            SAFE(read_int32(&tmp32, f));
            nr->requested = tmp32;
            insert_requestnick(nr);
        }
    }
    close_db(f);
}

void load_ns_dbase(void)
{
    dbFILE *f;
    int ver, i, j, c;
    NickAlias *na, **nalast, *naprev;
    NickCore *nc, **nclast, *ncprev;
    int failed = 0;
    uint16 tmp16;
    uint32 tmp32;
    char *s;

    if (!(f = open_db(s_NickServ, NickDBName, "r", NICK_VERSION)))
        return;

    ver = get_file_version(f);

    if (ver <= 11) {
        close_db(f);
        load_old_ns_dbase();
        return;
    }

    /* First we load nick cores */
    for (i = 0; i < 1024 && !failed; i++) {
        nclast = &nclists[i];
        ncprev = NULL;

        while ((c = getc_db(f)) == 1) {
            if (c != 1)
                fatal("Invalid format in %s", NickDBName);

            nc = scalloc(1, sizeof(NickCore));
            *nclast = nc;
            nclast = &nc->next;
            nc->prev = ncprev;
            ncprev = nc;

            slist_init(&nc->aliases);

            SAFE(read_string(&nc->display, f));
            SAFE(read_string(&nc->pass, f));
            SAFE(read_string(&nc->email, f));
            SAFE(read_string(&nc->greet, f));
            SAFE(read_int32(&nc->icq, f));
            SAFE(read_string(&nc->url, f));

            SAFE(read_int32(&nc->flags, f));
            if (!NSAllowKillImmed)
                nc->flags &= ~NI_KILL_IMMED;
#ifdef USE_ENCRYPTION
            if (nc->pass && !(nc->flags & NI_ENCRYPTEDPW)) {
                if (debug)
                    alog("debug: %s: encrypting password for `%s' on load",
                         s_NickServ, nc->display);
                if (encrypt_in_place(nc->pass, PASSMAX) < 0)
                    fatal("%s: Can't encrypt `%s' nickname password!",
                          s_NickServ, nc->display);
                nc->flags |= NI_ENCRYPTEDPW;
            }
#else
            if (nc->flags & NI_ENCRYPTEDPW) {
                /* Bail: it makes no sense to continue with encrypted
                 * passwords, since we won't be able to verify them */
                fatal
                    ("%s: load database: password for %s encrypted but encryption disabled, aborting",
                     s_NickServ, nc->display);
            }
#endif
            SAFE(read_int16(&nc->language, f));

            /* Add services opers and admins to the appropriate list, but
               only if the database version is more than 10. */
            if (nc->flags & NI_SERVICES_ADMIN)
                slist_add(&servadmins, nc);
            if (nc->flags & NI_SERVICES_OPER)
                slist_add(&servopers, nc);

            SAFE(read_int16(&nc->accesscount, f));
            if (nc->accesscount) {
                char **access;
                access = scalloc(sizeof(char *) * nc->accesscount, 1);
                nc->access = access;
                for (j = 0; j < nc->accesscount; j++, access++)
                    SAFE(read_string(access, f));
            }

            SAFE(read_int16(&tmp16, f));
            nc->memos.memocount = (int16) tmp16;
            SAFE(read_int16(&tmp16, f));
            nc->memos.memomax = (int16) tmp16;
            if (nc->memos.memocount) {
                Memo *memos;
                memos = scalloc(sizeof(Memo) * nc->memos.memocount, 1);
                nc->memos.memos = memos;
                for (j = 0; j < nc->memos.memocount; j++, memos++) {
                    SAFE(read_int32(&memos->number, f));
                    SAFE(read_int16(&memos->flags, f));
                    SAFE(read_int32(&tmp32, f));
                    memos->time = tmp32;
                    SAFE(read_buffer(memos->sender, f));
                    SAFE(read_string(&memos->text, f));
                    memos->moduleData = NULL;
                }
            }

            SAFE(read_int16(&nc->channelcount, f));
            SAFE(read_int16(&tmp16, f));
            nc->channelmax = CSMaxReg;

            if (ver < 13) {
                /* Used to be dead authentication system */
                SAFE(read_int16(&tmp16, f));
                SAFE(read_int32(&tmp32, f));
                SAFE(read_int16(&tmp16, f));
                SAFE(read_string(&s, f));
            }

        }                       /* while (getc_db(f) != 0) */
        *nclast = NULL;
    }                           /* for (i) */

    for (i = 0; i < 1024 && !failed; i++) {
        nalast = &nalists[i];
        naprev = NULL;
        while ((c = getc_db(f)) == 1) {
            if (c != 1)
                fatal("Invalid format in %s", NickDBName);

            na = scalloc(1, sizeof(NickAlias));

            SAFE(read_string(&na->nick, f));

            SAFE(read_string(&na->last_usermask, f));
            SAFE(read_string(&na->last_realname, f));
            SAFE(read_string(&na->last_quit, f));

            SAFE(read_int32(&tmp32, f));
            na->time_registered = tmp32;
            SAFE(read_int32(&tmp32, f));
            na->last_seen = tmp32;
            SAFE(read_int16(&na->status, f));
            na->status &= ~NS_TEMPORARY;

            SAFE(read_string(&s, f));
            na->nc = findcore(s);
            free(s);

            slist_add(&na->nc->aliases, na);

            if (!(na->status & NS_VERBOTEN)) {
                if (!na->last_usermask)
                    na->last_usermask = sstrdup("");
                if (!na->last_realname)
                    na->last_realname = sstrdup("");
            }

            na->nc->flags &= ~NI_SERVICES_ROOT;

            *nalast = na;
            nalast = &na->next;
            na->prev = naprev;
            naprev = na;

        }                       /* while (getc_db(f) != 0) */

        *nalast = NULL;
    }                           /* for (i) */

    close_db(f);

    for (i = 0; i < 1024; i++) {
        NickAlias *next;

        for (na = nalists[i]; na; na = next) {
            next = na->next;
            /* We check for coreless nicks (although it should never happen) */
            if (!na->nc) {
                alog("%s: while loading database: %s has no core! We delete it.", s_NickServ, na->nick);
                delnick(na);
                continue;
            }

            /* Add the Services root flag if needed. */
            for (j = 0; j < RootNumber; j++)
                if (!stricmp(ServicesRoots[j], na->nick))
                    na->nc->flags |= NI_SERVICES_ROOT;
        }
    }
}

#undef SAFE

/*************************************************************************/

#define SAFE(x) do {						\
    if ((x) < 0) {						\
	restore_db(f);						\
	log_perror("Write error on %s", NickDBName);		\
	if (time(NULL) - lastwarn > WarningTimeout) {		\
	    anope_cmd_global(NULL, "Write error on %s: %s", NickDBName,	\
			strerror(errno));			\
	    lastwarn = time(NULL);				\
	}							\
	return;							\
    }								\
} while (0)



void save_ns_dbase(void)
{
    dbFILE *f;
    int i, j;
    NickAlias *na;
    NickCore *nc;
    char **access;
    Memo *memos;
    static time_t lastwarn = 0;

    if (!(f = open_db(s_NickServ, NickDBName, "w", NICK_VERSION)))
        return;

    for (i = 0; i < 1024; i++) {
        for (nc = nclists[i]; nc; nc = nc->next) {
            SAFE(write_int8(1, f));

            SAFE(write_string(nc->display, f));
            SAFE(write_string(nc->pass, f));

            SAFE(write_string(nc->email, f));
            SAFE(write_string(nc->greet, f));
            SAFE(write_int32(nc->icq, f));
            SAFE(write_string(nc->url, f));

            SAFE(write_int32(nc->flags, f));
            SAFE(write_int16(nc->language, f));

            SAFE(write_int16(nc->accesscount, f));
            for (j = 0, access = nc->access; j < nc->accesscount;
                 j++, access++)
                SAFE(write_string(*access, f));

            SAFE(write_int16(nc->memos.memocount, f));
            SAFE(write_int16(nc->memos.memomax, f));
            memos = nc->memos.memos;
            for (j = 0; j < nc->memos.memocount; j++, memos++) {
                SAFE(write_int32(memos->number, f));
                SAFE(write_int16(memos->flags, f));
                SAFE(write_int32(memos->time, f));
                SAFE(write_buffer(memos->sender, f));
                SAFE(write_string(memos->text, f));
            }

            SAFE(write_int16(nc->channelcount, f));
            SAFE(write_int16(nc->channelmax, f));

        }                       /* for (nc) */

        SAFE(write_int8(0, f));

    }                           /* for (i) */

    for (i = 0; i < 1024; i++) {
        for (na = nalists[i]; na; na = na->next) {
            SAFE(write_int8(1, f));

            SAFE(write_string(na->nick, f));

            SAFE(write_string(na->last_usermask, f));
            SAFE(write_string(na->last_realname, f));
            SAFE(write_string(na->last_quit, f));

            SAFE(write_int32(na->time_registered, f));
            SAFE(write_int32(na->last_seen, f));

            SAFE(write_int16(na->status, f));

            SAFE(write_string(na->nc->display, f));

        }                       /* for (na) */
        SAFE(write_int8(0, f));
    }                           /* for (i) */

    close_db(f);

}

void save_ns_req_dbase(void)
{
    dbFILE *f;
    int i;
    NickRequest *nr;
    static time_t lastwarn = 0;

    if (!(f = open_db(s_NickServ, PreNickDBName, "w", PRE_NICK_VERSION)))
        return;

    for (i = 0; i < 1024; i++) {
        for (nr = nrlists[i]; nr; nr = nr->next) {
            SAFE(write_int8(1, f));
            SAFE(write_string(nr->nick, f));
            SAFE(write_string(nr->passcode, f));
            SAFE(write_string(nr->password, f));
            SAFE(write_string(nr->email, f));
            SAFE(write_int32(nr->requested, f));
            SAFE(write_int8(0, f));
        }
    }
    close_db(f);

}

#undef SAFE

void save_ns_rdb_dbase(void)
{
#ifdef USE_RDB
    int i;
    NickAlias *na;
    NickCore *nc;

    if (!rdb_open())
        return;

    rdb_tag_table("anope_ns_core");
    rdb_tag_table("anope_ns_alias");
    rdb_scrub_table("anope_ms_info", "serv='NICK'");
    rdb_clear_table("anope_ns_access");

    for (i = 0; i < 1024; i++) {
        for (nc = nclists[i]; nc; nc = nc->next) {
            rdb_save_ns_core(nc);

        }                       /* for (nc) */
    }                           /* for (i) */

    for (i = 0; i < 1024; i++) {
        for (na = nalists[i]; na; na = na->next) {
            rdb_save_ns_alias(na);

        }                       /* for (na) */
    }                           /* for (i) */

    rdb_scrub_table("anope_ns_core", "active='0'");
    rdb_scrub_table("anope_ns_alias", "active='0'");
    rdb_close();
#endif
}

void save_ns_req_rdb_dbase(void)
{
#ifdef USE_RDB
    int i;
    NickRequest *nr;

    if (!rdb_open())
        return;

    rdb_tag_table("anope_ns_request");

    for (i = 0; i < 1024; i++) {
        for (nr = nrlists[i]; nr; nr = nr->next) {
            rdb_save_ns_req(nr);
        }
    }

    rdb_scrub_table("anope_ns_request", "active='0'");
    rdb_close();
#endif

}

/*************************************************************************/

/* Check whether a user is on the access list of the nick they're using If
 * not, send warnings as appropriate.  If so (and not NI_SECURE), update
 * last seen info.
 * Return 1 if the user is valid and recognized, 0 otherwise (note
 * that this means an NI_SECURE nick will return 0 from here).
 * If the user's nick is not registered, 0 is returned.
 */

int validate_user(User * u)
{
    NickAlias *na;
    NickRequest *nr;

    int on_access;

    if ((nr = findrequestnick(u->nick))) {
        notice_lang(s_NickServ, u, NICK_IS_PREREG);
    }

    if (!(na = u->na))
        return 0;

    if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_MAY_NOT_BE_USED);
        collide(na, 0);
        return 0;
    }

    if (na->nc->flags & NI_SUSPENDED) {
        notice_lang(s_NickServ, u, NICK_X_SUSPENDED, u->nick);
        collide(na, 0);
        return 0;
    }

    on_access = is_on_access(u, na->nc);
    if (on_access)
        na->status |= NS_ON_ACCESS;

    if (!(na->nc->flags & NI_SECURE) && on_access) {
        na->status |= NS_RECOGNIZED;
        na->last_seen = time(NULL);
        if (na->last_usermask)
            free(na->last_usermask);
        na->last_usermask =
            scalloc(strlen(common_get_vident(u)) +
                    strlen(common_get_vhost(u)) + 2, 1);
        sprintf(na->last_usermask, "%s@%s", common_get_vident(u),
                common_get_vhost(u));
        if (na->last_realname)
            free(na->last_realname);
        na->last_realname = sstrdup(u->realname);
        return 1;
    }

    if (on_access || !(na->nc->flags & NI_KILL_IMMED)) {
        if (na->nc->flags & NI_SECURE)
            notice_lang(s_NickServ, u, NICK_IS_SECURE, s_NickServ);
        else
            notice_lang(s_NickServ, u, NICK_IS_REGISTERED, s_NickServ);
    }

    if ((na->nc->flags & NI_KILLPROTECT) && !on_access) {
        if (na->nc->flags & NI_KILL_IMMED) {
            notice_lang(s_NickServ, u, FORCENICKCHANGE_NOW);
            collide(na, 0);
        } else if (na->nc->flags & NI_KILL_QUICK) {
            notice_lang(s_NickServ, u, FORCENICKCHANGE_IN_20_SECONDS);
            add_ns_timeout(na, TO_COLLIDE, 20);
        } else {
            notice_lang(s_NickServ, u, FORCENICKCHANGE_IN_1_MINUTE);
            add_ns_timeout(na, TO_COLLIDE, 60);
        }
    }

    return 0;
}

/*************************************************************************/

/* Cancel validation flags for a nick (i.e. when the user with that nick
 * signs off or changes nicks).  Also cancels any impending collide. */

void cancel_user(User * u)
{
    NickAlias *na = u->na;

    if (na) {
        if (na->status & NS_GUESTED) {
            if (ircd->svshold) {
                if (UseSVSHOLD) {
                    anope_cmd_svshold(na->nick);
                } else {
                    if (ircd->svsnick) {
                        anope_cmd_guest_nick(u->nick, NSEnforcerUser,
                                             NSEnforcerHost,
                                             "Services Enforcer", "+");
                        add_ns_timeout(na, TO_RELEASE, NSReleaseTimeout);
                    } else {
                        anope_cmd_svskill(s_NickServ, u->nick,
                                          "Killing to enforce nick");
                    }
                }
            } else {
                if (ircd->svsnick) {
                    anope_cmd_guest_nick(u->nick, NSEnforcerUser,
                                         NSEnforcerHost,
                                         "Services Enforcer", "+");
                    add_ns_timeout(na, TO_RELEASE, NSReleaseTimeout);
                } else {
                    anope_cmd_svskill(s_NickServ, u->nick,
                                      "Killing to enforce nick");
                }
            }
            na->status &= ~NS_TEMPORARY;
            na->status |= NS_KILL_HELD;
        } else {
            na->status &= ~NS_TEMPORARY;
        }
        del_ns_timeout(na, TO_COLLIDE);
    }
}

/*************************************************************************/

/* Return whether a user has identified for their nickname. */

int nick_identified(User * u)
{
    if (u) {
        if (u->na) {
            if (u->na->status) {
                return (u->na->status & NS_IDENTIFIED);
            } else {
                return 0;
            }
        } else {
            return 0;
        }
    }
    return 0;
}

/*************************************************************************/

/* Return whether a user is recognized for their nickname. */

int nick_recognized(User * u)
{
    if (u) {
        if (u->na) {
            if (u->na->status) {
                return (u->na->status & (NS_IDENTIFIED | NS_RECOGNIZED));
            } else {
                return 0;
            }
        } else {
            return 0;
        }
    }
    return 0;
}

/*************************************************************************/

/* Returns whether a user is identified AND in the group nc */

int group_identified(User * u, NickCore * nc)
{
    return nick_identified(u) && u->na->nc == nc;
}

/*************************************************************************/

/* Remove all nicks which have expired.  Also update last-seen time for all
 * nicks.
 */

void expire_nicks()
{
    int i;
    NickAlias *na, *next;
    time_t now = time(NULL);
    char *tmpnick;

    for (i = 0; i < 1024; i++) {
        for (na = nalists[i]; na; na = next) {
            next = na->next;

            if (na->u
                && ((na->nc->flags & NI_SECURE) ? nick_identified(na->u) :
                    nick_recognized(na->u))) {
                if (debug >= 2)
                    alog("debug: NickServ: updating last seen time for %s",
                         na->nick);
                na->last_seen = now;
                continue;
            }

            if (NSExpire && now - na->last_seen >= NSExpire
                && !(na->status & (NS_VERBOTEN | NS_NO_EXPIRE))) {
                alog("Expiring nickname %s (group: %s) (e-mail: %s)",
                     na->nick, na->nc->display,
                     (na->nc->email ? na->nc->email : "none"));
                tmpnick = sstrdup(na->nick);
                delnick(na);
                send_event(EVENT_NICK_EXPIRE, 1, tmpnick);
                free(tmpnick);
            }
        }
    }
}

void expire_requests()
{
    int i;
    NickRequest *nr, *next;
    time_t now = time(NULL);
    for (i = 0; i < 1024; i++) {
        for (nr = nrlists[i]; nr; nr = next) {
            next = nr->next;
            if (NSRExpire && now - nr->requested >= NSRExpire) {
                alog("Request for nick %s expiring", nr->nick);
                delnickrequest(nr);
            }
        }
    }
}

/*************************************************************************/
/*************************************************************************/
/* Return the NickRequest structire for the given nick, or NULL */

NickRequest *findrequestnick(const char *nick)
{
    NickRequest *nr;

    if (!*nick || !nick) {
        if (debug) {
            alog("debug: findrequestnick() called with NULL values");
        }
        return NULL;
    }

    for (nr = nrlists[HASH(nick)]; nr; nr = nr->next) {
        if (stricmp(nr->nick, nick) == 0)
            return nr;
    }
    return NULL;
}

/* Return the NickAlias structure for the given nick, or NULL if the nick
 * isn't registered. */

NickAlias *findnick(const char *nick)
{
    NickAlias *na;

    if (!nick || !*nick) {
        if (debug) {
            alog("debug: findnick() called with NULL values");
        }
        return NULL;
    }

    for (na = nalists[HASH(nick)]; na; na = na->next) {
        if (stricmp(na->nick, nick) == 0)
            return na;
    }

    return NULL;
}

/*************************************************************************/

/* Return the NickCore structure for the given nick, or NULL if the core
 * doesn't exist. */

NickCore *findcore(const char *nick)
{
    NickCore *nc;

    if (!nick || !*nick) {
        if (debug) {
            alog("debug: findcore() called with NULL values");
        }
        return NULL;
    }

    for (nc = nclists[HASH(nick)]; nc; nc = nc->next) {
        if (stricmp(nc->display, nick) == 0)
            return nc;
    }

    return NULL;
}

/*************************************************************************/
/*********************** NickServ private routines ***********************/
/*************************************************************************/

/* Is the given user's address on the given nick's access list?  Return 1
 * if so, 0 if not. */

int is_on_access(User * u, NickCore * nc)
{
    int i;
    char *buf;
    char *buf2 = NULL;

    if (nc->accesscount == 0)
        return 0;

    buf = scalloc(strlen(u->username) + strlen(u->host) + 2, 1);
    sprintf(buf, "%s@%s", u->username, u->host);
    if (ircd->vhost) {
        if (u->vhost) {
            buf2 = scalloc(strlen(u->username) + strlen(u->vhost) + 2, 1);
            sprintf(buf2, "%s@%s", u->username, u->vhost);
        }
    }

    for (i = 0; i < nc->accesscount; i++) {
        if (match_wild_nocase(nc->access[i], buf)
            || (ircd->vhost ? match_wild_nocase(nc->access[i], buf2) : 0)) {
            free(buf);
            if (ircd->vhost) {
                if (u->vhost) {
                    free(buf2);
                }
            }
            return 1;
        }
    }
    free(buf);
    if (ircd->vhost) {
        free(buf2);
    }
    return 0;
}

/*************************************************************************/

/* Insert a nick alias alphabetically into the database. */

void alpha_insert_alias(NickAlias * na)
{
    NickAlias *ptr, *prev;
    char *nick;
    int index;

    if (!na) {
        if (debug) {
            alog("debug: alpha_insert_alias called with NULL values");
        }
        return;
    }

    nick = na->nick;
    index = HASH(nick);

    for (prev = NULL, ptr = nalists[index];
         ptr && stricmp(ptr->nick, nick) < 0; prev = ptr, ptr = ptr->next);
    na->prev = prev;
    na->next = ptr;
    if (!prev)
        nalists[index] = na;
    else
        prev->next = na;
    if (ptr)
        ptr->prev = na;
}

/*************************************************************************/

/* Insert a nick core into the database. */

void insert_core(NickCore * nc)
{
    int index;

    if (!nc) {
        if (debug) {
            alog("debug: insert_core called with NULL values");
        }
        return;
    }

    index = HASH(nc->display);

    nc->prev = NULL;
    nc->next = nclists[index];
    if (nc->next)
        nc->next->prev = nc;
    nclists[index] = nc;
}

/*************************************************************************/
void insert_requestnick(NickRequest * nr)
{
    int index = HASH(nr->nick);
    if (!nr) {
        if (debug) {
            alog("debug: insert_requestnick called with NULL values");
        }
        return;
    }


    nr->prev = NULL;
    nr->next = nrlists[index];
    if (nr->next)
        nr->next->prev = nr;
    nrlists[index] = nr;
}

/*************************************************************************/

/* Sets nc->display to newdisplay. If newdisplay is NULL, it will change
 * it to the first alias in the list.
 */

void change_core_display(NickCore * nc, char *newdisplay)
{
    if (!newdisplay) {
        NickAlias *na;

        if (nc->aliases.count <= 0)
            return;

        na = nc->aliases.list[0];
        newdisplay = na->nick;
    }

    /* Log ... */
    alog("%s: changing %s nickname group display to %s", s_NickServ,
         nc->display, newdisplay);

#ifdef USE_RDB
    /* Reflect this change in the database right away. This
     * ensures that we know how to deal with this "new" nick
     * on the next /OS UPDATE might need it on /NS DROP too...
     */
    if (rdb_open()) {
        rdb_ns_set_display(newdisplay, nc->display);
        rdb_close();
    }
#endif

    /* Remove the core from the list */
    if (nc->next)
        nc->next->prev = nc->prev;
    if (nc->prev)
        nc->prev->next = nc->next;
    else
        nclists[HASH(nc->display)] = nc->next;

    free(nc->display);
    nc->display = sstrdup(newdisplay);
    insert_core(nc);

}

/*************************************************************************/

/* Deletes the core. This must be called only when there is no more
 * aliases for it, because no cleanup is done.
 * This function removes all references to the core as well.
 */

static int delcore(NickCore * nc)
{
    int i;
#ifdef USE_RDB
    static char clause[128];
#endif
    /* (Hopefully complete) cleanup */
    cs_remove_nick(nc);
    os_remove_nick(nc);

    /* Remove the core from the list */
    if (nc->next)
        nc->next->prev = nc->prev;
    if (nc->prev)
        nc->prev->next = nc->next;
    else
        nclists[HASH(nc->display)] = nc->next;

    /* Log .. */
    alog("%s: deleting nickname group %s", s_NickServ, nc->display);

#ifdef USE_RDB
    /* Reflect this change in the database right away. */
    if (rdb_open()) {

        snprintf(clause, sizeof(clause), "display='%s'", nc->display);
        rdb_scrub_table("anope_ns_access", clause);
        rdb_scrub_table("anope_ns_core", clause);
        rdb_scrub_table("anope_cs_access", clause);
        /* I'm unsure how to clean up the OS ADMIN/OPER list on the db */
        /* I wish the "display" primary key would be the same on all tables */
        snprintf(clause, sizeof(clause), "receiver='%s' AND serv='NICK'",
                 nc->display);
        rdb_scrub_table("anope_ms_info", clause);
        rdb_close();
    }
#endif

    /* Now we can safely free it. */
    free(nc->display);
    if (nc->pass)
        free(nc->pass);

    if (nc->email)
        free(nc->email);
    if (nc->greet)
        free(nc->greet);
    if (nc->url)
        free(nc->url);

    if (nc->access) {
        for (i = 0; i < nc->accesscount; i++) {
            if (nc->access[i])
                free(nc->access[i]);
        }
        free(nc->access);
    }

    if (nc->memos.memos) {
        for (i = 0; i < nc->memos.memocount; i++) {
            if (nc->memos.memos[i].text)
                free(nc->memos.memos[i].text);
            moduleCleanStruct(&nc->memos.memos[i].moduleData);
        }
        free(nc->memos.memos);
    }

    moduleCleanStruct(&nc->moduleData);

    free(nc);

    return 1;
}


/*************************************************************************/
int delnickrequest(NickRequest * nr)
{
    if (nr) {
        nrlists[HASH(nr->nick)] = nr->next;
        if (nr->nick)
            free(nr->nick);
        if (nr->passcode)
            free(nr->passcode);
        if (nr->password)
            free(nr->password);
        if (nr->email)
            free(nr->email);
        free(nr);
    }

    return 0;
}

/*************************************************************************/

/* Deletes an alias. The core will also be deleted if it has no more
 * nicks attached to it. Easy but powerful.
 * Well, we must also take care that the nick being deleted is not
 * the core display, and if so, change it to the next alias in the list,
 * otherwise weird things will happen.
 * Returns 1 on success, 0 otherwise.
 */

int delnick(NickAlias * na)
{
#ifdef USE_RDB
    static char clause[128];
#endif
    /* First thing to do: remove any timeout belonging to the nick we're deleting */
    clean_ns_timeouts(na);

    /* Second thing to do: look for an user using the alias
     * being deleted, and make appropriate changes */

    if (na->u) {
        na->u->na = NULL;

        if (ircd->modeonunreg)
            common_svsmode(na->u, ircd->modeonunreg, "1");

    }

    delHostCore(na->nick);      /* delete any vHost's for this nick */

    /* Accept nicks that have no core, because of database load functions */
    if (na->nc) {
        /* Next: see if our core is still useful. */
        slist_remove(&na->nc->aliases, na);
        if (na->nc->aliases.count == 0) {
            if (!delcore(na->nc))
                return 0;
            na->nc = NULL;
        } else {
            /* Display updating stuff */
            if (!stricmp(na->nick, na->nc->display))
                change_core_display(na->nc, NULL);
        }
    }

    /* Remove us from the aliases list */
    if (na->next)
        na->next->prev = na->prev;
    if (na->prev)
        na->prev->next = na->next;
    else
        nalists[HASH(na->nick)] = na->next;

#ifdef USE_RDB
    /* Reflect this change in the database right away. */
    if (rdb_open()) {

        snprintf(clause, sizeof(clause), "nick='%s'", na->nick);
        rdb_scrub_table("anope_ns_alias", clause);
        rdb_close();
    }
#endif

    free(na->nick);
    if (na->last_usermask)
        free(na->last_usermask);
    if (na->last_realname)
        free(na->last_realname);
    if (na->last_quit)
        free(na->last_quit);

    moduleCleanStruct(&na->moduleData);

    free(na);


    return 1;
}

/*************************************************************************/
/*************************************************************************/

/* Collide a nick.
 *
 * When connected to a network using DALnet servers, version 4.4.15 and above,
 * Services is now able to force a nick change instead of killing the user.
 * The new nick takes the form "Guest######". If a nick change is forced, we
 * do not introduce the enforcer nick until the user's nick actually changes.
 * This is watched for and done in cancel_user(). -TheShadow
 */

void collide(NickAlias * na, int from_timeout)
{
    char guestnick[NICKMAX];

    if (!from_timeout)
        del_ns_timeout(na, TO_COLLIDE);

    /* Old system was unsure since there can be more than one collide
     * per second. So let use another safer method.
     *          --lara
     */
    /* So you should check the length of NSGuestNickPrefix, eh Lara?
     *          --Certus
     */

    if (ircd->svsnick) {
        /* We need to make sure the guestnick is free -- heinz */
        do {
            snprintf(guestnick, sizeof(guestnick), "%s%d",
                     NSGuestNickPrefix, getrandom16());
        } while (finduser(guestnick));
        notice_lang(s_NickServ, na->u, FORCENICKCHANGE_CHANGING,
                    guestnick);
        anope_cmd_svsnick(na->nick, guestnick, time(NULL));
        na->status |= NS_GUESTED;
    } else {
        kill_user(s_NickServ, na->nick, "Services nickname-enforcer kill");
    }
}

/*************************************************************************/

/* Release hold on a nick. */

void release(NickAlias * na, int from_timeout)
{
    if (!from_timeout)
        del_ns_timeout(na, TO_RELEASE);
    if (ircd->svshold) {
        if (UseSVSHOLD) {
            anope_cmd_release_svshold(na->nick);
        } else {
            anope_cmd_quit(na->nick, NULL);
        }
    } else {
        anope_cmd_quit(na->nick, NULL);
    }
    na->status &= ~NS_KILL_HELD;
}

/*************************************************************************/
/*************************************************************************/

static struct my_timeout {
    struct my_timeout *next, *prev;
    NickAlias *na;
    Timeout *to;
    int type;
} *my_timeouts;

/*************************************************************************/

/* Remove a collide/release timeout from our private list. */

static void rem_ns_timeout(NickAlias * na, int type)
{
    struct my_timeout *t, *t2;

    t = my_timeouts;
    while (t) {
        if (t->na == na && t->type == type) {
            t2 = t->next;
            if (t->next)
                t->next->prev = t->prev;
            if (t->prev)
                t->prev->next = t->next;
            else
                my_timeouts = t->next;
            free(t);
            t = t2;
        } else {
            t = t->next;
        }
    }
}

/*************************************************************************/

/* Collide a nick on timeout. */

static void timeout_collide(Timeout * t)
{
    NickAlias *na = t->data;

    rem_ns_timeout(na, TO_COLLIDE);
    /* If they identified or don't exist anymore, don't kill them. */
    if ((na->status & NS_IDENTIFIED) || !na->u
        || na->u->my_signon > t->settime)
        return;
    /* The RELEASE timeout will always add to the beginning of the
     * list, so we won't see it.  Which is fine because it can't be
     * triggered yet anyway. */
    collide(na, 1);
}

/*************************************************************************/

/* Release a nick on timeout. */

static void timeout_release(Timeout * t)
{
    NickAlias *na = t->data;

    rem_ns_timeout(na, TO_RELEASE);
    release(na, 1);
}

/*************************************************************************/

/* Add a collide/release timeout. */

void add_ns_timeout(NickAlias * na, int type, time_t delay)
{
    Timeout *to;
    struct my_timeout *t;
    void (*timeout_routine) (Timeout *);

    if (type == TO_COLLIDE)
        timeout_routine = timeout_collide;
    else if (type == TO_RELEASE)
        timeout_routine = timeout_release;
    else {
        alog("NickServ: unknown timeout type %d! na=0x%p (%s), delay=%ld",
             type, (void *) na, na->nick, (long int) delay);
        return;
    }

    to = add_timeout(delay, timeout_routine, 0);
    to->data = na;

    t = scalloc(sizeof(struct my_timeout), 1);
    t->na = na;
    t->to = to;
    t->type = type;

    t->prev = NULL;
    t->next = my_timeouts;
    my_timeouts = t;
    /* Andy Church should stop coding while being drunk.
     * Here's the two lines he forgot that produced the timed_update evil bug
     * and a *big* memory leak.
     */
    if (t->next)
        t->next->prev = t;
}

/*************************************************************************/

/* Delete a collide/release timeout. */

void del_ns_timeout(NickAlias * na, int type)
{
    struct my_timeout *t, *t2;

    t = my_timeouts;
    while (t) {
        if (t->na == na && t->type == type) {
            t2 = t->next;
            if (t->next)
                t->next->prev = t->prev;
            if (t->prev)
                t->prev->next = t->next;
            else
                my_timeouts = t->next;
            del_timeout(t->to);
            free(t);
            t = t2;
        } else {
            t = t->next;
        }
    }
}

/*************************************************************************/

/* Deletes all timeouts belonging to a given nick.
 * This should only be called before nick deletion.
 */

void clean_ns_timeouts(NickAlias * na)
{
    struct my_timeout *t, *next;

    for (t = my_timeouts; t; t = next) {
        next = t->next;
        if (t->na == na) {
            if (debug)
                alog("debug: %s deleting timeout type %d from %s",
                     s_NickServ, t->type, t->na->nick);
            /* If the timeout has the TO_RELEASE type, we should release the user */
            if (t->type == TO_RELEASE)
                release(na, 1);
            if (t->next)
                t->next->prev = t->prev;
            if (t->prev)
                t->prev->next = t->next;
            else
                my_timeouts = t->next;
            del_timeout(t->to);
            free(t);
        }
    }
}

/*************************************************************************/
/*********************** NickServ command routines ***********************/
/*************************************************************************/
int should_mode_change(int16 status, int16 mode)
{
    switch (mode) {
    case CUS_OP:
        if (status & CUS_OP) {
            return 0;
        }
        break;
    case CUS_VOICE:
        if (status & CUS_OP) {
            return 0;
        }
        if (status & CUS_HALFOP) {
            return 0;
        }
        if (status & CUS_VOICE) {
            return 0;
        }
        return 1;
        break;
    case CUS_HALFOP:
        if (status & CUS_OP) {
            return 0;
        }
        if (status & CUS_HALFOP) {
            return 0;
        }
        return 1;
        break;
    case CUS_OWNER:
        if (ircd->owner) {
            if (status & CUS_OWNER) {
                return 0;
            }
        }
        break;
    case CUS_PROTECT:
        if (ircd->protect) {
            if (status & CUS_PROTECT) {
                return 0;
            }
        }
        break;
    }
    return 1;
}

/*************************************************************************/

int do_setmodes(User * u)
{
    struct u_chanlist *uc;
    Channel *c;

    /* Walk users current channels */
    for (uc = u->chans; uc; uc = uc->next) {
        if ((c = uc->chan))
            chan_set_correct_modes(u, c, 1);
    }
    return MOD_CONT;
}

/*************************************************************************/
/*               
 * Nick tracking
 */

/**
 * Start Nick tracking and store the nick core display under the user struct.
 * @param u The user to track nicks for
 **/
void nsStartNickTracking(User * u)
{
    NickCore *nc;

    /* We only track identified users */
    if (nick_identified(u)) {
        nc = u->na->nc;

        /* Release memory if needed */
        if (u->nickTrack)
            free(u->nickTrack);

        /* Copy the nick core displayed nick to
           the user structure for further checks */
        u->nickTrack = sstrdup(nc->display);
    }
}

/**
 * Stop Nick tracking and remove the nick core display under the user struct.
 * @param u The user to stop tracking for
 **/
void nsStopNickTracking(User * u)
{
    /* Simple enough. If its there, release it */
    if (u->nickTrack) {
        free(u->nickTrack);
        u->nickTrack = NULL;
    }
}

/**
 * Boolean function to check if the user requesting a nick has the tracking
 * signature of that core in its structure.
 * @param u The user whom to check tracking for
 **/
int nsCheckNickTracking(User * u)
{
    NickCore *nc;
    NickAlias *na;
    char *nick;

    /* No nick alias or nick return false by default */
    if ((!(na = u->na)) || (!(nick = na->nick))) {
        return 0;
    }

    /* nick is forbidden best return 0 */
    if (na->status & NS_VERBOTEN) {
        return 0;
    }

    /* Get the core for the requested nick */
    nc = na->nc;

    /* If the core and the tracking displayed nick are there,
     * and they match, return true
     */
    if (nc && u->nickTrack && (strcmp(nc->display, u->nickTrack) == 0))
        return 1;
    else
        return 0;
}
