/*
 *   IRC - Internet Relay Chat, modules/m_sjoin.c
 *
 *   Copyright (C) 2000-2003 TR-IRCD Development
 *
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Co Center
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define CMODE_MODULAR 1

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "channel.h"
#include "msg.h"
#include "h.h"
#include "hook.h"
#include "chanmode.h"
#include "s_conf.h"

static char *token = TOK1_SJOIN;

static int hookid_kill_list = 0;

static struct Message _msgtab[] = {
    {MSG_SJOIN, 0, MAXPARA, M_SLOW, 0L,
     m_unregistered, m_ignore, m_ignore, m_sjoin, m_ignore}
};

#ifndef STATIC_MODULES

char *_version = "$Revision: 1.5 $";

void _modinit(void)
{
    mod_add_cmd(_msgtab);
    hookid_kill_list = hook_add_event("kill paramode list");
    tok1_msgtab[(u_char) *token].msg = _msgtab;
}

void _moddeinit(void)
{
    mod_del_cmd(_msgtab);
    tok1_msgtab[(u_char) *token].msg = NULL;
}

#else
void m_sjoin_init(void)
{
    mod_add_cmd(_msgtab);
    hookid_kill_list = hook_add_event("kill paramode list");
    tok1_msgtab[(u_char) *token].msg = _msgtab;
}
#endif

#define REMOVE_THEIR_MODES	0x01
#define APPEND_THEIR_MODES	0x02
#define ACCEPT_THEIR_MODES	0x04
#define REMOVE_OUR_MODES	0x08

static char modebuf[REALMODEBUFLEN], parabuf[REALMODEBUFLEN], sjbuf[BUFSIZE];

static void prepare_modebuf(int, IRCU32, int, char *, int, int, char *, int);
static void add_new_modes_to_channel(aClient *, aClient *, aChannel *, char **, int);
static void send_modes_to_channel(aChannel *, aClient *, int);
static void introduce_new_channel_to_other_servers(aClient *, aClient *, aChannel *);
static int add_new_users_to_channel(aClient *, aClient *, aChannel *, char *, int);

#define ADD_SJBUF(p) para = p; \
		     if(sjbufpos) \
			sjbuf[sjbufpos++] = ' '; \
                     while(*para) \
			sjbuf[sjbufpos++] = *para++; \
		     sjbuf[sjbufpos] = '\0';

/*
 * m_sjoin
 * parv[0] - sender
 * parv[1] - TS
 * parv[2] - channel   
 * parv[3] - modes + n arguments (key and/or limit)
 * parv[4+n] - flags+nick list (all in one parameter)
 *
 *
 * process a SJOIN, taking the TS's into account to either ignore the
 * incoming modes or undo the existing ones or merge them, and JOIN all
 * the specified users while sending JOIN/MODEs to non-TS servers and
 * to clients
 */

