/* Channel-handling routines.
 *
 * (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: channels.c 5 2004-03-29 01:29:50Z dane $
 *
 */

#include "services.h"
#include "language.h"

Channel *chanlist[1024];

#define HASH(chan)	((chan)[1] ? ((chan)[1]&31)<<5 | ((chan)[2]&31) : 0)

static void add_ban(Channel * chan, char *mask);
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_HYBRID)
static void add_exception(Channel * chan, char *mask);
#endif
static void chan_adduser2(User * user, Channel * c);
static Channel *chan_create(const char *chan);
static void chan_delete(Channel * c);
static void del_ban(Channel * chan, char *mask);
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_HYBRID)
static void del_exception(Channel * chan, char *mask);
#endif
#ifdef HAS_FMODE
static char *get_flood(Channel * chan);
#endif
static char *get_key(Channel * chan);
static char *get_limit(Channel * chan);
#ifdef HAS_LMODE
static char *get_redirect(Channel * chan);
#endif
static Channel *join_user_update(User * user, Channel * chan, char *name);
#ifdef HAS_FMODE
static void set_flood(Channel * chan, char *value);
#endif
static void set_key(Channel * chan, char *value);
static void set_limit(Channel * chan, char *value);
#ifdef HAS_LMODE
static void set_redirect(Channel * chan, char *value);
#endif
void do_mass_mode(char *modes);

/*************************************************************************/
/* *INDENT-OFF* */

CBMode cbmodes[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 },
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3)
	{ CMODE_A, CBM_NO_USER_MLOCK, NULL, NULL },
#else
	{ 0 }, /* A */
#endif
	{ 0 }, /* B */
#ifdef IRC_UNREAL
	{ CMODE_C, 0, NULL, NULL },
#else
	{ 0 }, /* C */
#endif
	{ 0 }, /* D */
	{ 0 }, /* E */
	{ 0 }, /* F */
#ifdef IRC_UNREAL
	{ CMODE_G, 0, NULL, NULL },
	{ CMODE_H, CBM_NO_USER_MLOCK, NULL, NULL },
#else
	{ 0 }, /* G */
	{ 0 }, /* H */
#endif
#ifdef IRC_ULTIMATE
	{ CMODE_I },
#else
	{ 0 }, /* I */
#endif
	{ 0 }, /* J */
#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3)
	{ CMODE_K, 0, NULL, NULL },
#else
	{ 0 }, /* K */
#endif
#ifdef HAS_LMODE
	{ CMODE_L, 0, set_redirect, cs_set_redirect },
#else
	{ 0 }, /* L */
#endif
#ifdef IRC_BAHAMUT
	{ CMODE_M },
#else
	{ 0 }, /* M */
#endif
#if defined (IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined (IRC_PTLINK)
	{ CMODE_N, 0, NULL, NULL },
#else
	{ 0 }, /* N */
#endif
#if defined(IRC_BAHAMUT) || defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3)
	{ CMODE_O, CBM_NO_USER_MLOCK, NULL, NULL },
#else
	{ 0 }, /* O */
#endif
	{ 0 }, /* P */
#ifdef IRC_UNREAL
	{ CMODE_Q, 0, NULL, NULL },
#else
	{ 0 }, /* Q */
#endif
#ifndef IRC_HYBRID
	{ CMODE_R, 0, NULL, NULL }, /* R */
#else
        { 0 },
#endif
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined (IRC_PTLINK)
	{ CMODE_S, 0, NULL, NULL },
#else
	{ 0 }, /* S */
#endif
	{ 0 }, /* T */
	{ 0 }, /* U */
#ifdef IRC_UNREAL
	{ CMODE_V, 0, NULL, NULL },
#else
	{ 0 }, /* V */
#endif
	{ 0 }, /* W */
	{ 0 }, /* X */
	{ 0 }, /* Y */
	{ 0 }, /* Z */
	{ 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 },
#ifdef IRC_HYBRID
        { CMODE_a, 0, NULL, NULL },
#else
	{ 0 }, /* a */
#endif
	{ 0 }, /* b */
#if defined(IRC_BAHAMUT) || defined(IRC_UNREAL) || defined (IRC_PTLINK)
	{ CMODE_c, 0, NULL, NULL },
#else
	{ 0 }, /* c */
#endif
#ifdef IRC_PTLINK
	{ CMODE_d, 0, NULL, NULL },
#else
	{ 0 }, /* d */
#endif
	{ 0 }, /* e */
#ifdef HAS_FMODE
	{ CMODE_f, 0, set_flood, cs_set_flood },
#else
	{ 0 }, /* f */
#endif
	{ 0 }, /* g */
	{ 0 }, /* h */
	{ CMODE_i, 0, NULL, NULL },
	{ 0 }, /* j */
	{ CMODE_k, 0, set_key, cs_set_key },
	{ CMODE_l, CBM_MINUS_NO_ARG, set_limit, cs_set_limit },
	{ CMODE_m, 0, NULL, NULL },
	{ CMODE_n, 0, NULL, NULL },
	{ 0 }, /* o */
	{ CMODE_p, 0, NULL, NULL },
