/* Routines to maintain a list of online users.
 *
 * (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: users.c 5 2004-03-29 01:29:50Z dane $
 *
 */

#include "services.h"

#define HASH(nick)	(((nick)[0]&31)<<5 | ((nick)[1]&31))
User *userlist[1024];

int32 usercnt = 0, opcnt = 0, maxusercnt = 0;
time_t maxusertime;

static unsigned long umodes[128] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, UMODE_A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
    UMODE_P,
#else
    0,
#endif
    0,
#if defined(IRC_BAHAMUT) || defined(IRC_ULTIMATE)
    UMODE_R,
#else
    0,
#endif
    0, 0, 0, 0, 0, 0, 0,
#ifdef IRC_ULTIMATE3
    UMODE_Z,
#else
    0,
#endif
    0, 0, 0, 0, 0,
    0, UMODE_a, 0, 0, 0, 0, 0,
#ifdef IRC_DREAMFORGE
    UMODE_g,
#else
    0,
#endif
    UMODE_h, UMODE_i, 0, 0, 0, 0, 0, UMODE_o,
#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
    UMODE_p,
#else
    0,
#endif
    0, UMODE_r, 0, 0, 0, 0, UMODE_w,
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA)
    UMODE_x,
#else
    0,
#endif
    0, 0, 0, 0, 0, 0, 0
};

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

/* Allocate a new User structure, fill in basic values, link it to the
 * overall list, and return it.  Always successful.
 */

static User *new_user(const char *nick)
{
    User *user, **list;

    user = scalloc(sizeof(User), 1);
    if (!nick)
        nick = "";
    strscpy(user->nick, nick, NICKMAX);
    list = &userlist[HASH(user->nick)];
    user->next = *list;
    if (*list)
        (*list)->prev = user;
    *list = user;
    user->na = findnick(nick);
    if (user->na)
        user->na->u = user;
    usercnt++;
    if (usercnt > maxusercnt) {
        maxusercnt = usercnt;
        maxusertime = time(NULL);
        if (LogMaxUsers)
            alog("user: New maximum user count: %d", maxusercnt);
    }
    user->isSuperAdmin = 0;     /* always set SuperAdmin to 0 for new users */
    return user;
}

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

/* Change the nickname of a user, and move pointers as necessary. */

static void change_user_nick(User * user, const char *nick)
{
    User **list;
    int is_same = (!stricmp(user->nick, nick) ? 1 : 0);

    if (user->prev)
        user->prev->next = user->next;
    else
        userlist[HASH(user->nick)] = user->next;
    if (user->next)
        user->next->prev = user->prev;
    user->nick[1] = 0;          /* paranoia for zero-length nicks */
    strscpy(user->nick, nick, NICKMAX);
    list = &userlist[HASH(user->nick)];
    user->next = *list;
    user->prev = NULL;
    if (*list)
        (*list)->prev = user;
    *list = user;

    /* Only if old and new nick aren't the same; no need to waste time */
    if (!is_same) {
        if (user->na)
            user->na->u = NULL;
        user->na = findnick(nick);
        if (user->na)
            user->na->u = user;
    }
}

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

#ifdef HAS_VHOST

static void update_host(User * user)
{
    if (user->na && (nick_identified(user)
                     || (!(user->na->nc->flags & NI_SECURE)
                         && nick_recognized(user)))) {
        if (user->na->last_usermask)
            free(user->na->last_usermask);

        user->na->last_usermask =
            smalloc(strlen(GetIdent(user)) + strlen(GetHost(user)) + 2);
        sprintf(user->na->last_usermask, "%s@%s", GetIdent(user),
                GetHost(user));
    }

    if (debug)
        alog("debug: %s changes its host to %s", user->nick,
             GetHost(user));
}

#endif

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

#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA) || defined(IRC_PTLINK)

/* Change the (virtual) hostname of a user. */

void change_user_host(User * user, const char *host)
{
    if (user->vhost)
        free(user->vhost);
    user->vhost = sstrdup(host);

    if (debug)
        alog("debug: %s changes its vhost to %s", user->nick, host);



    update_host(user);
}

#endif
/*************************************************************************/

#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_PTLINK)
/* Change the realname of a user. */

