mirror of https://github.com/pissnet/pissircd.git
608 lines
18 KiB
C
608 lines
18 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))
|
|
{
|
|
*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 the different types of JOIN (due to CAP extended-join).
|
|
*/
|
|
void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags)
|
|
{
|
|
sendto_channel(channel, client, NULL, NULL,
|
|
CAP_EXTENDED_JOIN|CAP_INVERT,
|
|
CHECK_INVISIBLE|SEND_LOCAL,
|
|
mtags,
|
|
":%s JOIN :%s",
|
|
client->name, channel->name);
|
|
|
|
sendto_channel(channel, client, NULL, NULL,
|
|
CAP_EXTENDED_JOIN,
|
|
CHECK_INVISIBLE|SEND_LOCAL,
|
|
mtags,
|
|
":%s JOIN %s %s :%s",
|
|
client->name, channel->name,
|
|
IsLoggedIn(client) ? client->user->account : "*",
|
|
client->info);
|
|
}
|
|
|
|
/* 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;
|
|
if (!HasCapability(client,"draft/no-implicit-names") /* && !HasCapability(client, "no-implicit-names") */)
|
|
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 {
|
|
if (!(client->uplink && !IsSynched(client->uplink)))
|
|
{
|
|
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 >= get_setting_for_user_number(client, SET_MAX_CHANNELS_PER_USER))
|
|
{
|
|
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 "";
|
|
}
|