#ifdef IRC_PTLINK
	{ CMODE_q, 0, NULL, NULL },
#else
	{ 0 }, /* q */
#endif
#ifndef IRC_HYBRID
	{ CMODE_r, CBM_NO_MLOCK, NULL, NULL },
#else
	{ 0 },
#endif
	{ CMODE_s, 0, NULL, NULL },
	{ CMODE_t, 0, NULL, NULL },
#ifdef IRC_UNREAL
	{ CMODE_u, 0, NULL, NULL },
#else
	{ 0 },
#endif
	{ 0 }, /* v */
	{ 0 }, /* w */
#ifdef IRC_ULTIMATE
	{ CMODE_x },
#else
	{ 0 }, /* x */
#endif
	{ 0 }, /* y */
#ifdef IRC_UNREAL
	{ CMODE_z, 0, NULL, NULL },
#else
	{ 0 }, /* z */
#endif
	{ 0 }, { 0 }, { 0 }, { 0 }
};

CBModeInfo cbmodeinfos[] = {
#if defined(IRC_HYBRID)
	{ 'a', CMODE_a, 0, NULL, NULL },
#endif
#if defined(IRC_BAHAMUT) || defined(IRC_UNREAL) || defined(IRC_PTLINK)
	{ 'c', CMODE_c, 0, NULL, NULL },
#endif
#if defined(IRC_PTLINK)
	{ 'd', CMODE_d, 0, NULL, NULL },
#endif
#ifdef HAS_FMODE
	{ 'f', CMODE_f, 0, get_flood, cs_get_flood },
#endif
	{ 'i', CMODE_i, 0, NULL, NULL },
	{ 'k', CMODE_k, 0, get_key, cs_get_key },
	{ 'l', CMODE_l, CBM_MINUS_NO_ARG, get_limit, cs_get_limit },
	{ 'm', CMODE_m, 0, NULL, NULL },
	{ 'n', CMODE_n, 0, NULL, NULL },
	{ 'p', CMODE_p, 0, NULL, NULL },
#ifdef IRC_PTLINK
	{ 'q', CMODE_q, 0, NULL, NULL },
#endif
#ifndef IRC_HYBRID
	{ 'r', CMODE_r, 0, NULL, NULL },
#endif
	{ 's', CMODE_s, 0, NULL, NULL },
	{ 't', CMODE_t, 0, NULL, NULL },
#ifdef IRC_UNREAL
	{ 'u', CMODE_u, 0, NULL, NULL },
#endif
#ifdef IRC_ULTIMATE
	{ 'x', CMODE_x, 0, NULL, NULL },
#endif
#ifdef IRC_UNREAL
	{ 'z', CMODE_z, 0, NULL, NULL },
#endif
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_PTLINK)
	{ 'A', CMODE_A, 0, NULL, NULL },
#endif
#ifdef IRC_UNREAL
	{ 'C', CMODE_C, 0, NULL, NULL },
	{ 'G', CMODE_G, 0, NULL, NULL },
	{ 'H', CMODE_H, 0, NULL, NULL },
#endif
#ifdef IRC_ULTIMATE
	{ 'I', CMODE_I, 0, NULL, NULL },
#endif
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_PTLINK) || defined(IRC_ULTIMATE3)
	{ 'K', CMODE_K, 0, NULL, NULL },
#endif
#ifdef HAS_LMODE
	{ 'L', CMODE_L, 0, get_redirect, cs_get_redirect },
#endif
#ifdef IRC_BAHAMUT
#ifndef IRC_ULTIMATE3
	{ 'M', CMODE_M, 0, NULL, NULL },
#endif
#endif
#if defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_PTLINK)
	{ 'N', CMODE_N, 0, NULL, NULL },
#endif
#if defined(IRC_BAHAMUT) || defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3)
	{ 'O', CMODE_O, 0, NULL, NULL },
#endif
#ifdef IRC_UNREAL
	{ 'Q', CMODE_Q, 0, NULL, NULL },
#endif
#ifndef IRC_HYBRID
	{ 'R', CMODE_R, 0, NULL, NULL },
#endif
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_PTLINK)
	{ 'S', CMODE_S, 0, NULL, NULL },
#endif
#ifdef IRC_UNREAL
	{ 'V', CMODE_V, 0, NULL, NULL },
#endif
	{ 0 }
};

static CMMode cmmodes[128] = {
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL },
	{ NULL },
	{ add_ban, del_ban },
	{ NULL },
	{ NULL },
#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_HYBRID)
	{ add_exception, del_exception },
#endif
	{ NULL },
	{ NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
	{ NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }
};

 #if defined(IRC_BAHAMUT) || defined(IRC_HYBRID) || defined(IRC_PTLINK)

static char csmodes[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,
 #if defined(IRC_ULTIMATE3) || defined(IRC_HYBRID)
        'a', /* (33) * Channel Admins */
 #else
        0,
 #endif
	 0, 0, 0,
 #ifdef IRC_ULTIMATE3
        'h', /* (37) % Channel halfops */
 #else
        0,
 #endif
        0, 0, 0, 0, 0,

        'v', 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

	'o', 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
};

