/* NickServ functions.
 *
 * (C) 2003 Anope Team
 * Contact us at info@anope.org
 *
 * Please read COPYING and README for furhter details.
 *
 * Based on the original code of Epona by Lara.
 * Based on the original code of Services by Andy Church. 
 * 
 * $Id: nickserv.c,v 1.86 2004/01/18 04:54:40 dane Exp $
 *
 */

#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];

static 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);

static int is_on_access(User * u, NickCore * nc);
static void alpha_insert_alias(NickAlias * na);
static void insert_core(NickCore * nc);
static void insert_requestnick(NickRequest * nr);
static NickAlias *makenick(const char *nick);
static NickRequest *makerequest(const char *nick);
static NickAlias *makealias(const char *nick, NickCore * nc);
static void change_core_display(NickCore * nc, char *newdisplay);
static int group_identified(User * u, NickCore * nc);

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

static int do_help(User * u);
static int do_register(User * u);
static int do_confirm(User * u);
static int do_group(User * u);
static int do_nickupdate(User * u);
static int do_identify(User * u);
static int do_logout(User * u);
static int do_drop(User * u);
static int do_set(User * u);
static int do_set_display(User * u, NickCore * nc, char *param);
static int do_set_password(User * u, NickCore * nc, char *param);
static int do_set_language(User * u, NickCore * nc, char *param);
static int do_set_url(User * u, NickCore * nc, char *param);
static int do_set_email(User * u, NickCore * nc, char *param);
static int do_set_greet(User * u, NickCore * nc, char *param);
static int do_set_icq(User * u, NickCore * nc, char *param);
static int do_set_kill(User * u, NickCore * nc, char *param);
static int do_set_secure(User * u, NickCore * nc, char *param);
static int do_set_private(User * u, NickCore * nc, char *param);
static int do_set_msg(User * u, NickCore * nc, char *param);
static int do_set_hide(User * u, NickCore * nc, char *param);
static int do_set_noexpire(User * u, NickAlias * nc, char *param);
static int do_access(User * u);
static int do_info(User * u);
static int do_list(User * u);
static int do_alist(User * u);
static int do_glist(User * u);
static int do_recover(User * u);
static int do_release(User * u);
static int do_ghost(User * u);
static int do_status(User * u);
static int do_getpass(User * u);
static int do_getemail(User * u);
static int do_sendpass(User * u);
static int do_forbid(User * u);
static int do_resend(User * u);

/* Obsolete commands */
static int do_link(User * u);
static int do_unlink(User * u);
static int do_listlinks(User * u);