void change_user_realname(User * user, const char *realname)
{
    if (user->realname)
        free(user->realname);
    user->realname = sstrdup(realname);

    if (user->na && (nick_identified(user)
                     || (!(user->na->nc->flags & NI_SECURE)
                         && nick_recognized(user)))) {
        if (user->na->last_realname)
            free(user->na->last_realname);
        user->na->last_realname = sstrdup(realname);
    }

    if (debug)
        alog("debug: %s changes its realname to %s", user->nick, realname);
}


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

/* Change the username of a user. */

void change_user_username(User * user, const char *username)
{
    if (user->username)
        free(user->username);
    user->username = sstrdup(username);
    if (user->na && (nick_identified(user)
                     || (!(user->na->nc->flags & NI_SECURE)
                         && nick_recognized(user)))) {
        if (user->na->last_usermask)
            free(user->na->last_usermask);

        user->na->last_usermask =
            smalloc(strlen(GetIdent(user)) + strlen(GetHost(user)) + 2);
        sprintf(user->na->last_usermask, "%s@%s", GetIdent(user),
                GetHost(user));
    }
    if (debug)
        alog("debug: %s changes its username to %s", user->nick, username);
}

#endif

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

void set_umode(User * user, int ac, char **av)
{
    int add = 1;                /* 1 if adding modes, 0 if deleting */
    char *modes = av[0];

    ac--;

    if (debug)
        alog("debug: Changing mode for %s to %s", user->nick, modes);

    while (*modes) {

        add ? (user->mode |= umodes[(int) *modes]) : (user->mode &=
                                                      ~umodes[(int)
                                                              *modes]);

        switch (*modes++) {
        case '+':
            add = 1;
            break;
        case '-':
            add = 0;
            break;
#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
        case 'a':
            if (add && !is_services_oper(user)) {
                send_cmd(ServerName, "SVSMODE %s -a", user->nick);
                user->mode &= ~UMODE_a;
            }
            break;
        case 'P':
            if (add && !is_services_admin(user)) {
                send_cmd(ServerName, "SVSMODE %s -P", user->nick);
                user->mode &= ~UMODE_P;
            }
            break;
#endif
#if defined(IRC_ULTIMATE)
        case 'R':
            if (add && !is_services_root(user)) {
                send_cmd(ServerName, "SVSMODE %s -R", user->nick);
                user->mode &= ~UMODE_R;
            }
            break;
#endif
#if defined(IRC_ULTIMATE3)
        case 'Z':
            if (add && !is_services_root(user)) {
                send_cmd(ServerName, "SVSMODE %s -Z", user->nick);
                user->mode &= ~UMODE_Z;
            }
            break;
#endif
        case 'd':
            if (ac == 0) {
#if !defined(IRC_ULTIMATE) && !defined(IRC_UNREAL)
                alog("user: umode +d with no parameter (?) for user %s",
                     user->nick);
#endif
                break;
            }

            ac--;
            av++;
            user->svid = strtoul(*av, NULL, 0);
            break;
        case 'o':
            if (add) {
                opcnt++;

                if (WallOper)
                    wallops(s_OperServ, "\2%s\2 is now an IRC operator.",
                            user->nick);
                display_news(user, NEWS_OPER);
#if defined(IRC_PTLINK)
                if (is_services_admin(user)) {
                    send_cmd(ServerName, "SVSMODE %s +a", user->nick);
                    user->mode |= UMODE_a;
                }
#endif
#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
                if (is_services_oper(user)) {
                    send_cmd(ServerName, "SVSMODE %s +a", user->nick);
                    user->mode |= UMODE_a;
                }

                if (is_services_admin(user)) {
                    send_cmd(ServerName, "SVSMODE %s +P", user->nick);
                    user->mode |= UMODE_P;
                }
#endif

#ifdef IRC_ULTIMATE
                if (is_services_root(user)) {
                    send_cmd(ServerName, "SVSMODE %s +R", user->nick);
                    user->mode |= UMODE_R;
                }
#endif
#ifdef IRC_ULTIMATE3
                if (is_services_root(user)) {
                    send_cmd(ServerName, "SVSMODE %s +Z", user->nick);
                    user->mode |= UMODE_Z;
                }
#endif
            } else {
                opcnt--;
            }
            break;
        case 'r':
            if (add && !nick_identified(user)) {
                send_cmd(ServerName, "SVSMODE %s -r", user->nick);
                user->mode &= ~UMODE_r;
            }
            break;
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA)
        case 'x':
            update_host(user);
            break;
#endif
        }
    }
}

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