#endif

static CUMode cumodes[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 }, { 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 },

#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
        { CUS_PROTECT, CUF_PROTECT_BOTSERV, check_valid_op },
#else
#if defined(IRC_ULTIMATE3)
        { CUS_PROTECT, CUF_PROTECT_BOTSERV, check_valid_admin },
#else
        { 0 }, /* a */
#endif
#endif
	{ 0 }, /* b */
	{ 0 }, /* c */
	{ 0 }, /* d */
	{ 0 }, /* e */
	{ 0 }, /* f */
	{ 0 }, /* g */
#ifdef HAS_HALFOP
	{ CUS_HALFOP, 0, check_valid_op },
#else
	{ 0 }, /* h */
#endif
	{ 0 }, /* i */
	{ 0 }, /* j */
	{ 0 }, /* k */
	{ 0 }, /* l */
	{ 0 }, /* m */
	{ 0 }, /* n */
	{ CUS_OP, CUF_PROTECT_BOTSERV, check_valid_op },
	{ 0 }, /* p */
#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
	{ CUS_OWNER, 0, check_valid_op },
#else
	{ 0 }, /* q */
#endif
	{ 0 }, /* r */
	{ 0 }, /* s */
	{ 0 }, /* t */
	{ 0 }, /* u */
	{ CUS_VOICE, 0, NULL },
	{ 0 }, /* w */
	{ 0 }, /* x */
	{ 0 }, /* y */
	{ 0 }, /* z */
	{ 0 }, { 0 }, { 0 }, { 0 }, { 0 }
};

/* *INDENT-ON* */
/*************************************************************************/
/**************************** External Calls *****************************/
/*************************************************************************/

void chan_deluser(User * user, Channel * c)
{
    struct c_userlist *u;

    if (c->ci)
        update_cs_lastseen(user, c->ci);

    for (u = c->users; u && u->user != user; u = u->next);
    if (!u)
        return;

    if (u->ud) {
        if (u->ud->lastline)
            free(u->ud->lastline);
        free(u->ud);
    }

    if (u->next)
        u->next->prev = u->prev;
    if (u->prev)
        u->prev->next = u->next;
    else
        c->users = u->next;
    free(u);
    c->usercount--;

    if (s_BotServ && c->ci && c->ci->bi && c->usercount == BSMinUsers - 1) {
        send_cmd(c->ci->bi->nick, "PART %s", c->name);
    }

    if (!c->users)
        chan_delete(c);
}

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

/* Returns a fully featured binary modes string. If complete is 0, the
 * eventual parameters won't be added to the string.
 */

char *chan_get_modes(Channel * chan, int complete, int plus)
{
    static char res[BUFSIZE];
    char *end = res;

    if (chan->mode) {
        int n = 0;
        CBModeInfo *cbmi = cbmodeinfos;

        do {
            if (chan->mode & cbmi->flag)
                *end++ = cbmi->mode;
        } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);

        if (complete) {
            cbmi = cbmodeinfos;

            do {
                if (cbmi->getvalue && (chan->mode & cbmi->flag) &&
                    (plus || !(cbmi->flags & CBM_MINUS_NO_ARG))) {
                    char *value = cbmi->getvalue(chan);

                    if (value) {
                        *end++ = ' ';
                        while (*value)
                            *end++ = *value++;
                    }
                }
            } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
        }
    }

    *end = 0;

    return res;
}

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

/* Retrieves the status of an user on a channel */

int chan_get_user_status(Channel * chan, User * user)
{
    struct u_chanlist *uc;

    for (uc = user->chans; uc; uc = uc->next)
        if (uc->chan == chan)
            return uc->status;

    return 0;
}

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

/* Has the given user the given status on the given channel? :p */

int chan_has_user_status(Channel * chan, User * user, int16 status)
{
    struct u_chanlist *uc;

    for (uc = user->chans; uc; uc = uc->next)
        if (uc->chan == chan)
            return (uc->status & status);

    return 0;
}

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

/* Remove the status of an user on a channel */

void chan_remove_user_status(Channel * chan, User * user, int16 status)
{
    struct u_chanlist *uc;

    for (uc = user->chans; uc; uc = uc->next) {
        if (uc->chan == chan) {
            uc->status &= ~status;
            break;
        }
    }
}

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