int m_sjoin(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
    aChannel *chptr;
    long newts, oldts;
    int people = 0;
    int newchan = 0;
    struct hook_data thisdata;

    if (parc < 3)
	return 0;

    if (!IsGlobalChan(parv[2]))
	return 0;

    newts = atol(parv[1]);

    chptr = create_channel(sptr, parv[2], &newchan, 0);
    if (!chptr)
	return 0;

    oldts = chptr->tsval;

    if (newchan) {
	oldts = newts;
	chptr->tsval = newts;
    }

    if ((parc == 3) && IsPerson(sptr)) {

	/* This is a client joining a channel
	 * :nickname SJOIN <channel> <timestamp> is the format we parse.
	 * This is introduced with the SSJOIN capability from Bahamut IRCD.
	 * -TimeMr14C
	 */

	if (oldts == 0)
	    chptr->tsval = newts;

	if (!IsMember(sptr, chptr)) {
	    if (!add_user_to_channel(chptr, sptr, 0))
		return 0;
	    sendto_channel_butserv_short(chptr, sptr, TOK1_JOIN);
	}
	sendto_serv_butone(cptr, sptr, TOK1_SJOIN, "%T %s", chptr, parv[2]);
	sendto_service(SERVICE_SEE_JOINS, 0, sptr, chptr, TOK1_JOIN, "");
	return 0;

    }

    if (parc == 4) {

	/* This is a server only whishing to set the channel timestamp.
	 * The server is intending to remove our channelmodes, if our
	 * timestamp is younger.
	 * It is currently not clear, if we really need this case.
	 * Therefore it is currently empty.
	 * -TimeMr14C
	 */
    }

    modebuf[0] = '\0';
    parabuf[0] = '\0';

    if ((parc >= 5) && IsServer(sptr)) {

	/* This is a server issuing classic SJOIN to introduce channel,
	 * its modes and its users. 
	 * 
	 * Format is :servername SJOIN <channel> <timestamp> <channelmodes> (params) :<nickname list>
	 *
	 * -TimeMr14C
	 */

	if (oldts < newts) {

	    /* This means, the remote side is introducing a younger channel. 
	     * Since we assume that TS is implemented correctly on the remote side,
	     * they will remove their modes, since our channel is older.
	     * Therefore, there is no need to check the parv[x].
	     * x refers to the parv containing mode information
	     * -TimeMr14C 
	     */
	    people =
		add_new_users_to_channel(cptr, sptr, chptr, parv[(parc - 1)], REMOVE_THEIR_MODES);

	} else if (oldts == newts) {

	    /* This means, mostly, that there was a netsplit and then a netjoin.
	     * Both sides have the channel, with the same creation time. This implies
	     * that the modes for the channel we do not have, AND modes for the users
	     * in the channel will be accepted. And the channel will be synched.
	     * -TimeMr14C
	     */

	    add_new_modes_to_channel(cptr, sptr, chptr, parv, APPEND_THEIR_MODES);
	    people =
		add_new_users_to_channel(cptr, sptr, chptr, parv[(parc - 1)], ACCEPT_THEIR_MODES);

	} else if (oldts > newts) {

	    /* This means, that we have linked to a network, where channels we own
	     * already exist, with older timestamp values. Since our channel is
	     * younger, we have to care for the removal of all the modes we have
	     * in the channel. -TimeMr14C 
	     */

	    chptr->tsval = newts;
	    thisdata.client_p = sptr;
	    thisdata.channel = chptr;

	    hook_call_event(hookid_kill_list, &thisdata);

	    sendto_channel_butserv(chptr, &me, TOK1_NOTICE, 0,
				   ":TS Change from %ld to %ld in %H. Modes will be erased",
				   oldts, newts, chptr);

	    add_new_modes_to_channel(cptr, sptr, chptr, parv, REMOVE_OUR_MODES | ACCEPT_THEIR_MODES);
	    people = add_new_users_to_channel(cptr, sptr, chptr, parv[(parc - 1)],
					      REMOVE_OUR_MODES | ACCEPT_THEIR_MODES);

	}

    }
    if (people)
	introduce_new_channel_to_other_servers(cptr, sptr, chptr);
    return 0;

}

static void prepare_modebuf(int add, IRCU32 modes, int limit, char *key,
			    int lines, int intime, char *link, int joindelay)
{
    char *mbuf = modebuf + strlen(modebuf);
    char *pbuf = parabuf + strlen(parabuf);

    if (modes & MODE_CHANMODE) {
    int i = 0;
	*mbuf++ = (add ? '+' : '-');
	for (i = 0; i < 128; i++) {
	    if (modetab[i].in_use && !(modetab[i].flags & MFLG_IGNORE)) {
		if (modes & modetab[i].type)
		    *mbuf++ = (char) i;
	    }
	}
	if (limit) {
    char tmp[9];
    int len;
	    len = ircsprintf(tmp, " %d", limit);
	    strncat(pbuf, tmp, len);
	}
	if (key && key[0]) {
    char k[KEYLEN + 1];
    int len;
	    len = ircsprintf(k, " %s", key);
	    strncat(pbuf, k, len);
	}
	if (lines > 0 && intime > 0) {
    char t[20];
    int len;
	    len = ircsprintf(t, " %d:%d", lines, intime);
	    strncat(pbuf, t, len);
	}
	if (link && link[0]) {
    char L[CHANNELLEN + 1];
    int len;
	    len = ircsprintf(L, " %s", link);
	    strncat(pbuf, L, len);
	}
  	if (joindelay) {
    char tmp[9];
    int len;
            len = ircsprintf(tmp, " %d", joindelay);
            strncat(pbuf, tmp, len);
        }
    }
    *mbuf++ = '\0';
    *pbuf++ = '\0';

    return;
}