/* Remove and free a User structure. */

static void delete_user(User * user)
{
    struct u_chanlist *c, *c2;
    struct u_chaninfolist *ci, *ci2;

    if (LogUsers) {
#ifdef HAS_VHOST
        alog("LOGUSERS: %s (%s@%s => %s) (%s) left the network (%s).",
             user->nick, user->username, user->host,
             (user->vhost ? user->vhost : "(none)"), user->realname,
             user->server);
#else
        alog("LOGUSERS: %s (%s@%s) (%s) left the network (%s).",
             user->nick, user->username, user->host,
             user->realname, user->server);
#endif
    }

    if (debug >= 2)
        alog("debug: delete_user() called");
    usercnt--;
    if (is_oper(user))
        opcnt--;
    if (debug >= 2)
        alog("debug: delete_user(): free user data");
    free(user->username);
    free(user->host);
#ifdef HAS_VHOST
    if (user->vhost)
        free(user->vhost);
#endif
    free(user->realname);
    free(user->server);
    if (debug >= 2)
        alog("debug: delete_user(): remove from channels");
    c = user->chans;
    while (c) {
        c2 = c->next;
        chan_deluser(user, c->chan);
        free(c);
        c = c2;
    }
    /* This called only here now */
    cancel_user(user);
    if (user->na)
        user->na->u = NULL;
    if (debug >= 2)
        alog("debug: delete_user(): free founder data");
    ci = user->founder_chans;
    while (ci) {
        ci2 = ci->next;
        free(ci);
        ci = ci2;
    }
    if (debug >= 2)
        alog("debug: delete_user(): delete from list");
    if (user->prev)
        user->prev->next = user->next;
    else
        userlist[HASH(user->nick)] = user->next;
    if (user->next)
        user->next->prev = user->prev;
    if (debug >= 2)
        alog("debug: delete_user(): free user structure");
    free(user);
    if (debug >= 2)
        alog("debug: delete_user() done");
}

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

/* Return statistics.  Pointers are assumed to be valid. */

void get_user_stats(long *nusers, long *memuse)
{
    long count = 0, mem = 0;
    int i;
    User *user;
    struct u_chanlist *uc;
    struct u_chaninfolist *uci;

    for (i = 0; i < 1024; i++) {
        for (user = userlist[i]; user; user = user->next) {
            count++;
            mem += sizeof(*user);
            if (user->username)
                mem += strlen(user->username) + 1;
            if (user->host)
                mem += strlen(user->host) + 1;
#ifdef HAS_VHOST
            if (user->vhost)
                mem += strlen(user->vhost) + 1;
#endif
            if (user->realname)
                mem += strlen(user->realname) + 1;
            if (user->server)
                mem += strlen(user->server) + 1;
            for (uc = user->chans; uc; uc = uc->next)
                mem += sizeof(*uc);
            for (uci = user->founder_chans; uci; uci = uci->next)
                mem += sizeof(*uci);
        }
    }
    *nusers = count;
    *memuse = mem;
}

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

/* Find a user by nick.  Return NULL if user could not be found. */

User *finduser(const char *nick)
{
    User *user;

    if (debug >= 3)
        alog("debug: finduser(%p)", nick);
    user = userlist[HASH(nick)];
    while (user && stricmp(user->nick, nick) != 0)
        user = user->next;
    if (debug >= 3)
        alog("debug: finduser(%s) -> %p", nick, user);
    return user;
}

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

/* Iterate over all users in the user list.  Return NULL at end of list. */

static User *current;
static int next_index;

User *firstuser(void)
{
    next_index = 0;
    while (next_index < 1024 && current == NULL)
        current = userlist[next_index++];
    if (debug)
        alog("debug: firstuser() returning %s",
             current ? current->nick : "NULL (end of list)");
    return current;
}

User *nextuser(void)
{
    if (current)
        current = current->next;
    if (!current && next_index < 1024) {
        while (next_index < 1024 && current == NULL)
            current = userlist[next_index++];
    }
    if (debug)
        alog("debug: nextuser() returning %s",
             current ? current->nick : "NULL (end of list)");
    return current;
}

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

/* Handle a server NICK command. */