/*************************************************************************/
/* *INDENT-OFF* */
void moduleAddNickServCmds(void) {
    Command *c;
    c = createCommand("HELP",     do_help,     NULL,  -1,                     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("REGISTER", do_register, NULL,  NICK_HELP_REGISTER,     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("RESEND", do_resend,     NULL,  -1,                     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("CONFIRM",  do_confirm,  NULL,  -1,		      -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("GROUP",    do_group,    NULL,  NICK_HELP_GROUP,        -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("UPDATE",   do_nickupdate,    NULL,  NICK_HELP_UPDATE,        -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("ID",       do_identify, NULL,  NICK_HELP_IDENTIFY,     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("IDENTIFY", do_identify, NULL,  NICK_HELP_IDENTIFY,     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SIDENTIFY",do_identify, NULL,  -1,                     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("LOGOUT",   do_logout,   NULL,  -1,NICK_HELP_LOGOUT, NICK_SERVADMIN_HELP_LOGOUT,NICK_SERVADMIN_HELP_LOGOUT, NICK_SERVADMIN_HELP_LOGOUT); addCoreCommand(NICKSERV,c);
    c = createCommand("DROP",     do_drop,     NULL,  -1,NICK_HELP_DROP, NICK_SERVADMIN_HELP_DROP,NICK_SERVADMIN_HELP_DROP, NICK_SERVADMIN_HELP_DROP); addCoreCommand(NICKSERV,c);
    c = createCommand("LINK",     do_link,     NULL,  -1,                     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("UNLINK",   do_unlink,   NULL,  -1,                     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("LISTLINKS",do_listlinks,NULL,  -1,                     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("ACCESS",   do_access,   NULL,  NICK_HELP_ACCESS,       -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET",      do_set,      NULL,  NICK_HELP_SET,-1, NICK_SERVADMIN_HELP_SET,NICK_SERVADMIN_HELP_SET, NICK_SERVADMIN_HELP_SET); addCoreCommand(NICKSERV,c);
    c = createCommand("SET DISPLAY",  NULL,    NULL,  NICK_HELP_SET_DISPLAY,  -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET PASSWORD", NULL,    NULL,  NICK_HELP_SET_PASSWORD, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET URL",      NULL,    NULL,  NICK_HELP_SET_URL,      -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET EMAIL",    NULL,    NULL,  NICK_HELP_SET_EMAIL,    -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET ICQ",      NULL,    NULL,  NICK_HELP_SET_ICQ,      -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET GREET",    NULL,    NULL,  NICK_HELP_SET_GREET,    -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET KILL",     NULL,    NULL,  NICK_HELP_SET_KILL,     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET SECURE",   NULL,    NULL,  NICK_HELP_SET_SECURE,   -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET PRIVATE",  NULL,    NULL,  NICK_HELP_SET_PRIVATE,  -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET MSG",      NULL,    NULL,  NICK_HELP_SET_MSG,      -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET HIDE",     NULL,    NULL,  NICK_HELP_SET_HIDE,     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SET NOEXPIRE", NULL,    NULL,  -1, -1,NICK_SERVADMIN_HELP_SET_NOEXPIRE,NICK_SERVADMIN_HELP_SET_NOEXPIRE,NICK_SERVADMIN_HELP_SET_NOEXPIRE); addCoreCommand(NICKSERV,c);
    c = createCommand("RECOVER",  do_recover,  NULL,  NICK_HELP_RECOVER,      -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("RELEASE",  do_release,  NULL,  NICK_HELP_RELEASE,      -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("GHOST",    do_ghost,    NULL,  NICK_HELP_GHOST,        -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("INFO",     do_info,     NULL,  NICK_HELP_INFO,-1, NICK_HELP_INFO, NICK_SERVADMIN_HELP_INFO,NICK_SERVADMIN_HELP_INFO); addCoreCommand(NICKSERV,c);
    c = createCommand("LIST",     do_list,     NULL,  -1,NICK_HELP_LIST, NICK_SERVADMIN_HELP_LIST,NICK_SERVADMIN_HELP_LIST, NICK_SERVADMIN_HELP_LIST); addCoreCommand(NICKSERV,c);
    c = createCommand("ALIST",    do_alist,    NULL,  -1,NICK_HELP_ALIST, NICK_SERVADMIN_HELP_ALIST,NICK_SERVADMIN_HELP_ALIST, NICK_SERVADMIN_HELP_ALIST); addCoreCommand(NICKSERV,c);
    c = createCommand("GLIST",    do_glist,    NULL,  -1,NICK_HELP_GLIST, NICK_SERVADMIN_HELP_GLIST,NICK_SERVADMIN_HELP_GLIST, NICK_SERVADMIN_HELP_GLIST); addCoreCommand(NICKSERV,c);
    c = createCommand("STATUS",   do_status,   NULL,  NICK_HELP_STATUS,       -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("SENDPASS", do_sendpass, NULL,  NICK_HELP_SENDPASS,     -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
    c = createCommand("GETPASS",  do_getpass,  is_services_admin,  -1,-1, NICK_SERVADMIN_HELP_GETPASS,NICK_SERVADMIN_HELP_GETPASS, NICK_SERVADMIN_HELP_GETPASS); addCoreCommand(NICKSERV,c);
    c = createCommand("GETEMAIL", do_getemail, is_services_admin,  -1,-1, NICK_SERVADMIN_HELP_GETEMAIL,NICK_SERVADMIN_HELP_GETEMAIL, NICK_SERVADMIN_HELP_GETEMAIL); addCoreCommand(NICKSERV,c);
    c = createCommand("FORBID",   do_forbid,   is_services_admin,  -1,-1, NICK_SERVADMIN_HELP_FORBID,NICK_SERVADMIN_HELP_FORBID, NICK_SERVADMIN_HELP_FORBID); addCoreCommand(NICKSERV,c);
}
/* *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(buf, sizeof(buf) - (end - buf), "%sSecurity",
                            need_comma ? commastr : "");
            need_comma = 1;
        }
        if (na->nc->flags & NI_PRIVATE) {
            end += snprintf(buf, sizeof(buf) - (end - buf), "%sPrivate",
                            need_comma ? commastr : "");
            need_comma = 1;
        }
        if (na->status & NS_NO_EXPIRE) {
            end += snprintf(buf, 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 = "\1";
        notice(s_NickServ, u->nick, "\1PING %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;

    int16 tmp16;
    int32 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(&nc->memos.memocount, f));
                SAFE(read_int16(&nc->memos.memomax, f));
                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));
                    }
                }

                /* 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;
    int32 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;
    int16 tmp16;
    int32 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(&nc->memos.memocount, f));
            SAFE(read_int16(&nc->memos.memomax, f));
            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));
                }
            }

            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) {		\
	    wallops(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;
    }

    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(GetIdent(u)) + strlen(GetHost(u)) + 2, 1);
        sprintf(na->last_usermask, "%s@%s", GetIdent(u), GetHost(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) {
            NEWNICK(u->nick, NSEnforcerUser, NSEnforcerHost,
                    "Services Enforcer", "+", 0);
            add_ns_timeout(na, TO_RELEASE, NSReleaseTimeout);
            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)
{
    return u->na && (u->na->status & NS_IDENTIFIED);
}

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

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

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

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

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

static 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);

    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"));
                delnick(na);
            }
        }
    }
}

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)
        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;

    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;

    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. */

static int is_on_access(User * u, NickCore * nc)
{
    int i;
    char *buf;
#ifdef HAS_VHOST
    char *buf2 = NULL;
#endif

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

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

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

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

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

static void alpha_insert_alias(NickAlias * na)
{
    NickAlias *ptr, *prev;
    char *nick = na->nick;
    int 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. */

static void insert_core(NickCore * nc)
{
    int index = HASH(nc->display);

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

/*************************************************************************/
static void insert_requestnick(NickRequest * nr)
{
    int index = HASH(nr->nick);

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

/*************************************************************************/
/* Creates a new Nick Request */
static NickRequest *makerequest(const char *nick)
{
    NickRequest *nr;

    nr = scalloc(1, sizeof(NickRequest));
    nr->nick = sstrdup(nick);
    insert_requestnick(nr);
    alog("%s: Nick %s has been requested", s_NickServ, nr->nick);
    return nr;
}



/* Creates a full new nick (alias + core) in NickServ database. */

static NickAlias *makenick(const char *nick)
{
    NickAlias *na;
    NickCore *nc;

    /* First make the core */
    nc = scalloc(1, sizeof(NickCore));
    nc->display = sstrdup(nick);
    slist_init(&nc->aliases);
    insert_core(nc);
    alog("%s: group %s has been created", s_NickServ, nc->display);

    /* Then make the alias */
    na = scalloc(1, sizeof(NickAlias));
    na->nick = sstrdup(nick);
    na->nc = nc;
    slist_add(&nc->aliases, na);
    alpha_insert_alias(na);
    return na;
}

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

/* Creates a new alias in NickServ database. */

static NickAlias *makealias(const char *nick, NickCore * nc)
{
    NickAlias *na;

    /* Just need to make the alias */
    na = scalloc(1, sizeof(NickAlias));
    na->nick = sstrdup(nick);
    na->nc = nc;
    slist_add(&nc->aliases, na);
    alpha_insert_alias(na);
    return na;
}

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

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

static 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);
        }
        free(nc->memos.memos);
    }

    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;

#ifndef IRC_PTLINK
        change_user_mode(na->u, "-r+d", "1");
#else
        change_user_mode(na->u, "-r", NULL);
#endif


    }

    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);

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

static 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
     */

    snprintf(guestnick, sizeof(guestnick), "%s%d", NSGuestNickPrefix,
             guestnum++);
#ifdef IRC_HYBRID
    kill_user(s_NickServ, na->nick, "Services nickname-enforcer kill");
#else
    notice_lang(s_NickServ, na->u, FORCENICKCHANGE_CHANGING, guestnick);
    send_cmd(NULL, "SVSNICK %s %s :%ld", na->nick, guestnick, time(NULL));
    na->status |= NS_GUESTED;
#endif
}

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

/* Release hold on a nick. */

static void release(NickAlias * na, int from_timeout)
{
    if (!from_timeout)
        del_ns_timeout(na, TO_RELEASE);
    send_cmd(na->nick, "QUIT");
    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=%p (%s), delay=%ld",
             type, na, na->nick, 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. */

static 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("%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 ***********************/
/*************************************************************************/

/* Return a help message. */

static int do_help(User * u)
{
    char *cmd = strtok(NULL, "");

    if (!cmd) {
        notice_help(s_NickServ, u, NICK_HELP);
        if (NSExpire >= 86400)
            notice_help(s_NickServ, u, NICK_HELP_EXPIRES,
                        NSExpire / 86400);
        if (is_services_oper(u))
            notice_help(s_NickServ, u, NICK_SERVADMIN_HELP);
        moduleDisplayHelp(1, u);
    } else if (stricmp(cmd, "SET LANGUAGE") == 0) {
        int i;
        notice_help(s_NickServ, u, NICK_HELP_SET_LANGUAGE);
        for (i = 0; i < NUM_LANGS && langlist[i] >= 0; i++)
            notice_user(s_NickServ, u, "    %2d) %s", i + 1,
                        langnames[langlist[i]]);
    } else {
        mod_help_cmd(s_NickServ, u, NICKSERV, cmd);
    }
    return MOD_CONT;
}

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

/* Register a nick. */

static int do_register(User * u)
{
    NickRequest *nr = NULL, *anr = NULL;
    int prefixlen = strlen(NSGuestNickPrefix);
    int nicklen = strlen(u->nick);
    char *pass = strtok(NULL, " ");
    char *email = strtok(NULL, " ");
    char passcode[11];
    int idx, min = 1, max = 62;
    int chars[] =
        { ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
        'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
        'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
        'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
        'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
    };

    if (readonly) {
        notice_lang(s_NickServ, u, NICK_REGISTRATION_DISABLED);
        return MOD_CONT;
    }

    if (checkDefCon(DEFCON_NO_NEW_NICKS)) {
        notice_lang(s_NickServ, u, OPER_DEFCON_DENIED);
        return MOD_CONT;
    }

    if ((anr = findrequestnick(u->nick))) {
        notice_lang(s_NickServ, u, NICK_REQUESTED);
        return MOD_CONT;
    }
    /* Prevent "Guest" nicks from being registered. -TheShadow */

    /* Guest nick can now have a series of between 1 and 7 digits.
     *      --lara
     */
    if (nicklen <= prefixlen + 7 && nicklen >= prefixlen + 1 &&
        stristr(u->nick, NSGuestNickPrefix) == u->nick &&
        strspn(u->nick + prefixlen, "1234567890") == nicklen - prefixlen) {
        notice_lang(s_NickServ, u, NICK_CANNOT_BE_REGISTERED, u->nick);
        return MOD_CONT;
    }

    if (!pass || (NSForceEmail && !email)) {
        syntax_error(s_NickServ, u, "REGISTER",
                     NICK_REGISTER_SYNTAX_EMAIL);
    } else if (time(NULL) < u->lastnickreg + NSRegDelay) {
        notice_lang(s_NickServ, u, NICK_REG_PLEASE_WAIT, NSRegDelay);
    } else if (u->na) {         /* i.e. there's already such a nick regged */
        if (u->na->status & NS_VERBOTEN) {
            alog("%s: %s@%s tried to register FORBIDden nick %s",
                 s_NickServ, u->username, GetHost(u), u->nick);
            notice_lang(s_NickServ, u, NICK_CANNOT_BE_REGISTERED, u->nick);
        } else {
            notice_lang(s_NickServ, u, NICK_ALREADY_REGISTERED, u->nick);
        }
    } else if (stricmp(u->nick, pass) == 0
               || (StrictPasswords && strlen(pass) < 5)) {
        notice_lang(s_NickServ, u, MORE_OBSCURE_PASSWORD);
    } else if (email && !MailValidate(email)) {
        notice_lang(s_NickServ, u, MAIL_X_INVALID, email);
    } else {
#ifdef USE_ENCRYPTION
        if (strlen(pass) > PASSMAX) {
            pass[PASSMAX] = 0;
            notice_lang(s_NickServ, u, PASSWORD_TRUNCATED, PASSMAX);
        }
#else
        if (strlen(pass) > PASSMAX - 1) {       /* -1 for null byte */
            pass[PASSMAX] = 0;
            notice_lang(s_NickServ, u, PASSWORD_TRUNCATED, PASSMAX - 1);
        }
#endif
        srand((unsigned) time(NULL));
        for (idx = 0; idx < 9; idx++) {
            passcode[idx] =
                chars[(1 +
                       (int) (((float) (max - min)) * rand() /
                              (RAND_MAX + 1.0)) + min)];
        } passcode[idx] = '\0';
        nr = makerequest(u->nick);
        nr->passcode = sstrdup(passcode);
        nr->password = sstrdup(pass);
        if (email) {
            nr->email = sstrdup(email);
        }
        nr->requested = time(NULL);
        if (NSEmailReg) {
            if (do_sendregmail(u, nr) == 0) {
                notice_lang(s_NickServ, u, NICK_ENTER_REG_CODE, email,
                            s_NickServ);
                alog("%s: sent registration verification code to %s",
                     s_NickServ, nr->email);
            } else {
                alog("%s: Unable to send registration verification mail",
                     s_NickServ);
                notice_lang(s_NickServ, u, NICK_REG_UNABLE);
                delnickrequest(nr);     /* Delete the NickRequest if we couldnt send the mail */
                return MOD_CONT;
            }
        } else {
            do_confirm(u);
        }

    }
    return MOD_CONT;
}


static int do_resend(User * u)
{
    NickRequest *nr = NULL;
    if (NSEmailReg) {
        if ((nr = findrequestnick(u->nick))) {
            if (do_sendregmail(u, nr) == 0) {
                notice_lang(s_NickServ, u, NICK_REG_RESENT, nr->email);
                alog("%s: re-sent registration verification code for %s to %s", s_NickServ, nr->nick, nr->email);
            } else {
                alog("%s: Unable to re-send registration verification mail for %s", s_NickServ, nr->nick);
                return MOD_CONT;
            }
        }
    }
    return MOD_CONT;
}

static int do_sendregmail(User * u, NickRequest * nr)
{
    MailInfo *mail = NULL;
    char buf[BUFSIZE];

    if (!(nr || u)) {
        return -1;
    }
    snprintf(buf, sizeof(buf), getstring2(NULL, NICK_REG_MAIL_SUBJECT),
             nr->nick);
    mail = MailRegBegin(u, nr, buf, s_NickServ);
    if (!mail) {
        return -1;
    }
    fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_HEAD));
    fprintf(mail->pipe, "\n\n");
    fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_1), nr->nick);
    fprintf(mail->pipe, "\n\n");
    fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_2), s_NickServ,
            nr->passcode);
    fprintf(mail->pipe, "\n\n");
    fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_3));
    fprintf(mail->pipe, "\n\n");
    fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_4));
    fprintf(mail->pipe, "\n\n");
    fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_5),
            NetworkName);
    fprintf(mail->pipe, "\n.\n");
    MailEnd(mail);
    return 0;
}

static int do_confirm(User * u)
{

    NickRequest *nr = NULL;
    NickAlias *na = NULL;
    char *passcode = strtok(NULL, " ");
    char *pass = NULL;
    char *email = NULL;
    int forced = 0;
#ifdef USE_ENCRYPTION
    int len;
#endif
    nr = findrequestnick(u->nick);

    if (NSEmailReg) {
        if (!passcode) {
            notice_lang(s_NickServ, u, NICK_CONFIRM_INVALID);
            return MOD_CONT;
        }

        if (!nr) {
            if (is_services_admin(u)) {
/* If an admin, thier nick is obviously already regged, so look at the passcode to get the nick
   of the user they are trying to validate, and push that user through regardless of passcode */
                nr = findrequestnick(passcode);
                if (nr) {
                    passcode = sstrdup(nr->passcode);
                    forced = 1;
                } else {
                    notice_lang(s_NickServ, u, NICK_CONFIRM_NOT_FOUND,
                                s_NickServ);
                    return MOD_CONT;
                }
            } else {
                notice_lang(s_NickServ, u, NICK_CONFIRM_NOT_FOUND,
                            s_NickServ);
                return MOD_CONT;
            }
        }

        if (stricmp(nr->passcode, passcode) != 0) {
            notice_lang(s_NickServ, u, NICK_CONFIRM_INVALID);
            return MOD_CONT;
        }
    }

    if (!nr) {
        notice_lang(s_NickServ, u, NICK_REGISTRATION_FAILED);
        return MOD_CONT;
    }
    pass = sstrdup(nr->password);

    if (nr->email) {
        email = sstrdup(nr->email);
    }
    na = makenick(nr->nick);

    if (na) {
        int i;
#if !defined(IRC_PTLINK)
        char tsbuf[16];
#endif

#ifdef USE_ENCRYPTION
        len = strlen(pass);
        na->nc->pass = smalloc(PASSMAX);
        if (encrypt(pass, len, na->nc->pass, PASSMAX) < 0) {
            memset(pass, 0, strlen(pass));
            alog("%s: Failed to encrypt password for %s (register)",
                 s_NickServ, nr->nick);
            notice_lang(s_NickServ, u, NICK_REGISTRATION_FAILED);
            return MOD_CONT;
        }
        memset(pass, 0, strlen(pass));
        na->status = NS_IDENTIFIED | NS_RECOGNIZED;
        na->nc->flags |= NI_ENCRYPTEDPW;
#else
        na->nc->pass = sstrdup(pass);
        na->status = NS_IDENTIFIED | NS_RECOGNIZED;
#endif
        na->nc->flags |= NSDefFlags;
        for (i = 0; i < RootNumber; i++) {
            if (!stricmp(ServicesRoots[i], nr->nick)) {
                na->nc->flags |= NI_SERVICES_ROOT;
                break;
            }
        }
        na->nc->memos.memomax = MSMaxMemos;
        na->nc->channelmax = CSMaxReg;
        if (forced == 1) {
            na->last_usermask = sstrdup("*@*");
            na->last_realname = sstrdup("unknown");
        } else {
            na->last_usermask =
                scalloc(strlen(GetIdent(u)) + strlen(GetHost(u)) + 2, 1);
            sprintf(na->last_usermask, "%s@%s", GetIdent(u), GetHost(u));
            na->last_realname = sstrdup(u->realname);
        }
        na->time_registered = na->last_seen = time(NULL);
        na->nc->accesscount = 1;
        na->nc->access = scalloc(sizeof(char *), 1);
        na->nc->access[0] = create_mask(u);
        na->nc->language = NSDefLanguage;
        if (email)
            na->nc->email = sstrdup(email);
        if (forced != 1) {
            u->na = na;
            na->u = u;
            alog("%s: `%s' registered by %s@%s (e-mail: %s)", s_NickServ,
                 u->nick, u->username, GetHost(u),
                 (email ? email : "none"));
            notice_lang(s_NickServ, u, NICK_REGISTERED, u->nick,
                        na->nc->access[0]);
#ifndef USE_ENCRYPTION
            notice_lang(s_NickServ, u, NICK_PASSWORD_IS, na->nc->pass);
#endif
            u->lastnickreg = time(NULL);
#if !defined(IRC_PTLINK)
            snprintf(tsbuf, sizeof(tsbuf), "%lu", u->timestamp);
            change_user_mode(u, "+rd", tsbuf);
#else
            change_user_mode(u, "+r", NULL);
#endif

        } else {
            notice_lang(s_NickServ, u, NICK_FORCE_REG, nr->nick);
        }
        delnickrequest(nr);     /* remove the nick request */
    } else {
        alog("%s: makenick(%s) failed", s_NickServ, u->nick);
        notice_lang(s_NickServ, u, NICK_REGISTRATION_FAILED);
    }

    return MOD_CONT;
}

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

/* Register a nick in a specified group. */

static int do_group(User * u)
{
    NickAlias *na, *target;
    char *nick = strtok(NULL, " ");
    char *pass = strtok(NULL, " ");
    int i;
#if !defined(IRC_PTLINK)
    char tsbuf[16];
#endif

    if (readonly) {
        notice_lang(s_NickServ, u, NICK_GROUP_DISABLED);
        return MOD_CONT;
    }
    if (checkDefCon(DEFCON_NO_NEW_NICKS)) {
        notice_lang(s_NickServ, u, OPER_DEFCON_DENIED);
        return MOD_CONT;
    }

    if (!nick || !pass) {
        syntax_error(s_NickServ, u, "GROUP", NICK_GROUP_SYNTAX);
    } else if (time(NULL) < u->lastnickreg + NSRegDelay) {
        notice_lang(s_NickServ, u, NICK_GROUP_PLEASE_WAIT, NSRegDelay);
    } else if (u->na && (u->na->status & NS_VERBOTEN)) {
        alog("%s: %s@%s tried to use GROUP from FORBIDden nick %s",
             s_NickServ, u->username, GetHost(u), u->nick);
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u->nick);
    } else if (u->na && NSNoGroupChange) {
        notice_lang(s_NickServ, u, NICK_GROUP_CHANGE_DISABLED, s_NickServ);
    } else if (u->na && !nick_identified(u)) {
        notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
    } else if (!(target = findnick(nick))) {
        notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
    } else if (target->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
    } else if (u->na && target->nc == u->na->nc) {
        notice_lang(s_NickServ, u, NICK_GROUP_SAME, target->nick);
    } else if (NSMaxAliases && (target->nc->aliases.count >= NSMaxAliases)
               && !nick_is_services_admin(target->nc)) {
        notice_lang(s_NickServ, u, NICK_GROUP_TOO_MANY, target->nick,
                    s_NickServ, s_NickServ);
    } else if (check_password(pass, target->nc->pass) != 1) {
        alog("%s: Failed GROUP for %s!%s@%s (invalid password)",
             s_NickServ, u->nick, u->username, GetHost(u));
        notice_lang(s_NickServ, u, PASSWORD_INCORRECT);
        bad_password(u);
    } else {
        /* If the nick is already registered, drop it.
         * If not, check that it is valid.
         */
        if (u->na) {
            delnick(u->na);
        } else {
            int prefixlen = strlen(NSGuestNickPrefix);
            int nicklen = strlen(u->nick);

            if (nicklen <= prefixlen + 7 && nicklen >= prefixlen + 1
                && stristr(u->nick, NSGuestNickPrefix) == u->nick
                && strspn(u->nick + prefixlen,
                          "1234567890") == nicklen - prefixlen) {
                notice_lang(s_NickServ, u, NICK_CANNOT_BE_REGISTERED,
                            u->nick);
                return MOD_CONT;
            }
        }
        na = makealias(u->nick, target->nc);

        if (na) {
            na->last_usermask =
                scalloc(strlen(GetIdent(u)) + strlen(GetHost(u)) + 2, 1);
            sprintf(na->last_usermask, "%s@%s", GetIdent(u), GetHost(u));
            na->last_realname = sstrdup(u->realname);
            na->time_registered = na->last_seen = time(NULL);
            na->status = NS_IDENTIFIED | NS_RECOGNIZED;

            if (!(na->nc->flags & NI_SERVICES_ROOT)) {
                for (i = 0; i < RootNumber; i++) {
                    if (!stricmp(ServicesRoots[i], u->nick)) {
                        na->nc->flags |= NI_SERVICES_ROOT;
                        break;
                    }
                }
            }

            u->na = na;
            na->u = u;

#ifdef USE_RDB
            /* Is this really needed? Since this is a new alias it will get
             * its unique id on the next update, since it was previously
             * deleted by delnick. Must observe...
             */
            if (rdb_open()) {
                rdb_save_ns_alias(na);
                rdb_close();
            }
#endif
            alog("%s: %s!%s@%s makes %s join group of %s (%s) (e-mail: %s)", s_NickServ, u->nick, u->username, GetHost(u), u->nick, target->nick, target->nc->display, (target->nc->email ? target->nc->email : "none"));
            notice_lang(s_NickServ, u, NICK_GROUP_JOINED, target->nick);

            u->lastnickreg = time(NULL);
#if !defined(IRC_PTLINK)
            snprintf(tsbuf, sizeof(tsbuf), "%lu", u->timestamp);
            change_user_mode(u, "+rd", tsbuf);
#else
            change_user_mode(u, "+r", NULL);
#endif

            check_memos(u);
        } else {
            alog("%s: makealias(%s) failed", s_NickServ, u->nick);
            notice_lang(s_NickServ, u, NICK_GROUP_FAILED);
        }
    }
    return MOD_CONT;
}

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

static int do_nickupdate(User * u)
{
    NickAlias *na;

    if (!nick_identified(u)) {
        notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
    } else {
        na = u->na;
        if (NSModeOnID)
            do_setmodes(u);
        check_memos(u);
        if (na->last_realname)
            free(na->last_realname);
        na->last_realname = sstrdup(u->realname);
        na->status |= NS_IDENTIFIED;
        na->last_seen = time(NULL);
        do_on_id(u);
        notice_lang(s_NickServ, u, NICK_UPDATE_SUCCESS, s_NickServ);
    }
    return MOD_CONT;
}

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

static int do_identify(User * u)
{
    char *pass = strtok(NULL, " ");
    NickAlias *na;
    NickRequest *nr;
    int res;
#if !defined(IRC_PTLINK)
    char tsbuf[16];
#endif

    if (!pass) {
        syntax_error(s_NickServ, u, "IDENTIFY", NICK_IDENTIFY_SYNTAX);
    } else if (!(na = u->na)) {
        if ((nr = findrequestnick(u->nick))) {
            notice_lang(s_NickServ, u, NICK_IS_PREREG);
        } else {
            notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
        }
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else if (!(res = check_password(pass, na->nc->pass))) {
        alog("%s: Failed IDENTIFY for %s!%s@%s", s_NickServ, u->nick,
             u->username, GetHost(u));
        notice_lang(s_NickServ, u, PASSWORD_INCORRECT);
        bad_password(u);
    } else if (res == -1) {
        notice_lang(s_NickServ, u, NICK_IDENTIFY_FAILED);
    } else if (nick_identified(u)) {
        notice_lang(s_NickServ, u, NICK_ALREADY_IDENTIFIED);
    } else {
        if (!(na->status & NS_IDENTIFIED) && !(na->status & NS_RECOGNIZED)) {
            if (na->last_usermask)
                free(na->last_usermask);
            na->last_usermask =
                scalloc(strlen(GetIdent(u)) + strlen(GetHost(u)) + 2, 1);
            sprintf(na->last_usermask, "%s@%s", GetIdent(u), GetHost(u));
            if (na->last_realname)
                free(na->last_realname);
            na->last_realname = sstrdup(u->realname);
        }

        na->status |= NS_IDENTIFIED;
        na->last_seen = time(NULL);

#ifndef IRC_PTLINK
        snprintf(tsbuf, sizeof(tsbuf), "%lu", u->timestamp);
        change_user_mode(u, "+rd", tsbuf);
#else
        change_user_mode(u, "+r", "");
#endif                          /* IRC_PTLINK */


        alog("%s: %s!%s@%s identified for nick %s", s_NickServ, u->nick,
             u->username, GetHost(u), u->nick);
        notice_lang(s_NickServ, u, NICK_IDENTIFY_SUCCEEDED);
        do_on_id(u);
        if (NSModeOnID) {
            do_setmodes(u);
        }

        if (NSForceEmail && u->na && !u->na->nc->email) {
            notice_lang(s_NickServ, u, NICK_IDENTIFY_EMAIL_REQUIRED);
            notice_help(s_NickServ, u, NICK_IDENTIFY_EMAIL_HOWTO);
        }

        if (!(na->status & NS_RECOGNIZED))
            check_memos(u);
    }
    return MOD_CONT;
}

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;
        }
#ifdef HAS_HALFOP
        if (status & CUS_HALFOP) {
            return 0;
        }
#endif
        if (status & CUS_VOICE) {
            return 0;
        }
        return 1;
        break;
#ifdef HAS_HALFOP


    case CUS_HALFOP:
        if (status & CUS_OP) {
            return 0;
        }
        if (status & CUS_HALFOP) {
            return 0;
        }
        return 1;
        break;
#endif
#ifdef IRC_UNREAL
    case CUS_OWNER:
        if (status & CUS_OWNER) {
            return 0;
        }
        break;
    case CUS_PROTECT:
        if (status & CUS_OWNER) {
            return 0;
        }
        if (status & CUS_PROTECT) {
            return 0;
        }
        break;
#endif
#ifdef IRC_VIAGRA
    case CUS_OWNER:
        if (status & CUS_OWNER) {
            return 0;
        }
        break;
    case CUS_PROTECT:
        if (status & CUS_OWNER) {
            return 0;
        }
        if (status & CUS_PROTECT) {
            return 0;
        }
        break;
#endif
#ifdef IRC_ULTIMATE3
    case CUS_PROTECT:
        if (status & CUS_PROTECT) {
            return 0;
        }
        break;
#endif
    }
    return 1;
}

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

    /* Walk users current channels */
    for (uc = u->chans; uc; uc = uc->next) {
        if ((c = uc->chan)) {
            chan = c->name;
#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
            if (should_mode_change(uc->status, CUS_OWNER)
                && check_should_owner(u, chan)) {
                chan_set_user_status(c, u, CUS_OWNER);
            } else
#endif
#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_ULTIMATE3)
                if (should_mode_change(uc->status, CUS_PROTECT)
                    && check_should_protect(u, chan)) {
                chan_set_user_status(c, u, CUS_PROTECT);
            } else
#endif
                if (should_mode_change(uc->status, CUS_OP)
                    && check_should_op(u, chan)) {
                chan_set_user_status(c, u, CUS_OP);
            } else
#ifdef HAS_HALFOP
                if (should_mode_change(uc->status, CUS_HALFOP)
                    && check_should_halfop(u, chan)) {
                chan_set_user_status(c, u, CUS_HALFOP);
            } else
#endif
                if (should_mode_change(uc->status, CUS_VOICE)
                    && check_should_voice(u, chan)) {
                chan_set_user_status(c, u, CUS_VOICE);
            }
        }
    }
    return MOD_CONT;
}


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

static int do_logout(User * u)
{
    char *nick = strtok(NULL, " ");
    char *param = strtok(NULL, " ");
    User *u2;

    if (!is_services_admin(u) && nick) {
        syntax_error(s_NickServ, u, "LOGOUT", NICK_LOGOUT_SYNTAX);
    } else if (!(u2 = (nick ? finduser(nick) : u))) {
        notice_lang(s_NickServ, u, NICK_X_NOT_IN_USE, nick);
    } else if (!u2->na) {
        if (nick)
            notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
        else
            notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
    } else if (u2->na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u2->na->nick);
    } else if (!nick && !nick_identified(u)) {
        notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
    } else if (nick && is_services_admin(u2)) {
        notice_lang(s_NickServ, u, NICK_LOGOUT_SERVICESADMIN, nick);
    } else {
        if (nick && param && !stricmp(param, "REVALIDATE")) {
            cancel_user(u2);
            validate_user(u2);
        } else {
            u2->na->status &= ~(NS_IDENTIFIED | NS_RECOGNIZED);
        }

        change_user_mode(u2, "-r+d", "1");

        u->isSuperAdmin = 0;    /* Dont let people logout and remain a SuperAdmin */
        alog("%s: %s!%s@%s logged out nickname %s", s_NickServ, u->nick,
             u->username, GetHost(u), u2->nick);

        if (nick)
            notice_lang(s_NickServ, u, NICK_LOGOUT_X_SUCCEEDED, nick);
        else
            notice_lang(s_NickServ, u, NICK_LOGOUT_SUCCEEDED);
    }
    return MOD_CONT;
}

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

static int do_drop(User * u)
{
    char *nick = strtok(NULL, " ");
    NickAlias *na;
    NickRequest *nr = NULL;
    int is_servadmin = is_services_admin(u);
    int is_mine;                /* Does the nick being dropped belong to the user that is dropping? */

    if (readonly && !is_servadmin) {
        notice_lang(s_NickServ, u, NICK_DROP_DISABLED);
        return MOD_CONT;
    }

    if (!(na = (nick ? findnick(nick) : u->na))) {
        if (nick) {
            if ((nr = findrequestnick(nick)) && is_servadmin) {
                if (readonly)
                    notice_lang(s_NickServ, u, READ_ONLY_MODE);
                if (WallDrop)
                    wallops(s_NickServ, "\2%s\2 used DROP on \2%s\2",
                            u->nick, nick);
                alog("%s: %s!%s@%s dropped nickname %s (e-mail: %s)",
                     s_NickServ, u->nick, u->username, GetHost(u),
                     nr->nick, nr->email);
                delnickrequest(nr);
                notice_lang(s_NickServ, u, NICK_X_DROPPED, nick);
            } else {
                notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
            }
        } else
            notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
        return MOD_CONT;
    }

    is_mine = (u->na && (u->na->nc == na->nc));

    if (is_mine && !nick_identified(u)) {
        notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
    } else if (!is_mine && !is_servadmin) {
        notice_lang(s_NickServ, u, ACCESS_DENIED);
    } else if (NSSecureAdmins && !is_mine && nick_is_services_admin(na->nc)
               && !is_services_root(u)) {
        notice_lang(s_NickServ, u, PERMISSION_DENIED);
    } else {
        if (readonly)
            notice_lang(s_NickServ, u, READ_ONLY_MODE);

        alog("%s: %s!%s@%s dropped nickname %s (group %s) (e-mail: %s)",
             s_NickServ, u->nick, u->username, GetHost(u), na->nick,
             na->nc->display, (na->nc->email ? na->nc->email : "none"));
        delnick(na);

        if (!is_mine) {
            if (WallDrop)
                wallops(s_NickServ, "\2%s\2 used DROP on \2%s\2", u->nick,
                        nick);
            notice_lang(s_NickServ, u, NICK_X_DROPPED, nick);
        } else {
            if (nick)
                notice_lang(s_NickServ, u, NICK_X_DROPPED, nick);
            else
                notice_lang(s_NickServ, u, NICK_DROPPED);
        }
    }
    return MOD_CONT;
}

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

static int do_set(User * u)
{
    char *cmd = strtok(NULL, " ");
    char *param = strtok(NULL, " ");

    NickAlias *na;
    int is_servadmin = is_services_admin(u);
    int set_nick = 0;

    if (readonly) {
        notice_lang(s_NickServ, u, NICK_SET_DISABLED);
        return MOD_CONT;
    }

    if (is_servadmin && cmd && (na = findnick(cmd))) {
        cmd = param;
        param = strtok(NULL, " ");
        set_nick = 1;
    } else {
        na = u->na;
    }

    if (!param
        && (!cmd
            || (stricmp(cmd, "URL") != 0 && stricmp(cmd, "EMAIL") != 0
                && stricmp(cmd, "GREET") != 0
                && stricmp(cmd, "ICQ") != 0))) {
        if (is_servadmin) {
            syntax_error(s_NickServ, u, "SET", NICK_SET_SERVADMIN_SYNTAX);
        } else {
            syntax_error(s_NickServ, u, "SET", NICK_SET_SYNTAX);
        }
    } else if (!na) {
        notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else if (!is_servadmin && !nick_identified(u)) {
        notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
    } else if (stricmp(cmd, "DISPLAY") == 0) {
        do_set_display(u, na->nc, param);
    } else if (stricmp(cmd, "PASSWORD") == 0) {
        do_set_password(u, na->nc, param);
    } else if (stricmp(cmd, "LANGUAGE") == 0) {
        do_set_language(u, na->nc, param);
    } else if (stricmp(cmd, "URL") == 0) {
        do_set_url(u, na->nc, param);
    } else if (stricmp(cmd, "EMAIL") == 0) {
        do_set_email(u, na->nc, param);
    } else if (stricmp(cmd, "ICQ") == 0) {
        do_set_icq(u, na->nc, param);
    } else if (stricmp(cmd, "GREET") == 0) {
        do_set_greet(u, na->nc, param);
    } else if (stricmp(cmd, "KILL") == 0) {
        do_set_kill(u, na->nc, param);
    } else if (stricmp(cmd, "SECURE") == 0) {
        do_set_secure(u, na->nc, param);
    } else if (stricmp(cmd, "PRIVATE") == 0) {
        do_set_private(u, na->nc, param);
    } else if (stricmp(cmd, "MSG") == 0) {
        do_set_msg(u, na->nc, param);
    } else if (stricmp(cmd, "HIDE") == 0) {
        do_set_hide(u, na->nc, param);
    } else if (stricmp(cmd, "NOEXPIRE") == 0) {
        do_set_noexpire(u, na, param);
    } else {
        if (is_servadmin)
            notice_lang(s_NickServ, u, NICK_SET_UNKNOWN_OPTION_OR_BAD_NICK,
                        cmd);
        else
            notice_lang(s_NickServ, u, NICK_SET_UNKNOWN_OPTION, cmd);
    }
    return MOD_CONT;
}

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

static int do_set_display(User * u, NickCore * nc, char *param)
{
    int i;
    NickAlias *na;

    /* First check whether param is a valid nick of the group */
    for (i = 0; i < nc->aliases.count; i++) {
        na = nc->aliases.list[i];
        if (!stricmp(na->nick, param)) {
            param = na->nick;   /* Because case may differ */
            break;
        }
    }

    if (i == nc->aliases.count) {
        notice_lang(s_NickServ, u, NICK_SET_DISPLAY_INVALID);
        return MOD_CONT;
    }

    change_core_display(nc, param);
    notice_lang(s_NickServ, u, NICK_SET_DISPLAY_CHANGED, nc->display);
    return MOD_CONT;
}

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

static int do_set_password(User * u, NickCore * nc, char *param)
{
    int len = strlen(param);

    if (NSSecureAdmins && u->na->nc != nc && nick_is_services_admin(nc)
        && !is_services_root(u)) {
        notice_lang(s_NickServ, u, PERMISSION_DENIED);
        return MOD_CONT;
    } else if (stricmp(nc->display, param) == 0
               || (StrictPasswords && len < 5)) {
        notice_lang(s_NickServ, u, MORE_OBSCURE_PASSWORD);
        return MOD_CONT;
    }

    if (nc->pass)
        free(nc->pass);

#ifdef USE_ENCRYPTION
    nc->pass = smalloc(PASSMAX);

    if (encrypt(param, len, nc->pass, PASSMAX) < 0) {
        memset(param, 0, len);
        alog("%s: Failed to encrypt password for %s (set)", s_NickServ,
             nc->display);
        notice_lang(s_NickServ, u, NICK_SET_PASSWORD_FAILED);
        return MOD_CONT;
    }

    memset(param, 0, len);
    notice_lang(s_NickServ, u, NICK_SET_PASSWORD_CHANGED);
#else
    nc->pass = sstrdup(param);
    notice_lang(s_NickServ, u, NICK_SET_PASSWORD_CHANGED_TO, nc->pass);
#endif

    if (u->na && u->na->nc != nc && is_services_admin(u)) {
        alog("%s: %s!%s@%s used SET PASSWORD as Services admin on %s (e-mail: %s)", s_NickServ, u->nick, u->username, GetHost(u), nc->display, (nc->email ? nc->email : "none"));
        if (WallSetpass)
            wallops(s_NickServ,
                    "\2%s\2 used SET PASSWORD as Services admin on \2%s\2",
                    u->nick, nc->display);
    } else {
        alog("%s: %s!%s@%s (e-mail: %s) changed its password.", s_NickServ,
             u->nick, u->username, GetHost(u),
             (nc->email ? nc->email : "none"));
    }
    return MOD_CONT;
}

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

static int do_set_language(User * u, NickCore * nc, char *param)
{
    int langnum;

    if (param[strspn(param, "0123456789")] != 0) {      /* i.e. not a number */
        syntax_error(s_NickServ, u, "SET LANGUAGE",
                     NICK_SET_LANGUAGE_SYNTAX);
        return MOD_CONT;
    }
    langnum = atoi(param) - 1;
    if (langnum < 0 || langnum >= NUM_LANGS || langlist[langnum] < 0) {
        notice_lang(s_NickServ, u, NICK_SET_LANGUAGE_UNKNOWN, langnum + 1,
                    s_NickServ);
        return MOD_CONT;
    }
    nc->language = langlist[langnum];
    notice_lang(s_NickServ, u, NICK_SET_LANGUAGE_CHANGED);
    return MOD_CONT;
}

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

static int do_set_url(User * u, NickCore * nc, char *param)
{
    if (nc->url)
        free(nc->url);

    if (param) {
        nc->url = sstrdup(param);
        notice_lang(s_NickServ, u, NICK_SET_URL_CHANGED, param);
    } else {
        nc->url = NULL;
        notice_lang(s_NickServ, u, NICK_SET_URL_UNSET);
    }
    return MOD_CONT;
}

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

static int do_set_email(User * u, NickCore * nc, char *param)
{
    if (!param && NSForceEmail) {
        notice_lang(s_NickServ, u, NICK_SET_EMAIL_UNSET_IMPOSSIBLE);
        return MOD_CONT;
    } else if (param && !MailValidate(param)) {
        notice_lang(s_NickServ, u, MAIL_X_INVALID, param);
        return MOD_CONT;
    }

    alog("%s: %s!%s@%s (e-mail: %s) changed its e-mail to %s.", s_NickServ,
         u->nick, u->username, GetHost(u),
         (nc->email ? nc->email : "none"), (param ? param : "none"));

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

    if (param) {
        nc->email = sstrdup(param);
        notice_lang(s_NickServ, u, NICK_SET_EMAIL_CHANGED, param);
    } else {
        nc->email = NULL;
        notice_lang(s_NickServ, u, NICK_SET_EMAIL_UNSET);
    }
    return MOD_CONT;
}

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

static int do_set_icq(User * u, NickCore * nc, char *param)
{
    if (param) {
        int32 tmp = atol(param);
        if (!tmp) {
            notice_lang(s_NickServ, u, NICK_SET_ICQ_INVALID, param);
        } else {
            nc->icq = tmp;
            notice_lang(s_NickServ, u, NICK_SET_ICQ_CHANGED, param);
        }
    } else {
        nc->icq = 0;
        notice_lang(s_NickServ, u, NICK_SET_ICQ_UNSET);
    }
    return MOD_CONT;
}

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

static int do_set_greet(User * u, NickCore * nc, char *param)
{
    if (nc->greet)
        free(nc->greet);

    if (param) {
        char buf[BUFSIZE];
        char *end = strtok(NULL, "");

        snprintf(buf, sizeof(buf), "%s%s%s", param, (end ? " " : ""),
                 (end ? end : ""));

        nc->greet = sstrdup(buf);
        notice_lang(s_NickServ, u, NICK_SET_GREET_CHANGED, buf);
    } else {
        nc->greet = NULL;
        notice_lang(s_NickServ, u, NICK_SET_GREET_UNSET);
    }
    return MOD_CONT;
}

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

static int do_set_kill(User * u, NickCore * nc, char *param)
{
    if (stricmp(param, "ON") == 0) {
        nc->flags |= NI_KILLPROTECT;
        nc->flags &= ~(NI_KILL_QUICK | NI_KILL_IMMED);
        notice_lang(s_NickServ, u, NICK_SET_KILL_ON);
    } else if (stricmp(param, "QUICK") == 0) {
        nc->flags |= NI_KILLPROTECT | NI_KILL_QUICK;
        nc->flags &= ~NI_KILL_IMMED;
        notice_lang(s_NickServ, u, NICK_SET_KILL_QUICK);
    } else if (stricmp(param, "IMMED") == 0) {
        if (NSAllowKillImmed) {
            nc->flags |= NI_KILLPROTECT | NI_KILL_IMMED;
            nc->flags &= ~NI_KILL_QUICK;
            notice_lang(s_NickServ, u, NICK_SET_KILL_IMMED);
        } else {
            notice_lang(s_NickServ, u, NICK_SET_KILL_IMMED_DISABLED);
        }
    } else if (stricmp(param, "OFF") == 0) {
        nc->flags &= ~(NI_KILLPROTECT | NI_KILL_QUICK | NI_KILL_IMMED);
        notice_lang(s_NickServ, u, NICK_SET_KILL_OFF);
    } else {
        syntax_error(s_NickServ, u, "SET KILL",
                     NSAllowKillImmed ? NICK_SET_KILL_IMMED_SYNTAX :
                     NICK_SET_KILL_SYNTAX);
    }
    return MOD_CONT;
}

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

static int do_set_secure(User * u, NickCore * nc, char *param)
{
    if (stricmp(param, "ON") == 0) {
        nc->flags |= NI_SECURE;
        notice_lang(s_NickServ, u, NICK_SET_SECURE_ON);
    } else if (stricmp(param, "OFF") == 0) {
        nc->flags &= ~NI_SECURE;
        notice_lang(s_NickServ, u, NICK_SET_SECURE_OFF);
    } else {
        syntax_error(s_NickServ, u, "SET SECURE", NICK_SET_SECURE_SYNTAX);
    }
    return MOD_CONT;
}

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

static int do_set_private(User * u, NickCore * nc, char *param)
{
    if (stricmp(param, "ON") == 0) {
        nc->flags |= NI_PRIVATE;
        notice_lang(s_NickServ, u, NICK_SET_PRIVATE_ON);
    } else if (stricmp(param, "OFF") == 0) {
        nc->flags &= ~NI_PRIVATE;
        notice_lang(s_NickServ, u, NICK_SET_PRIVATE_OFF);
    } else {
        syntax_error(s_NickServ, u, "SET PRIVATE",
                     NICK_SET_PRIVATE_SYNTAX);
    }
    return MOD_CONT;
}

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

static int do_set_msg(User * u, NickCore * nc, char *param)
{
    if (!UsePrivmsg) {
        notice_lang(s_NickServ, u, NICK_SET_OPTION_DISABLED, "MSG");
        return MOD_CONT;
    }

    if (stricmp(param, "ON") == 0) {
        nc->flags |= NI_MSG;
        notice_lang(s_NickServ, u, NICK_SET_MSG_ON);
    } else if (stricmp(param, "OFF") == 0) {
        nc->flags &= ~NI_MSG;
        notice_lang(s_NickServ, u, NICK_SET_MSG_OFF);
    } else {
        syntax_error(s_NickServ, u, "SET MSG", NICK_SET_MSG_SYNTAX);
    }
    return MOD_CONT;
}

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

static int do_set_hide(User * u, NickCore * nc, char *param)
{
    int flag, onmsg, offmsg;

    if (stricmp(param, "EMAIL") == 0) {
        flag = NI_HIDE_EMAIL;
        onmsg = NICK_SET_HIDE_EMAIL_ON;
        offmsg = NICK_SET_HIDE_EMAIL_OFF;
    } else if (stricmp(param, "USERMASK") == 0) {
        flag = NI_HIDE_MASK;
        onmsg = NICK_SET_HIDE_MASK_ON;
        offmsg = NICK_SET_HIDE_MASK_OFF;
    } else if (stricmp(param, "QUIT") == 0) {
        flag = NI_HIDE_QUIT;
        onmsg = NICK_SET_HIDE_QUIT_ON;
        offmsg = NICK_SET_HIDE_QUIT_OFF;
    } else {
        syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
        return MOD_CONT;
    }

    param = strtok(NULL, " ");
    if (!param) {
        syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
    } else if (stricmp(param, "ON") == 0) {
        nc->flags |= flag;
        notice_lang(s_NickServ, u, onmsg, s_NickServ);
    } else if (stricmp(param, "OFF") == 0) {
        nc->flags &= ~flag;
        notice_lang(s_NickServ, u, offmsg, s_NickServ);
    } else {
        syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
    }
    return MOD_CONT;
}

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

static int do_set_noexpire(User * u, NickAlias * na, char *param)
{
    if (!is_services_admin(u)) {
        notice_lang(s_NickServ, u, PERMISSION_DENIED);
        return MOD_CONT;
    }
    if (!param) {
        syntax_error(s_NickServ, u, "SET NOEXPIRE",
                     NICK_SET_NOEXPIRE_SYNTAX);
        return MOD_CONT;
    }
    if (stricmp(param, "ON") == 0) {
        na->status |= NS_NO_EXPIRE;
        notice_lang(s_NickServ, u, NICK_SET_NOEXPIRE_ON, na->nick);
    } else if (stricmp(param, "OFF") == 0) {
        na->status &= ~NS_NO_EXPIRE;
        notice_lang(s_NickServ, u, NICK_SET_NOEXPIRE_OFF, na->nick);
    } else {
        syntax_error(s_NickServ, u, "SET NOEXPIRE",
                     NICK_SET_NOEXPIRE_SYNTAX);
    }
    return MOD_CONT;
}

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

static int do_link(User * u)
{
    notice_lang(s_NickServ, u, OBSOLETE_COMMAND, "GROUP");
    return MOD_CONT;
}

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

static int do_unlink(User * u)
{
    notice_lang(s_NickServ, u, OBSOLETE_COMMAND, "DROP");
    return MOD_CONT;
}

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

static int do_listlinks(User * u)
{
    notice_lang(s_NickServ, u, OBSOLETE_COMMAND, "GLIST");
    return MOD_CONT;
}

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

static int do_access(User * u)
{
    char *cmd = strtok(NULL, " ");
    char *mask = strtok(NULL, " ");
    NickAlias *na;
    int i;
    char **access;

    if (cmd && stricmp(cmd, "LIST") == 0 && mask && is_services_admin(u)
        && (na = findnick(mask))) {

        if (na->status & NS_VERBOTEN) {
            notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
            return MOD_CONT;
        }

        notice_lang(s_NickServ, u, NICK_ACCESS_LIST_X, mask);
        mask = strtok(NULL, " ");
        for (access = na->nc->access, i = 0; i < na->nc->accesscount;
             access++, i++) {
            if (mask && !match_wild(mask, *access))
                continue;
            notice_user(s_NickServ, u, "    %s", *access);
        }

    } else if (!cmd || ((stricmp(cmd, "LIST") == 0) ? !!mask : !mask)) {
        syntax_error(s_NickServ, u, "ACCESS", NICK_ACCESS_SYNTAX);

    } else if (mask && !strchr(mask, '@')) {
        notice_lang(s_NickServ, u, BAD_USERHOST_MASK);
        notice_lang(s_NickServ, u, MORE_INFO, s_NickServ, "ACCESS");

    } else if (!(na = u->na)) {
        notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);

    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);

    } else if (!nick_identified(u)) {
        notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);

    } else if (stricmp(cmd, "ADD") == 0) {
        if (na->nc->accesscount >= NSAccessMax) {
            notice_lang(s_NickServ, u, NICK_ACCESS_REACHED_LIMIT,
                        NSAccessMax);
            return MOD_CONT;
        }

        for (access = na->nc->access, i = 0; i < na->nc->accesscount;
             access++, i++) {
            if (strcmp(*access, mask) == 0) {
                notice_lang(s_NickServ, u, NICK_ACCESS_ALREADY_PRESENT,
                            *access);
                return MOD_CONT;
            }
        }

        na->nc->accesscount++;
        na->nc->access =
            srealloc(na->nc->access, sizeof(char *) * na->nc->accesscount);
        na->nc->access[na->nc->accesscount - 1] = sstrdup(mask);
        notice_lang(s_NickServ, u, NICK_ACCESS_ADDED, mask);

    } else if (stricmp(cmd, "DEL") == 0) {

        for (access = na->nc->access, i = 0; i < na->nc->accesscount;
             access++, i++) {
            if (stricmp(*access, mask) == 0)
                break;
        }
        if (i == na->nc->accesscount) {
            notice_lang(s_NickServ, u, NICK_ACCESS_NOT_FOUND, mask);
            return MOD_CONT;
        }

        notice_lang(s_NickServ, u, NICK_ACCESS_DELETED, *access);
        free(*access);
        na->nc->accesscount--;
        if (i < na->nc->accesscount)    /* if it wasn't the last entry... */
            memmove(access, access + 1,
                    (na->nc->accesscount - i) * sizeof(char *));
        if (na->nc->accesscount)        /* if there are any entries left... */
            na->nc->access =
                srealloc(na->nc->access,
                         na->nc->accesscount * sizeof(char *));
        else {
            free(na->nc->access);
            na->nc->access = NULL;
        }
    } else if (stricmp(cmd, "LIST") == 0) {
        notice_lang(s_NickServ, u, NICK_ACCESS_LIST);
        for (access = na->nc->access, i = 0; i < na->nc->accesscount;
             access++, i++) {
            if (mask && !match_wild(mask, *access))
                continue;
            notice_user(s_NickServ, u, "    %s", *access);
        }
    } else {
        syntax_error(s_NickServ, u, "ACCESS", NICK_ACCESS_SYNTAX);

    }
    return MOD_CONT;
}

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

