1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-08 20:23:12 +02:00
Files
unrealircd/src/channel.c
T
Bram Matthys 27a3fb8d97 unreal_server_compat: fix always using EXBTYPE_BAN even for +e/+I.
For the extbans that we ship, no problem, as this isn't used in
any of our extbans, but for third party it may matter, or for us
in the future.

Just something we came across while looking into the issue from
previous commit.
2026-03-14 10:20:24 +01:00

1534 lines
39 KiB
C

/* Unreal Internet Relay Chat Daemon, src/channel.c
* (C) Copyright 1990 Jarkko Oikarinen and
* University of Oulu, Co Center
* (C) Copyright 1999-present The UnrealIRCd team
*
* 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.
*/
/** @file
* @brief Various important (common) channel functions.
*/
#include "unrealircd.h"
/** Lazy way to signal an OperOverride MODE */
long opermode = 0;
/** Lazy way to signal an SAJOIN MODE */
long sajoinmode = 0;
/** List of all channels on the server.
* @ingroup ListFunctions
* @section channels_example Example
* This code will list all channels on the network.
* @code
* sendnotice(client, "List of all channels:");
* for (channel = channels; channel; channel=channel->nextch)
* sendnotice(client, "Channel %s", channel->name);
* @endcode
*/
Channel *channels = NULL;
static mp_pool_t *channel_pool = NULL;
/** This describes the letters, modes and options for core channel modes.
* These are +ntmispklr and also the list modes +vhoaq and +beI.
*/
CoreChannelModeTable corechannelmodetable[] = {
{MODE_BAN, 'b', 1, 1},
{MODE_EXCEPT, 'e', 1, 1}, /* exception ban */
{MODE_INVEX, 'I', 1, 1}, /* invite-only exception */
{0x0, 0x0, 0x0, 0x0}
};
/** The advertised supported channel modes in the 004 numeric */
char cmodestring[512];
/** Returns 1 if the IRCOp can override or is a remote connection */
inline int op_can_override(const char *acl, Client *client, Channel *channel, void* extra)
{
#ifndef NO_OPEROVERRIDE
if (MyUser(client) && !(ValidatePermissionsForPath(acl,client,NULL,channel,extra)))
return 0;
return 1;
#else
return 0;
#endif
}
/** Returns 1 if a half-op can set this channel mode */
int Halfop_mode(long mode)
{
CoreChannelModeTable *tab = &corechannelmodetable[0];
while (tab->mode != 0x0)
{
if (tab->mode == mode)
return (tab->halfop == 1 ? TRUE : FALSE);
tab++;
}
return TRUE;
}
/** Find client in a Member linked list (eg: channel->members) */
Member *find_member_link(Member *lp, Client *ptr)
{
if (ptr)
{
while (lp)
{
if (lp->client == ptr)
return (lp);
lp = lp->next;
}
}
return NULL;
}
/** Find channel in a Membership linked list (eg: client->user->channel) */
Membership *find_membership_link(Membership *lp, Channel *ptr)
{
if (ptr)
while (lp)
{
if (lp->channel == ptr)
return (lp);
lp = lp->next;
}
return NULL;
}
/** Allocate and return an empty Member struct */
static Member *make_member(void)
{
Member *lp;
unsigned int i;
if (freemember == NULL)
{
for (i = 1; i <= (4072/sizeof(Member)); ++i)
{
lp = safe_alloc(sizeof(Member));
AddListItem(lp, freemember);
}
}
/* Now take the first entry */
lp = freemember;
DelListItem(lp, freemember);
memset(lp, 0, sizeof(Member));
return lp;
}
/** Free a Member struct */
static void free_member(Member *lp)
{
if (!lp)
return;
moddata_free_member(lp);
memset(lp, 0, sizeof(Member));
AddListItem(lp, freemember);
}
/** Allocate and return an empty Membership struct */
static Membership *make_membership(void)
{
Membership *m = NULL;
unsigned int i;
if (freemembership == NULL)
{
for (i = 1; i <= (4072/sizeof(Membership)); i++)
{
m = safe_alloc(sizeof(Membership));
AddListItem(m, freemembership);
}
}
/* Now take the first entry */
m = freemembership;
DelListItem(m, freemembership);
memset(m, 0, sizeof(Membership));
return m;
}
/** Free a Membership struct */
static void free_membership(Membership *m)
{
if (m)
{
moddata_free_membership(m);
memset(m, 0, sizeof(Membership));
AddListItem(m, freemembership);
}
}
/** Find a client by nickname, hunt for older nick names if not found.
* This can be handy, for example for /KILL nick, if 'nick' keeps
* nick-changing and you are slow with typing.
* @param client The requestor
* @param user The nick name (or server name)
* @param chasing This will be set to 1 if the client was found
* only after searching through the nick history.
* @returns The client (if found) or NULL (if not found).
*/
Client *find_chasing(Client *client, const char *user, int *chasing)
{
Client *who = find_client(user, NULL);
if (chasing)
*chasing = 0;
if (who)
{
if (!IsServer(who))
return who;
else
return NULL;
}
if (!(who = get_history(user, (long)KILLCHASETIMELIMIT)))
{
sendnumeric(client, ERR_NOSUCHNICK, user);
return NULL;
}
if (chasing)
*chasing = 1;
if (!IsServer(who))
return who;
else return NULL;
}
/** Return 1 if the bans are identical, taking into account special handling for extbans */
int identical_ban(const char *one, const char *two)
{
#if 0
if (is_extended_ban(one) && is_extended_ban(two))
{
/* compare the first 3 characters case-sensitive and if identical then compare
* the remainder of the string case-insensitive.
*/
if (!strncmp(one, two, 3) && !strcasecmp(one+3, two+3))
return 1;
} else {
if (!mycmp(one, two))
return 1;
}
#else
/* Actually I think we can live with this nowadays.
* We are pushing towards named extbans, and all the
* letter extbans that could clash no longer exist.
*/
if (!mycmp(one, two))
return 1;
#endif
return 0;
}
/** Add a listmode (+beI) with the specified banid to
* the specified channel. (Extended version with
* set by nick and set on timestamp)
* @retval 1 Added
* @retval 0 Updated
* @retval -1 Ban list full
*/
int add_listmode_ex(Ban **list, Client *client, Channel *channel, const char *banid, const char *setby, time_t seton)
{
Ban *ban;
int cnt = 0;
int do_not_add = 0;
char isnew = 0;
//if (MyUser(client))
// collapse(banid);
if (!*list && (MAXBANS < 1))
{
if (MyUser(client))
{
/* Only send the error to local clients */
sendnumeric(client, ERR_BANLISTFULL, channel->name, banid);
}
do_not_add = 1;
}
for (ban = *list; ban; ban = ban->next)
{
/* Check MAXBANS only for local clients and 'me' (for +b's set during +f).
*/
if ((MyUser(client) || IsMe(client)) && (++cnt >= MAXBANS))
do_not_add = 1;
if (identical_ban(ban->banstr, banid))
break; /* update existing ban (potentially) */
}
/* Create a new ban if needed */
if (!ban)
{
if (do_not_add)
{
/* The banlist is full and trying to add a new ban.
* This is not permitted.
*/
if (MyUser(client))
{
/* Only send the error to local clients */
sendnumeric(client, ERR_BANLISTFULL, channel->name, banid);
}
return -1;
}
ban = make_ban();
ban->next = *list;
*list = ban;
isnew = 1;
}
if ((ban->when > 0) && (seton >= ban->when))
{
/* Trying to add the same ban while an older version
* or identical version of the ban already exists.
*/
return -1;
}
/* Update/set if this ban is new or older than existing one */
safe_strldup(ban->banstr, banid, MAXBANLEN+1); /* cAsE may differ, use oldest version of it */
safe_strldup(ban->who, setby, NICKLEN+USERLEN+HOSTLEN+4);
ban->when = seton;
return isnew ? 1 : 0;
}
/** Add a listmode (+beI) with the specified banid to
* the specified channel. (Simplified version)
*/
int add_listmode(Ban **list, Client *client, Channel *channel, const char *banid)
{
char *setby = client->name;
char nuhbuf[NICKLEN+USERLEN+HOSTLEN+4];
if (IsUser(client) && (iConf.ban_setter == SETTER_NICK_USER_HOST))
setby = make_nick_user_host_r(nuhbuf, sizeof(nuhbuf), client->name, client->user->username, GetHost(client));
return add_listmode_ex(list, client, channel, banid, setby, TStime());
}
/** Delete a listmode (+beI) from a channel that matches the specified banid.
*/
int del_listmode(Ban **list, Channel *channel, const char *banid)
{
Ban **ban;
Ban *tmp;
if (!banid)
return -1;
for (ban = list; *ban; ban = &((*ban)->next))
{
if (identical_ban(banid, (*ban)->banstr))
{
tmp = *ban;
*ban = tmp->next;
safe_free(tmp->banstr);
safe_free(tmp->who);
free_ban(tmp);
return 0;
}
}
return -1;
}
/** is_banned - Check if a user is banned on a channel.
* @param client Client to check (can be remote client)
* @param channel Channel to check
* @param type Type of ban to check for (BANCHK_*)
* @param msg Message, only for some BANCHK_* types, otherwise NULL
* @param errmsg Error message returned, could be NULL (which does not
* indicate absence of an error).
* @returns A pointer to the ban struct if banned, otherwise NULL.
* @comments Simple wrapper for is_banned_with_nick()
*/
inline Ban *is_banned(Client *client, Channel *channel, int type, const char **msg, const char **errmsg)
{
return is_banned_with_nick(client, channel, type, NULL, msg, errmsg);
}
/** ban_check_mask - Checks if the user matches the specified n!u@h mask -or- run an extended ban.
* This is basically extracting the mask and extban check from is_banned_with_nick,
* but with being a bit more strict in what an extban is.
* @param b Ban context, see BanContext
* @returns Nonzero if the mask/extban succeeds. Zero if it doesn't.
*/
inline int ban_check_mask(BanContext *b)
{
if (!b->no_extbans && is_extended_ban(b->banstr))
{
/* Is an extended ban. */
const char *nextbanstr;
Extban *extban = findmod_by_bantype(b->banstr, &nextbanstr);
if (!extban || !(extban->is_banned_events & b->ban_check_types))
{
return 0;
} else {
b->banstr = nextbanstr;
return extban->is_banned(b);
}
}
else
{
/* Is a n!u@h mask. */
return match_user(b->banstr, b->client, MATCH_CHECK_ALL);
}
}
/** is_banned_with_nick - Check if a user is banned on a channel.
* @param client Client to check (can be remote client)
* @param channel Channel to check
* @param type Type of ban to check for (BANCHK_*)
* @param nick Nick of the user (or NULL, to default to client->name)
* @param msg Message, only for some BANCHK_* types, otherwise NULL
* @returns A pointer to the ban struct if banned, otherwise NULL.
*/
Ban *is_banned_with_nick(Client *client, Channel *channel, int type, const char *nick, const char **msg, const char **errmsg)
{
Ban *ban, *ex;
char savednick[NICKLEN+1];
BanContext *b = safe_alloc(sizeof(BanContext));
/* It's not really doable to pass 'nick' to all the ban layers,
* including extbans (with stacking) and so on. Or at least not
* without breaking several module API's.
* So, instead, we temporarily set 'client->name' to 'nick' and
* restore it to the orginal value at the end of this function.
* This is possible because all these layers never send a message
* to 'client' and only indicate success/failure.
* Note that all this ONLY happens if is_banned_with_nick() is called
* with a non-NULL nick. That doesn't happen much. In UnrealIRCd
* only in case of '/NICK newnick'. This fixes #5165.
*/
if (nick)
{
strlcpy(savednick, client->name, sizeof(savednick));
strlcpy(client->name, nick, sizeof(client->name));
}
b->client = client;
b->channel = channel;
b->ban_check_types = type;
if (msg)
b->msg = *msg;
/* We check +b first, if a +b is found we then see if there is a +e.
* If a +e was found we return NULL, if not, we return the ban.
*/
b->ban_type = EXBTYPE_BAN;
for (ban = channel->banlist; ban; ban = ban->next)
{
b->banstr = ban->banstr;
if (ban_check_mask(b))
break;
}
if (ban)
{
/* Ban found, now check for +e */
b->ban_type = EXBTYPE_EXCEPT;
for (ex = channel->exlist; ex; ex = ex->next)
{
b->banstr = ex->banstr;
if (ban_check_mask(b))
{
/* except matched */
ban = NULL;
break;
}
}
/* user is not on except, 'ban' stays non-NULL. */
}
if (nick)
{
/* Restore the nick */
strlcpy(client->name, savednick, sizeof(client->name));
}
/* OUT: */
if (msg)
*msg = b->msg;
if (errmsg)
*errmsg = b->error_msg;
safe_free(b);
return ban;
}
/** Checks if a ban already exists */
int ban_exists(Ban *lst, const char *str)
{
for (; lst; lst = lst->next)
if (!mycmp(lst->banstr, str))
return 1;
return 0;
}
/** Checks if a ban already exists - special version.
* This ignores the "~time:xx:" suffixes in the banlist.
* So it will return 1 if a ban is there for ~time:5:blah!*@*
* and you call ban_exists_ignore_time(channel->banlist, "blah!*@*")
*/
int ban_exists_ignore_time(Ban *lst, const char *str)
{
const char *p;
for (; lst; lst = lst->next)
{
if (!strncmp(lst->banstr, "~time:", 6))
{
/* Special treatment for ~time:xx: */
p = strchr(lst->banstr+6, ':');
if (p)
{
p++;
if (!mycmp(p, str))
return 1;
}
} else
{
/* The simple version */
if (!mycmp(lst->banstr, str))
return 1;
}
}
return 0;
}
/** Add user to the channel.
* This adds both the Member struct to the channel->members linked list
* and also the Membership struct to the client->user->channel linked list.
* @note This does NOT send the JOIN, it only does the linked list stuff.
*/
void add_user_to_channel(Channel *channel, Client *client, const char *modes)
{
Member *m;
LocalMember *lm = NULL;
Membership *mb;
const char *p;
if (!client->user)
return;
/* Add to channel->members */
m = make_member();
m->client = client;
AddListItem(m, channel->members);
channel->users++;
if (MyConnect(client))
{
/* Add to channel->local_members */
lm = safe_alloc(sizeof(LocalMember));
lm->ptr = m;
AddListItem(lm, channel->local_members);
}
/* Add to client->user->channel */
mb = make_membership();
mb->channel = channel;
AddListItem(mb, client->user->channel);
client->user->joined++;
/* Set friend relationships */
m->related = mb;
m->local_member = lm;
mb->related = m;
for (p = modes; *p; p++)
add_member_mode_fast(m, mb, *p);
RunHook(HOOKTYPE_JOIN_DATA, client, channel);
}
/** Remove the user from the channel - with membership entry.
* This frees the memberships, decreases the user counts,
* destroys the channel if needed, etc.
* This does not send any PART/KICK/..!
* @param client The client that is removed from the channel
* @param channel The channel
* @param mb The membership entry
* @param dont_log Set to 1 if it should not be logged as a part,
* for example if you are already logging it as a kick.
*/
int remove_user_from_channel_withmb(Client *client, Channel *channel, Membership *mb, int dont_log)
{
/* Find & free related LocalMember */
if (mb->related->local_member)
{
DelListItem(mb->related->local_member, channel->local_members);
safe_free(mb->related->local_member);
}
/* Now find & free related Member */
DelListItem(mb->related, channel->members);
free_member(mb->related);
/* And finally, free the Membership */
DelListItem(mb, client->user->channel);
free_membership(mb);
/* Update user record to reflect 1 less joined */
client->user->joined--;
if (!dont_log)
{
if (MyUser(client))
{
unreal_log(ULOG_INFO, "part", "LOCAL_CLIENT_PART", client,
"User $client left $channel",
log_data_channel("channel", channel));
} else {
unreal_log(ULOG_INFO, "part", "REMOTE_CLIENT_PART", client,
"User $client left $channel",
log_data_channel("channel", channel));
}
}
/* Now sub1_from_channel() will deal with the channel record
* and destroy the channel if needed.
*/
return sub1_from_channel(channel);
}
/** Remove the user from the channel.
* This frees the memberships, decreases the user counts,
* destroys the channel if needed, etc.
* This does not send any PART/KICK/..!
* @param client The client that is removed from the channel
* @param channel The channel
* @param dont_log Set to 1 if it should not be logged as a part,
* for example if you are already logging it as a kick.
*/
int remove_user_from_channel(Client *client, Channel *channel, int dont_log)
{
Membership *mb;
int found = 0;
for (mb = client->user->channel; mb; mb = mb->next)
if (mb->channel == channel)
return remove_user_from_channel_withmb(client, channel, mb, dont_log);
/* If we get here, the entry was not found */
#ifdef DEBUGMODE
abort(); /* This should never happen, right? */
#endif
return 0;
}
/** Returns 1 if channel has this channel mode set and 0 if not */
int has_channel_mode(Channel *channel, char mode)
{
Cmode *cm;
for (cm=channelmodes; cm; cm = cm->next)
if ((cm->letter == mode) && (channel->mode.mode & cm->mode))
return 1;
return 0; /* Not found */
}
/** Returns 1 if channel has this mode is set and 0 if not */
int has_channel_mode_raw(Cmode_t m, char mode)
{
Cmode *cm;
for (cm=channelmodes; cm; cm = cm->next)
if ((cm->letter == mode) && (m & cm->mode))
return 1;
return 0; /* Not found */
}
/** Get the extended channel mode 'bit' value (eg: 0x20) by character (eg: 'Z') */
Cmode_t get_extmode_bitbychar(char m)
{
Cmode *cm;
for (cm=channelmodes; cm; cm = cm->next)
if (cm->letter == m)
return cm->mode;
return 0;
}
/** Write the "simple" list of channel modes for channel channel onto buffer mbuf with the parameters in pbuf.
* @param client The client requesting the mode list (can be NULL)
* @param mbuf Modes will be stored here
* @param pbuf Mode parameters will be stored here
* @param mbuf_size Length of the mbuf buffer
* @param pbuf_size Length of the pbuf buffer
* @param channel The channel to fetch modes from
* @param hide_local_modes If set to 1 then we will hide local channel modes like Z and d
* (eg: if you intend to send the buffer to a remote server)
*/
void channel_modes(Client *client, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel, int hide_local_modes)
{
int show_mode_parameters = 0;
Cmode *cm;
if (!mbuf_size || !pbuf_size)
return;
if (!client || IsMember(client, channel) || IsServer(client) || IsMe(client) || IsULine(client) ||
ValidatePermissionsForPath("channel:see:mode:remote",client,NULL,channel,NULL))
{
show_mode_parameters = 1;
}
*pbuf = '\0';
strlcpy(mbuf, "+", mbuf_size);
for (cm=channelmodes; cm; cm = cm->next)
{
if (cm->letter &&
!(hide_local_modes && cm->local) &&
(channel->mode.mode & cm->mode))
{
char flag = cm->letter;
if (mbuf_size)
strlcat_letter(mbuf, flag, mbuf_size);
if (cm->paracount && show_mode_parameters)
{
strlcat(pbuf, cm_getparameter(channel, flag), pbuf_size);
strlcat(pbuf, " ", pbuf_size);
}
}
}
/* Remove the trailing space from the parameters -- codemastr */
if (*pbuf)
pbuf[strlen(pbuf)-1]='\0';
}
/** Make a pretty mask from the input string - only used by SILENCE
*/
char *pretty_mask(const char *mask_in)
{
char mask[512];
char *cp, *user, *host;
strlcpy(mask, mask_in, sizeof(mask));
if ((user = strchr((cp = mask), '!')))
*user++ = '\0';
if ((host = strrchr(user ? user : cp, '@')))
{
*host++ = '\0';
if (!user)
return make_nick_user_host(NULL, cp, host);
}
else if (!user && strchr(cp, '.'))
{
return make_nick_user_host(NULL, NULL, cp);
}
return make_nick_user_host(cp, user, host);
}
/** Trim a string - rather than cutting it off sharply, this adds a * at the end.
* So "toolong" becomes "toolon*"
*/
char *trim_str(char *str, int len)
{
int l;
if (!str)
return NULL;
if ((l = strlen(str)) > len)
{
str += l - len;
*str = '*';
}
return str;
}
/* Convert regular ban (non-extban) if needed.
* This does things like:
* nick!user@host -> nick!user@host (usually no change)
* nickkkkkkkkkkkkkkkkkkkkkkkkkk!user@host -> nickkkkkkk*!user@host (dealing with NICKLEN restrictions and such).
* user@host -> *!user@host
* 1.2.3.4 -> *!*@1.2.3.4 (converting IP to a proper mask)
* @param mask Incoming mask (this will be touched/fragged!)
* @param buf Output buffer
* @param buflen Length of the output buffer, eg sizeof(buf)
* @retval The sanitized mask, or NULL if it should be rejected fully.
* @note Since 'mask' will be fragged, you most likely wish to pass a copy of it rather than the original.
*/
const char *convert_regular_ban(char *mask, char *buf, size_t buflen)
{
static char namebuf[NICKLEN + USERLEN + HOSTLEN + 3];
char *user, *host;
if (!*mask)
return NULL; /* empty extban */
if (!buf)
{
buf = namebuf;
buflen = sizeof(namebuf);
}
if ((*mask == '~') && !strchr(mask, '@'))
{
/* has a '~', which makes it look like an extban,
* but is not a user@host ban, too confusing.
*/
return NULL;
}
if ((user = strchr(mask, '!')))
*user++ = '\0';
if ((host = strrchr(user ? user : mask, '@')))
{
*host++ = '\0';
if (!user)
return make_nick_user_host_r(buf, buflen, NULL, trim_str(mask,USERLEN), trim_str(host,HOSTLEN));
}
else if (!user && (strchr(mask, '.') || strchr(mask, ':')))
{
/* 1.2.3.4 -> *!*@1.2.3.4 (and the same for IPv6) */
return make_nick_user_host_r(buf, buflen, NULL, NULL, trim_str(mask,HOSTLEN));
}
/* regular nick!user@host with the auto-trimming feature */
return make_nick_user_host_r(buf, buflen, trim_str(mask,NICKLEN), trim_str(user,USERLEN), trim_str(host,HOSTLEN));
}
/** Make a proper ban mask.
* This takes user input (eg: "nick") and converts it to a mask suitable
* in the +beI lists (eg: "nick!*@*"). It also deals with extended bans,
* in which case it will call the extban->conv_param() function.
* @param mask_in The ban mask
* @param what MODE_DEL or MODE_ADD
* @param ban_type One of EXBTYPE_*, such as EXBTYPE_BAN.
* @param client The client adding/removing this ban mask
* @param channel The channel on which this entry will be added or removed
* @param conv_options Options for BanContext.conv_options (eg BCTX_CONV_OPTION_WRITE_LETTER_BANS)
* @returns pointer to correct banmask or NULL in case of error
* @note A pointer is returned to a static buffer, which is overwritten
* on next clean_ban_mask or make_nick_user_host call.
*/
const char *clean_ban_mask(const char *mask_in, int what, ExtbanType ban_type, Client *client, Channel *channel, int conv_options)
{
char *cp, *x;
static char mask[512];
/* Strip any ':' at beginning since that would cause a desync */
for (; (*mask_in && (*mask_in == ':')); mask_in++);
if (!*mask_in)
return NULL;
/* Work on a copy */
strlcpy(mask, mask_in, sizeof(mask));
cp = strchr(mask, ' ');
if (cp)
*cp = '\0';
/* Forbid ASCII <= 32 in all bans */
for (x = mask; *x; x++)
if (*x <= ' ')
return NULL;
/* Extended ban? */
if (is_extended_ban(mask))
{
const char *nextbanstr;
Extban *extban;
extban = findmod_by_bantype(mask, &nextbanstr);
if (!extban)
{
/* extended bantype not supported, what to do?
* Here are the rules:
* - if from a remote client/server: allow it (easy upgrading,
* no desync)
* - if from a local client trying to REMOVE the extban,
* allow it too (so you don't get "unremovable" extbans).
*/
if (!MyUser(client) || (what == MODE_DEL))
{
if ((what == MODE_ADD) && (strlen(mask) > MAXBANLEN))
mask[MAXBANLEN] = '\0';
return mask; /* allow it */
}
return NULL; /* reject */
}
if (extban->conv_param)
{
const char *ret;
static char retbuf[512];
BanContext *b = safe_alloc(sizeof(BanContext));
b->client = client;
b->channel = channel;
b->what = what;
b->ban_type = ban_type;
b->banstr = nextbanstr;
b->conv_options = conv_options;
ret = extban->conv_param(b, extban);
ret = prefix_with_extban(ret, b, extban, retbuf, sizeof(retbuf));
safe_free(b);
if (ret && strlen(ret) > MAXBANLEN)
retbuf[MAXBANLEN] = '\0';
return ret;
}
/* else, do some basic sanity checks and cut it off at 80 bytes */
if ((mask[1] != ':') || (mask[2] == '\0'))
return NULL; /* require a ":<char>" after extban type */
if (strlen(mask) > 80)
mask[80] = '\0';
return mask;
}
return convert_regular_ban(mask, NULL, 0);
}
/** Check if 'client' matches an invite exception (+I) on 'channel' */
int find_invex(Channel *channel, Client *client)
{
Ban *inv;
BanContext *b = safe_alloc(sizeof(BanContext));
b->client = client;
b->channel = channel;
b->ban_check_types = BANCHK_JOIN;
b->ban_type = EXBTYPE_INVEX;
for (inv = channel->invexlist; inv; inv = inv->next)
{
b->banstr = inv->banstr;
if (ban_check_mask(b))
{
safe_free(b);
return 1;
}
}
safe_free(b);
return 0;
}
/** Remove unwanted characters from channel name.
* You must call this before creating a new channel,
* eg in case of /JOIN.
*/
int valid_channelname(const char *cname)
{
const char *p;
/* Channel name must start with a dash */
if (*cname != '#')
return 0;
if (strlen(cname) > CHANNELLEN)
return 0;
if ((iConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY) || !iConf.allowed_channelchars)
{
/* The default up to and including UnrealIRCd 4 */
for (p = cname; *p; p++)
{
if (*p < 33 || *p == ',' || *p == ':')
return 0;
}
} else
if (iConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ASCII)
{
/* The strict setting: only allow ASCII 32-128, except some chars */
for (p = cname; *p; p++)
{
if (*p < 33 || *p == ',' || *p == ':' || *p > 127)
return 0;
}
} else
if (iConf.allowed_channelchars == ALLOWED_CHANNELCHARS_UTF8)
{
/* Only allow UTF8, and also disallow some chars */
for (p = cname; *p; p++)
{
if (*p < 33 || *p == ',' || *p == ':')
return 0;
}
/* And run it through the UTF8 validator */
if (!unrl_utf8_validate(cname, (const char **)&p))
return 0;
} else
{
/* Impossible */
abort();
}
return 1; /* Valid */
}
void initlist_channels(void)
{
channel_pool = mp_pool_new(sizeof(Channel), 512 * 1024);
}
/** Create channel 'name' (or if it exists, return the existing one)
* @param name Channel name
* @param flag If set to 'CREATE' then the channel is
* created if it does not exist.
* @returns Pointer to channel (new or existing).
* @note Be sure to call valid_channelname() first before
* you blindly call this function!
*/
Channel *make_channel(const char *name)
{
Channel *channel;
int len;
char *p;
char namebuf[CHANNELLEN+1];
if (BadPtr(name))
return NULL;
/* Copy and silently truncate */
strlcpy(namebuf, name, sizeof(namebuf));
/* Copied from valid_channelname(), the minimal requirements */
for (p = namebuf; *p; p++)
{
if (*p < 33 || *p == ',' || *p == ':')
{
*p = '\0';
break;
}
}
/* Exists? Return it. */
if ((channel = find_channel(namebuf)))
return channel;
channel = mp_pool_get(channel_pool);
memset(channel, 0, sizeof(Channel));
strlcpy(channel->name, namebuf, sizeof(channel->name));
if (channels)
channels->prevch = channel;
channel->topic = NULL;
channel->topic_nick = NULL;
channel->prevch = NULL;
channel->nextch = channels;
channel->creationtime = TStime();
channels = channel;
add_to_channel_hash_table(channel->name, channel);
irccounts.channels++;
RunHook(HOOKTYPE_CHANNEL_CREATE, channel);
return channel;
}
/** Is the user 'client' invited to channel 'channel' by a chanop?
* @param client The client who was invited
* @param channel The channel to which the person was invited
*/
int is_invited(Client *client, Channel *channel)
{
int invited = 0;
RunHook(HOOKTYPE_IS_INVITED, client, channel, &invited);
return invited;
}
/** Subtract one user from channel i. Free the channel if it became empty.
* @param channel The channel
* @returns 1 if the channel was freed, 0 if the channel still exists.
*/
int sub1_from_channel(Channel *channel)
{
Ban *ban;
Link *lp;
int should_destroy = 1;
--channel->users;
if (channel->users > 0)
return 0;
/* No users in the channel anymore */
channel->users = 0; /* to be sure */
/* If the channel is +P then this hook will actually stop destruction. */
RunHook(HOOKTYPE_CHANNEL_DESTROY, channel, &should_destroy);
if (!should_destroy)
return 0;
/* We are now going to destroy the channel.
* But first we will destroy all kinds of references and lists...
*/
moddata_free_channel(channel);
while (channel->banlist)
{
ban = channel->banlist;
channel->banlist = ban->next;
safe_free(ban->banstr);
safe_free(ban->who);
free_ban(ban);
}
while (channel->exlist)
{
ban = channel->exlist;
channel->exlist = ban->next;
safe_free(ban->banstr);
safe_free(ban->who);
free_ban(ban);
}
while (channel->invexlist)
{
ban = channel->invexlist;
channel->invexlist = ban->next;
safe_free(ban->banstr);
safe_free(ban->who);
free_ban(ban);
}
/* free extcmode params */
extcmode_free_paramlist(channel->mode.mode_params);
safe_free(channel->mode_lock);
safe_free(channel->topic);
safe_free(channel->topic_nick);
if (channel->prevch)
channel->prevch->nextch = channel->nextch;
else
channels = channel->nextch;
if (channel->nextch)
channel->nextch->prevch = channel->prevch;
del_from_channel_hash_table(channel->name, channel);
irccounts.channels--;
mp_pool_release(channel);
return 1;
}
/** Set channel mode lock on the channel, these are modes that users cannot change.
* @param client The client or server issueing the MLOCK
* @param channel The channel that will be MLOCK'ed
* @param newmlock The MLOCK string: list of mode characters that are locked
*/
void set_channel_mlock(Client *client, Channel *channel, const char *newmlock, int propagate)
{
safe_strdup(channel->mode_lock, newmlock);
if (propagate)
{
sendto_server(client, 0, 0, NULL, ":%s MLOCK %lld %s :%s",
client->id, (long long)channel->creationtime, channel->name,
BadPtr(channel->mode_lock) ? "" : channel->mode_lock);
}
}
/** Parse a channelmode line.
* @in pm A ParseMode struct, used to return values and to maintain internal state.
* @in modebuf_in Buffer pointing to mode characters (eg: +snk-l)
* @in parabuf_in Buffer pointing to all parameters (eg: key 123)
* @retval Returns 1 if we have valid data to return, 0 if at end of mode line.
* @section parse_chanmode_example Example:
* @code
* ParseMode pm;
* int ret;
* for (ret = parse_chanmode(&pm, modebuf, parabuf); ret; ret = parse_chanmode(&pm, NULL, NULL))
* {
* unreal_log(ULOG_INFO, "test", "TEST", "Got %c%c %s",
* pm.what == MODE_ADD ? '+' : '-',
* pm.modechar,
* pm.param ? pm.param : "");
* }
* @endcode
*/
int parse_chanmode(ParseMode *pm, const char *modebuf_in, const char *parabuf_in)
{
if (modebuf_in)
{
/* Initialize */
memset(pm, 0, sizeof(ParseMode));
pm->modebuf = modebuf_in;
pm->parabuf = parabuf_in;
pm->what = MODE_ADD;
}
while(1)
{
if (*pm->modebuf == '\0')
return 0;
else if (*pm->modebuf == '+')
{
pm->what = MODE_ADD;
pm->modebuf++;
continue;
}
else if (*pm->modebuf == '-')
{
pm->what = MODE_DEL;
pm->modebuf++;
continue;
}
else
{
CoreChannelModeTable *tab = &corechannelmodetable[0];
Cmode *cm;
int eatparam = 0;
/* Set some defaults */
pm->extm = NULL;
pm->modechar = *pm->modebuf;
pm->param = NULL;
while (tab->mode != 0x0)
{
if (tab->flag == *pm->modebuf)
break;
tab++;
}
if (tab->mode)
{
/* INTERNAL MODE */
if (tab->parameters)
{
eatparam = 1;
}
} else {
/* EXTENDED CHANNEL MODE */
int found = 0;
for (cm=channelmodes; cm; cm = cm->next)
{
if (cm->letter == *pm->modebuf)
{
found = 1;
break;
}
}
if (!found)
{
/* Not found. Will be ignored, just move on.. */
pm->modebuf++;
continue;
}
pm->extm = cm;
if (cm->paracount == 1)
{
if (pm->what == MODE_ADD)
eatparam = 1;
else if (cm->unset_with_param)
eatparam = 1;
/* else 0 (if MODE_DEL && !unset_with_param) */
}
}
if (eatparam)
{
/* Hungry.. */
if (pm->parabuf && *pm->parabuf)
{
const char *start, *end;
for (; *pm->parabuf == ' '; pm->parabuf++); /* skip whitespace */
start = pm->parabuf;
if (*pm->parabuf == '\0')
{
pm->modebuf++;
continue; /* invalid, got mode but no parameter available */
}
end = strchr(start, ' ');
/* copy start .. end (where end may be null, then just copy all) */
if (end)
{
pm->parabuf = end + 1; /* point to next param, or \0 */
if (end - start + 1 > sizeof(pm->buf))
end = start + sizeof(pm->buf); /* 'never' reached */
strlcpy(pm->buf, start, end - start + 1);
}
else
{
strlcpy(pm->buf, start, sizeof(pm->buf));
pm->parabuf = pm->parabuf + strlen(pm->parabuf); /* point to \0 at end */
}
stripcrlf(pm->buf); /* needed for unreal_server_compat.c */
pm->param = pm->buf;
} else {
pm->modebuf++;
continue; /* invalid, got mode but no parameter available */
}
}
}
pm->modebuf++; /* advance pointer */
return 1;
}
}
/** Returns 1 if both clients are at least in 1 same channel */
int has_common_channels(Client *c1, Client *c2)
{
Membership *lp;
for (lp = c1->user->channel; lp; lp = lp->next)
{
if (IsMember(c2, lp->channel) && user_can_see_member(c1, c2, lp->channel))
return 1;
}
return 0;
}
/** Returns 1 if user 'user' can see channel member 'target' - fast version.
* This may return 0 if the user is 'invisible' due to mode +D rules.
* @param user The user who is looking around
* @param target The target user who is being investigated
* @param channel The channel
* @param target_member The Member * struct of 'target'
* @param user_member_modes The member modes that 'user' has, eg "o". Can be NULL if not in channel.
*/
int user_can_see_member_fast(Client *user, Client *target, Channel *channel, Member *target_member, const char *user_member_modes)
{
Hook *h;
int j = 0;
if (user == target)
return 1;
/* Requested to hide the person, but make sure neither one is +hoaq... */
if ((target_member->memb_flags & MEMB_FLAG_INVISIBLE) &&
!check_channel_access_member(target_member, "vhoaq") &&
!(user_member_modes && check_channel_access_string(user_member_modes, "hoaq")))
{
return 0;
}
return 1;
}
/** Returns 1 if user 'user' can see channel member 'target' - fast version.
* This may return 0 if the user is 'invisible' due to mode +D rules.
* @param user The user who is looking around
* @param target The target user who is being investigated
* @param channel The channel
* @param target_member The Member * struct of 'target'
* @param user_member_modes The member modes that 'user' has, eg "o". Can be NULL if not in channel.
*/
int user_can_see_membership_fast(Client *user, Client *target, Channel *channel, Membership *target_member, const char *user_member_modes)
{
Hook *h;
int j = 0;
if (user == target)
return 1;
/* Requested to hide the person, but make sure neither one is +hoaq... */
if ((target_member->memb_flags & MEMB_FLAG_INVISIBLE) &&
!check_channel_access_membership(target_member, "vhoaq") &&
!(user_member_modes && check_channel_access_string(user_member_modes, "hoaq")))
{
return 0;
}
return 1;
}
/** Returns 1 if user 'user' can see channel member 'target'.
* This may return 0 if the user is 'invisible' due to mode +D rules.
* NOTE: Membership is unchecked, assumed membership of both.
*/
int user_can_see_member(Client *user, Client *target, Channel *channel)
{
Membership *user_member = NULL;
Membership *target_member = NULL;
if (user == target)
return 1;
if (IsUser(user))
user_member = find_membership_link(user->user->channel, channel);
if (IsUser(target))
target_member = find_membership_link(target->user->channel, channel);
/* User is not in channel, yeah what shall we return? :D */
if (!target_member)
return 0;
return user_can_see_membership_fast(user, target, channel, target_member, user_member ? user_member->member_modes : NULL);
}
/** Returns 1 if user 'target' is invisible in channel 'channel' */
int invisible_user_in_channel(Client *target, Channel *channel)
{
Hook *h;
Membership *target_member;
int j = 0;
target_member = find_membership_link(target->user->channel, channel);
if (!target_member)
return 0; /* not in channel */
if (target_member->memb_flags & MEMB_FLAG_INVISIBLE)
return 1;
return 0;
}
int channel_has_invisible_users(Channel *channel)
{
Member *mb;
for (mb = channel->members; mb; mb = mb->next)
if (mb->memb_flags & MEMB_FLAG_INVISIBLE)
return 1;
return 0;
}
void set_user_invisible(Client *client, Channel *channel, int invisible)
{
Membership *m;
if (!IsUser(client))
return;
m = find_membership_link(client->user->channel, channel);
if (!m)
return;
if (invisible)
{
m->memb_flags |= MEMB_FLAG_INVISIBLE;
m->related->memb_flags |= MEMB_FLAG_INVISIBLE;
} else {
m->memb_flags &= ~MEMB_FLAG_INVISIBLE;
m->related->memb_flags &= ~MEMB_FLAG_INVISIBLE;
}
}
/** Send a message to the user that (s)he is using an invalid channel name.
* This is usually called after an if (MyUser(client) && !valid_channelname(name)).
* @param client The client to send the message to.
* @param channelname The (invalid) channel that the user tried to join.
*/
void send_invalid_channelname(Client *client, const char *channelname)
{
const char *reason;
if (*channelname != '#')
{
reason = "Channel name must start with a hash mark (#)";
} else
if (strlen(channelname) > CHANNELLEN)
{
reason = "Channel name is too long";
} else {
switch(iConf.allowed_channelchars)
{
case ALLOWED_CHANNELCHARS_ASCII:
reason = "Channel name contains illegal characters (must be ASCII)";
break;
case ALLOWED_CHANNELCHARS_UTF8:
reason = "Channel name contains illegal characters (must be valid UTF8)";
break;
case ALLOWED_CHANNELCHARS_ANY:
default:
reason = "Channel name contains illegal characters";
}
}
sendnumeric(client, ERR_FORBIDDENCHANNEL, channelname, reason);
}
/** Is the provided string possibly an extended ban?
* Note that it still may not exist, it just tests the first part.
* @param str The string to check (eg "~account:xyz")
*/
int is_extended_ban(const char *str)
{
const char *p;
if (*str != '~')
return 0;
for (p = str+1; *p; p++)
{
if (!isalnum(*p))
{
if (*p == ':')
return 1;
}
}
return 0;
}
/** Is the provided string possibly an extended server ban?
* Actually this is only a very light check.
* It may still not exist, it just tests the first part.
* @param str The string to check (eg "~account:xyz")
* The only difference between this and is_extended_ban()
* is that we allow a % at the beginning for soft-bans.
* @see is_extended_ban()
*/
int is_extended_server_ban(const char *str)
{
if (*str == '%')
str++;
return is_extended_ban(str);
}
/** Check if it is an empty (useless) mode, namely "", "+" or "-".
* Typically called as: empty_mode(modebuf)
*/
int empty_mode(const char *m)
{
if (!*m || (((m[0] == '+') || (m[0] == '-')) && m[1] == '\0'))
return 1;
return 0;
}
/** Free everything of/in a MultiLineMode */
void free_multilinemode(MultiLineMode *m)
{
int i;
if (m == NULL)
return;
for (i=0; i < m->numlines; i++)
{
safe_free(m->modeline[i]);
safe_free(m->paramline[i]);
}
safe_free(m);
}
ExtbanType mode_letter_to_extbantype(char c)
{
if (c == 'e')
return EXBTYPE_EXCEPT;
else if (c == 'I')
return EXBTYPE_INVEX;
/* Else default to 'b' */
return EXBTYPE_BAN;
}
ExtbanType listmode_sjoin_prefix_to_extbantype(char c)
{
if (c == '"')
return EXBTYPE_EXCEPT;
if (c == '\'')
return EXBTYPE_INVEX;
return EXBTYPE_BAN;
}