User *do_nick(const char *source, char *nick, char *username, char *host,
              char *server, char *realname, time_t ts, uint32 svid, ...)
{
    User *user;

    char *tmp = NULL;
    NickAlias *old_na;          /* Old nick rec */
    int nc_changed = 1;         /* Did nick core change? */
    int status = 0;             /* Status to apply */
    char mask[USERMAX + HOSTMAX + 2];

    if (!*source) {
#ifdef HAS_NICKIP
        char ipbuf[16];
        struct in_addr addr;
        uint32 ip;
#endif
#ifdef HAS_VHOST
        char *vhost = NULL;
#endif
#if defined(HAS_NICKIP) || defined(HAS_NICKVHOST)
        va_list args;
        va_start(args, svid);
#endif

#ifdef HAS_NICKIP
        ip = va_arg(args, uint32);
#endif

#ifdef HAS_NICKVHOST
        vhost = va_arg(args, char *);
        if (!strcmp(vhost, "*")) {
            vhost = NULL;
            if (debug)
                alog("debug: new user with no vhost in NICK command: %s",
                     nick);
        }
#endif

        /* This is a new user; create a User structure for it. */
        if (debug)
            alog("debug: new user: %s", nick);

        if (LogUsers) {
        /**
	 * Ugly swap routine for Flop's bug :)
 	 **/
            tmp = strchr(realname, '%');
            while (tmp) {
                *tmp = '-';
                tmp = strchr(realname, '%');
            }
        /**
	 * End of ugly swap
	 **/

#ifdef HAS_NICKIP
            addr.s_addr = htonl(ip);
            ntoa(addr, ipbuf, sizeof(ipbuf));
#endif

#ifdef HAS_NICKVHOST
# ifdef HAS_NICKIP
            alog("LOGUSERS: %s (%s@%s => %s) (%s) [%s] connected to the network (%s).", nick, username, host, vhost, realname, ipbuf, server);
# else
            alog("LOGUSERS: %s (%s@%s => %s) (%s) connected to the network (%s).", nick, username, host, vhost, realname, server);
# endif
#else
# ifdef HAS_NICKIP
            alog("LOGUSERS: %s (%s@%s) (%s) [%s] connected to the network (%s).", nick, username, host, realname, ipbuf, server);
# else
            alog("LOGUSERS: %s (%s@%s) (%s) connected to the network (%s).", nick, username, host, realname, server);
# endif
#endif
        }

        /* We used to ignore the ~ which a lot of ircd's use to indicate no
         * identd response.  That caused channel bans to break, so now we
         * just take what the server gives us.  People are still encouraged
         * to read the RFCs and stop doing anything to usernames depending
         * on the result of an identd lookup.
         */

        /* First check for AKILLs. */
        /* DONT just return null if its an akill match anymore - yes its more efficent to, however, now that ircd's are
         * starting to use things like E/F lines, we cant be 100% sure the client will be removed from the network :/
         * as such, create a user_struct, and if the client is removed, we'll delete it again when the QUIT notice
         * comes in from the ircd.
         **/
        if (check_akill(nick, username, host,
#ifdef HAS_NICKVHOST
                        vhost,
#else
                        NULL,
#endif
#ifdef HAS_NICKIP
                        ipbuf)) {
#else
                        NULL)) {
#endif
/*            return NULL; */
        }

/**
 * DefCon AKILL system, if we want to akill all connecting user's here's where to do it
 * then force check_akill again on them...
 **/
        if (checkDefCon(DEFCON_AKILL_NEW_CLIENTS)) {
            strncpy(mask, "*@", 3);
            strncat(mask, host, HOSTMAX);
            alog("DEFCON: adding akill for %s", mask);
            add_akill(NULL, mask, s_OperServ,
                      time(NULL) + dotime(DefConAKILL),
                      DefConAkillReason ? DefConAkillReason :
                      "DEFCON AKILL");
            if (check_akill(nick, username, host,
#ifdef HAS_NICKVHOST
                            vhost,
#else
                            NULL,
#endif
#ifdef HAS_NICKIP
                            ipbuf)) {
#else
                            NULL)) {
#endif
/*            return NULL; */
            }
        }
#ifdef IRC_BAHAMUT
        /* Next for SGLINEs */
        if (check_sgline(nick, realname))
            return NULL;
#endif

        /* And for SQLINEs */
        if (check_sqline(nick, 0))
            return NULL;