/* Show hidden info to nick owners and sadmins when the "ALL" parameter is
 * supplied. If a nick is online, the "Last seen address" changes to "Is
 * online from".
 * Syntax: INFO <nick> {ALL}
 * -TheShadow (13 Mar 1999)
 */

static int do_info(User * u)
{
    char *nick = strtok(NULL, " ");
    char *param = strtok(NULL, " ");

    NickAlias *na;
    NickRequest *nr = NULL;
    int is_servadmin = is_services_admin(u);

#ifdef HAS_VHOST
    char *vHost;
#endif

    if (!nick) {
        syntax_error(s_NickServ, u, "INFO", NICK_INFO_SYNTAX);
    } else if (!(na = findnick(nick))) {
        if ((nr = findrequestnick(nick))) {
            notice_lang(s_NickServ, u, NICK_IS_PREREG);
            if (param && stricmp(param, "ALL") == 0 && is_servadmin) {
                notice_lang(s_NickServ, u, NICK_INFO_EMAIL, nr->email);
            } else {
                if (is_servadmin) {
                    notice_lang(s_NickServ, u, NICK_INFO_FOR_MORE,
                                s_NickServ, nr->nick);
                }
            }
        } else {
            notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
        }
    } else if (na->status & NS_VERBOTEN) {
        if (is_oper(u) && na->last_usermask)
            notice_lang(s_NickServ, u, NICK_X_FORBIDDEN_OPER, nick,
                        na->last_usermask,
                        (na->last_realname ? na->
                         last_realname : getstring(u->na, NO_REASON)));
        else
            notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
    } else {
        struct tm *tm;
        char buf[BUFSIZE], *end;
        const char *commastr = getstring(u->na, COMMA_SPACE);
        int need_comma = 0;
        int nick_online = 0;
        int show_hidden = 0;

        /* Is the real owner of the nick we're looking up online? -TheShadow */
        if (na->status & (NS_RECOGNIZED | NS_IDENTIFIED))
            nick_online = 1;

        /* Only show hidden fields to owner and sadmins and only when the ALL
         * parameter is used. -TheShadow */
        if (param && stricmp(param, "ALL") == 0 && u->na
            && ((nick_identified(u) && (na->nc == u->na->nc))
                || is_servadmin))
            show_hidden = 1;

        notice_lang(s_NickServ, u, NICK_INFO_REALNAME, na->nick,
                    na->last_realname);

        if (nick_is_services_admin(na->nc))     /* This will also include the services root(s) */
            notice_lang(s_NickServ, u, NICK_INFO_SERVICES_ADMIN, na->nick);
        else if (nick_is_services_oper(na->nc))
            notice_lang(s_NickServ, u, NICK_INFO_SERVICES_OPER, na->nick);


        if (nick_online) {
            if (show_hidden || !(na->nc->flags & NI_HIDE_MASK))
                notice_lang(s_NickServ, u, NICK_INFO_ADDRESS_ONLINE,
                            na->last_usermask);
            else
                notice_lang(s_NickServ, u, NICK_INFO_ADDRESS_ONLINE_NOHOST,
                            na->nick);
        } else {
            if (show_hidden || !(na->nc->flags & NI_HIDE_MASK))
                notice_lang(s_NickServ, u, NICK_INFO_ADDRESS,
                            na->last_usermask);
        }

        tm = localtime(&na->time_registered);
        strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
        notice_lang(s_NickServ, u, NICK_INFO_TIME_REGGED, buf);

        if (!nick_online) {
            tm = localtime(&na->last_seen);
            strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT,
                          tm);
            notice_lang(s_NickServ, u, NICK_INFO_LAST_SEEN, buf);
        }

        if (na->last_quit
            && (show_hidden || !(na->nc->flags & NI_HIDE_QUIT)))
            notice_lang(s_NickServ, u, NICK_INFO_LAST_QUIT, na->last_quit);

        if (na->nc->url)
            notice_lang(s_NickServ, u, NICK_INFO_URL, na->nc->url);
        if (na->nc->email
            && (show_hidden || !(na->nc->flags & NI_HIDE_EMAIL)))
            notice_lang(s_NickServ, u, NICK_INFO_EMAIL, na->nc->email);
        if (na->nc->icq)
            notice_lang(s_NickServ, u, NICK_INFO_ICQ, na->nc->icq);

        if (show_hidden) {
#ifdef HAS_VHOST
            if (s_HostServ) {
                if (getvHost(na->nick) != NULL) {
                    vHost = smalloc(strlen(getvHost(na->nick)) + 2);
                    bzero(vHost, sizeof(vHost));
                    snprintf(vHost, strlen(getvHost(na->nick)) + 2, "%s",
                             getvHost(na->nick));
                    notice_lang(s_NickServ, u, NICK_INFO_VHOST, vHost);
                    free(vHost);
                }
            }
#endif
            if (na->nc->greet)
                notice_lang(s_NickServ, u, NICK_INFO_GREET, na->nc->greet);

            *buf = 0;
            end = buf;

            if (na->nc->flags & NI_KILLPROTECT) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s",
                                getstring(u->na, NICK_INFO_OPT_KILL));
                need_comma = 1;
            }
            if (na->nc->flags & NI_SECURE) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, NICK_INFO_OPT_SECURE));
                need_comma = 1;
            }
            if (na->nc->flags & NI_PRIVATE) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, NICK_INFO_OPT_PRIVATE));
                need_comma = 1;
            }
            if (na->nc->flags & NI_MSG) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, NICK_INFO_OPT_MSG));
                need_comma = 1;
            }

            notice_lang(s_NickServ, u, NICK_INFO_OPTIONS,
                        *buf ? buf : getstring(u->na, NICK_INFO_OPT_NONE));

            if (na->status & NS_NO_EXPIRE)
                notice_lang(s_NickServ, u, NICK_INFO_NO_EXPIRE);
        }

        if (!show_hidden
            && ((u->na && (na->nc == u->na->nc) && nick_identified(u))
                || is_servadmin))
            notice_lang(s_NickServ, u, NICK_INFO_FOR_MORE, s_NickServ,
                        na->nick);
    }
    return MOD_CONT;
}

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