void chan_set_modes(const char *source, Channel * chan, int ac, char **av,
                    int check)
{
    int add = 1;
    int servermode = !!strchr(source, '.');
    char *modes = av[0], mode;
    CBMode *cbm;
    CMMode *cmm;
    CUMode *cum;

    if (debug)
        alog("debug: Changing modes for %s to %s", chan->name,
             merge_args(ac, av));

    ac--;

    while ((mode = *modes++)) {

        switch (mode) {
        case '+':
            add = 1;
            continue;
        case '-':
            add = 0;
            continue;
        }

        if (((int) mode) < 0) {
            if (debug)
                alog("Debug: Malformed mode detected on %s.", chan->name);
            continue;
        }

        if ((cum = &cumodes[(int) mode])->status != 0) {
            User *user;

            if (ac == 0) {
                alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name);
                continue;
            }
            ac--;
            av++;

            if ((cum->flags & CUF_PROTECT_BOTSERV) && !add) {
                BotInfo *bi;

                if ((bi = findbot(*av))) {
                    send_cmd(bi->nick, "MODE %s +%c %s", chan->name, mode,
                             bi->nick);
                    continue;
                }
            }

            if (!(user = finduser(*av))) {
                alog("channel: MODE %s %c%c for nonexistent user %s",
                     chan->name, (add ? '+' : '-'), mode, *av);
                continue;
            }

            if (debug)
                alog("debug: Setting %c%c on %s for %s", (add ? '+' : '-'),
                     mode, chan->name, user->nick);

            if (add) {
                if (check && cum->is_valid
                    && !cum->is_valid(user, chan, servermode))
                    continue;
                chan_set_user_status(chan, user, cum->status);
            } else {
                chan_remove_user_status(chan, user, cum->status);
            }
        } else if ((cbm = &cbmodes[(int) mode])->flag != 0) {
            if (add)
                chan->mode |= cbm->flag;
            else
                chan->mode &= ~cbm->flag;

            if (cbm->setvalue) {
                if (add || !(cbm->flags & CBM_MINUS_NO_ARG)) {
                    if (ac == 0) {
                        alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name);
                        continue;
                    }
                    ac--;
                    av++;
                }
                cbm->setvalue(chan, add ? *av : NULL);
            }
        } else if ((cmm = &cmmodes[(int) mode])->addmask) {
            if (ac == 0) {
                alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name);
                continue;
            }

            ac--;
            av++;
            add ? cmm->addmask(chan, *av) : cmm->delmask(chan, *av);
        }
    }

    if (check)
        check_modes(chan);
}

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

/* Set the status of an user on a channel */

void chan_set_user_status(Channel * chan, User * user, int16 status)
{
    struct u_chanlist *uc;

    if (HelpChannel && status == CUS_OP
        && !stricmp(chan->name, HelpChannel))
        change_user_mode(user, "+h", NULL);

    for (uc = user->chans; uc; uc = uc->next) {
        if (uc->chan == chan) {
            uc->status |= status;
            break;
        }
    }
}

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

/* Return the Channel structure corresponding to the named channel, or NULL
 * if the channel was not found.  chan is assumed to be non-NULL and valid
 * (i.e. pointing to a channel name of 2 or more characters). */

Channel *findchan(const char *chan)
{
    Channel *c;

    if (debug >= 3)
        alog("debug: findchan(%p)", chan);
    c = chanlist[HASH(chan)];
    while (c) {
        if (stricmp(c->name, chan) == 0)
            return c;
        c = c->next;
    }
    if (debug >= 3)
        alog("debug: findchan(%s) -> %p", chan, c);
    return NULL;
}

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

/* Iterate over all channels in the channel list.  Return NULL at end of
 * list.
 */

static Channel *current;
static int next_index;

Channel *firstchan(void)
{
    next_index = 0;
    while (next_index < 1024 && current == NULL)
        current = chanlist[next_index++];
    if (debug >= 3)
        alog("debug: firstchan() returning %s",
             current ? current->name : "NULL (end of list)");
    return current;
}

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

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

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

void get_channel_stats(long *nrec, long *memuse)
{
    long count = 0, mem = 0;
    Channel *chan;
    struct c_userlist *cu;
    BanData *bd;
    int i, j;

    for (i = 0; i < 1024; i++) {
        for (chan = chanlist[i]; chan; chan = chan->next) {
            count++;
            mem += sizeof(*chan);
            if (chan->topic)
                mem += strlen(chan->topic) + 1;
            if (chan->key)
                mem += strlen(chan->key) + 1;
#ifdef HAS_FMODE
            if (chan->flood)
                mem += strlen(chan->flood) + 1;
#endif
#ifdef HAS_LMODE
            if (chan->redirect)
                mem += strlen(chan->redirect) + 1;
#endif
            mem += sizeof(char *) * chan->bansize;
            for (j = 0; j < chan->bancount; j++) {
                if (chan->bans[j])
                    mem += strlen(chan->bans[j]) + 1;
            }
#if defined (IRC_ULTIMATE) || defined (IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined(IRC_HYBRID)
            mem += sizeof(char *) * chan->exceptsize;
            for (j = 0; j < chan->exceptcount; j++) {
                if (chan->excepts[j])
                    mem += strlen(chan->excepts[j]) + 1;
            }
#endif
            for (cu = chan->users; cu; cu = cu->next) {
                mem += sizeof(*cu);
                if (cu->ud) {
                    mem += sizeof(*cu->ud);
                    if (cu->ud->lastline)
                        mem += strlen(cu->ud->lastline) + 1;
                }
            }
            for (bd = chan->bd; bd; bd = bd->next) {
                if (bd->mask)
                    mem += strlen(bd->mask) + 1;
                mem += sizeof(*bd);
            }
        }
    }
    *nrec = count;
    *memuse = mem;
}

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

