pissircd/src/modules/sjoin.c

908 lines
25 KiB
C

/*
* IRC - Internet Relay Chat, src/modules/sjoin.c
* (C) 2004 The UnrealIRCd Team
*
* See file AUTHORS in IRC package for additional names of
* the programmers.
*
* 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 1, 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.
*/
#include "unrealircd.h"
CMD_FUNC(cmd_sjoin);
#define MSG_SJOIN "SJOIN"
ModuleHeader MOD_HEADER
= {
"sjoin",
"5.1",
"command /sjoin",
"UnrealIRCd Team",
"unrealircd-5",
};
MOD_INIT()
{
CommandAdd(modinfo->handle, MSG_SJOIN, cmd_sjoin, MAXPARA, CMD_SERVER);
MARK_AS_OFFICIAL_MODULE(modinfo);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
typedef struct xParv aParv;
struct xParv {
int parc;
char *parv[256];
};
aParv pparv;
aParv *mp2parv(char *xmbuf, char *parmbuf)
{
int c;
char *p, *s;
pparv.parv[0] = xmbuf;
c = 1;
for (s = strtoken(&p, parmbuf, " "); s; s = strtoken(&p, NULL, " "))
{
pparv.parv[c] = s;
c++; /* in my dreams */
}
pparv.parv[c] = NULL;
pparv.parc = c;
return (&pparv);
}
void send_local_chan_mode(MessageTag *recv_mtags, Client *client, Channel *channel, char *modebuf, char *parabuf)
{
MessageTag *mtags = NULL;
new_message_special(client, recv_mtags, &mtags, ":%s MODE %s %s %s", client->name, channel->chname, modebuf, parabuf);
sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
":%s MODE %s %s %s", client->name, channel->chname, modebuf, parabuf);
if (MyConnect(client))
RunHook7(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1);
else
RunHook7(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1);
free_message_tags(mtags);
}
/** SJOIN: Synchronize channel modes, +beI lists and users (server-to-server command)
* Extensive technical documentation is available at:
* https://www.unrealircd.org/docs/Server_protocol:SJOIN_command
*
* parv[1] = channel timestamp
* parv[2] = channel name
*
* if parc == 3:
* parv[3] = nick names + modes - all in one parameter
*
* if parc == 4:
* parv[3] = channel modes
* parv[4] = nick names + modes - all in one parameter
*
* if parc > 4:
* parv[3] = channel modes
* parv[4 to parc - 2] = mode parameters
* parv[parc - 1] = nick names + modes
*/
/* Note: with regards to message tags we use new_message_special()
* here extensively. This because one SJOIN command can (often)
* generate multiple events that are sent to clients,
* for example 1 SJOIN can cause multiple joins, +beI, etc.
* -- Syzop
*/
/* Some ugly macros, but useful */
#define Addit(mode,param) if ((strlen(parabuf) + strlen(param) + 11 < MODEBUFLEN) && (b <= MAXMODEPARAMS)) { \
if (*parabuf) \
strcat(parabuf, " ");\
strcat(parabuf, param);\
modebuf[b++] = mode;\
modebuf[b] = 0;\
}\
else {\
send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf); \
strcpy(parabuf,param);\
/* modebuf[0] should stay what it was ('+' or '-') */ \
modebuf[1] = mode;\
modebuf[2] = '\0';\
b = 2;\
}
#define Addsingle(x) do { modebuf[b] = x; b++; modebuf[b] = '\0'; } while(0)
#define CheckStatus(x,y) do { if (modeflags & (y)) { Addit((x), acptr->name); } } while(0)
CMD_FUNC(cmd_sjoin)
{
unsigned short nopara;
unsigned short nomode; /**< An SJOIN without MODE? */
unsigned short removeours; /**< Remove our modes */
unsigned short removetheirs; /**< Remove their modes (or actually: do not ADD their modes, the MODE -... line will be sent later by the other side) */
unsigned short merge; /**< same timestamp: merge their & our modes */
char pvar[MAXMODEPARAMS][MODEBUFLEN + 3];
char cbuf[1024];
char nick[1024]; /**< nick or ban/invex/exempt being processed */
char scratch_buf[1024]; /**< scratch buffer */
char prefix[16]; /**< prefix of nick for server to server traffic (eg: @) */
char uid_buf[BUFSIZE]; /**< Buffer for server-to-server traffic which will be broadcasted to others (servers supporting SID/UID) */
char uid_sjsby_buf[BUFSIZE]; /**< Buffer for server-to-server traffic which will be broadcasted to others (servers supporting SID/UID and SJSBY) */
char sj3_parabuf[BUFSIZE]; /**< Prefix for the above SJOIN buffers (":xxx SJOIN #channel +mode :") */
char *s = NULL;
Channel *channel; /**< Channel */
aParv *ap;
int pcount, i;
Hook *h;
time_t ts, oldts;
unsigned short b=0;
char *tp, *p, *saved = NULL;
long modeflags;
char queue_s=0, queue_c=0; /* oh this is soooooo ugly :p */
if (!IsServer(client) || parc < 4)
return;
if (!IsChannelName(parv[2]))
return;
merge = nopara = nomode = removeours = removetheirs = 0;
if (parc < 6)
nopara = 1;
if (parc < 5)
nomode = 1;
channel = get_channel(client, parv[2], CREATE);
ts = (time_t)atol(parv[1]);
if (channel->creationtime > ts)
{
removeours = 1;
oldts = channel->creationtime;
channel->creationtime = ts;
}
else if ((channel->creationtime < ts) && (channel->creationtime != 0))
{
removetheirs = 1;
}
else if (channel->creationtime == ts)
{
merge = 1;
}
if (channel->creationtime == 0)
{
oldts = -1;
channel->creationtime = ts;
}
else
{
oldts = channel->creationtime;
}
// FIXME: make it so services cannot screw this up so easily --- if possible...
if (ts < 750000)
{
if (ts != 0)
sendto_ops
("Warning! Possible desync: SJOIN for channel %s has a fishy timestamp (%lld) [%s/%s]",
channel->chname, (long long)ts, client->name, client->direction->name);
}
parabuf[0] = '\0';
modebuf[0] = '+';
modebuf[1] = '\0';
/* Grab current modes -> modebuf & parabuf */
channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 1);
/* Do we need to remove all our modes, bans/exempt/inves lists and -vhoaq our users? */
if (removeours)
{
Member *lp;
Membership *lp2;
modebuf[0] = '-';
/* remove our modes if any */
if (modebuf[1] != '\0')
{
MessageTag *mtags = NULL;
ap = mp2parv(modebuf, parabuf);
set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar, 0);
send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
}
/* remove bans */
/* reset the buffers */
modebuf[0] = '-';
modebuf[1] = '\0';
parabuf[0] = '\0';
b = 1;
while(channel->banlist)
{
Ban *ban = channel->banlist;
Addit('b', ban->banstr);
channel->banlist = ban->next;
safe_free(ban->banstr);
safe_free(ban->who);
free_ban(ban);
}
while(channel->exlist)
{
Ban *ban = channel->exlist;
Addit('e', ban->banstr);
channel->exlist = ban->next;
safe_free(ban->banstr);
safe_free(ban->who);
free_ban(ban);
}
while(channel->invexlist)
{
Ban *ban = channel->invexlist;
Addit('I', ban->banstr);
channel->invexlist = ban->next;
safe_free(ban->banstr);
safe_free(ban->who);
free_ban(ban);
}
for (lp = channel->members; lp; lp = lp->next)
{
lp2 = find_membership_link(lp->client->user->channel, channel);
if (!lp2)
{
sendto_realops("Oops! channel->members && !find_membership_link");
continue;
}
if (lp->flags & MODE_CHANOWNER)
{
lp->flags &= ~MODE_CHANOWNER;
Addit('q', lp->client->name);
}
if (lp->flags & MODE_CHANADMIN)
{
lp->flags &= ~MODE_CHANADMIN;
Addit('a', lp->client->name);
}
if (lp->flags & MODE_CHANOP)
{
lp->flags &= ~MODE_CHANOP;
Addit('o', lp->client->name);
}
if (lp->flags & MODE_HALFOP)
{
lp->flags &= ~MODE_HALFOP;
Addit('h', lp->client->name);
}
if (lp->flags & MODE_VOICE)
{
lp->flags &= ~MODE_VOICE;
Addit('v', lp->client->name);
}
/* Those should always match anyways */
lp2->flags = lp->flags;
}
if (b > 1)
{
modebuf[b] = '\0';
send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
}
/* since we're dropping our modes, we want to clear the mlock as well. --nenolod */
set_channel_mlock(client, channel, NULL, FALSE);
}
/* Mode setting done :), now for our beloved clients */
parabuf[0] = 0;
modebuf[0] = '+';
modebuf[1] = '\0';
b = 1;
strlcpy(cbuf, parv[parc-1], sizeof cbuf);
sj3_parabuf[0] = '\0';
for (i = 2; i <= (parc - 2); i++)
{
if (!parv[i])
{
sendto_ops("Got null parv in SJ3 code");
continue;
}
strlcat(sj3_parabuf, parv[i], sizeof sj3_parabuf);
if (((i + 1) <= (parc - 2)))
strlcat(sj3_parabuf, " ", sizeof sj3_parabuf);
}
/* Now process adding of users & adding of list modes (bans/exempt/invex) */
snprintf(uid_buf, sizeof uid_buf, ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
snprintf(uid_sjsby_buf, sizeof uid_sjsby_buf, ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
for (s = strtoken(&saved, cbuf, " "); s; s = strtoken(&saved, NULL, " "))
{
char *setby = client->name; /**< Set by (nick, nick!user@host, or server name) */
time_t setat = TStime(); /**< Set at timestamp */
int sjsby_info = 0; /**< Set to 1 if we receive SJSBY info to alter the above 2 vars */
modeflags = 0;
i = 0;
tp = s;
/* UnrealIRCd 4.2.2 and later support "SJSBY" which allows communicating
* setat/setby information for bans, ban exempts and invite exceptions.
*/
if (SupportSJSBY(client->direction) && (*tp == '<'))
{
/* Special prefix to communicate timestamp and setter:
* "<" + timestamp + "," + nick[!user@host] + ">" + normal SJOIN stuff
* For example: "<12345,nick>&some!nice@ban"
*/
char *end = strchr(tp, '>'), *p;
if (!end)
{
/* this obviously should never happen */
sendto_ops("Malformed SJOIN piece from %s for channel %s: %s",
client->name, channel->chname, tp);
continue;
}
*end++ = '\0';
p = strchr(tp, ',');
if (!p)
{
/* missing setby parameter */
sendto_ops("Malformed SJOIN piece from %s for channel %s: %s",
client->name, channel->chname, tp);
continue;
}
*p++ = '\0';
setat = atol(tp+1);
setby = p;
sjsby_info = 1;
tp = end; /* the remainder is used for the actual ban/exempt/invex */
}
while (
(*tp == '@') || (*tp == '+') || (*tp == '%')
|| (*tp == '*') || (*tp == '~') || (*tp == '&')
|| (*tp == '"') || (*tp == '\''))
{
switch (*(tp++))
{
case '@':
modeflags |= CHFL_CHANOP;
break;
case '%':
modeflags |= CHFL_HALFOP;
break;
case '+':
modeflags |= CHFL_VOICE;
break;
case '*':
modeflags |= CHFL_CHANOWNER;
break;
case '~':
modeflags |= CHFL_CHANADMIN;
break;
case '&':
modeflags = CHFL_BAN;
goto getnick;
case '"':
modeflags = CHFL_EXCEPT;
goto getnick;
case '\'':
modeflags = CHFL_INVEX;
goto getnick;
}
}
getnick:
/* First, set the appropriate prefix for server to server traffic.
* Note that 'prefix' is a 16 byte buffer but it's safe due to the limited
* number of choices as can be seen below:
*/
*prefix = '\0';
p = prefix;
if (modeflags == CHFL_INVEX)
*p++ = '\'';
else if (modeflags == CHFL_EXCEPT)
*p++ = '\"';
else if (modeflags == CHFL_BAN)
*p++ = '&';
else
{
/* multiple options possible at the same time */
if (modeflags & CHFL_CHANOWNER)
*p++ = '*';
if (modeflags & CHFL_CHANADMIN)
*p++ = '~';
if (modeflags & CHFL_CHANOP)
*p++ = '@';
if (modeflags & CHFL_HALFOP)
*p++ = '%';
if (modeflags & CHFL_VOICE)
*p++ = '+';
}
*p = '\0';
/* Now copy the "nick" (which can actually be a ban/invex/exempt).
* There's no size checking here but nick is 1024 bytes and we
* have 512 bytes input max.
*/
i = 0;
while ((*tp != ' ') && (*tp != '\0'))
nick[i++] = *(tp++); /* get nick */
nick[i] = '\0';
if (nick[0] == ' ')
continue;
if (nick[0] == '\0')
continue;
Debug((DEBUG_DEBUG, "Got nick: %s", nick));
if (!(modeflags & CHFL_BAN) && !(modeflags & CHFL_EXCEPT) && !(modeflags & CHFL_INVEX))
{
Client *acptr;
/* A person joining */
/* The user may no longer exist. This can happen in case of a
* SVSKILL traveling in the other direction. Nothing to worry about.
*/
if (!(acptr = find_person(nick, NULL)))
continue;
if (acptr->direction != client->direction)
{
if (IsMember(acptr, channel))
{
/* Nick collision, don't kick or it desyncs -Griever*/
continue;
}
sendto_one(client, NULL,
":%s KICK %s %s :Fake direction",
me.id, channel->chname, acptr->name);
sendto_realops
("Fake direction from user %s in SJOIN from %s(%s) at %s",
nick, client->srvptr->name,
client->name, channel->chname);
continue;
}
if (removetheirs)
{
modeflags = 0;
}
if (!IsMember(acptr, channel))
{
/* User joining the channel, send JOIN to local users.
*/
MessageTag *mtags = NULL;
add_user_to_channel(channel, acptr, modeflags);
RunHook4(HOOKTYPE_REMOTE_JOIN, acptr, channel, recv_mtags, NULL);
new_message_special(acptr, recv_mtags, &mtags, ":%s JOIN %s", acptr->name, channel->chname);
send_join_to_local_users(acptr, channel, mtags);
free_message_tags(mtags);
}
CheckStatus('q', CHFL_CHANOWNER);
CheckStatus('a', CHFL_CHANADMIN);
CheckStatus('o', CHFL_CHANOP);
CheckStatus('h', CHFL_HALFOP);
CheckStatus('v', CHFL_VOICE);
if (strlen(uid_buf) + strlen(prefix) + IDLEN > BUFSIZE - 10)
{
/* Send what we have and start a new buffer */
sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
snprintf(uid_buf, sizeof(uid_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
/* Double-check the new buffer is sufficient to concat the data */
if (strlen(uid_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5)
{
ircd_log(LOG_ERROR, "Oversized SJOIN: '%s' + '%s%s'",
uid_buf, prefix, acptr->id);
sendto_realops("Oversized SJOIN for %s -- see ircd log", channel->chname);
continue;
}
}
sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, acptr->id);
if (strlen(uid_sjsby_buf) + strlen(prefix) + IDLEN > BUFSIZE - 10)
{
/* Send what we have and start a new buffer */
sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
snprintf(uid_sjsby_buf, sizeof(uid_sjsby_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
/* Double-check the new buffer is sufficient to concat the data */
if (strlen(uid_sjsby_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5)
{
ircd_log(LOG_ERROR, "Oversized SJOIN: '%s' + '%s%s'",
uid_sjsby_buf, prefix, acptr->id);
sendto_realops("Oversized SJOIN for %s -- see ircd log", channel->chname);
continue;
}
}
sprintf(uid_sjsby_buf+strlen(uid_sjsby_buf), "%s%s ", prefix, acptr->id);
}
else
{
if (removetheirs)
continue;
/* For list modes (beI): validate the syntax */
if (modeflags & (CHFL_BAN|CHFL_EXCEPT|CHFL_INVEX))
{
char *str;
/* non-extbans: prevent bans without ! or @. a good case of "should never happen". */
if ((nick[0] != '~') && (!strchr(nick, '!') || !strchr(nick, '@') || (nick[0] == '!')))
continue;
str = clean_ban_mask(nick, MODE_ADD, client);
if (!str)
continue; /* invalid ban syntax */
strlcpy(nick, str, sizeof(nick));
}
/* Adding of list modes */
if (modeflags & CHFL_BAN)
{
if (add_listmode_ex(&channel->banlist, client, channel, nick, setby, setat) != -1)
{
Addit('b', nick);
}
}
if (modeflags & CHFL_EXCEPT)
{
if (add_listmode_ex(&channel->exlist, client, channel, nick, setby, setat) != -1)
{
Addit('e', nick);
}
}
if (modeflags & CHFL_INVEX)
{
if (add_listmode_ex(&channel->invexlist, client, channel, nick, setby, setat) != -1)
{
Addit('I', nick);
}
}
if (strlen(uid_buf) + strlen(prefix) + strlen(nick) > BUFSIZE - 10)
{
/* Send what we have and start a new buffer */
sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
snprintf(uid_buf, sizeof(uid_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
/* Double-check the new buffer is sufficient to concat the data */
if (strlen(uid_buf) + strlen(prefix) + strlen(nick) > BUFSIZE - 5)
{
ircd_log(LOG_ERROR, "Oversized SJOIN: '%s' + '%s%s'",
uid_buf, prefix, nick);
sendto_realops("Oversized SJOIN for %s -- see ircd log", channel->chname);
continue;
}
}
sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, nick);
*scratch_buf = '\0';
if (sjsby_info)
add_sjsby(scratch_buf, setby, setat);
strcat(scratch_buf, prefix);
strcat(scratch_buf, nick);
strcat(scratch_buf, " ");
if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 10)
{
/* Send what we have and start a new buffer */
sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
snprintf(uid_sjsby_buf, sizeof(uid_sjsby_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
/* Double-check the new buffer is sufficient to concat the data */
if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 5)
{
ircd_log(LOG_ERROR, "Oversized SJOIN: '%s' + '%s'", uid_sjsby_buf, scratch_buf);
sendto_realops("Oversized SJOIN for %s -- see ircd log", channel->chname);
continue;
}
}
strcpy(uid_sjsby_buf+strlen(uid_sjsby_buf), scratch_buf); /* size already checked above */
}
continue;
}
/* Send out any possible remainder.. */
Debug((DEBUG_DEBUG, "Sending '%li %s :%s' to ", ts, parabuf, parv[parc - 1]));
sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
if (modebuf[1])
{
modebuf[b] = '\0';
send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
}
if (!merge && !removetheirs && !nomode)
{
char paraback[1024];
MessageTag *mtags = NULL;
strlcpy(modebuf, parv[3], sizeof modebuf);
parabuf[0] = '\0';
if (!nopara)
{
for (b = 4; b <= (parc - 2); b++)
{
strlcat(parabuf, parv[b], sizeof parabuf);
strlcat(parabuf, " ", sizeof parabuf);
}
}
strlcpy(paraback, parabuf, sizeof paraback);
ap = mp2parv(modebuf, parabuf);
set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar, 0);
send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
}
if (merge && !nomode)
{
CoreChannelModeTable *acp;
Mode oldmode; /**< The old mode (OUR mode) */
/* Copy current mode to oldmode (need to duplicate all extended mode params too..) */
memcpy(&oldmode, &channel->mode, sizeof(oldmode));
memset(&oldmode.extmodeparams, 0, sizeof(oldmode.extmodeparams));
extcmode_duplicate_paramlist(channel->mode.extmodeparams, oldmode.extmodeparams);
/* Now merge the modes */
strlcpy(modebuf, parv[3], sizeof modebuf);
parabuf[0] = '\0';
if (!nopara)
{
for (b = 4; b <= (parc - 2); b++)
{
strlcat(parabuf, parv[b], sizeof parabuf);
strlcat(parabuf, " ", sizeof parabuf);
}
}
ap = mp2parv(modebuf, parabuf);
set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar, 0);
/* Good, now we got modes, now for the differencing and outputting of modes
* We first see if any para modes are set.
*/
strlcpy(modebuf, "-", sizeof modebuf);
parabuf[0] = '\0';
b = 1;
/* however, is this really going to happen at all? may be unneeded */
if (oldmode.limit && !channel->mode.limit)
{
Addsingle('l');
}
if (oldmode.key[0] && !channel->mode.key[0])
{
Addit('k', oldmode.key);
}
/* First, check if we have something they don't have..
* note that: oldmode.* = us, channel->mode.* = them.
*/
for (i=0; i <= Channelmode_highest; i++)
{
if (Channelmode_Table[i].flag &&
!Channelmode_Table[i].local &&
(oldmode.extmode & Channelmode_Table[i].mode) &&
!(channel->mode.extmode & Channelmode_Table[i].mode))
{
if (Channelmode_Table[i].paracount)
{
char *parax = cm_getparameter_ex(oldmode.extmodeparams, Channelmode_Table[i].flag);
//char *parax = Channelmode_Table[i].get_param(extcmode_get_struct(oldmode.extmodeparam, Channelmode_Table[i].flag));
Addit(Channelmode_Table[i].flag, parax);
} else {
Addsingle(Channelmode_Table[i].flag);
}
}
}
/* Check if we had +s and it became +p, then revert it... */
if ((oldmode.mode & MODE_SECRET) && (channel->mode.mode & MODE_PRIVATE))
{
/* stay +s ! */
channel->mode.mode &= ~MODE_PRIVATE;
channel->mode.mode |= MODE_SECRET;
Addsingle('p'); /* - */
queue_s = 1;
}
/* Add single char modes... */
for (acp = corechannelmodetable; acp->mode; acp++)
{
if ((oldmode.mode & acp->mode) && !(channel->mode.mode & acp->mode) && !acp->parameters)
{
Addsingle(acp->flag);
}
}
if (b > 1)
{
Addsingle('+');
}
else
{
strlcpy(modebuf, "+", sizeof modebuf);
b = 1;
}
if (queue_s)
Addsingle('s');
if (queue_c)
Addsingle('c');
for (acp = corechannelmodetable; acp->mode; acp++)
{
if (!(oldmode.mode & acp->mode) && (channel->mode.mode & acp->mode) && !acp->parameters)
{
Addsingle(acp->flag);
}
}
/* Now, check if they have something we don't have..
* note that: oldmode.* = us, channel->mode.* = them.
*/
for (i=0; i <= Channelmode_highest; i++)
{
if ((Channelmode_Table[i].flag) &&
!(oldmode.extmode & Channelmode_Table[i].mode) &&
(channel->mode.extmode & Channelmode_Table[i].mode))
{
if (Channelmode_Table[i].paracount)
{
char *parax = cm_getparameter(channel, Channelmode_Table[i].flag);
if (parax)
{
Addit(Channelmode_Table[i].flag, parax);
}
} else {
Addsingle(Channelmode_Table[i].flag);
}
}
}
/* now, if we had diffent para modes - this loop really could be done better, but */
/* +l (limit) difference? */
if (oldmode.limit && channel->mode.limit && (oldmode.limit != channel->mode.limit))
{
channel->mode.limit = MAX(oldmode.limit, channel->mode.limit);
if (oldmode.limit != channel->mode.limit)
{
Addit('l', my_itoa(channel->mode.limit));
}
}
/* +k (key) difference? */
if (oldmode.key[0] && channel->mode.key[0] && strcmp(oldmode.key, channel->mode.key))
{
if (strcmp(oldmode.key, channel->mode.key) > 0)
{
strlcpy(channel->mode.key, oldmode.key, sizeof channel->mode.key);
}
else
{
Addit('k', channel->mode.key);
}
}
/* Now, check for any param differences in extended channel modes..
* note that: oldmode.* = us, channel->mode.* = them.
* if we win: copy oldmode to channel mode, if they win: send the mode
*/
for (i=0; i <= Channelmode_highest; i++)
{
if (Channelmode_Table[i].flag && Channelmode_Table[i].paracount &&
(oldmode.extmode & Channelmode_Table[i].mode) &&
(channel->mode.extmode & Channelmode_Table[i].mode))
{
int r;
char *parax;
char flag = Channelmode_Table[i].flag;
void *ourm = GETPARASTRUCTEX(oldmode.extmodeparams, flag);
void *theirm = GETPARASTRUCT(channel, flag);
//CmodeParam *ourm = extcmode_get_struct(oldmode.extmodeparam,Channelmode_Table[i].flag);
//CmodeParam *theirm = extcmode_get_struct(channel->mode.extmodeparam, Channelmode_Table[i].flag);
r = Channelmode_Table[i].sjoin_check(channel, ourm, theirm);
switch (r)
{
case EXSJ_WEWON:
parax = cm_getparameter_ex(oldmode.extmodeparams, flag); /* grab from old */
cm_putparameter(channel, flag, parax); /* put in new (won) */
break;
case EXSJ_THEYWON:
parax = cm_getparameter(channel, flag);
Debug((DEBUG_DEBUG, "sjoin: they won: '%s'", parax));
Addit(Channelmode_Table[i].flag, parax);
break;
case EXSJ_SAME:
Debug((DEBUG_DEBUG, "sjoin: equal"));
break;
case EXSJ_MERGE:
parax = cm_getparameter_ex(oldmode.extmodeparams, flag); /* grab from old */
cm_putparameter(channel, flag, parax); /* put in new (won) */
Addit(flag, parax);
break;
default:
ircd_log(LOG_ERROR, "channel.c:m_sjoin:param diff checker: got unk. retval 0x%x??", r);
break;
}
}
}
Addsingle('\0');
if (modebuf[1])
{
send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
}
/* free the oldmode.* crap :( */
extcmode_free_paramlist(oldmode.extmodeparams);
/* memset(&oldmode.extmodeparams, 0, sizeof(oldmode.extmodeparams)); -- redundant? */
}
for (h = Hooks[HOOKTYPE_CHANNEL_SYNCED]; h; h = h->next)
{
int i = (*(h->func.intfunc))(channel,merge,removetheirs,nomode);
if (i == 1)
return; /* channel no longer exists */
}
/* we should be synced by now, */
if ((oldts != -1) && (oldts != channel->creationtime))
{
MessageTag *mtags = NULL;
new_message(client, NULL, &mtags);
sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, NULL,
":%s NOTICE %s :*** TS for %s changed from %lld to %lld",
me.name, channel->chname, channel->chname,
(long long)oldts, (long long)channel->creationtime);
free_message_tags(mtags);
}
/* If something went wrong with processing of the SJOIN above and
* the channel actually has no users in it at this point,
* then destroy the channel.
*/
if (!channel->users)
{
sub1_from_channel(channel);
return;
}
}