/* SADMINS can search for nicks based on their NS_VERBOTEN and NS_NO_EXPIRE
 * status. The keywords FORBIDDEN and NOEXPIRE represent these two states
 * respectively. These keywords should be included after the search pattern.
 * Multiple keywords are accepted and should be separated by spaces. Only one
 * of the keywords needs to match a nick's state for the nick to be displayed.
 * Forbidden nicks can be identified by "[Forbidden]" appearing in the last
 * seen address field. Nicks with NOEXPIRE set are preceeded by a "!". Only
 * SADMINS will be shown forbidden nicks and the "!" indicator.
 * Syntax for sadmins: LIST pattern [FORBIDDEN] [NOEXPIRE]
 * -TheShadow
 */

static int do_list(User * u)
{
    char *pattern = strtok(NULL, " ");
    char *keyword;
    NickAlias *na;
    NickCore *mync;
    int nnicks, i;
    char buf[BUFSIZE];
    int is_servadmin = is_services_admin(u);
    int16 matchflags = 0;
    NickRequest *nr = NULL;
    int nronly = 0;
    char noexpire_char = ' ';
    int count = 0, from = 0, to = 0;
    char *tmp = NULL;
    char *s = NULL;

    if (NSListOpersOnly && !(is_oper(u))) {
        notice_lang(s_NickServ, u, PERMISSION_DENIED);
        return MOD_CONT;
    }

    if (!pattern) {
        syntax_error(s_NickServ, u, "LIST",
                     is_servadmin ? NICK_LIST_SERVADMIN_SYNTAX :
                     NICK_LIST_SYNTAX);
    } else {

        if (pattern) {
            if (pattern[0] == '#') {
                tmp = myStrGetOnlyToken((pattern + 1), '-', 0); /* Read FROM out */
                if (!tmp) {
                    return MOD_CONT;
                }
                for (s = tmp; *s; s++) {
                    if (!isdigit(*s)) {
                        return MOD_CONT;
                    }
                }
                from = atoi(tmp);
                tmp = myStrGetTokenRemainder(pattern, '-', 1);  /* Read TO out */
                if (!tmp) {
                    return MOD_CONT;
                }
                for (s = tmp; *s; s++) {
                    if (!isdigit(*s)) {
                        return MOD_CONT;
                    }
                }
                to = atoi(tmp);
                pattern = sstrdup("*");
            }
        }

        nnicks = 0;

        while (is_servadmin && (keyword = strtok(NULL, " "))) {
            if (stricmp(keyword, "FORBIDDEN") == 0)
                matchflags |= NS_VERBOTEN;
            if (stricmp(keyword, "NOEXPIRE") == 0)
                matchflags |= NS_NO_EXPIRE;
            if (stricmp(keyword, "UNCONFIRMED") == 0)
                nronly = 1;
        }

        mync = (nick_identified(u) ? u->na->nc : NULL);

        notice_lang(s_NickServ, u, NICK_LIST_HEADER, pattern);
        if (nronly != 1) {
            for (i = 0; i < 1024; i++) {
                for (na = nalists[i]; na; na = na->next) {
                    /* Don't show private and forbidden nicks to non-services admins. */
                    if ((na->status & NS_VERBOTEN) && !is_servadmin)
                        continue;
                    if ((na->nc->flags & NI_PRIVATE) && !is_servadmin
                        && na->nc != mync)
                        continue;
                    if ((matchflags != 0) && !(na->status & matchflags))
                        continue;

                    /* We no longer compare the pattern against the output buffer.
                     * Instead we build a nice nick!user@host buffer to compare.
                     * The output is then generated separately. -TheShadow */
                    snprintf(buf, sizeof(buf), "%s!%s", na->nick,
                             (na->last_usermask
                              && !(na->status & NS_VERBOTEN)) ? na->
                             last_usermask : "*@*");
                    if (stricmp(pattern, na->nick) == 0
                        || match_wild_nocase(pattern, buf)) {

                        if ((((count + 1 >= from) && (count + 1 <= to))
                             || ((from == 0) && (to == 0)))
                            && (++nnicks <= NSListMax)) {
                            if (is_servadmin
                                && (na->status & NS_NO_EXPIRE))
                                noexpire_char = '!';
                            else {
                                noexpire_char = ' ';
                            }
                            if ((na->nc->flags & NI_HIDE_MASK)
                                && !is_servadmin && na->nc != mync) {
                                snprintf(buf, sizeof(buf),
                                         "%-20s  [Hidden]", na->nick);
                            } else if (na->status & NS_VERBOTEN) {
                                snprintf(buf, sizeof(buf),
                                         "%-20s  [Forbidden]", na->nick);
                            } else {
                                snprintf(buf, sizeof(buf), "%-20s  %s",
                                         na->nick, na->last_usermask);
                            }
                            notice_user(s_NickServ, u, "   %c%s",
                                        noexpire_char, buf);
                        }
                        count++;
                    }
                }
            }
        }
        noexpire_char = ' ';
        for (i = 0; i < 1024; i++) {
            for (nr = nrlists[i]; nr; nr = nr->next) {
                snprintf(buf, sizeof(buf), "%s!*@*", nr->nick);
                if (stricmp(pattern, nr->nick) == 0
                    || match_wild_nocase(pattern, buf)) {
                    if (++nnicks <= NSListMax) {
                        snprintf(buf, sizeof(buf),
                                 "%-20s  [UNCONFIRMED]", nr->nick);
                        notice_user(s_NickServ, u, "   %c%s",
                                    noexpire_char, buf);
                    }
                }
            }
        }
        notice_lang(s_NickServ, u, NICK_LIST_RESULTS,
                    nnicks > NSListMax ? NSListMax : nnicks, nnicks);
    }
    return MOD_CONT;
}

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

