mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-30 11:06:38 +02:00
0d139c6e7c
and as it should be IMO. Both for invites by channel ops and for OperOverride. This also fixes a bug where an IRCOp with OperOverride could not bypass +l and other restrictions. Only +b and +i could be bypassed. Module coders: HOOKTYPE_OPER_INVITE_BAN is now gone and HOOKTYPE_INVITE_BYPASS is now new. The HOOKTYPE_INVITE_BYPASS is called when the user is joining a channel to which they were invited to. If you return HOOK_DENY there then the join is still blocked, otherwise it is allowed. Using this hook would be sortof unusual since usually you would want users to be able to bypass restrictions when they were invited by another user or when they invited themselves using OperOverride. The only example where we use it in UnrealIRCd is for +O channels so an IRCOp cannot use OperOverride to join +O channels when they would otherwise not be allowed to do so. Actually even that is a corner case that you could debate about, but.. whatever.
616 lines
19 KiB
C
616 lines
19 KiB
C
/*
|
|
* 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, const char *member_modes);
|
|
void _do_join(Client *client, int parc, const char *parv[]);
|
|
int _can_join(Client *client, Channel *channel, const char *key, char **errmsg);
|
|
void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags);
|
|
char *_get_chmodes_for_user(Client *client, const char *flags);
|
|
void send_cannot_join_error(Client *client, int numeric, char *fmtstr, char *channel_name);
|
|
|
|
/* Externs */
|
|
extern MODVAR int spamf_ugly_vchanoverride;
|
|
extern int find_invex(Channel *channel, Client *client);
|
|
|
|
/* Local vars */
|
|
static int bouncedtimes = 0;
|
|
long CAP_EXTENDED_JOIN = 0L;
|
|
|
|
/* 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_SEND_JOIN_TO_LOCAL_USERS, _send_join_to_local_users);
|
|
EfunctionAddPVoid(modinfo->handle, EFUNC_GET_CHMODES_FOR_USER, TO_PVOIDFUNC(_get_chmodes_for_user));
|
|
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
ClientCapabilityInfo c;
|
|
memset(&c, 0, sizeof(c));
|
|
c.name = "extended-join";
|
|
ClientCapabilityAdd(modinfo->handle, &c, &CAP_EXTENDED_JOIN);
|
|
|
|
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, const char *key, char **errmsg)
|
|
{
|
|
Hook *h;
|
|
|
|
/* An /INVITE lets you bypass all restrictions */
|
|
if (is_invited(client, channel))
|
|
{
|
|
int j = 0;
|
|
for (h = Hooks[HOOKTYPE_INVITE_BYPASS]; h; h = h->next)
|
|
{
|
|
j = (*(h->func.intfunc))(client,channel);
|
|
if (j != 0)
|
|
break;
|
|
}
|
|
/* Bypass is OK, unless a HOOKTYPE_INVITE_BYPASS hook returns HOOK_DENY */
|
|
if (j != HOOK_DENY)
|
|
return 0;
|
|
}
|
|
|
|
for (h = Hooks[HOOKTYPE_CAN_JOIN]; h; h = h->next)
|
|
{
|
|
int i = (*(h->func.intfunc))(client,channel,key, errmsg);
|
|
if (i != 0)
|
|
return i;
|
|
}
|
|
|
|
/* See if we can evade this ban */
|
|
if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
|
|
{
|
|
*errmsg = STR_ERR_BANNEDFROMCHAN;
|
|
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))
|
|
{
|
|
*errmsg = STR_ERR_OPERSPVERIFY;
|
|
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)
|
|
{
|
|
unreal_log(ULOG_ERROR, "join", "BUG_JOIN_BOUNCEDTIMES", NULL,
|
|
"[BUG] join: bouncedtimes is not initialized to zero ($bounced_times)!! "
|
|
"Please report at https://bugs.unrealircd.org/",
|
|
log_data_integer("bounced_times", 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];
|
|
|
|
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 && !check_channel_access_member(lp, "hoaq") && (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);
|
|
}
|
|
}
|
|
|
|
/* Routine that actually makes a user join the channel
|
|
* this does no actual checking (banned, etc.) it just adds the user.
|
|
* Note: this is called for local JOIN and remote JOIN, but not for SJOIN.
|
|
*/
|
|
void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, const char *member_modes)
|
|
{
|
|
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) */
|
|
const char *parv[3];
|
|
|
|
/* 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, member_modes);
|
|
|
|
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, modes_to_sjoin_prefix(member_modes), 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, (long long)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 && MODES_ON_JOIN)
|
|
{
|
|
MessageTag *mtags_mode = NULL;
|
|
Cmode *cm;
|
|
char modebuf[BUFSIZE], parabuf[BUFSIZE];
|
|
int should_destroy = 0;
|
|
|
|
channel->mode.mode = MODES_ON_JOIN;
|
|
|
|
/* Param fun */
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
{
|
|
if (!cm->letter || !cm->paracount)
|
|
continue;
|
|
if (channel->mode.mode & cm->mode)
|
|
cm_putparameter(channel, cm->letter, iConf.modes_on_join.extparams[cm->letter]);
|
|
}
|
|
|
|
*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);
|
|
RunHook(HOOKTYPE_LOCAL_CHANMODE, &me, channel, mtags_mode, modebuf, parabuf, 0, 0, &should_destroy);
|
|
free_message_tags(mtags_mode);
|
|
}
|
|
|
|
parv[0] = NULL;
|
|
parv[1] = channel->name;
|
|
parv[2] = NULL;
|
|
do_cmd(client, NULL, "NAMES", 2, parv);
|
|
|
|
unreal_log(ULOG_INFO, "join", "LOCAL_CLIENT_JOIN", client,
|
|
"User $client joined $channel",
|
|
log_data_channel("channel", channel),
|
|
log_data_string("modes", member_modes));
|
|
|
|
RunHook(HOOKTYPE_LOCAL_JOIN, client, channel, mtags);
|
|
} else {
|
|
unreal_log(ULOG_INFO, "join", "REMOTE_CLIENT_JOIN", client,
|
|
"User $client joined $channel",
|
|
log_data_channel("channel", channel),
|
|
log_data_string("modes", member_modes));
|
|
RunHook(HOOKTYPE_REMOTE_JOIN, client, channel, mtags);
|
|
}
|
|
|
|
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, const char *parv[])
|
|
{
|
|
char request[BUFSIZE];
|
|
char request_key[BUFSIZE];
|
|
char jbuf[BUFSIZE], jbuf2[BUFSIZE];
|
|
const char *orig_parv1;
|
|
Membership *lp;
|
|
Channel *channel;
|
|
char *name, *key = NULL;
|
|
int i, ishold;
|
|
char *p = NULL, *p2 = NULL;
|
|
TKL *tklban;
|
|
int ntargets = 0;
|
|
int maxtargets = max_targets_for_command("JOIN");
|
|
const char *member_modes = "";
|
|
|
|
#define RET() do { bouncedtimes--; parv[1] = orig_parv1; return; } while(0)
|
|
|
|
if (parc < 2 || *parv[1] == '\0')
|
|
{
|
|
sendnumeric(client, ERR_NEEDMOREPARAMS, "JOIN");
|
|
return;
|
|
}
|
|
|
|
/* For our tests we need super accurate time for JOINs or they mail fail. */
|
|
gettimeofday(&timeofday_tv, NULL);
|
|
timeofday = timeofday_tv.tv_sec;
|
|
|
|
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.
|
|
*/
|
|
strlcpy(request, parv[1], sizeof(request));
|
|
for (i = 0, name = strtoken(&p, request, ","); 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])
|
|
{
|
|
strlcpy(request_key, parv[2], sizeof(request_key));
|
|
key = strtoken(&p2, request_key, ",");
|
|
}
|
|
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))
|
|
RunHook(HOOKTYPE_LOCAL_PART, client, channel, mtags, "Left all channels");
|
|
|
|
remove_user_from_channel(client, channel, 0);
|
|
free_message_tags(mtags);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (MyConnect(client))
|
|
{
|
|
member_modes = (ChannelExists(name)) ? "" : 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)
|
|
{
|
|
unreal_log(ULOG_INFO, "join", "JOIN_DENIED_FORBIDDEN_CHANNEL", client,
|
|
"Client $client.details tried to join forbidden channel $channel",
|
|
log_data_string("channel", 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))
|
|
member_modes = "";
|
|
else
|
|
{
|
|
Hook *h;
|
|
char *errmsg = NULL;
|
|
for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next)
|
|
{
|
|
i = (*(h->func.intfunc))(client,channel,key);
|
|
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, &errmsg)))
|
|
{
|
|
if (i != -1)
|
|
send_cannot_join_error(client, i, errmsg, 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, member_modes);
|
|
free_message_tags(mtags);
|
|
}
|
|
RET();
|
|
#undef RET
|
|
}
|
|
|
|
#if defined(__GNUC__)
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
#endif
|
|
void send_cannot_join_error(Client *client, int numeric, char *fmtstr, char *channel_name)
|
|
{
|
|
// TODO: add single %s validation !
|
|
sendnumericfmt(client, numeric, fmtstr, channel_name);
|
|
}
|
|
#if defined(__GNUC__)
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
/* 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, const char *member_flags)
|
|
{
|
|
static char modebuf[512]; /* returned */
|
|
char flagbuf[8]; /* For holding "vhoaq" */
|
|
char parabuf[512];
|
|
int n, i;
|
|
|
|
if (BadPtr(member_flags))
|
|
return "";
|
|
|
|
parabuf[0] = '\0';
|
|
n = strlen(member_flags);
|
|
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", member_flags, parabuf);
|
|
return modebuf;
|
|
}
|
|
|
|
return "";
|
|
}
|