/* Is the given nick on the given channel? */

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

    for (uc = u->chans; uc; uc = uc->next)
        if (uc->chan == c)
            return 1;

    return 0;
}

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

/* Is the given nick on the given channel?
   This function supports links. */

User *nc_on_chan(Channel * c, NickCore * nc)
{
    struct c_userlist *u;

    if (!c || !nc)
        return NULL;

    for (u = c->users; u; u = u->next) {
        if (u->user->na && u->user->na->nc == nc
            && nick_recognized(u->user))
            return u->user;
    }
    return NULL;
}

/*************************************************************************/
/*************************** Message Handling ****************************/
/*************************************************************************/

/* Handle a JOIN command.
 *	av[0] = channels to join
 */

void do_join(const char *source, int ac, char **av)
{
    User *user;
    char *s, *t;
    struct u_chanlist *c, *nextc;

    user = finduser(source);
    if (!user) {
        alog("user: JOIN from nonexistent user %s: %s", source,
             merge_args(ac, av));
        return;
    }

    t = av[0];
    while (*(s = t)) {
        t = s + strcspn(s, ",");
        if (*t)
            *t++ = 0;
        if (debug)
            alog("debug: %s joins %s", source, s);

        if (*s == '0') {
            c = user->chans;
            while (c) {
                nextc = c->next;
                chan_deluser(user, c->chan);
                free(c);
                c = nextc;
            }
            user->chans = NULL;
            continue;
        }

        /* Make sure check_kick comes before chan_adduser, so banned users
         * don't get to see things like channel keys. */
        if (check_kick(user, s))
            continue;

/*	chan_adduser(user, s); */
        join_user_update(user, findchan(s), s);


/*        c = scalloc(sizeof(*c), 1);
        c->next = user->chans;
        if (user->chans)
            user->chans->prev = c;
        user->chans = c;
        c->chan = findchan(s); */
    }
}

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

/* Handle a KICK command.
 *	av[0] = channel
 *	av[1] = nick(s) being kicked
 *	av[2] = reason
 */

void do_kick(const char *source, int ac, char **av)
{
    BotInfo *bi;
    ChannelInfo *ci;
    User *user;
    char *s, *t;
    struct u_chanlist *c;

    t = av[1];
    while (*(s = t)) {
        t = s + strcspn(s, ",");
        if (*t)
            *t++ = 0;

        /* If it is the bot that is being kicked, we make it rejoin the
         * channel and stop immediately.
         *      --lara
         */
        if (s_BotServ && (bi = findbot(s)) && (ci = cs_findchan(av[0]))) {
            bot_join(ci);
            continue;
        }

        user = finduser(s);
        if (!user) {
            alog("user: KICK for nonexistent user %s on %s: %s", s, av[0],
                 merge_args(ac - 2, av + 2));
            continue;
        }
        if (debug)
            alog("debug: kicking %s from %s", s, av[0]);
        for (c = user->chans; c && stricmp(av[0], c->chan->name) != 0;
             c = c->next);
        if (c) {
            chan_deluser(user, c->chan);
            if (c->next)
                c->next->prev = c->prev;
            if (c->prev)
                c->prev->next = c->next;
            else
                user->chans = c->next;
            free(c);
        }
    }
}

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

/* Handle a PART command.
 *	av[0] = channels to leave
 *	av[1] = reason (optional)
 */

void do_part(const char *source, int ac, char **av)
{
    User *user;
    char *s, *t;
    struct u_chanlist *c;

    user = finduser(source);
    if (!user) {
        alog("user: PART from nonexistent user %s: %s", source,
             merge_args(ac, av));
        return;
    }
    t = av[0];
    while (*(s = t)) {
        t = s + strcspn(s, ",");
        if (*t)
            *t++ = 0;
        if (debug)
            alog("debug: %s leaves %s", source, s);
        for (c = user->chans; c && stricmp(s, c->chan->name) != 0;
             c = c->next);
        if (c) {
            if (!c->chan) {
                alog("user: BUG parting %s: channel entry found but c->chan NULL", s);
                return;
            }
            chan_deluser(user, c->chan);
            if (c->next)
                c->next->prev = c->prev;
            if (c->prev)
                c->prev->next = c->next;
            else
                user->chans = c->next;
            free(c);
        }
    }
}

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

#if defined(IRC_BAHAMUT) || defined(IRC_HYBRID) || defined(IRC_PTLINK)

/* Handle a SJOIN command.

   On channel creation, syntax is:

   av[0] = timestamp
   av[1] = channel name
   av[2|3|4] = modes   \   depends of whether the modes k and l
   av[3|4|5] = users   /   are set or not.

   When a single user joins an (existing) channel, it is:

   av[0] = timestamp
   av[1] = user

*/