static int do_glist(User * u)
{
    char *nick = strtok(NULL, " ");

    NickAlias *na, *na2;
    int i;

    if ((nick ? !is_services_admin(u) : !nick_identified(u))) {
        notice_lang(s_NickServ, u, ACCESS_DENIED);
    } else if ((!nick ? !(na = u->na) : !(na = findnick(nick)))) {
        notice_lang(s_NickServ, u,
                    (!nick ? NICK_NOT_REGISTERED : NICK_X_NOT_REGISTERED),
                    nick);
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else {
        notice_lang(s_NickServ, u,
                    nick ? NICK_GLIST_HEADER_X : NICK_GLIST_HEADER,
                    na->nc->display);
        for (i = 0; i < na->nc->aliases.count; i++) {
            na2 = na->nc->aliases.list[i];
            if (na2->nc == na->nc)
                notice_user(s_NickServ, u, "   %c%s",
                            ((na2->status & NS_NO_EXPIRE) ? '!' : ' '),
                            na2->nick);
        }
        notice_lang(s_NickServ, u, NICK_GLIST_FOOTER,
                    na->nc->aliases.count);
    }
    return MOD_CONT;
}

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

/**
 * List the channels that the given nickname has access on
 *
 * /ns ALIST [level]
 * /ns ALIST [nickname] [level]
 *
 * -jester
 */
static int do_alist(User * u)
{
    char *lev = strtok(NULL, " ");

    NickAlias *na;

    int min_level = 0;
    int is_servadmin = is_services_admin(u);

    /* Services admins can request ALIST on nicks.
     * Check if 'lev' (first token) is actually a nick
     * and, if so, reassign pointers.
     */
    if (is_servadmin && lev && (na = findnick(lev))) {
        lev = strtok(NULL, " ");
    } else {
        na = u->na;
    }

    /* if a level was given, make sure it's an int for later */
    if (lev) {
        if (stricmp(lev, "FOUNDER") == 0) {
            min_level = ACCESS_FOUNDER;
        } else if (stricmp(lev, "SOP") == 0) {
            min_level = ACCESS_SOP;
        } else if (stricmp(lev, "AOP") == 0) {
            min_level = ACCESS_AOP;
#ifdef HAS_HALFOP
        } else if (stricmp(lev, "HOP") == 0) {
            min_level = ACCESS_HOP;
#endif
        } else if (stricmp(lev, "VOP") == 0) {
            min_level = ACCESS_VOP;
        } else {
            min_level = atoi(lev);
        }
    }

    if (!nick_identified(u)) {
        notice_lang(s_NickServ, u, ACCESS_DENIED);
    } else if (lev && !na) {
        notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, na->nick);
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else {
        int i, level;
        int chan_count = 0;
        int match_count = 0;
        ChannelInfo *ci;

        notice_lang(s_NickServ, u, (is_servadmin ? NICK_ALIST_HEADER_X :
                                    NICK_ALIST_HEADER), na->nick);

        for (i = 0; i < 256; i++) {
            for ((ci = chanlists[i]); ci; (ci = ci->next)) {

                if ((level = get_access_level(ci, na))) {
                    chan_count++;

                    if (min_level > level) {
                        continue;
                    }

                    match_count++;

                    if ((ci->flags & CI_XOP) || (level == ACCESS_FOUNDER)) {
                        char *xop;

                        xop = get_xop_level(level);

                        notice_lang(s_NickServ, u, NICK_ALIST_XOP_FORMAT,
                                    match_count,
                                    ((ci->
                                      flags & CI_NO_EXPIRE) ? '!' : ' '),
                                    ci->name, xop,
                                    (ci->desc ? ci->desc : ""));
                    } else {
                        notice_lang(s_NickServ, u,
                                    NICK_ALIST_ACCESS_FORMAT, match_count,
                                    ((ci->
                                      flags & CI_NO_EXPIRE) ? '!' : ' '),
                                    ci->name, level,
                                    (ci->desc ? ci->desc : ""));

                    }
                }
            }
        }

        notice_lang(s_NickServ, u, NICK_ALIST_FOOTER, match_count,
                    chan_count);
    }
    return MOD_CONT;
}

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

static int do_recover(User * u)
{
    char *nick = strtok(NULL, " ");
    char *pass = strtok(NULL, " ");
    NickAlias *na;
    User *u2;

    if (!nick) {
        syntax_error(s_NickServ, u, "RECOVER", NICK_RECOVER_SYNTAX);
    } else if (!(u2 = finduser(nick))) {
        notice_lang(s_NickServ, u, NICK_X_NOT_IN_USE, nick);
    } else if (!(na = u2->na)) {
        notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else if (stricmp(nick, u->nick) == 0) {
        notice_lang(s_NickServ, u, NICK_NO_RECOVER_SELF);
    } else if (pass) {
        int res = check_password(pass, na->nc->pass);

        if (res == 1) {
            notice_lang(s_NickServ, u2, FORCENICKCHANGE_NOW);
            collide(na, 0);
            notice_lang(s_NickServ, u, NICK_RECOVERED, s_NickServ, nick);
        } else {
            notice_lang(s_NickServ, u, ACCESS_DENIED);
            if (res == 0) {
                alog("%s: RECOVER: invalid password for %s by %s!%s@%s",
                     s_NickServ, nick, u->nick, u->username, GetHost(u));
                bad_password(u);
            }
        }
    } else {
        if (group_identified(u, na->nc)
            || (!(na->nc->flags & NI_SECURE) && is_on_access(u, na->nc))) {
            notice_lang(s_NickServ, u2, FORCENICKCHANGE_NOW);
            collide(na, 0);
            notice_lang(s_NickServ, u, NICK_RECOVERED, s_NickServ, nick);
        } else {
            notice_lang(s_NickServ, u, ACCESS_DENIED);
        }
    }
    return MOD_CONT;
}

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

static int do_release(User * u)
{
    char *nick = strtok(NULL, " ");
    char *pass = strtok(NULL, " ");
    NickAlias *na;

    if (!nick) {
        syntax_error(s_NickServ, u, "RELEASE", NICK_RELEASE_SYNTAX);
    } else if (!(na = findnick(nick))) {
        notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else if (!(na->status & NS_KILL_HELD)) {
        notice_lang(s_NickServ, u, NICK_RELEASE_NOT_HELD, nick);
    } else if (pass) {
        int res = check_password(pass, na->nc->pass);
        if (res == 1) {
            release(na, 0);
            notice_lang(s_NickServ, u, NICK_RELEASED);
        } else {
            notice_lang(s_NickServ, u, ACCESS_DENIED);
            if (res == 0) {
                alog("%s: RELEASE: invalid password for %s by %s!%s@%s",
                     s_NickServ, nick, u->nick, u->username, GetHost(u));
                bad_password(u);
            }
        }
    } else {
        if (group_identified(u, na->nc)
            || (!(na->nc->flags & NI_SECURE) && is_on_access(u, na->nc))) {
            release(na, 0);
            notice_lang(s_NickServ, u, NICK_RELEASED);
        } else {
            notice_lang(s_NickServ, u, ACCESS_DENIED);
        }
    }
    return MOD_CONT;
}

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

static int do_ghost(User * u)
{
    char *nick = strtok(NULL, " ");
    char *pass = strtok(NULL, " ");
    NickAlias *na;
    User *u2;

    if (!nick) {
        syntax_error(s_NickServ, u, "GHOST", NICK_GHOST_SYNTAX);
    } else if (!(u2 = finduser(nick))) {
        notice_lang(s_NickServ, u, NICK_X_NOT_IN_USE, nick);
    } else if (!(na = u2->na)) {
        notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else if (stricmp(nick, u->nick) == 0) {
        notice_lang(s_NickServ, u, NICK_NO_GHOST_SELF);
    } else if (pass) {
        int res = check_password(pass, na->nc->pass);
        if (res == 1) {
            char buf[NICKMAX + 32];
            snprintf(buf, sizeof(buf), "GHOST command used by %s",
                     u->nick);
#ifdef IRC_BAHAMUT
            send_cmd(NULL, "SVSKILL %s :%s", nick, buf);
#else
            kill_user(s_NickServ, nick, buf);
#endif
            notice_lang(s_NickServ, u, NICK_GHOST_KILLED, nick);
        } else {
            notice_lang(s_NickServ, u, ACCESS_DENIED);
            if (res == 0) {
                alog("%s: GHOST: invalid password for %s by %s!%s@%s",
                     s_NickServ, nick, u->nick, u->username, GetHost(u));
                bad_password(u);
            }
        }
    } else {
        if (group_identified(u, na->nc)
            || (!(na->nc->flags & NI_SECURE) && is_on_access(u, na->nc))) {
            char buf[NICKMAX + 32];
            snprintf(buf, sizeof(buf), "GHOST command used by %s",
                     u->nick);
#ifdef IRC_BAHAMUT
            send_cmd(NULL, "SVSKILL %s :%s", nick, buf);
#else
            kill_user(s_NickServ, nick, buf);
#endif
            notice_lang(s_NickServ, u, NICK_GHOST_KILLED, nick);
        } else {
            notice_lang(s_NickServ, u, ACCESS_DENIED);
        }
    }
    return MOD_CONT;
}

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

static int do_status(User * u)
{
    char *nick;
    User *u2;
    int i = 0;

    while ((nick = strtok(NULL, " ")) && (i++ < 16)) {
        if (!(u2 = finduser(nick)))
            notice_user(s_NickServ, u, "STATUS %s 0", nick);
        else if (nick_identified(u2))
            notice_user(s_NickServ, u, "STATUS %s 3", nick);
        else if (nick_recognized(u2))
            notice_user(s_NickServ, u, "STATUS %s 2", nick);
        else
            notice_user(s_NickServ, u, "STATUS %s 1", nick);
    }
    return MOD_CONT;
}

/*************************************************************************/
/* A simple call to check for all emails that a user may have registered */
/* with. It returns the nicks that match the email you provide. Wild     */
/* Cards are not excepted. Must use user@email-host.                     */
/*************************************************************************/
static int do_getemail(User * u)
{
    char *email = strtok(NULL, " ");
    int i, j = 0;
    NickCore *nc;

    if (!email) {
        syntax_error(s_NickServ, u, "GETMAIL", NICK_GETEMAIL_SYNTAX);
        return MOD_CONT;
    }
    alog("%s: %s!%s@%s used GETEMAIL on %s", s_NickServ, u->nick,
         u->username, GetHost(u), email);
    for (i = 0; i < 1024; i++) {
        for (nc = nclists[i]; nc; nc = nc->next) {
            if (nc->email) {
                if (stricmp(nc->email, email) == 0) {
                    j++;
                    notice_lang(s_NickServ, u, NICK_GETEMAIL_EMAILS_ARE,
                                nc->display, email);
                }
            }
        }
    }
    if (j <= 0) {
        notice_lang(s_NickServ, u, NICK_GETEMAIL_NOT_USED, email);
        return MOD_CONT;
    }
    return MOD_CONT;
}

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

static int do_getpass(User * u)
{
#ifndef USE_ENCRYPTION
    char *nick = strtok(NULL, " ");
    NickAlias *na;
    NickRequest *nr = NULL;
#endif

    /* Assumes that permission checking has already been done. */
#ifdef USE_ENCRYPTION
    notice_lang(s_NickServ, u, NICK_GETPASS_UNAVAILABLE);
#else
    if (!nick) {
        syntax_error(s_NickServ, u, "GETPASS", NICK_GETPASS_SYNTAX);
    } else if (!(na = findnick(nick))) {
        if ((nr = findrequestnick(nick))) {
            alog("%s: %s!%s@%s used GETPASS on %s", s_NickServ, u->nick,
                 u->username, GetHost(u), nick);
            if (WallGetpass)
                wallops(s_NickServ, "\2%s\2 used GETPASS on \2%s\2",
                        u->nick, nick);
            notice_lang(s_NickServ, u, NICK_GETPASS_PASSCODE_IS, nick,
                        nr->passcode);
        } else {
            notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
        }
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else if (NSSecureAdmins && nick_is_services_admin(na->nc)
               && !is_services_root(u)) {
        notice_lang(s_NickServ, u, PERMISSION_DENIED);
    } else if (NSRestrictGetPass && !is_services_root(u)) {
        notice_lang(s_NickServ, u, PERMISSION_DENIED);
    } else {
        alog("%s: %s!%s@%s used GETPASS on %s", s_NickServ, u->nick,
             u->username, GetHost(u), nick);
        if (WallGetpass)
            wallops(s_NickServ, "\2%s\2 used GETPASS on \2%s\2", u->nick,
                    nick);
        notice_lang(s_NickServ, u, NICK_GETPASS_PASSWORD_IS, nick,
                    na->nc->pass);
    }
#endif
    return MOD_CONT;
}

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

static int do_sendpass(User * u)
{
#ifndef USE_ENCRYPTION
    char *nick = strtok(NULL, " ");
    NickAlias *na;
#endif

#ifdef USE_ENCRYPTION
    notice_lang(s_NickServ, u, NICK_SENDPASS_UNAVAILABLE);
#else
    if (!nick) {
        syntax_error(s_NickServ, u, "SENDPASS", NICK_SENDPASS_SYNTAX);
    } else if (RestrictMail && !is_oper(u)) {
        notice_lang(s_NickServ, u, PERMISSION_DENIED);
    } else if (!(na = findnick(nick))) {
        notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
    } else {
        char buf[BUFSIZE];
        MailInfo *mail;

        snprintf(buf, sizeof(buf), getstring(na, NICK_SENDPASS_SUBJECT),
                 na->nick);
        mail = MailBegin(u, na->nc, buf, s_NickServ);
        if (!mail)
            return MOD_CONT;

        fprintf(mail->pipe, getstring(na, NICK_SENDPASS_HEAD));
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_1), na->nick);
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_2),
                na->nc->pass);
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_3));
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_4));
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_5),
                NetworkName);
        fprintf(mail->pipe, "\n.\n");

        MailEnd(mail);

        alog("%s: %s!%s@%s used SENDPASS on %s", s_NickServ, u->nick,
             u->username, GetHost(u), nick);
        notice_lang(s_NickServ, u, NICK_SENDPASS_OK, nick);
    }