static void introduce_new_channel_to_other_servers(aClient *cptr, aClient *sptr, aChannel *chptr)
{
    *modebuf = '\0';
    *parabuf = '\0';

    /* We do not do buffer overflow checks in this function,
     * because the SJOIN line we get can never be longer than the buffer.
     * -TimeMr14C 
     */

    prepare_modebuf(1, chptr->mode.mode, chptr->mode.limit, chptr->mode.key, chptr->mode.lines,
		    chptr->mode.intime, chptr->mode.link, chptr->mode.joindelay);
    sendto_serv_butone(cptr, sptr, TOK1_SJOIN, "%T %H %s%s :%s", chptr, chptr, modebuf,
		       parabuf, sjbuf);
    modebuf[0] = '\0';
    parabuf[0] = '\0';
    return;
}

static void add_new_modes_to_channel(aClient *cptr, aClient *sptr, aChannel *chptr,
				     char **parv, int reference)
{
    IRCU32 recvmode = 0;
    IRCU32 sendmode = 0;
    IRCU32 diffmode = 0;
    int recvlim = 0;
    int recvlines = 0;
    int recvintime = 0;
    int recvdelay = 0;
    char *recvkey = NULL;
    char *recvlink = NULL;
    char *modes = parv[3];
    char *p = NULL;
    int start = 4;

    modes++;			/* The trailing "+" has to be skipped */

    while (*modes) {
	recvmode |= modetab[(u_char) *modes].type;
	modes++;
    }

    if (recvmode & MODE_LIMIT) {
	recvlim = atol(parv[start]);
	start++;
    }
    if (recvmode & MODE_KEY) {
	recvkey = parv[start];
	start++;
    }
    if (recvmode & MODE_FLOOD) {
	recvlines = atol(strtoken(&p, parv[start], ":"));
	recvintime = atol(strtoken(&p, NULL, ":"));
	start++;
    }
    if (recvmode & MODE_LINKED) {
	recvlink = parv[start];
	start++;
    }

    if (recvmode & MODE_JOINDELAY) {
	recvdelay = atol(parv[start]);
    }

    if (reference & APPEND_THEIR_MODES) {
	diffmode = (chptr->mode.mode ^ recvmode);
	if (diffmode & recvmode) {
	    sendmode = diffmode;
	    prepare_modebuf(1, diffmode, recvlim, recvkey, recvlines, recvintime, recvlink, recvdelay);
	}
	chptr->mode.mode |= recvmode;
	if (sendmode & MODE_LIMIT)
	    chptr->mode.limit = recvlim;
	if (sendmode & MODE_KEY)
	    strlcpy_irc(chptr->mode.key, recvkey, KEYLEN);
	if (sendmode & MODE_FLOOD) {
	    chptr->mode.lines = recvlines;
	    chptr->mode.intime = recvintime;
	}
	if (sendmode & MODE_LINKED)
	    strlcpy_irc(chptr->mode.link, recvlink, CHANNELLEN);
	if (sendmode & MODE_JOINDELAY)
	    chptr->mode.joindelay = recvdelay;
    }
    if (reference & (ACCEPT_THEIR_MODES | REMOVE_OUR_MODES)) {
	diffmode = ((chptr->mode.mode & recvmode) ^ chptr->mode.mode);
	prepare_modebuf(0, diffmode, 0, NULL, 0, 0, NULL, 0);
	sendmode = ((chptr->mode.mode & recvmode) ^ recvmode);
	prepare_modebuf(1, sendmode, recvlim, recvkey, recvlines, recvintime, recvlink, recvdelay);
	chptr->mode.mode = recvmode;
	if (recvmode & MODE_LIMIT)
	    chptr->mode.limit = recvlim;
	else
	    chptr->mode.limit = 0;
        if ((recvmode & MODE_KEY) && recvkey && recvkey[0])
            strlcpy_irc(chptr->mode.key, recvkey, KEYLEN);
	else
	    chptr->mode.key[0] = '\0';
	if (recvmode & MODE_FLOOD) {
	    chptr->mode.lines = recvlines;
	    chptr->mode.intime = recvintime;
	} else {
	    chptr->mode.lines = 0;
	    chptr->mode.intime = 0;
	}
	if (recvmode & MODE_LINKED)
	    strlcpy_irc(chptr->mode.link, recvlink, CHANNELLEN);
	else
	    chptr->mode.link[0] = '\0';
	if (recvmode & MODE_JOINDELAY)
	    chptr->mode.joindelay = recvdelay;
	else
	    chptr->mode.joindelay = 0;
    }

}