void do_sjoin(const char *source, int ac, char **av)
{
    Channel *c;
    User *user;

    int is_sqlined = 0;

    /* Double check to avoid unknown modes that need parameters */
    if (ac >= 4 && ac <= 6) {
        char *s, *end, cubuf[CHAN_MAX_SYMBOLS + 2], *end2,
            *cumodes[CHAN_MAX_SYMBOLS + 1];

        c = findchan(av[1]);
#ifndef IRC_HYBRID
#ifndef IRC_PTLINK
        if (!c)
            is_sqlined = check_chan_sqline(av[1]);
#endif
#endif

        cubuf[0] = '+';
        cumodes[0] = cubuf;

        /* We make all the users join */
        s = av[ac - 1];         /* Users are always the last element */

        while (*s) {
            end = strchr(s, ' ');
            if (end)
                *end = 0;

            end2 = cubuf + 1;
            while (csmodes[(int) *s] != 0)
                *end2++ = csmodes[(int) *s++];
            *end2 = 0;

            user = finduser(s);
            if (!user) {
                alog("user: SJOIN for nonexistent user %s on %s", s,
                     av[1]);
                return;
            }

            if (is_sqlined && !is_oper(user)) {
                send_cmd(s_OperServ, "KICK %s %s :Q-Lined", av[1], s);
            } else {
                if (!check_kick(user, av[1])) {
                    /* Make the user join; if the channel does not exist it
                     * will be created there. This ensures that the channel
                     * is not created to be immediately destroyed, and
                     * that the locked key or topic is not shown to anyone
                     * who joins the channel when empty.
                     */
                    c = join_user_update(user, c, av[1]);

                    /* We update user mode on the channel */
                    if (end2 - cubuf > 1) {
                        int i;

                        for (i = 1; i < end2 - cubuf; i++)
                            cumodes[i] = user->nick;
                        chan_set_modes(source, c, 1 + (end2 - cubuf - 1),
                                       cumodes, 1);
                    }
                }
            }

            if (!end)
                break;
            s = end + 1;
        }

        if (c) {
            /* Set the timestamp */
            c->creation_time = strtoul(av[0], NULL, 10);
            /* We now update the channel mode. */
            chan_set_modes(source, c, ac - 3, &av[2], 1);
        }
    } else if (ac == 2) {
        user = finduser(source);
        if (!user) {
            alog("user: SJOIN for nonexistent user %s on %s", source,
                 av[1]);
            return;
        }

        if (check_kick(user, av[1]))
            return;

        c = findchan(av[1]);
#ifndef IRC_HYBRID
#ifndef IRC_PTLINK
        if (!c)
            is_sqlined = check_chan_sqline(av[1]);
#endif
#endif
        if (is_sqlined && !is_oper(user)) {
            send_cmd(s_OperServ, "KICK %s %s :Q-Lined", av[1], user->nick);
        } else {
            c = join_user_update(user, c, av[1]);
            c->creation_time = strtoul(av[0], NULL, 10);
        }
    }
}

#endif

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

/* Handle a channel MODE command. */

void do_cmode(const char *source, int ac, char **av)
{
    Channel *chan;
    ChannelInfo *ci = NULL;

    chan = findchan(av[0]);
    if (!chan) {
        ci = cs_findchan(av[0]);
        if (!(ci && (ci->flags & CI_VERBOTEN)))
            alog("channel: MODE %s for nonexistent channel %s",
                 merge_args(ac - 1, av + 1), av[0]);
        return;
    }

    /* This shouldn't trigger on +o, etc. */
    if (strchr(source, '.') && !av[1][strcspn(av[1], "bovahq")]) {
        if (time(NULL) != chan->server_modetime) {
            chan->server_modecount = 0;
            chan->server_modetime = time(NULL);
        }
        chan->server_modecount++;
    }

    ac--;
    av++;
    chan_set_modes(source, chan, ac, av, 1);
}

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

/* Handle a TOPIC command. */

void do_topic(const char *source, int ac, char **av)
{
    Channel *c = findchan(av[0]);
    time_t topic_time = strtoul(av[2], NULL, 10);

    if (!c) {
        alog("channel: TOPIC %s for nonexistent channel %s",
             merge_args(ac - 1, av + 1), av[0]);
        return;
    }

    if (check_topiclock(c, topic_time))
        return;

    if (c->topic) {
        free(c->topic);
        c->topic = NULL;
    }
    if (ac > 3 && *av[3])
        c->topic = sstrdup(av[3]);

    strscpy(c->topic_setter, av[1], sizeof(c->topic_setter));
    c->topic_time = topic_time;

    record_topic(av[0]);
}

/*************************************************************************/
/**************************** Internal Calls *****************************/
/*************************************************************************/

static void add_ban(Channel * chan, char *mask)
{
    if (s_BotServ && BSSmartJoin && chan->ci && chan->ci->bi
        && chan->usercount >= BSMinUsers) {
        char botmask[BUFSIZE];
        BotInfo *bi = chan->ci->bi;

        snprintf(botmask, sizeof(botmask), "%s!%s@%s", bi->nick, bi->user,
                 bi->host);
        if (match_wild_nocase(mask, botmask)) {
            send_cmd(bi->nick, "MODE %s -b %s", chan->name, mask);
            return;
        }
    }

    if (chan->bancount >= chan->bansize) {
        chan->bansize += 8;
        chan->bans = srealloc(chan->bans, sizeof(char *) * chan->bansize);
    }
    chan->bans[chan->bancount++] = sstrdup(mask);

    if (debug)
        alog("debug: Added ban %s to channel %s", mask, chan->name);
}

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

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