#endif
    return MOD_CONT;
}

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

static int do_forbid(User * u)
{
    NickAlias *na;
    char *nick = strtok(NULL, " ");
    char *reason = strtok(NULL, "");

    /* Assumes that permission checking has already been done. */
    if (!nick || (ForceForbidReason && !reason)) {
        syntax_error(s_NickServ, u, "FORBID",
                     (ForceForbidReason ? NICK_FORBID_SYNTAX_REASON :
                      NICK_FORBID_SYNTAX));
        return MOD_CONT;
    }

    if (readonly)
        notice_lang(s_NickServ, u, READ_ONLY_MODE);
    if ((na = findnick(nick)) != NULL) {
        if (NSSecureAdmins && nick_is_services_admin(na->nc)
            && !is_services_root(u)) {
            notice_lang(s_NickServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }
        delnick(na);
    }
    na = makenick(nick);
    if (na) {
        na->status |= NS_VERBOTEN;
        na->last_usermask = sstrdup(u->nick);
        if (reason)
            na->last_realname = sstrdup(reason);

        na->u = finduser(na->nick);
        if (na->u)
            na->u->na = na;

        if (na->u) {
            notice_lang(s_NickServ, na->u, FORCENICKCHANGE_NOW);
            collide(na, 0);
        }

        if (WallForbid)
            wallops(s_NickServ, "\2%s\2 used FORBID on \2%s\2", u->nick,
                    nick);

        alog("%s: %s set FORBID for nick %s", s_NickServ, u->nick, nick);
        notice_lang(s_NickServ, u, NICK_FORBID_SUCCEEDED, nick);
    } else {
        alog("%s: Valid FORBID for %s by %s failed", s_NickServ, nick,
             u->nick);
        notice_lang(s_NickServ, u, NICK_FORBID_FAILED, nick);
    }
    return MOD_CONT;
}

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

int ns_do_register(User * u)
{
    return do_register(u);
}