#ifndef STREAMLINED
        /* Now check for session limits */
        if (LimitSessions && !add_session(nick, host))
            return NULL;
#endif

        /* And finally, for proxy ;) */
#ifdef USE_THREADS
# ifdef HAS_NICKIP
        if (ProxyDetect && proxy_check(nick, host, ip))
# else
        if (ProxyDetect && proxy_check(nick, host, 0))
# endif
            return NULL;
#endif

        /* Allocate User structure and fill it in. */
        user = new_user(nick);
        user->username = sstrdup(username);
        user->host = sstrdup(host);
        user->server = sstrdup(server);
        user->realname = sstrdup(realname);
        user->timestamp = ts;
        user->my_signon = time(NULL);

#ifdef HAS_VHOST
        user->vhost = vhost ? sstrdup(vhost) : sstrdup(host);
#endif

        if (CheckClones) {
            /* Check to see if it looks like clones. */
            check_clones(user);
        }

        if (svid == 0) {
            display_news(user, NEWS_LOGON);
            display_news(user, NEWS_RANDOM);
        }

        if (svid == ts && user->na) {
            /* Timestamp and svid match, and nick is registered; automagically identify the nick */
            user->svid = svid;
            user->na->status |= NS_IDENTIFIED;
            check_memos(user);
            nc_changed = 0;
        } else if (svid != 1) {
            /* Resets the svid because it doesn't match */
            user->svid = 1;
#ifdef IRC_BAHAMUT
            send_cmd(ServerName, "SVSMODE %s %lu +d 1", user->nick,
                     user->timestamp);
#else
#ifndef IRC_PTLINK
            send_cmd(ServerName, "SVSMODE %s +d 1", user->nick);
#endif
#endif
        } else {
            user->svid = 1;
        }

    } else {
        /* An old user changing nicks. */
        user = finduser(source);
        if (!user) {
            alog("user: NICK from nonexistent nick %s", source);
            return NULL;
        }
        user->isSuperAdmin = 0; /* Dont let people nick change and stay SuperAdmins */
        if (debug)
            alog("debug: %s changes nick to %s", source, nick);

        if (LogUsers) {
#ifdef HAS_VHOST
            alog("LOGUSERS: %s (%s@%s => %s) (%s) changed his nick to %s (%s).", user->nick, user->username, user->host, (user->vhost ? user->vhost : "(none)"), user->realname, nick, user->server);
#else
            alog("LOGUSERS: %s (%s@%s) (%s) changed his nick to %s (%s).",
                 user->nick, user->username, user->host,
                 user->realname, nick, user->server);
#endif
        }

        user->timestamp = ts;

        if (stricmp(nick, user->nick) == 0) {
            /* No need to redo things */
            change_user_nick(user, nick);
            nc_changed = 0;
        } else {
            /* Update this only if nicks aren't the same */
            user->my_signon = time(NULL);

            old_na = user->na;
            if (old_na) {
                if (nick_recognized(user))
                    user->na->last_seen = time(NULL);
                status = old_na->status & NS_TRANSGROUP;
                cancel_user(user);
            }

            change_user_nick(user, nick);

            if ((old_na ? old_na->nc : NULL) ==
                (user->na ? user->na->nc : NULL))
                nc_changed = 0;

            if (!nc_changed && (user->na))
                user->na->status |= status;
            else {
#if !defined(IRC_BAHAMUT) && !defined(IRC_PTLINK)
                /* Because on Bahamut it would be already -r */
                change_user_mode(user, "-r+d", "1");
#else
                change_user_mode(user, "+d", "1");
#endif
            }
        }

        if (!is_oper(user) && check_sqline(user->nick, 1))
            return NULL;

    }                           /* if (!*source) */

    if (nc_changed || !nick_recognized(user)) {
        if (validate_user(user))
            check_memos(user);
    } else {
        if (nick_identified(user)) {
            user->na->last_seen = time(NULL);

            if (user->na->last_usermask)
                free(user->na->last_usermask);
            user->na->last_usermask =
                smalloc(strlen(GetIdent(user)) + strlen(GetHost(user)) +
                        2);
            sprintf(user->na->last_usermask, "%s@%s", GetIdent(user),
                    GetHost(user));

#ifdef IRC_PTLINK
            change_user_mode(user, "+r", NULL);
#endif

#if !defined(IRC_BAHAMUT) && !defined(IRC_PTLINK)
            if (user->svid != user->timestamp) {
                char tsbuf[16];
                snprintf(tsbuf, sizeof(tsbuf), "%lu", user->timestamp);
                change_user_mode(user, "+rd", tsbuf);
            } else {
                change_user_mode(user, "+r", NULL);
            }
#endif


            alog("%s: %s!%s@%s automatically identified for nick %s",
                 s_NickServ, user->nick, user->username, GetHost(user),
                 user->nick);
        }
    }