static void add_exception(Channel * chan, char *mask)
{
    if (chan->exceptcount >= chan->exceptsize) {
        chan->exceptsize += 8;
        chan->excepts =
            srealloc(chan->excepts, sizeof(char *) * chan->exceptsize);
    }
    chan->excepts[chan->exceptcount++] = sstrdup(mask);

    if (debug)
        alog("debug: Added except %s to channel %s", mask, chan->name);
}

#endif

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

/* Add/remove a user to/from a channel, creating or deleting the channel as
 * necessary.  If creating the channel, restore mode lock and topic as
 * necessary.  Also check for auto-opping and auto-voicing.
 * Modified, so ignored users won't get any status via services -certus */


static void chan_adduser2(User * user, Channel * c)
{
    struct c_userlist *u;
    char *chan = c->name;

    if (get_ignore(user->nick) == NULL) {

#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
        if (check_should_owner(user, chan)) {
            chan_set_user_status(c, user, CUS_OWNER | CUS_OP);
        } else
#endif
#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_ULTIMATE3)
        if (check_should_protect(user, chan)) {
            chan_set_user_status(c, user, CUS_PROTECT | CUS_OP);
        } else
#endif
        if (check_should_op(user, chan)) {
            chan_set_user_status(c, user, CUS_OP);
        } else
#ifdef HAS_HALFOP
        if (check_should_halfop(user, chan)) {
            chan_set_user_status(c, user, CUS_HALFOP);
        } else
#endif
        if (check_should_voice(user, chan)) {
            chan_set_user_status(c, user, CUS_VOICE);
        }
    }

    u = scalloc(sizeof(struct c_userlist), 1);
    u->next = c->users;
    if (c->users)
        c->users->prev = u;
    c->users = u;
    u->user = user;
    c->usercount++;

    if (get_ignore(user->nick) == NULL) {
        if (c->ci && (check_access(user, c->ci, CA_MEMO))
            && (c->ci->memos.memocount > 0)) {
            if (c->ci->memos.memocount == 1) {
                notice_lang(s_MemoServ, user, MEMO_X_ONE_NOTICE,
                            c->ci->memos.memocount, c->ci->name);
            } else {
                notice_lang(s_MemoServ, user, MEMO_X_MANY_NOTICE,
                            c->ci->memos.memocount, c->ci->name);
            }
        }

        if (c->ci && c->ci->entry_message)
            notice_user(whosends(c->ci), user, "%s", c->ci->entry_message);

        if (s_BotServ && c->ci && c->ci->bi) {
            if (c->usercount == BSMinUsers)
                bot_join(c->ci);
            if (c->usercount >= BSMinUsers && (c->ci->botflags & BS_GREET)
                && user->na && user->na->nc->greet
                && check_access(user, c->ci, CA_GREET)) {
                send_cmd(c->ci->bi->nick, "PRIVMSG %s :[%s] %s", c->name,
                         user->na->nick, user->na->nc->greet);
                c->ci->bi->lastmsg = time(NULL);
            }
        }
    }
}

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

/* This creates the channel structure (was originally in
   chan_adduser, but splitted to make it more efficient to use for
   SJOINs). */

static Channel *chan_create(const char *chan)
{
    Channel *c;
    Channel **list;

    if (debug)
        alog("debug: Creating channel %s", chan);
    /* Allocate pre-cleared memory */
    c = scalloc(sizeof(Channel), 1);
    strscpy(c->name, chan, sizeof(c->name));
    list = &chanlist[HASH(c->name)];
    c->next = *list;
    if (*list)
        (*list)->prev = c;
    *list = c;
    c->creation_time = time(NULL);
    /* Store ChannelInfo pointer in channel record */
    c->ci = cs_findchan(chan);
    if (c->ci)
        c->ci->c = c;
    /* Restore locked modes and saved topic */
    if (c->ci) {
        check_modes(c);
        restore_topic(chan);
        stick_all(c->ci);
    }

    return c;
}

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

/* This destroys the channel structure, freeing everything in it. */

