/* * 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-6", }; char modebuf[BUFSIZE], parabuf[BUFSIZE]; 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; const 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); } static void send_local_chan_mode(MessageTag *recv_mtags, Client *client, Channel *channel, char *modebuf, char *parabuf) { MessageTag *mtags = NULL; int destroy_channel = 0; new_message_special(client, recv_mtags, &mtags, ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf); sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf); if (MyConnect(client)) RunHook(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1, &destroy_channel); else RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1, &destroy_channel); free_message_tags(mtags); } /** Call send_local_chan_mode() for multiline modes */ static void send_local_chan_mode_mlm(MessageTag *recv_mtags, Client *client, Channel *channel, MultiLineMode *mlm) { if (mlm) { int i; for (i = 0; i < mlm->numlines; i++) send_local_chan_mode(recv_mtags, client, channel, mlm->modeline[i], mlm->paramline[i]); } } /** 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 scratch_buf[1024]; /**< scratch buffer */ char item[1024]; /**< nick or ban/invex/exempt being processed */ char item_modes[MEMBERMODESLEN]; /**< item modes, eg "b" or "vhoaq" */ char prefix[16]; /**< SJOIN prefix of item 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; Cmode *cm; time_t ts, oldts; unsigned short b=0; char *tp, *p, *saved = NULL; 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 = find_channel(parv[2]); if (!channel) { channel = make_channel(parv[2]); oldts = -1; } else { oldts = channel->creationtime; } ts = (time_t)atol(parv[1]); if (IsInvalidChannelTS(ts)) { unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_TIMESTAMP", client, "SJOIN for channel $channel has invalid timestamp $send_timestamp (from $client)", log_data_channel("channel", channel), log_data_integer("send_timestamp", ts)); /* Pretend they match our creation time (matches U6 behavior in m_mode.c) */ ts = channel->creationtime; } if (oldts == -1) { /* Newly created channel (from our POV), so set the correct creationtime here */ channel->creationtime = ts; } else if (channel->creationtime > ts) { removeours = 1; channel->creationtime = ts; } else if (channel->creationtime < ts) { removetheirs = 1; } else if (channel->creationtime == ts) { merge = 1; } 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; modebuf[0] = '-'; /* remove our modes if any */ if (!empty_mode(modebuf)) { MessageTag *mtags = NULL; MultiLineMode *mlm; ap = mp2parv(modebuf, parabuf); mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar); send_local_chan_mode_mlm(recv_mtags, client, channel, mlm); safe_free_multilinemode(mlm); } /* 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) { Membership *lp2 = find_membership_link(lp->client->user->channel, channel); /* Remove all our modes, one by one */ for (p = lp->member_modes; *p; p++) { Addit(*p, lp->client->name); } /* And clear all the flags in memory */ *lp->member_modes = *lp2->member_modes = '\0'; } 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++) { 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 */ *item_modes = 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 */ unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_SJSBY", client, "SJOIN for channel $channel has invalid SJSBY in item '$item' (from $client)", log_data_channel("channel", channel), log_data_string("item", s)); continue; } *end++ = '\0'; p = strchr(tp, ','); if (!p) { /* missing setby parameter */ unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_SJSBY", client, "SJOIN for channel $channel has invalid SJSBY in item '$item' (from $client)", log_data_channel("channel", channel), log_data_string("item", s)); continue; } *p++ = '\0'; setat = atol(tp+1); setby = p; sjsby_info = 1; tp = end; /* the remainder is used for the actual ban/exempt/invex */ } /* Process the SJOIN prefixes... */ for (p = tp; *p; p++) { char m = sjoin_prefix_to_mode(*p); if (!m) break; /* end of prefix stuff, or so we hope anyway :D */ // TODO: do we want safety here for if one side has prefixmodes loaded // and the other does not? and if so, in what way do we want this? strlcat_letter(item_modes, m, sizeof(item_modes)); /* For list modes (+beI) stop processing immediately, * so we don't accidentally eat additional prefix chars. */ if (strchr("beI", m)) { p++; break; } } /* Now set 'prefix' to the prefixes we encountered. * This is basically the range tp..p */ strlncpy(prefix, tp, sizeof(prefix), p - tp); /* Now copy the "nick" (which can actually be a ban/invex/exempt) */ strlcpy(item, p, sizeof(item)); if (*item == '\0') continue; /* If not a list mode... then we deal with users... */ if (!strchr(item_modes, 'b') && !strchr(item_modes, 'e') && !strchr(item_modes, 'I')) { Client *acptr; /* 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_user(item, 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->name, acptr->name); unreal_log(ULOG_WARNING, "sjoin", "SJOIN_FAKE_DIRECTION", client, "Fake direction from server $client in SJOIN " "for user $existing_client on $existing_client.user.servername " "(item: $buf)", log_data_client("existing_client", acptr), log_data_string("buf", item)); continue; } if (removetheirs) *item_modes = '\0'; if (!IsMember(acptr, channel)) { /* User joining the channel, send JOIN to local users. */ MessageTag *mtags = NULL; add_user_to_channel(channel, acptr, item_modes); RunHook(HOOKTYPE_REMOTE_JOIN, acptr, channel, recv_mtags); new_message_special(acptr, recv_mtags, &mtags, ":%s JOIN %s", acptr->name, channel->name); send_join_to_local_users(acptr, channel, mtags); free_message_tags(mtags); } /* Set the +vhoaq */ for (p = item_modes; *p; p++) Addit(*p, acptr->name); 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, channel->name); /* Double-check the new buffer is sufficient to concat the data */ if (strlen(uid_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5) { unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client, "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'", log_data_channel("channel", channel), log_data_string("sjoin_place", "UID-MEMBER"), log_data_string("str", prefix), log_data_string("str2", acptr->id), log_data_string("buf", uid_buf)); 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, channel->name); /* Double-check the new buffer is sufficient to concat the data */ if (strlen(uid_sjsby_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5) { unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client, "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'", log_data_channel("channel", channel), log_data_string("sjoin_place", "SJS-MEMBER"), log_data_string("str", prefix), log_data_string("str2", acptr->id), log_data_string("buf", uid_sjsby_buf)); continue; } } sprintf(uid_sjsby_buf+strlen(uid_sjsby_buf), "%s%s ", prefix, acptr->id); } else { /* It's a list mode................ */ const char *str; if (removetheirs) continue; /* Validate syntax */ /* non-extbans: prevent bans without ! or @. a good case of "should never happen". */ if ((item[0] != '~') && (!strchr(item, '!') || !strchr(item, '@') || (item[0] == '!'))) continue; str = clean_ban_mask(item, MODE_ADD, client, 0); if (!str) continue; /* invalid ban syntax */ strlcpy(item, str, sizeof(item)); /* Adding of list modes */ if (*item_modes == 'b') { if (add_listmode_ex(&channel->banlist, client, channel, item, setby, setat) != -1) { Addit('b', item); } } if (*item_modes == 'e') { if (add_listmode_ex(&channel->exlist, client, channel, item, setby, setat) != -1) { Addit('e', item); } } if (*item_modes == 'I') { if (add_listmode_ex(&channel->invexlist, client, channel, item, setby, setat) != -1) { Addit('I', item); } } if (strlen(uid_buf) + strlen(prefix) + strlen(item) > 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, channel->name); /* Double-check the new buffer is sufficient to concat the data */ if (strlen(uid_buf) + strlen(prefix) + strlen(item) > BUFSIZE - 5) { unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client, "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'", log_data_channel("channel", channel), log_data_string("sjoin_place", "UID-LMODE"), log_data_string("str", prefix), log_data_string("str2", item), log_data_string("buf", uid_buf)); continue; } } sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, item); *scratch_buf = '\0'; if (sjsby_info) add_sjsby(scratch_buf, setby, setat); strcat(scratch_buf, prefix); strcat(scratch_buf, item); 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, channel->name); /* Double-check the new buffer is sufficient to concat the data */ if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 5) { unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client, "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str' to '$buf'", log_data_channel("channel", channel), log_data_string("sjoin_place", "SJS-LMODE"), log_data_string("str", scratch_buf), log_data_string("buf", uid_sjsby_buf)); continue; } } strcpy(uid_sjsby_buf+strlen(uid_sjsby_buf), scratch_buf); /* size already checked above */ } continue; } /* Send out any possible remainder.. */ sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf); sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf); if (!empty_mode(modebuf)) { modebuf[b] = '\0'; send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf); } if (!merge && !removetheirs && !nomode) { MessageTag *mtags = NULL; MultiLineMode *mlm; 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); mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar); send_local_chan_mode_mlm(recv_mtags, client, channel, mlm); safe_free_multilinemode(mlm); } if (merge && !nomode) { CoreChannelModeTable *acp; MultiLineMode *mlm; 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.mode_params, 0, sizeof(oldmode.mode_params)); extcmode_duplicate_paramlist(channel->mode.mode_params, oldmode.mode_params); /* 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); } } /* First we set the mode (in memory) BUT we don't send the * mode change out to anyone, hence the immediate freeing * of 'mlm'. We do the actual rebuilding of the string and * sending it out a few lines further down. */ ap = mp2parv(modebuf, parabuf); mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar); safe_free_multilinemode(mlm); /* 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; /* Check if we had +s and it became +p, then revert it silently (as it is no-change) */ if (has_channel_mode_raw(oldmode.mode, 's') && has_channel_mode(channel, 'p')) { /* stay +s ! */ long mode_p = get_extmode_bitbychar('p'); long mode_s = get_extmode_bitbychar('s'); channel->mode.mode &= ~mode_p; channel->mode.mode |= mode_s; /* TODO: all the code of above would ideally be in a module */ } /* (And the other condition, +p to +s, is already handled below by the generic code) */ /* First, check if we had something that is now gone * note that: oldmode.* = us, channel->mode.* = merged. */ for (cm=channelmodes; cm; cm = cm->next) { if (cm->letter && !cm->local && (oldmode.mode & cm->mode) && !(channel->mode.mode & cm->mode)) { if (cm->paracount) { const char *parax = cm_getparameter_ex(oldmode.mode_params, cm->letter); //char *parax = cm->get_param(extcmode_get_struct(oldmode.modeparam, cm->letter)); Addit(cm->letter, parax); } else { Addsingle(cm->letter); } } } if (b > 1) { Addsingle('+'); } else { strlcpy(modebuf, "+", sizeof modebuf); b = 1; } /* Now, check if merged modes contain something we didn't have before. * note that: oldmode.* = us before, channel->mode.* = merged. * * First the simple single letter modes... */ for (cm=channelmodes; cm; cm = cm->next) { if ((cm->letter) && !(oldmode.mode & cm->mode) && (channel->mode.mode & cm->mode)) { if (cm->paracount) { const char *parax = cm_getparameter(channel, cm->letter); if (parax) { Addit(cm->letter, parax); } } else { Addsingle(cm->letter); } } } /* now, if we had diffent para modes - this loop really could be done better, but */ /* Now, check for any param differences in extended channel modes.. * note that: oldmode.* = us before, channel->mode.* = merged. * if we win: copy oldmode to channel mode, if they win: send the mode */ for (cm=channelmodes; cm; cm = cm->next) { if (cm->letter && cm->paracount && (oldmode.mode & cm->mode) && (channel->mode.mode & cm->mode)) { int r; const char *parax; char flag = cm->letter; void *ourm = GETPARASTRUCTEX(oldmode.mode_params, flag); void *theirm = GETPARASTRUCT(channel, flag); r = cm->sjoin_check(channel, ourm, theirm); switch (r) { case EXSJ_WEWON: parax = cm_getparameter_ex(oldmode.mode_params, flag); /* grab from old */ cm_putparameter(channel, flag, parax); /* put in new (won) */ break; case EXSJ_THEYWON: parax = cm_getparameter(channel, flag); Addit(cm->letter, parax); break; case EXSJ_SAME: break; case EXSJ_MERGE: parax = cm_getparameter_ex(oldmode.mode_params, flag); /* grab from old */ cm_putparameter(channel, flag, parax); /* put in new (won) */ Addit(flag, parax); break; default: unreal_log(ULOG_ERROR, "sjoin", "BUG_SJOIN_CHECK", client, "[BUG] channel.c:m_sjoin:param diff checker: unknown return value $return_value", log_data_integer("return_value", r)); break; } } } Addsingle('\0'); if (!empty_mode(modebuf)) send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf); /* free the oldmode.* crap :( */ extcmode_free_paramlist(oldmode.mode_params); } 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)) { unreal_log(ULOG_INFO, "channel", "CHANNEL_SYNC_TS_CHANGE", client, "Channel $channel: timestamp changed from $old_ts -> $new_ts " "after syncing with server $client.", log_data_channel("channel", channel), log_data_integer("old_ts", oldts), log_data_integer("new_ts", channel->creationtime)); } /* 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; } }