static void send_modes_to_channel(aChannel *chptr, aClient *from, int flags)
{
    struct ChanMember *cm;
    char *c;
    int count = 0, send = 0;
    aClient *acptr;
    dlink_node *ptr;
    int check = 0;
    int uflags = 0;

    c = modebuf + strlen(modebuf);

    for (ptr = chptr->members.head; ptr; ptr = ptr->next) {
	cm = ptr->data;
	if (!cm)
	    continue;
	acptr = cm->client_p;
	uflags = cm->flags;

	/* The following line is a trick! Do not think it has a mistake.
	 * The case is following. if DO_USER_DEOP is received, we will DE-MODE
	 * users WE have in the channel. New users will not exist in the channel
	 * By this time. None of our users will have DO_USER_JOIN set anyway.
	 * Therefore, the first sets them all -.
	 * The second case on the other hand, sends MODE for the NEW users who
	 * sjoin with SJOIN, because they will become channel members, before
	 * their modes are set, they will get a SJOIN flag, which is removed,
	 * after they have been added into the buffer.
	 * -TimeMr14C
	 */

	if (uflags) {

	    if (flags & DO_USER_DEOP) {

		if (!check) {
		    *c++ = '-';
		    check = 1;
		}

		if (uflags & CHFL_OWNER) {
		    *c++ = 'u';
		    update_userflags(acptr, chptr, 0, CHFL_OWNER);
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
		if (uflags & CHFL_PROTECT) {
		    *c++ = 'a';
		    update_userflags(acptr, chptr, 0, CHFL_PROTECT);
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
		if (uflags & CHFL_CHANOP) {
		    *c++ = 'o';
		    update_userflags(acptr, chptr, 0, CHFL_CHANOP);
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
		if (uflags & CHFL_HALFOP) {
		    *c++ = 'h';
		    update_userflags(acptr, chptr, 0, CHFL_HALFOP);
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
		if (uflags & CHFL_VOICE) {
		    *c++ = 'v';
		    update_userflags(acptr, chptr, 0, CHFL_VOICE);
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
	    }
	    if (flags & DO_USER_JOIN && (acptr->protoflags & DO_USER_JOIN)) {
		acptr->protoflags &= ~DO_USER_JOIN;
		if (!check) {
		    *c++ = '+';
		    check = 1;
		}
		if (uflags & CHFL_OWNER) {
		    *c++ = 'u';
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
		if (uflags & CHFL_PROTECT) {
		    *c++ = 'a';
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
		if (uflags & CHFL_CHANOP) {
		    *c++ = 'o';
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
		if (uflags & CHFL_HALFOP) {
		    *c++ = 'h';
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
		if (uflags & CHFL_VOICE) {
		    *c++ = 'v';
		    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
			if (*parabuf)
			    strcat(parabuf, " ");
			strcat(parabuf, acptr->name);
			count++;
		    }
		}
	    }
	}
	*c = '\0';
	if (count == MAXMODEPARAMS)
	    send = 1;
	if (send) {
	    sendto_channel_butserv(chptr, from, TOK1_MODE, 0, "%s %s", modebuf, parabuf);
	    send = 0;
	    *parabuf = '\0';
	    c = modebuf;
	    if (count != MAXMODEPARAMS) {
		count = 1;
		if (uflags) {

		    if (flags & DO_USER_DEOP) {

			if (!check) {
			    *c++ = '-';
			    check = 1;
			}

			if (uflags & CHFL_OWNER) {
			    *c++ = 'u';
			    update_userflags(acptr, chptr, 0, CHFL_OWNER);
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
			if (uflags & CHFL_PROTECT) {
			    *c++ = 'a';
			    update_userflags(acptr, chptr, 0, CHFL_PROTECT);
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
			if (uflags & CHFL_CHANOP) {
			    *c++ = 'o';
			    update_userflags(acptr, chptr, 0, CHFL_CHANOP);
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
			if (uflags & CHFL_HALFOP) {
			    *c++ = 'h';
			    update_userflags(acptr, chptr, 0, CHFL_HALFOP);
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
			if (uflags & CHFL_VOICE) {
			    *c++ = 'v';
			    update_userflags(acptr, chptr, 0, CHFL_VOICE);
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
		    }
		    if (flags & DO_USER_JOIN) {
			acptr->protoflags &= ~DO_USER_JOIN;
			if (!check) {
			    *c++ = '+';
			    check = 1;
			}
			if (uflags & CHFL_OWNER) {
			    *c++ = 'u';
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
			if (uflags & CHFL_PROTECT) {
			    *c++ = 'a';
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
			if (uflags & CHFL_CHANOP) {
			    *c++ = 'o';
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
			if (uflags & CHFL_HALFOP) {
			    *c++ = 'h';
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
			if (uflags & CHFL_VOICE) {
			    *c++ = 'v';
			    if (strlen(parabuf) + strlen(acptr->name) + 10 < (size_t) MODEBUFLEN) {
				if (*parabuf)
				    strcat(parabuf, " ");
				strcat(parabuf, acptr->name);
				count++;
			    }
			}
		    }
		}

	    } else
		count = 0;
	    *c = '\0';
	    check = 0;
	}
    }

    if (*parabuf)
	sendto_channel_butserv(chptr, from, TOK1_MODE, 0, "%s %s", modebuf, parabuf);
    *parabuf = '\0';
    *modebuf = '\0';
}

static int add_new_users_to_channel(aClient *cptr, aClient *sptr,
				    aChannel *chptr, char *userlist, int reference)
{
    int flags = 0;
    int sjbufpos = 0;
    char *p = NULL;
    char *n = NULL;
    char *para = NULL;
    char *nick = NULL;
    aClient *acptr = NULL;

    if (!userlist || userlist[0] == '\0')
	return 0;

    memset(sjbuf, 0, BUFSIZE);

    if (reference & REMOVE_OUR_MODES)
	send_modes_to_channel(chptr, cptr, DO_USER_DEOP);

    for (n = nick = strtoken(&p, userlist, " "); nick; n = nick = strtoken(&p, NULL, " ")) {

	flags = 0;

	if (reference & REMOVE_THEIR_MODES) {

	    while (*nick == '.' || *nick == '~' || *nick == '@' || *nick == '%' || *nick == '+')
		nick++;
	    if (*nick == '!') {
		nick++;
		if (!(acptr = find_by_base64_id(nick)))
		    continue;
	    } else {
		if (!(acptr = find_chasing(sptr, nick, NULL)))
		    continue;
	    }
	    if (acptr->from != cptr)
		continue;
	    ADD_SJBUF(nick);
	    if (!IsMember(acptr, chptr)) {
		if (!add_user_to_channel(chptr, acptr, 0))
		    continue;
		acptr->protoflags |= DO_USER_JOIN;
		sendto_channel_butserv_short(chptr, acptr, TOK1_JOIN);
		sendto_service(SERVICE_SEE_JOINS, 0, acptr, chptr, TOK1_JOIN, "");
	    }

	} else if (reference & ACCEPT_THEIR_MODES) {

	    while (*nick == '.' || *nick == '~' || *nick == '@' || *nick == '%' || *nick == '+') {
		flags |= modetab[(u_char) *nick].type;
		nick++;
	    }
	    if (*nick == '!') {
		nick++;
		if (!(acptr = find_by_base64_id(nick)))
		    continue;
	    } else {
		if (!(acptr = find_chasing(sptr, nick, NULL)))
		    continue;
	    }
	    if (acptr->from != cptr)
		continue;
	    ADD_SJBUF(n);
	    if (!IsMember(acptr, chptr)) {
		if (!add_user_to_channel(chptr, acptr, flags))
		    continue;
		acptr->protoflags |= DO_USER_JOIN;
		sendto_channel_butserv_short(chptr, acptr, TOK1_JOIN);
		sendto_service(SERVICE_SEE_JOINS, 0, acptr, chptr, TOK1_JOIN, "");
	    }

	}
    }

    if (reference & ACCEPT_THEIR_MODES)
	send_modes_to_channel(chptr, cptr, DO_USER_JOIN);

    return 1;
}

#undef CMODE_MODULAR


syntax highlighted by Code2HTML, v. 0.9.1