/* Bahamut sets -r on every nick changes, so we must test it even if nc_changed == 0 */
#ifdef IRC_BAHAMUT
    if (nick_identified(user)) {
        if (user->svid != user->timestamp) {
            char tsbuf[16];
            snprintf(tsbuf, sizeof(tsbuf), "%lu", user->timestamp);
            change_user_mode(user, "+rd", tsbuf);
        } else {
            change_user_mode(user, "+r", NULL);
        }
    }
#endif

    return user;
}

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

/* Handle a MODE command for a user.
 *	av[0] = nick to change mode for
 *	av[1] = modes
 */

void do_umode(const char *source, int ac, char **av)
{
    User *user;

    if (stricmp(source, av[0]) != 0) {
        alog("user: MODE %s %s from different nick %s!", av[0], av[1],
             source);
        wallops(NULL, "%s attempted to change mode %s for %s", source,
                av[1], av[0]);
        return;
    }

    user = finduser(source);
    if (!user) {
        alog("user: MODE %s for nonexistent nick %s: %s", av[1], source,
             merge_args(ac, av));
        return;
    }

    set_umode(user, ac - 1, &av[1]);
}

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

/* Handle a QUIT command.
 *	av[0] = reason
 */

void do_quit(const char *source, int ac, char **av)
{
    User *user;
    NickAlias *na;

    user = finduser(source);
    if (!user) {
        alog("user: QUIT from nonexistent user %s: %s", source,
             merge_args(ac, av));
        return;
    }
    if (debug)
        alog("debug: %s quits", source);
    if ((na = user->na) && (!(na->status & NS_VERBOTEN))
        && (na->status & (NS_IDENTIFIED | NS_RECOGNIZED))) {
        na->last_seen = time(NULL);
        if (na->last_quit)
            free(na->last_quit);
        na->last_quit = *av[0] ? sstrdup(av[0]) : NULL;
    }
#ifndef STREAMLINED
    if (LimitSessions)
        del_session(user->host);
#endif
    delete_user(user);
}

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

/* Handle a KILL command.
 *	av[0] = nick being killed
 *	av[1] = reason
 */

void do_kill(const char *source, int ac, char **av)
{
    User *user;
    NickAlias *na;

    user = finduser(av[0]);
    if (!user)
        return;
    if (debug)
        alog("debug: %s killed", av[0]);
    if ((na = user->na) && (!(na->status & NS_VERBOTEN))
        && (na->status & (NS_IDENTIFIED | NS_RECOGNIZED))) {
        na->last_seen = time(NULL);
        if (na->last_quit)
            free(na->last_quit);
        na->last_quit = *av[1] ? sstrdup(av[1]) : NULL;

    }
#ifndef STREAMLINED
    if (LimitSessions)
        del_session(user->host);
#endif
    delete_user(user);
}

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

#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)

/* Is the given user protected from kicks and negative mode changes? */

int is_protected(User * user)
{
    return (user->mode & UMODE_p);
}
#endif

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

/* Is the given nick an oper? */

int is_oper(User * user)
{
    return (user->mode & UMODE_o);
}

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

#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID)
/* Is the given user ban-excepted? */
int is_excepted(ChannelInfo * ci, User * user)
{
    int count, i;
    int isexcepted = 0;
    char **excepts;

    if (!ci->c)
        return 0;

    count = ci->c->exceptcount;
    excepts = scalloc(sizeof(char *) * count, 1);
    memcpy(excepts, ci->c->excepts, sizeof(char *) * count);

    for (i = 0; i < count; i++) {
        if (match_usermask(excepts[i], user)) {
            isexcepted = 1;
        }
    }
    free(excepts);
    return isexcepted;
}

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

