unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

sjoin.c (25091B)

      1 /*
      2  *   IRC - Internet Relay Chat, src/modules/sjoin.c
      3  *   (C) 2004 The UnrealIRCd Team
      4  *
      5  *   See file AUTHORS in IRC package for additional names of
      6  *   the programmers.
      7  *
      8  *   This program is free software; you can redistribute it and/or modify
      9  *   it under the terms of the GNU General Public License as published by
     10  *   the Free Software Foundation; either version 1, or (at your option)
     11  *   any later version.
     12  *
     13  *   This program is distributed in the hope that it will be useful,
     14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  *   GNU General Public License for more details.
     17  *
     18  *   You should have received a copy of the GNU General Public License
     19  *   along with this program; if not, write to the Free Software
     20  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     21  */
     22 
     23 #include "unrealircd.h"
     24 
     25 CMD_FUNC(cmd_sjoin);
     26 
     27 #define MSG_SJOIN 	"SJOIN"	
     28 
     29 ModuleHeader MOD_HEADER
     30   = {
     31 	"sjoin",
     32 	"5.1",
     33 	"command /sjoin", 
     34 	"UnrealIRCd Team",
     35 	"unrealircd-6",
     36     };
     37 
     38 char modebuf[BUFSIZE], parabuf[BUFSIZE];
     39 
     40 MOD_INIT()
     41 {
     42 	CommandAdd(modinfo->handle, MSG_SJOIN, cmd_sjoin, MAXPARA, CMD_SERVER);
     43 	MARK_AS_OFFICIAL_MODULE(modinfo);
     44 	return MOD_SUCCESS;
     45 }
     46 
     47 MOD_LOAD()
     48 {
     49 	return MOD_SUCCESS;
     50 }
     51 
     52 MOD_UNLOAD()
     53 {
     54 	return MOD_SUCCESS;
     55 }
     56 
     57 typedef struct xParv aParv;
     58 struct xParv {
     59 	int  parc;
     60 	const char *parv[256];
     61 };
     62 
     63 aParv pparv;
     64 
     65 aParv *mp2parv(char *xmbuf, char *parmbuf)
     66 {
     67 	int  c;
     68 	char *p, *s;
     69 
     70 	pparv.parv[0] = xmbuf;
     71 	c = 1;
     72 	
     73 	for (s = strtoken(&p, parmbuf, " "); s; s = strtoken(&p, NULL, " "))
     74 	{
     75 		pparv.parv[c] = s;
     76 		c++; /* in my dreams */
     77 	}
     78 	pparv.parv[c] = NULL;
     79 	pparv.parc = c;
     80 	return (&pparv);
     81 }
     82 
     83 static void send_local_chan_mode(MessageTag *recv_mtags, Client *client, Channel *channel, char *modebuf, char *parabuf)
     84 {
     85 	MessageTag *mtags = NULL;
     86 	int destroy_channel = 0;
     87 
     88 	new_message_special(client, recv_mtags, &mtags, ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf);
     89 	sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
     90 	               ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf);
     91 	if (MyConnect(client))
     92 		RunHook(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1, &destroy_channel);
     93 	else
     94 		RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1, &destroy_channel);
     95 	free_message_tags(mtags);
     96 }
     97 
     98 /** Call send_local_chan_mode() for multiline modes */
     99 static void send_local_chan_mode_mlm(MessageTag *recv_mtags, Client *client, Channel *channel, MultiLineMode *mlm)
    100 {
    101 	if (mlm)
    102 	{
    103 		int i;
    104 		for (i = 0; i < mlm->numlines; i++)
    105 			send_local_chan_mode(recv_mtags, client, channel, mlm->modeline[i], mlm->paramline[i]);
    106 	}
    107 }
    108 
    109 /** SJOIN: Synchronize channel modes, +beI lists and users (server-to-server command)
    110  * Extensive technical documentation is available at:
    111  * https://www.unrealircd.org/docs/Server_protocol:SJOIN_command
    112  *
    113  *  parv[1] = channel timestamp
    114  *  parv[2] = channel name
    115  *
    116  *  if parc == 3:
    117  *  parv[3] = nick names + modes - all in one parameter
    118  *
    119  *  if parc == 4:
    120  *  parv[3] = channel modes
    121  *  parv[4] = nick names + modes - all in one parameter
    122  *
    123  *  if parc > 4:
    124  *  parv[3] = channel modes
    125  *  parv[4 to parc - 2] = mode parameters
    126  *  parv[parc - 1] = nick names + modes
    127  */
    128 
    129 /* Note: with regards to message tags we use new_message_special()
    130  *       here extensively. This because one SJOIN command can (often)
    131  *       generate multiple events that are sent to clients,
    132  *       for example 1 SJOIN can cause multiple joins, +beI, etc.
    133  *       -- Syzop
    134  */
    135 
    136 /* Some ugly macros, but useful */
    137 #define Addit(mode,param) if ((strlen(parabuf) + strlen(param) + 11 < MODEBUFLEN) && (b <= MAXMODEPARAMS)) { \
    138 	if (*parabuf) \
    139 		strcat(parabuf, " ");\
    140 	strcat(parabuf, param);\
    141 	modebuf[b++] = mode;\
    142 	modebuf[b] = 0;\
    143 }\
    144 else {\
    145 	send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf); \
    146 	strcpy(parabuf,param);\
    147 	/* modebuf[0] should stay what it was ('+' or '-') */ \
    148 	modebuf[1] = mode;\
    149 	modebuf[2] = '\0';\
    150 	b = 2;\
    151 }
    152 #define Addsingle(x) do { modebuf[b] = x; b++; modebuf[b] = '\0'; } while(0)
    153 #define CheckStatus(x,y) do { if (modeflags & (y)) { Addit((x), acptr->name); } } while(0)
    154 
    155 CMD_FUNC(cmd_sjoin)
    156 {
    157 	unsigned short nopara;
    158 	unsigned short nomode; /**< An SJOIN without MODE? */
    159 	unsigned short removeours; /**< Remove our modes */
    160 	unsigned short removetheirs; /**< Remove their modes (or actually: do not ADD their modes, the MODE -... line will be sent later by the other side) */
    161 	unsigned short merge;	/**< same timestamp: merge their & our modes */
    162 	char pvar[MAXMODEPARAMS][MODEBUFLEN + 3];
    163 	char cbuf[1024];
    164 	char scratch_buf[1024]; /**< scratch buffer */
    165 	char item[1024]; /**< nick or ban/invex/exempt being processed */
    166 	char item_modes[MEMBERMODESLEN]; /**< item modes, eg "b" or "vhoaq" */
    167 	char prefix[16]; /**< SJOIN prefix of item for server to server traffic (eg: @) */
    168 	char uid_buf[BUFSIZE];  /**< Buffer for server-to-server traffic which will be broadcasted to others (servers supporting SID/UID) */
    169 	char uid_sjsby_buf[BUFSIZE];  /**< Buffer for server-to-server traffic which will be broadcasted to others (servers supporting SID/UID and SJSBY) */
    170 	char sj3_parabuf[BUFSIZE]; /**< Prefix for the above SJOIN buffers (":xxx SJOIN #channel +mode :") */
    171 	char *s = NULL;
    172 	Channel *channel; /**< Channel */
    173 	aParv *ap;
    174 	int pcount, i;
    175 	Hook *h;
    176 	Cmode *cm;
    177 	time_t ts, oldts;
    178 	unsigned short b=0;
    179 	char *tp, *p, *saved = NULL;
    180 	
    181 	if (!IsServer(client) || parc < 4)
    182 		return;
    183 
    184 	if (!IsChannelName(parv[2]))
    185 		return;
    186 
    187 	merge = nopara = nomode = removeours = removetheirs = 0;
    188 
    189 	if (parc < 6)
    190 		nopara = 1;
    191 
    192 	if (parc < 5)
    193 		nomode = 1;
    194 
    195 	channel = find_channel(parv[2]);
    196 	if (!channel)
    197 	{
    198 		channel = make_channel(parv[2]);
    199 		oldts = -1;
    200 	} else {
    201 		oldts = channel->creationtime;
    202 	}
    203 
    204 	ts = (time_t)atol(parv[1]);
    205 
    206 	if (IsInvalidChannelTS(ts))
    207 	{
    208 		unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_TIMESTAMP", client,
    209 			   "SJOIN for channel $channel has invalid timestamp $send_timestamp (from $client)",
    210 			   log_data_channel("channel", channel),
    211 			   log_data_integer("send_timestamp", ts));
    212 		/* Pretend they match our creation time (matches U6 behavior in m_mode.c) */
    213 		ts = channel->creationtime;
    214 	}
    215 
    216 	if (oldts == -1)
    217 	{
    218 		/* Newly created channel (from our POV), so set the correct creationtime here */
    219 		channel->creationtime = ts;
    220 	} else
    221 	if (channel->creationtime > ts)
    222 	{
    223 		removeours = 1;
    224 		channel->creationtime = ts;
    225 	}
    226 	else if (channel->creationtime < ts)
    227 	{
    228 		removetheirs = 1;
    229 	}
    230 	else if (channel->creationtime == ts)
    231 	{
    232 		merge = 1;
    233 	}
    234 
    235 	parabuf[0] = '\0';
    236 	modebuf[0] = '+';
    237 	modebuf[1] = '\0';
    238 
    239 	/* Grab current modes -> modebuf & parabuf */
    240 	channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 1);
    241 
    242 	/* Do we need to remove all our modes, bans/exempt/inves lists and -vhoaq our users? */
    243 	if (removeours)
    244 	{
    245 		Member *lp;
    246 
    247 		modebuf[0] = '-';
    248 
    249 		/* remove our modes if any */
    250 		if (!empty_mode(modebuf))
    251 		{
    252 			MessageTag *mtags = NULL;
    253 			MultiLineMode *mlm;
    254 			ap = mp2parv(modebuf, parabuf);
    255 			mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar);
    256 			send_local_chan_mode_mlm(recv_mtags, client, channel, mlm);
    257 			safe_free_multilinemode(mlm);
    258 		}
    259 		/* remove bans */
    260 		/* reset the buffers */
    261 		modebuf[0] = '-';
    262 		modebuf[1] = '\0';
    263 		parabuf[0] = '\0';
    264 		b = 1;
    265 		while(channel->banlist)
    266 		{
    267 			Ban *ban = channel->banlist;
    268 			Addit('b', ban->banstr);
    269 			channel->banlist = ban->next;
    270 			safe_free(ban->banstr);
    271 			safe_free(ban->who);
    272 			free_ban(ban);
    273 		}
    274 		while(channel->exlist)
    275 		{
    276 			Ban *ban = channel->exlist;
    277 			Addit('e', ban->banstr);
    278 			channel->exlist = ban->next;
    279 			safe_free(ban->banstr);
    280 			safe_free(ban->who);
    281 			free_ban(ban);
    282 		}
    283 		while(channel->invexlist)
    284 		{
    285 			Ban *ban = channel->invexlist;
    286 			Addit('I', ban->banstr);
    287 			channel->invexlist = ban->next;
    288 			safe_free(ban->banstr);
    289 			safe_free(ban->who);
    290 			free_ban(ban);
    291 		}
    292 		for (lp = channel->members; lp; lp = lp->next)
    293 		{
    294 			Membership *lp2 = find_membership_link(lp->client->user->channel, channel);
    295 
    296 			/* Remove all our modes, one by one */
    297 			for (p = lp->member_modes; *p; p++)
    298 			{
    299 				Addit(*p, lp->client->name);
    300 			}
    301 			/* And clear all the flags in memory */
    302 			*lp->member_modes = *lp2->member_modes = '\0';
    303 		}
    304 		if (b > 1)
    305 		{
    306 			modebuf[b] = '\0';
    307 			send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
    308 		}
    309 
    310 		/* since we're dropping our modes, we want to clear the mlock as well. --nenolod */
    311 		set_channel_mlock(client, channel, NULL, FALSE);
    312 	}
    313 	/* Mode setting done :), now for our beloved clients */
    314 	parabuf[0] = 0;
    315 	modebuf[0] = '+';
    316 	modebuf[1] = '\0';
    317 	b = 1;
    318 	strlcpy(cbuf, parv[parc-1], sizeof cbuf);
    319 
    320 	sj3_parabuf[0] = '\0';
    321 	for (i = 2; i <= (parc - 2); i++)
    322 	{
    323 		strlcat(sj3_parabuf, parv[i], sizeof sj3_parabuf);
    324 		if (((i + 1) <= (parc - 2)))
    325 			strlcat(sj3_parabuf, " ", sizeof sj3_parabuf);
    326 	}
    327 
    328 	/* Now process adding of users & adding of list modes (bans/exempt/invex) */
    329 
    330 	snprintf(uid_buf, sizeof uid_buf, ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
    331 	snprintf(uid_sjsby_buf, sizeof uid_sjsby_buf, ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
    332 
    333 	for (s = strtoken(&saved, cbuf, " "); s; s = strtoken(&saved, NULL, " "))
    334 	{
    335 		char *setby = client->name; /**< Set by (nick, nick!user@host, or server name) */
    336 		time_t setat = TStime(); /**< Set at timestamp */
    337 		int sjsby_info = 0; /**< Set to 1 if we receive SJSBY info to alter the above 2 vars */
    338 
    339 		*item_modes = 0;
    340 		i = 0;
    341 		tp = s;
    342 
    343 		/* UnrealIRCd 4.2.2 and later support "SJSBY" which allows communicating
    344 		 * setat/setby information for bans, ban exempts and invite exceptions.
    345 		 */
    346 		if (SupportSJSBY(client->direction) && (*tp == '<'))
    347 		{
    348 			/* Special prefix to communicate timestamp and setter:
    349 			 * "<" + timestamp + "," + nick[!user@host] + ">" + normal SJOIN stuff
    350 			 * For example: "<12345,nick>&some!nice@ban"
    351 			 */
    352 			char *end = strchr(tp, '>'), *p;
    353 			if (!end)
    354 			{
    355 				/* this obviously should never happen */
    356 				unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_SJSBY", client,
    357 					   "SJOIN for channel $channel has invalid SJSBY in item '$item' (from $client)",
    358 					   log_data_channel("channel", channel),
    359 					   log_data_string("item", s));
    360 				continue;
    361 			}
    362 			*end++ = '\0';
    363 
    364 			p = strchr(tp, ',');
    365 			if (!p)
    366 			{
    367 				/* missing setby parameter */
    368 				unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_SJSBY", client,
    369 					   "SJOIN for channel $channel has invalid SJSBY in item '$item' (from $client)",
    370 					   log_data_channel("channel", channel),
    371 					   log_data_string("item", s));
    372 				continue;
    373 			}
    374 			*p++ = '\0';
    375 
    376 			setat = atol(tp+1);
    377 			setby = p;
    378 			sjsby_info = 1;
    379 
    380 			tp = end; /* the remainder is used for the actual ban/exempt/invex */
    381 		}
    382 
    383 		/* Process the SJOIN prefixes... */
    384 		for (p = tp; *p; p++)
    385 		{
    386 			char m = sjoin_prefix_to_mode(*p);
    387 			if (!m)
    388 				break; /* end of prefix stuff, or so we hope anyway :D */
    389 			// TODO: do we want safety here for if one side has prefixmodes loaded
    390 			// and the other does not? and if so, in what way do we want this?
    391 
    392 			strlcat_letter(item_modes, m, sizeof(item_modes));
    393 
    394 			/* For list modes (+beI) stop processing immediately,
    395 			 * so we don't accidentally eat additional prefix chars.
    396 			 */
    397 			if (strchr("beI", m))
    398 			{
    399 				p++;
    400 				break;
    401 			}
    402 		}
    403 
    404 		/* Now set 'prefix' to the prefixes we encountered.
    405 		 * This is basically the range tp..p
    406 		 */
    407 		strlncpy(prefix, tp, sizeof(prefix), p - tp);
    408 
    409 		/* Now copy the "nick" (which can actually be a ban/invex/exempt) */
    410 		strlcpy(item, p, sizeof(item));
    411 		if (*item == '\0')
    412 			continue;
    413 
    414 		/* If not a list mode... then we deal with users... */
    415 		if (!strchr(item_modes, 'b') && !strchr(item_modes, 'e') && !strchr(item_modes, 'I'))
    416 		{
    417 			Client *acptr;
    418 
    419 			/* The user may no longer exist. This can happen in case of a
    420 			 * SVSKILL traveling in the other direction. Nothing to worry about.
    421 			 */
    422 			if (!(acptr = find_user(item, NULL)))
    423 				continue;
    424 
    425 			if (acptr->direction != client->direction)
    426 			{
    427 				if (IsMember(acptr, channel))
    428 				{
    429 					/* Nick collision, don't kick or it desyncs -Griever*/
    430 					continue;
    431 				}
    432 			
    433 				sendto_one(client, NULL,
    434 				    ":%s KICK %s %s :Fake direction",
    435 				    me.id, channel->name, acptr->name);
    436 				unreal_log(ULOG_WARNING, "sjoin", "SJOIN_FAKE_DIRECTION", client,
    437 				           "Fake direction from server $client in SJOIN "
    438 				           "for user $existing_client on $existing_client.user.servername "
    439 				           "(item: $buf)",
    440 				           log_data_client("existing_client", acptr),
    441 				           log_data_string("buf", item));
    442 				continue;
    443 			}
    444 
    445 			if (removetheirs)
    446 				*item_modes = '\0';
    447 
    448 			if (!IsMember(acptr, channel))
    449 			{
    450 				/* User joining the channel, send JOIN to local users.
    451 				 */
    452 				MessageTag *mtags = NULL;
    453 
    454 				add_user_to_channel(channel, acptr, item_modes);
    455 				unreal_log(ULOG_INFO, "join", "REMOTE_CLIENT_JOIN", acptr,
    456 					   "User $client joined $channel",
    457 					   log_data_channel("channel", channel),
    458 					   log_data_string("modes", item_modes));
    459 				RunHook(HOOKTYPE_REMOTE_JOIN, acptr, channel, recv_mtags);
    460 				new_message_special(acptr, recv_mtags, &mtags, ":%s JOIN %s", acptr->name, channel->name);
    461 				send_join_to_local_users(acptr, channel, mtags);
    462 				free_message_tags(mtags);
    463 			}
    464 
    465 			/* Set the +vhoaq */
    466 			for (p = item_modes; *p; p++)
    467 				Addit(*p, acptr->name);
    468 
    469 			if (strlen(uid_buf) + strlen(prefix) + IDLEN > BUFSIZE - 10)
    470 			{
    471 				/* Send what we have and start a new buffer */
    472 				sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
    473 				snprintf(uid_buf, sizeof(uid_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, channel->name);
    474 				/* Double-check the new buffer is sufficient to concat the data */
    475 				if (strlen(uid_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5)
    476 				{
    477 					unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client,
    478 					           "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'",
    479 						   log_data_channel("channel", channel),
    480 					           log_data_string("sjoin_place", "UID-MEMBER"),
    481 					           log_data_string("str", prefix),
    482 					           log_data_string("str2", acptr->id),
    483 					           log_data_string("buf", uid_buf));
    484 					continue;
    485 				}
    486 			}
    487 			sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, acptr->id);
    488 
    489 			if (strlen(uid_sjsby_buf) + strlen(prefix) + IDLEN > BUFSIZE - 10)
    490 			{
    491 				/* Send what we have and start a new buffer */
    492 				sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
    493 				snprintf(uid_sjsby_buf, sizeof(uid_sjsby_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, channel->name);
    494 				/* Double-check the new buffer is sufficient to concat the data */
    495 				if (strlen(uid_sjsby_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5)
    496 				{
    497 					unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client,
    498 					           "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'",
    499 						   log_data_channel("channel", channel),
    500 					           log_data_string("sjoin_place", "SJS-MEMBER"),
    501 					           log_data_string("str", prefix),
    502 					           log_data_string("str2", acptr->id),
    503 					           log_data_string("buf", uid_sjsby_buf));
    504 					continue;
    505 				}
    506 			}
    507 			sprintf(uid_sjsby_buf+strlen(uid_sjsby_buf), "%s%s ", prefix, acptr->id);
    508 		}
    509 		else
    510 		{
    511 			/* It's a list mode................ */
    512 			const char *str;
    513 			
    514 			if (removetheirs)
    515 				continue;
    516 
    517 			/* Validate syntax */
    518 
    519 			/* non-extbans: prevent bans without ! or @. a good case of "should never happen". */
    520 			if ((item[0] != '~') && (!strchr(item, '!') || !strchr(item, '@') || (item[0] == '!')))
    521 				continue;
    522 
    523 			str = clean_ban_mask(item, MODE_ADD, client, 0);
    524 			if (!str)
    525 				continue; /* invalid ban syntax */
    526 			strlcpy(item, str, sizeof(item));
    527 			
    528 			/* Adding of list modes */
    529 			if (*item_modes == 'b')
    530 			{
    531 				if (add_listmode_ex(&channel->banlist, client, channel, item, setby, setat) != -1)
    532 				{
    533 					Addit('b', item);
    534 				}
    535 			}
    536 			if (*item_modes == 'e')
    537 			{
    538 				if (add_listmode_ex(&channel->exlist, client, channel, item, setby, setat) != -1)
    539 				{
    540 					Addit('e', item);
    541 				}
    542 			}
    543 			if (*item_modes == 'I')
    544 			{
    545 				if (add_listmode_ex(&channel->invexlist, client, channel, item, setby, setat) != -1)
    546 				{
    547 					Addit('I', item);
    548 				}
    549 			}
    550 
    551 			if (strlen(uid_buf) + strlen(prefix) + strlen(item) > BUFSIZE - 10)
    552 			{
    553 				/* Send what we have and start a new buffer */
    554 				sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
    555 				snprintf(uid_buf, sizeof(uid_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, channel->name);
    556 				/* Double-check the new buffer is sufficient to concat the data */
    557 				if (strlen(uid_buf) + strlen(prefix) + strlen(item) > BUFSIZE - 5)
    558 				{
    559 					unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client,
    560 					           "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'",
    561 						   log_data_channel("channel", channel),
    562 					           log_data_string("sjoin_place", "UID-LMODE"),
    563 					           log_data_string("str", prefix),
    564 					           log_data_string("str2", item),
    565 					           log_data_string("buf", uid_buf));
    566 					continue;
    567 				}
    568 			}
    569 			sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, item);
    570 
    571 			*scratch_buf = '\0';
    572 			if (sjsby_info)
    573 				add_sjsby(scratch_buf, setby, setat);
    574 			strcat(scratch_buf, prefix);
    575 			strcat(scratch_buf, item);
    576 			strcat(scratch_buf, " ");
    577 			if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 10)
    578 			{
    579 				/* Send what we have and start a new buffer */
    580 				sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
    581 				snprintf(uid_sjsby_buf, sizeof(uid_sjsby_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, channel->name);
    582 				/* Double-check the new buffer is sufficient to concat the data */
    583 				if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 5)
    584 				{
    585 					unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client,
    586 					           "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str' to '$buf'",
    587 						   log_data_channel("channel", channel),
    588 					           log_data_string("sjoin_place", "SJS-LMODE"),
    589 					           log_data_string("str", scratch_buf),
    590 					           log_data_string("buf", uid_sjsby_buf));
    591 					continue;
    592 				}
    593 			}
    594 			strcpy(uid_sjsby_buf+strlen(uid_sjsby_buf), scratch_buf); /* size already checked above */
    595 		}
    596 		continue;
    597 	}
    598 
    599 	/* Send out any possible remainder.. */
    600 	sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
    601 	sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
    602 
    603 	if (!empty_mode(modebuf))
    604 	{
    605 		modebuf[b] = '\0';
    606 		send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
    607 	}
    608 	
    609 	if (!merge && !removetheirs && !nomode)
    610 	{
    611 		MessageTag *mtags = NULL;
    612 		MultiLineMode *mlm;
    613 
    614 		strlcpy(modebuf, parv[3], sizeof modebuf);
    615 		parabuf[0] = '\0';
    616 		if (!nopara)
    617 		{
    618 			for (b = 4; b <= (parc - 2); b++)
    619 			{
    620 				strlcat(parabuf, parv[b], sizeof parabuf);
    621 				strlcat(parabuf, " ", sizeof parabuf);
    622 			}
    623 		}
    624 		ap = mp2parv(modebuf, parabuf);
    625 		mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar);
    626 		send_local_chan_mode_mlm(recv_mtags, client, channel, mlm);
    627 		safe_free_multilinemode(mlm);
    628 	}
    629 
    630 	if (merge && !nomode)
    631 	{
    632 		CoreChannelModeTable *acp;
    633 		MultiLineMode *mlm;
    634 		Mode oldmode; /**< The old mode (OUR mode) */
    635 
    636 		/* Copy current mode to oldmode (need to duplicate all extended mode params too..) */
    637 		memcpy(&oldmode, &channel->mode, sizeof(oldmode));
    638 		memset(&oldmode.mode_params, 0, sizeof(oldmode.mode_params));
    639 		extcmode_duplicate_paramlist(channel->mode.mode_params, oldmode.mode_params);
    640 
    641 		/* Now merge the modes */
    642 		strlcpy(modebuf, parv[3], sizeof modebuf);
    643 		parabuf[0] = '\0';
    644 		if (!nopara)
    645 		{
    646 			for (b = 4; b <= (parc - 2); b++)
    647 			{
    648 				strlcat(parabuf, parv[b], sizeof parabuf);
    649 				strlcat(parabuf, " ", sizeof parabuf);
    650 			}
    651 		}
    652 
    653 		/* First we set the mode (in memory) BUT we don't send the
    654 		 * mode change out to anyone, hence the immediate freeing
    655 		 * of 'mlm'. We do the actual rebuilding of the string and
    656 		 * sending it out a few lines further down.
    657 		 */
    658 		ap = mp2parv(modebuf, parabuf);
    659 		mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar);
    660 		safe_free_multilinemode(mlm);
    661 
    662 		/* Good, now we got modes, now for the differencing and outputting of modes
    663 		 * We first see if any para modes are set.
    664 		 */
    665 		strlcpy(modebuf, "-", sizeof modebuf);
    666 		parabuf[0] = '\0';
    667 		b = 1;
    668 
    669 		/* Check if we had +s and it became +p, then revert it silently (as it is no-change) */
    670 		if (has_channel_mode_raw(oldmode.mode, 's') && has_channel_mode(channel, 'p'))
    671 		{
    672 			/* stay +s ! */
    673 			long mode_p = get_extmode_bitbychar('p');
    674 			long mode_s = get_extmode_bitbychar('s');
    675 			channel->mode.mode &= ~mode_p;
    676 			channel->mode.mode |= mode_s;
    677 			/* TODO: all the code of above would ideally be in a module */
    678 		}
    679 		/* (And the other condition, +p to +s, is already handled below by the generic code) */
    680 
    681 		/* First, check if we had something that is now gone
    682 		 * note that: oldmode.* = us, channel->mode.* = merged.
    683 		 */
    684 		for (cm=channelmodes; cm; cm = cm->next)
    685 		{
    686 			if (cm->letter &&
    687 			    !cm->local &&
    688 			    (oldmode.mode & cm->mode) &&
    689 			    !(channel->mode.mode & cm->mode))
    690 			{
    691 				if (cm->paracount)
    692 				{
    693 					const char *parax = cm_getparameter_ex(oldmode.mode_params, cm->letter);
    694 					//char *parax = cm->get_param(extcmode_get_struct(oldmode.modeparam, cm->letter));
    695 					Addit(cm->letter, parax);
    696 				} else {
    697 					Addsingle(cm->letter);
    698 				}
    699 			}
    700 		}
    701 
    702 		if (b > 1)
    703 		{
    704 			Addsingle('+');
    705 		}
    706 		else
    707 		{
    708 			strlcpy(modebuf, "+", sizeof modebuf);
    709 			b = 1;
    710 		}
    711 
    712 		/* Now, check if merged modes contain something we didn't have before.
    713 		 * note that: oldmode.* = us before, channel->mode.* = merged.
    714 		 *
    715 		 * First the simple single letter modes...
    716 		 */
    717 		for (cm=channelmodes; cm; cm = cm->next)
    718 		{
    719 			if ((cm->letter) &&
    720 			    !(oldmode.mode & cm->mode) &&
    721 			    (channel->mode.mode & cm->mode))
    722 			{
    723 				if (cm->paracount)
    724 				{
    725 					const char *parax = cm_getparameter(channel, cm->letter);
    726 					if (parax)
    727 					{
    728 						Addit(cm->letter, parax);
    729 					}
    730 				} else {
    731 					Addsingle(cm->letter);
    732 				}
    733 			}
    734 		}
    735 
    736 		/* now, if we had diffent para modes - this loop really could be done better, but */
    737 
    738 		/* Now, check for any param differences in extended channel modes..
    739 		 * note that: oldmode.* = us before, channel->mode.* = merged.
    740 		 * if we win: copy oldmode to channel mode, if they win: send the mode
    741 		 */
    742 		for (cm=channelmodes; cm; cm = cm->next)
    743 		{
    744 			if (cm->letter && cm->paracount &&
    745 			    (oldmode.mode & cm->mode) &&
    746 			    (channel->mode.mode & cm->mode))
    747 			{
    748 				int r;
    749 				const char *parax;
    750 				char flag = cm->letter;
    751 				void *ourm = GETPARASTRUCTEX(oldmode.mode_params, flag);
    752 				void *theirm = GETPARASTRUCT(channel, flag);
    753 				
    754 				r = cm->sjoin_check(channel, ourm, theirm);
    755 				switch (r)
    756 				{
    757 					case EXSJ_WEWON:
    758 						parax = cm_getparameter_ex(oldmode.mode_params, flag); /* grab from old */
    759 						cm_putparameter(channel, flag, parax); /* put in new (won) */
    760 						break;
    761 
    762 					case EXSJ_THEYWON:
    763 						parax = cm_getparameter(channel, flag);
    764 						Addit(cm->letter, parax);
    765 						break;
    766 
    767 					case EXSJ_SAME:
    768 						break;
    769 
    770 					case EXSJ_MERGE:
    771 						parax = cm_getparameter_ex(oldmode.mode_params, flag); /* grab from old */
    772 						cm_putparameter(channel, flag, parax); /* put in new (won) */
    773 						Addit(flag, parax);
    774 						break;
    775 
    776 					default:
    777 						unreal_log(ULOG_ERROR, "sjoin", "BUG_SJOIN_CHECK", client,
    778 						           "[BUG] channel.c:m_sjoin:param diff checker: unknown return value $return_value",
    779 						           log_data_integer("return_value", r));
    780 						break;
    781 				}
    782 			}
    783 		}
    784 
    785 		Addsingle('\0');
    786 
    787 		if (!empty_mode(modebuf))
    788 			send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
    789 
    790 		/* free the oldmode.* crap :( */
    791 		extcmode_free_paramlist(oldmode.mode_params);
    792 	}
    793 
    794 	for (h = Hooks[HOOKTYPE_CHANNEL_SYNCED]; h; h = h->next)
    795 	{
    796 		int i = (*(h->func.intfunc))(channel,merge,removetheirs,nomode);
    797 		if (i == 1)
    798 			return; /* channel no longer exists */
    799 	}
    800 
    801 	/* we should be synced by now, */
    802 	if ((oldts != -1) && (oldts != channel->creationtime))
    803 	{
    804 		unreal_log(ULOG_INFO, "channel", "CHANNEL_SYNC_TS_CHANGE", client,
    805 		           "Channel $channel: timestamp changed from $old_ts -> $new_ts "
    806 		           "after syncing with server $client.",
    807 		           log_data_channel("channel", channel),
    808 		           log_data_integer("old_ts", oldts),
    809 		           log_data_integer("new_ts", channel->creationtime));
    810 	}
    811 
    812 	/* If something went wrong with processing of the SJOIN above and
    813 	 * the channel actually has no users in it at this point,
    814 	 * then destroy the channel.
    815 	 */
    816 	if (!channel->users)
    817 	{
    818 		sub1_from_channel(channel);
    819 		return;
    820 	}
    821 }