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