/* Is the given MASK ban-excepted? */
int is_excepted_mask(ChannelInfo * ci, char *mask)
{
    int count, i;
    int isexcepted = 0;
    char **excepts;

    if (!ci->c)
        return 0;

    count = ci->c->exceptcount;
    excepts = scalloc(sizeof(char *) * count, 1);
    memcpy(excepts, ci->c->excepts, sizeof(char *) * count);

    for (i = 0; i < count; i++) {
        if (match_wild_nocase(excepts[i], mask)) {
            isexcepted = 1;
        }
    }
    free(excepts);
    return isexcepted;
}

#endif
/*************************************************************************/

/* Does the user's usermask match the given mask (either nick!user@host or
 * just user@host)?
 */

int match_usermask(const char *mask, User * user)
{
    char *mask2 = sstrdup(mask);
    char *nick, *username, *host;
    int result;

    if (strchr(mask2, '!')) {
        nick = strtok(mask2, "!");
        username = strtok(NULL, "@");
    } else {
        nick = NULL;
        username = strtok(mask2, "@");
    }
    host = strtok(NULL, "");
    if (!username || !host) {
        free(mask2);
        return 0;
    }

    if (nick) {
        result = match_wild_nocase(nick, user->nick)
            && match_wild_nocase(username, user->username)
            && (match_wild_nocase(host, user->host)
#ifdef HAS_VHOST
                || match_wild_nocase(host, user->vhost)
#endif
            );
    } else {
        result = match_wild_nocase(username, user->username)
            && (match_wild_nocase(host, user->host)
#ifdef HAS_VHOST
                || match_wild_nocase(host, user->vhost)
#endif
            );
    }

    free(mask2);
    return result;
}

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

/* Split a usermask up into its constitutent parts.  Returned strings are
 * malloc()'d, and should be free()'d when done with.  Returns "*" for
 * missing parts.
 */

void split_usermask(const char *mask, char **nick, char **user,
                    char **host)
{
    char *mask2 = sstrdup(mask);

    *nick = strtok(mask2, "!");
    *user = strtok(NULL, "@");
    *host = strtok(NULL, "");
    /* Handle special case: mask == user@host */
    if (*nick && !*user && strchr(*nick, '@')) {
        *nick = NULL;
        *user = strtok(mask2, "@");
        *host = strtok(NULL, "");
    }
    if (!*nick)
        *nick = "*";
    if (!*user)
        *user = "*";
    if (!*host)
        *host = "*";
    *nick = sstrdup(*nick);
    *user = sstrdup(*user);
    *host = sstrdup(*host);
    free(mask2);
}

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

/* Given a user, return a mask that will most likely match any address the
 * user will have from that location.  For IP addresses, wildcards the
 * appropriate subnet mask (e.g. 35.1.1.1 -> 35.*; 128.2.1.1 -> 128.2.*);
 * for named addresses, wildcards the leftmost part of the name unless the
 * name only contains two parts.  If the username begins with a ~, delete
 * it.  The returned character string is malloc'd and should be free'd
 * when done with.
 */

char *create_mask(User * u)
{
    char *mask, *s, *end;
    int ulen = strlen(GetIdent(u));

    /* Get us a buffer the size of the username plus hostname.  The result
     * will never be longer than this (and will often be shorter), thus we
     * can use strcpy() and sprintf() safely.
     */
    end = mask = smalloc(ulen + strlen(GetHost(u)) + 3);
    end += sprintf(end, "%s%s@",
                   (ulen <
                    (*(GetIdent(u)) ==
                     '~' ? USERMAX + 1 : USERMAX) ? "*" : ""),
                   (*(GetIdent(u)) ==
                    '~' ? GetIdent(u) + 1 : GetIdent(u)));

    if (strspn(GetHost(u), "0123456789.") == strlen(GetHost(u))
        && (s = strchr(GetHost(u), '.'))
        && (s = strchr(s + 1, '.'))
        && (s = strchr(s + 1, '.'))
        && (!strchr(s + 1, '.'))) {     /* IP addr */
        s = sstrdup(GetHost(u));
        *strrchr(s, '.') = 0;

        sprintf(end, "%s.*", s);
        free(s);
    } else {
        if ((s = strchr(GetHost(u), '.')) && strchr(s + 1, '.')) {
            s = sstrdup(strchr(GetHost(u), '.') - 1);
            *s = '*';
            strcpy(end, s);
            free(s);
        } else {
            strcpy(end, GetHost(u));
        }
    }
    return mask;
}

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