/* * IRC - Internet Relay Chat, src/modules/join.c * (C) 2005 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" /* Forward declarations */ CMD_FUNC(cmd_join); void _join_channel(Channel *channel, Client *client, MessageTag *mtags, int flags); void _do_join(Client *client, int parc, char *parv[]); int _can_join(Client *client, Channel *channel, char *key, char *parv[]); void _userhost_save_current(Client *client); void _userhost_changed(Client *client); void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags); /* Externs */ extern MODVAR int spamf_ugly_vchanoverride; extern int find_invex(Channel *channel, Client *client); /* Local vars */ static int bouncedtimes = 0; /* Macros */ #define MAXBOUNCE 5 /** Most sensible */ #define MSG_JOIN "JOIN" ModuleHeader MOD_HEADER = { "join", "5.0", "command /join", "UnrealIRCd Team", "unrealircd-6", }; MOD_TEST() { MARK_AS_OFFICIAL_MODULE(modinfo); EfunctionAddVoid(modinfo->handle, EFUNC_JOIN_CHANNEL, _join_channel); EfunctionAddVoid(modinfo->handle, EFUNC_DO_JOIN, _do_join); EfunctionAdd(modinfo->handle, EFUNC_CAN_JOIN, _can_join); EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_SAVE_CURRENT, _userhost_save_current); EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_CHANGED, _userhost_changed); EfunctionAddVoid(modinfo->handle, EFUNC_SEND_JOIN_TO_LOCAL_USERS, _send_join_to_local_users); return MOD_SUCCESS; } MOD_INIT() { CommandAdd(modinfo->handle, MSG_JOIN, cmd_join, MAXPARA, CMD_USER); MARK_AS_OFFICIAL_MODULE(modinfo); return MOD_SUCCESS; } MOD_LOAD() { return MOD_SUCCESS; } MOD_UNLOAD() { return MOD_SUCCESS; } /* This function checks if a locally connected user may join the channel. * It also provides an number of hooks where modules can plug in to. * Note that the order of checking has been carefully thought of * (eg: bans at the end), so don't change it unless you have a good reason * to do so -- Syzop. */ int _can_join(Client *client, Channel *channel, char *key, char *parv[]) { Link *lp; Ban *banned; Hook *h; int i=0, j=0; for (h = Hooks[HOOKTYPE_CAN_JOIN]; h; h = h->next) { i = (*(h->func.intfunc))(client,channel,key,parv); if (i != 0) return i; } for (h = Hooks[HOOKTYPE_OPER_INVITE_BAN]; h; h = h->next) { j = (*(h->func.intfunc))(client,channel); if (j != 0) break; } /* See if we can evade this ban */ banned = is_banned(client, channel, BANCHK_JOIN, NULL, NULL); if (banned && j == HOOK_DENY) return (ERR_BANNEDFROMCHAN); if (is_invited(client, channel)) return 0; /* allowed */ if (channel->users >= channel->mode.limit) { /* Hmmm.. don't really like this.. and not at this place */ for (h = Hooks[HOOKTYPE_CAN_JOIN_LIMITEXCEEDED]; h; h = h->next) { i = (*(h->func.intfunc))(client,channel,key,parv); if (i != 0) return i; } /* We later check again for this limit (in case +L was not set) */ } if (*channel->mode.key && (BadPtr(key) || strcmp(channel->mode.key, key))) return (ERR_BADCHANNELKEY); if ((channel->mode.mode & MODE_INVITEONLY) && !find_invex(channel, client)) return (ERR_INVITEONLYCHAN); if ((channel->mode.limit && channel->users >= channel->mode.limit)) return (ERR_CHANNELISFULL); if (banned) return (ERR_BANNEDFROMCHAN); #ifndef NO_OPEROVERRIDE #ifdef OPEROVERRIDE_VERIFY if (ValidatePermissionsForPath("channel:override:privsecret",client,NULL,channel,NULL) && (channel->mode.mode & MODE_SECRET || channel->mode.mode & MODE_PRIVATE) && !is_autojoin_chan(channel->name)) return (ERR_OPERSPVERIFY); #endif #endif return 0; } /* ** cmd_join ** parv[1] = channel ** parv[2] = channel password (key) ** ** Due to message tags, remote servers should only send 1 channel ** per JOIN. Or even better, use SJOIN instead. ** Otherwise we cannot use unique msgid's and such. ** UnrealIRCd 4 and probably UnrealIRCd 3.2.something already do ** this, so this comment is mostly for services coders, I guess. */ CMD_FUNC(cmd_join) { int r; if (bouncedtimes) sendto_realops("join: bouncedtimes=%d??? [please report at https://bugs.unrealircd.org/]", bouncedtimes); bouncedtimes = 0; if (IsServer(client)) return; do_join(client, parc, parv); bouncedtimes = 0; } /** Send JOIN message for 'client' to all users in 'channel'. * Taking into account that not everyone in channel should see the JOIN (mode +D) * and taking into account the different types of JOIN (due to CAP extended-join). */ void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags) { int chanops_only = invisible_user_in_channel(client, channel); Member *lp; Client *acptr; char joinbuf[512]; char exjoinbuf[512]; long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join"); long CAP_AWAY_NOTIFY = ClientCapabilityBit("away-notify"); ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN :%s", client->name, client->user->username, GetHost(client), channel->name); ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s", client->name, client->user->username, GetHost(client), channel->name, IsLoggedIn(client) ? client->user->account : "*", client->info); for (lp = channel->members; lp; lp = lp->next) { acptr = lp->client; if (!MyConnect(acptr)) continue; /* only locally connected clients */ if (chanops_only && !(lp->flags & (CHFL_HALFOP|CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANADMIN)) && (client != acptr)) continue; /* skip non-ops if requested to (used for mode +D), but always send to 'client' */ if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN)) sendto_one(acptr, mtags, "%s", exjoinbuf); else sendto_one(acptr, mtags, "%s", joinbuf); if (client->user->away && HasCapabilityFast(acptr, CAP_AWAY_NOTIFY)) { MessageTag *mtags_away = NULL; new_message(client, NULL, &mtags_away); sendto_one(acptr, mtags_away, ":%s!%s@%s AWAY :%s", client->name, client->user->username, GetHost(client), client->user->away); free_message_tags(mtags_away); } } } /* Routine that actually makes a user join the channel * this does no actual checking (banned, etc.) it just adds the user */ void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, int flags) { MessageTag *mtags = NULL; /** Message tags to send to local users (sender is :user) */ MessageTag *mtags_sjoin = NULL; /* Message tags to send to remote servers for SJOIN (sender is :me.id) */ char *parv[] = { 0, 0 }; /* Same way as in SJOIN */ new_message_special(client, recv_mtags, &mtags, ":%s JOIN %s", client->name, channel->name); new_message(&me, recv_mtags, &mtags_sjoin); add_user_to_channel(channel, client, flags); send_join_to_local_users(client, channel, mtags); sendto_server(client, 0, 0, mtags_sjoin, ":%s SJOIN %lld %s :%s%s ", me.id, (long long)channel->creationtime, channel->name, chfl_to_sjoin_symbol(flags), client->id); if (MyUser(client)) { /* ** Make a (temporal) creationtime, if someone joins ** during a net.reconnect : between remote join and ** the mode with TS. --Run */ if (channel->creationtime == 0) { channel->creationtime = TStime(); sendto_server(client, 0, 0, NULL, ":%s MODE %s + %lld", me.id, channel->name, (long long)channel->creationtime); } if (channel->topic) { sendnumeric(client, RPL_TOPIC, channel->name, channel->topic); sendnumeric(client, RPL_TOPICWHOTIME, channel->name, channel->topic_nick, channel->topic_time); } /* Set default channel modes (set::modes-on-join). * Set only if it's the 1st user and only if no other modes have been set * already (eg: +P, permanent). */ if ((channel->users == 1) && !channel->mode.mode && !channel->mode.extmode && (MODES_ON_JOIN || iConf.modes_on_join.extmodes)) { int i; MessageTag *mtags_mode = NULL; channel->mode.extmode = iConf.modes_on_join.extmodes; /* Param fun */ for (i = 0; i <= Channelmode_highest; i++) { if (!Channelmode_Table[i].flag || !Channelmode_Table[i].paracount) continue; if (channel->mode.extmode & Channelmode_Table[i].mode) cm_putparameter(channel, Channelmode_Table[i].flag, iConf.modes_on_join.extparams[i]); } channel->mode.mode = MODES_ON_JOIN; *modebuf = *parabuf = 0; channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 0); /* This should probably be in the SJOIN stuff */ new_message_special(&me, recv_mtags, &mtags_mode, ":%s MODE %s %s %s", me.name, channel->name, modebuf, parabuf); sendto_server(NULL, 0, 0, mtags_mode, ":%s MODE %s %s %s %lld", me.id, channel->name, modebuf, parabuf, (long long)channel->creationtime); sendto_one(client, mtags_mode, ":%s MODE %s %s %s", me.name, channel->name, modebuf, parabuf); free_message_tags(mtags_mode); } parv[0] = client->name; parv[1] = channel->name; do_cmd(client, NULL, "NAMES", 2, parv); RunHook4(HOOKTYPE_LOCAL_JOIN, client, channel, mtags, parv); } else { RunHook4(HOOKTYPE_REMOTE_JOIN, client, channel, mtags, parv); } free_message_tags(mtags); free_message_tags(mtags_sjoin); } /** User request to join a channel. * This routine is normally called from cmd_join but can also be called from * do_join->can_join->link module->do_join if the channel is 'linked' (chmode +L). * We therefore use a counter 'bouncedtimes' which is set to 0 in cmd_join, * increased every time we enter this loop and decreased anytime we leave the * loop. So be carefull not to use a simple 'return' after bouncedtimes++. -- Syzop */ void _do_join(Client *client, int parc, char *parv[]) { char jbuf[BUFSIZE], jbuf2[BUFSIZE]; char *orig_parv1; Membership *lp; Channel *channel; char *name, *key = NULL; int i, flags = 0, ishold; char *p = NULL, *p2 = NULL; TKL *tklban; int ntargets = 0; int maxtargets = max_targets_for_command("JOIN"); #define RET() do { bouncedtimes--; parv[1] = orig_parv1; return; } while(0) if (parc < 2 || *parv[1] == '\0') { sendnumeric(client, ERR_NEEDMOREPARAMS, "JOIN"); return; } bouncedtimes++; orig_parv1 = parv[1]; /* don't use 'return;' but 'RET();' from here ;p */ if (bouncedtimes > MAXBOUNCE) { /* bounced too many times. yeah.. should be in the link module, I know.. then again, who cares.. */ sendnotice(client, "*** Couldn't join %s ! - Link setting was too bouncy", parv[1]); RET(); } *jbuf = '\0'; /* ** Rebuild list of channels joined to be the actual result of the ** JOIN. Note that "JOIN 0" is the destructive problem. */ for (i = 0, name = strtoken(&p, parv[1], ","); name; i++, name = strtoken(&p, NULL, ",")) { if (MyUser(client) && (++ntargets > maxtargets)) { sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "JOIN"); break; } if (*name == '0' && !atoi(name)) { /* UnrealIRCd 5: we only support "JOIN 0", * "JOIN 0,#somechan" etc... so only at the beginning. * We do not support it half-way like "JOIN #a,0,#b" * since that doesn't make sense, unless you are flooding... * We still support it in remote joins for compatibility. */ if (MyUser(client) && (i != 0)) continue; strlcpy(jbuf, "0", sizeof(jbuf)); continue; } else if (MyConnect(client) && !valid_channelname(name)) { send_invalid_channelname(client, name); if (IsOper(client) && find_channel(name)) { /* Give IRCOps a bit more information */ sendnotice(client, "Channel '%s' is unjoinable because it contains illegal characters. " "However, it does exist because another server in your " "network, which has a more loose restriction, created it. " "See https://www.unrealircd.org/docs/Set_block#set::allowed-channelchars", name); } continue; } else if (!IsChannelName(name)) { if (MyUser(client)) sendnumeric(client, ERR_NOSUCHCHANNEL, name); continue; } if (*jbuf) strlcat(jbuf, ",", sizeof jbuf); strlcat(jbuf, name, sizeof(jbuf)); } /* We are going to overwrite 'jbuf' with the calls to strtoken() * a few lines further down. Copy it to 'jbuf2' and make that * the new parv[1].. or at least temporarily. */ strlcpy(jbuf2, jbuf, sizeof(jbuf2)); parv[1] = jbuf2; p = NULL; if (parv[2]) key = strtoken(&p2, parv[2], ","); parv[2] = NULL; /* for cmd_names call later, parv[parc] must == NULL */ for (name = strtoken(&p, jbuf, ","); name; key = key ? strtoken(&p2, NULL, ",") : NULL, name = strtoken(&p, NULL, ",")) { MessageTag *mtags = NULL; /* ** JOIN 0 sends out a part for all channels a user ** has joined. */ if (*name == '0' && !atoi(name)) { /* Rewritten so to generate a PART for each channel to servers, * so the same msgid is used for each part on all servers. -- Syzop */ while ((lp = client->user->channel)) { MessageTag *mtags = NULL; channel = lp->channel; new_message(client, NULL, &mtags); sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags, ":%s PART %s :%s", client->name, channel->name, "Left all channels"); sendto_server(client, 0, 0, mtags, ":%s PART %s :Left all channels", client->name, channel->name); if (MyConnect(client)) RunHook4(HOOKTYPE_LOCAL_PART, client, channel, mtags, "Left all channels"); remove_user_from_channel(client, channel); free_message_tags(mtags); } continue; } if (MyConnect(client)) { /* ** local client is first to enter previously nonexistant ** channel so make them (rightfully) the Channel ** Operator. */ /* Where did this come from? Potvin ? --Stskeeps flags = (ChannelExists(name)) ? CHFL_DEOPPED : CHFL_CHANOWNER; */ flags = (ChannelExists(name)) ? CHFL_DEOPPED : LEVEL_ON_JOIN; if (!ValidatePermissionsForPath("immune:maxchannelsperuser",client,NULL,NULL,NULL)) /* opers can join unlimited chans */ if (client->user->joined >= MAXCHANNELSPERUSER) { sendnumeric(client, ERR_TOOMANYCHANNELS, name); RET(); } /* RESTRICTCHAN */ if (conf_deny_channel) { if (!ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL)) { ConfigItem_deny_channel *d; if ((d = find_channel_allowed(client, name))) { if (d->warn) { sendto_snomask(SNO_EYES, "*** %s tried to join forbidden channel %s", get_client_name(client, 1), name); } if (d->reason) sendnumeric(client, ERR_FORBIDDENCHANNEL, name, d->reason); if (d->redirect) { sendnotice(client, "*** Redirecting you to %s", d->redirect); parv[0] = NULL; parv[1] = d->redirect; do_join(client, 2, parv); } if (d->class) sendnotice(client, "*** Can not join %s: Your class is not allowed", name); continue; } } } if (ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL) && (tklban = find_qline(client, name, &ishold))) { sendnumeric(client, ERR_FORBIDDENCHANNEL, name, tklban->ptr.nameban->reason); continue; } /* ugly set::spamfilter::virus-help-channel-deny hack.. */ if (SPAMFILTER_VIRUSCHANDENY && SPAMFILTER_VIRUSCHAN && !strcasecmp(name, SPAMFILTER_VIRUSCHAN) && !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL) && !spamf_ugly_vchanoverride) { Channel *channel = find_channel(name); if (!channel || !is_invited(client, channel)) { sendnotice(client, "*** Cannot join '%s' because it's the virus-help-channel " "which is reserved for infected users only", name); continue; } } } channel = make_channel(name); if (channel && (lp = find_membership_link(client->user->channel, channel))) continue; if (!channel) continue; i = HOOK_CONTINUE; if (!MyConnect(client)) flags = CHFL_DEOPPED; else { Hook *h; for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next) { /* Note: this is just a hack not to break the ABI but still be * able to fix https://bugs.unrealircd.org/view.php?id=5644 * In the future we should just drop the parv/parx argument * and use key as an argument instead. */ char *parx[4]; parx[0] = NULL; parx[1] = name; parx[2] = key; parx[3] = NULL; i = (*(h->func.intfunc))(client,channel,parx); if (i == HOOK_DENY || i == HOOK_ALLOW) break; } /* Denied, get out now! */ if (i == HOOK_DENY) { /* Rejected... if we just created a new chan we should destroy it too. -- Syzop */ if (!channel->users) sub1_from_channel(channel); continue; } /* If they are allowed, don't check can_join */ if (i != HOOK_ALLOW && (i = can_join(client, channel, key, parv))) { if (i != -1) { sendnumeric(client, i, name); } continue; } } /* Generate a new message without inheritance. * We can do this because remote joins don't follow this code path, * or are highly discouraged to anyway. * Remote servers use SJOIN and never reach this function. * Locally we do follow this code path with JOIN and then generating * a new_message() here is exactly what we want: * Each "JOIN #a,#b,#c" gets processed individually in this loop * and is sent by join_channel() as a SJOIN for #a, then SJOIN for #b, * and so on, each with their own unique msgid and such. */ new_message(client, NULL, &mtags); join_channel(channel, client, mtags, flags); free_message_tags(mtags); } RET(); #undef RET } /* Additional channel-related functions. I've put it here instead * of the core so it could be upgraded on the fly should it be necessary. */ char *get_chmodes_for_user(Client *client, int flags) { static char modebuf[512]; /* returned */ char flagbuf[8]; /* For holding "vhoaq" */ char *p = flagbuf; char parabuf[512]; int n, i; if (!flags) return ""; if (flags & MODE_CHANOWNER) *p++ = 'q'; if (flags & MODE_CHANADMIN) *p++ = 'a'; if (flags & MODE_CHANOP) *p++ = 'o'; if (flags & MODE_VOICE) *p++ = 'v'; if (flags & MODE_HALFOP) *p++ = 'h'; *p = '\0'; parabuf[0] = '\0'; n = strlen(flagbuf); if (n) { for (i=0; i < n; i++) { strlcat(parabuf, client->name, sizeof(parabuf)); if (i < n - 1) strlcat(parabuf, " ", sizeof(parabuf)); } /* And we have our mode line! */ snprintf(modebuf, sizeof(modebuf), "+%s %s", flagbuf, parabuf); return modebuf; } return ""; } static char remember_nick[NICKLEN+1]; static char remember_user[USERLEN+1]; static char remember_host[HOSTLEN+1]; /** Save current nick/user/host. Used later by userhost_changed(). */ void _userhost_save_current(Client *client) { strlcpy(remember_nick, client->name, sizeof(remember_nick)); strlcpy(remember_user, client->user->username, sizeof(remember_user)); strlcpy(remember_host, GetHost(client), sizeof(remember_host)); } /** User/Host changed for user. * Note that userhost_save_current() needs to be called before this * to save the old username/hostname. * This userhost_changed() function deals with notifying local clients * about the user/host change by sending PART+JOIN+MODE if * set::allow-userhost-change force-rejoin is in use, * and it wills end "CAP chghost" to such capable clients. * It will also deal with bumping fakelag for the user since a user/host * change is costly, doesn't matter if it was self-induced or not. * * Please call this function for any user/host change by doing: * userhost_save_current(client); * << change username or hostname here >> * userhost_changed(client); */ void _userhost_changed(Client *client) { Membership *channels; Member *lp; Client *acptr; int impact = 0; char buf[512]; long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join"); long CAP_CHGHOST = ClientCapabilityBit("chghost"); if (strcmp(remember_nick, client->name)) { ircd_log(LOG_ERROR, "[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: %s", client->name); ircd_log(LOG_ERROR, "Please report above bug on https://bugs.unrealircd.org/"); sendto_realops("[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: %s", client->name); sendto_realops("Please report above bug on https://bugs.unrealircd.org/"); return; /* We cannot safely process this request anymore */ } /* It's perfectly acceptable to call us even if the userhost didn't change. */ if (!strcmp(remember_user, client->user->username) && !strcmp(remember_host, GetHost(client))) return; /* Nothing to do */ /* Most of the work is only necessary for set::allow-userhost-change force-rejoin */ if (UHOST_ALLOWED == UHALLOW_REJOIN) { /* Walk through all channels of this user.. */ for (channels = client->user->channel; channels; channels = channels->next) { Channel *channel = channels->channel; int flags = channels->flags; char *modes; char partbuf[512]; /* PART */ char joinbuf[512]; /* JOIN */ char exjoinbuf[512]; /* JOIN (for CAP extended-join) */ char modebuf[512]; /* MODE (if any) */ int chanops_only = invisible_user_in_channel(client, channel); modebuf[0] = '\0'; /* If the user is banned, don't send any rejoins, it would only be annoying */ if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL)) continue; /* Prepare buffers for PART, JOIN, MODE */ ircsnprintf(partbuf, sizeof(partbuf), ":%s!%s@%s PART %s :%s", remember_nick, remember_user, remember_host, channel->name, "Changing host"); ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN %s", client->name, client->user->username, GetHost(client), channel->name); ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s", client->name, client->user->username, GetHost(client), channel->name, IsLoggedIn(client) ? client->user->account : "*", client->info); modes = get_chmodes_for_user(client, flags); if (!BadPtr(modes)) ircsnprintf(modebuf, sizeof(modebuf), ":%s MODE %s %s", me.name, channel->name, modes); for (lp = channel->members; lp; lp = lp->next) { acptr = lp->client; if (acptr == client) continue; /* skip self */ if (!MyConnect(acptr)) continue; /* only locally connected clients */ if (chanops_only && !(lp->flags & (CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANADMIN))) continue; /* skip non-ops if requested to (used for mode +D) */ if (HasCapabilityFast(acptr, CAP_CHGHOST)) continue; /* we notify 'CAP chghost' users in a different way, so don't send it here. */ impact++; /* FIXME: if a client does not have the "chghost" cap then * here we will not generate a proper new message, probably * needs to be fixed... I skipped doing it for now. */ sendto_one(acptr, NULL, "%s", partbuf); if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN)) sendto_one(acptr, NULL, "%s", exjoinbuf); else sendto_one(acptr, NULL, "%s", joinbuf); if (*modebuf) sendto_one(acptr, NULL, "%s", modebuf); } } } /* Now deal with "CAP chghost" clients. * This only needs to be sent one per "common channel". * This would normally call sendto_common_channels_local_butone() but the user already * has the new user/host.. so we do it here.. */ ircsnprintf(buf, sizeof(buf), ":%s!%s@%s CHGHOST %s %s", remember_nick, remember_user, remember_host, client->user->username, GetHost(client)); current_serial++; for (channels = client->user->channel; channels; channels = channels->next) { for (lp = channels->channel->members; lp; lp = lp->next) { acptr = lp->client; if (MyUser(acptr) && HasCapabilityFast(acptr, CAP_CHGHOST) && (acptr->local->serial != current_serial) && (client != acptr)) { /* FIXME: send mtag */ sendto_one(acptr, NULL, "%s", buf); acptr->local->serial = current_serial; } } } if (MyUser(client)) { /* We take the liberty of sending the CHGHOST to the impacted user as * well. This makes things easy for client coders. * (Note that this cannot be merged with the for loop from 15 lines up * since the user may not be in any channels) */ if (HasCapabilityFast(client, CAP_CHGHOST)) sendto_one(client, NULL, "%s", buf); /* A userhost change always generates the following network traffic: * server to server traffic, CAP "chghost" notifications, and * possibly PART+JOIN+MODE if force-rejoin had work to do. * We give the user a penalty so they don't flood... */ if (impact) add_fake_lag(client, 7000); /* Resulted in rejoins and such. */ else add_fake_lag(client, 4000); /* No rejoins */ } }