static void chan_delete(Channel * c)
{
    BanData *bd, *next;
    int i;

    if (debug)
        alog("debug: Deleting channel %s", c->name);

    for (bd = c->bd; bd; bd = next) {
        if (bd->mask)
            free(bd->mask);
        next = bd->next;
        free(bd);
    }

    if (c->ci)
        c->ci->c = NULL;

    if (c->topic)
        free(c->topic);

    if (c->key)
        free(c->key);
#ifdef HAS_FMODE
    if (c->flood)
        free(c->flood);
#endif
#ifdef HAS_LMODE
    if (c->redirect)
        free(c->redirect);
#endif

    for (i = 0; i < c->bancount; ++i) {
        if (c->bans[i])
            free(c->bans[i]);
        else
            alog("channel: BUG freeing %s: bans[%d] is NULL!", c->name, i);
    }
    if (c->bansize)
        free(c->bans);

#if defined (IRC_ULTIMATE) || defined (IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined(IRC_HYBRID)
    for (i = 0; i < c->exceptcount; ++i) {
        if (c->excepts[i])
            free(c->excepts[i]);
        else
            alog("channel: BUG freeing %s: exceps[%d] is NULL!", c->name,
                 i);
    }
    if (c->exceptsize)
        free(c->excepts);
#endif

    if (c->next)
        c->next->prev = c->prev;
    if (c->prev)
        c->prev->next = c->next;
    else
        chanlist[HASH(c->name)] = c->next;

    free(c);
}

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

static void del_ban(Channel * chan, char *mask)
{
    char **s = chan->bans;
    int i = 0;
    AutoKick *akick;

    while (i < chan->bancount && strcmp(*s, mask) != 0) {
        i++;
        s++;
    }

    if (i < chan->bancount) {
        chan->bancount--;
        if (i < chan->bancount)
            memmove(s, s + 1, sizeof(char *) * (chan->bancount - i));

        if (debug)
            alog("debug: Deleted ban %s from channel %s", mask,
                 chan->name);
    }

    if (chan->ci && (akick = is_stuck(chan->ci, mask)))
        stick_mask(chan->ci, akick);
}

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

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

static void del_exception(Channel * chan, char *mask)
{
    int reset = 0, i;
    for (i = 0; i <= chan->exceptcount; i++) {
        if (chan->excepts[i] == mask) {
            reset = 1;
        }
        if (reset) {
            if (i == chan->exceptcount)
                chan->excepts[i] = NULL;
            else
                chan->excepts[i] = chan->excepts[i + 1];
        }
    }
    chan->exceptcount--;
    if (debug)
        alog("debug: Deleted except %s to channel %s", mask, chan->name);
}

#endif

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

#ifdef HAS_FMODE

static char *get_flood(Channel * chan)
{
    return chan->flood;
}

#endif

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

static char *get_key(Channel * chan)
{
    return chan->key;
}

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

static char *get_limit(Channel * chan)
{
    static char limit[16];

    if (chan->limit == 0)
        return NULL;

    snprintf(limit, sizeof(limit), "%lu", chan->limit);
    return limit;
}

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

#ifdef HAS_LMODE

static char *get_redirect(Channel * chan)
{
    return chan->redirect;
}

#endif

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

static Channel *join_user_update(User * user, Channel * chan, char *name)
{
    struct u_chanlist *c;

    /* If it's a new channel, so we need to create it first. */
    if (!chan)
        chan = chan_create(name);

    if (debug)
        alog("debug: %s joins %s", user->nick, chan->name);

    c = scalloc(sizeof(*c), 1);
    c->next = user->chans;
    if (user->chans)
        user->chans->prev = c;
    user->chans = c;
    c->chan = chan;

    chan_adduser2(user, chan);

    return chan;
}

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

#ifdef HAS_FMODE

static void set_flood(Channel * chan, char *value)
{
    if (chan->flood)
        free(chan->flood);
    chan->flood = value ? sstrdup(value) : NULL;

    if (debug)
        alog("debug: Flood of channel %s set to %s", chan->name,
             chan->flood ? chan->flood : "no flood settings");
}

#endif

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

static void set_key(Channel * chan, char *value)
{
    if (chan->key)
        free(chan->key);
    chan->key = value ? sstrdup(value) : NULL;

    if (debug)
        alog("debug: Key of channel %s set to %s", chan->name,
             chan->key ? chan->key : "no key");
}

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

static void set_limit(Channel * chan, char *value)
{
    chan->limit = value ? strtoul(value, NULL, 10) : 0;

    if (debug)
        alog("debug: Limit of channel %s set to %u", chan->name,
             chan->limit);
}

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

#ifdef HAS_LMODE

static void set_redirect(Channel * chan, char *value)
{
    if (chan->redirect)
        free(chan->redirect);
    chan->redirect = value ? sstrdup(value) : NULL;

    if (debug)
        alog("debug: Redirect of channel %s set to %s", chan->name,
             chan->redirect ? chan->redirect : "no redirect");
}

#endif

void do_mass_mode(char *modes)
{
    int ac, i;
    char **av;
    Channel *c;
    char *myModes;

    if (!modes) {
        return;
    }

    /* Prevent modes being altered by split_buf */
    myModes = sstrdup(modes);
    ac = split_buf(myModes, &av, 1);

    for (i = 0; i < 1024; i++) {
        for (c = chanlist[i]; c; c = c->next) {
            if (c->bouncy_modes) {
                return;
            } else {
                send_cmd(s_OperServ, "MODE %s %s", c->name, modes);
                chan_set_modes(s_OperServ, c, ac, av, 1);
            }
        }
    }
}

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