/* * 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 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 (params) : * * -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