mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-25 16:46:38 +02:00
58551c2d43
later on, because unload_extcmode_commit() would call extcmode_para_delslot() even though member modes don't use a parameter slot, and hence it would NULLify a wrong slot, usually for the +H parameter mode. Fun. We now no longer crash and mass-unset the modes on everyone in the channel when such a mode is unloaded, just like we do when unloading any of the other channel modes. It is not done in an efficient way (one mode per line) but this should be an extremely rare event anyway. Crash reported by CrazyCat.
1173 lines
29 KiB
C
1173 lines
29 KiB
C
/************************************************************************
|
|
* IRC - Internet Relay Chat, src/api-channelmode.c
|
|
* (C) 2003-2007 Bram Matthys (Syzop) and 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.
|
|
*/
|
|
|
|
/** @file
|
|
* @brief The channel mode API used by modules.
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
|
|
/** This is the extended channel mode API,
|
|
* see also https://www.unrealircd.org/docs/Dev:Channel_Mode_API
|
|
* for more information.
|
|
* @defgroup ChannelModeAPI Channel mode API
|
|
* @{
|
|
*/
|
|
|
|
/** List of all channel modes, their handlers, etc */
|
|
Cmode *channelmodes = NULL;
|
|
|
|
/** @} */
|
|
|
|
/** Channel parameter to slot# mapping - used by GETPARAMSLOT() macro */
|
|
MODVAR unsigned char param_to_slot_mapping[256];
|
|
/** Extended channel modes in use - used by ISUPPORT/005 numeric only */
|
|
char extchmstr[4][64];
|
|
|
|
/* Private functions (forward declaration) and variables */
|
|
static void make_cmodestr(void);
|
|
static char previous_chanmodes[256];
|
|
static char previous_prefix[256];
|
|
static Cmode *ParamTable[MAXPARAMMODES+1];
|
|
static void unload_extcmode_commit(Cmode *cmode);
|
|
|
|
/** Create the strings that are used for CHANMODES=a,b,c,d in numeric 005 */
|
|
void make_extcmodestr()
|
|
{
|
|
char *p;
|
|
Cmode *cm;
|
|
int i;
|
|
|
|
extchmstr[0][0] = extchmstr[1][0] = extchmstr[2][0] = extchmstr[3][0] = '\0';
|
|
|
|
/* type 1: lists (like b/e) */
|
|
/* [NOT IMPLEMENTED IN EXTCMODES] */
|
|
|
|
/* type 2: 1 par to set/unset (has .unset_with_param) */
|
|
p = extchmstr[1];
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if (cm->paracount && cm->letter && cm->unset_with_param && (cm->type != CMODE_MEMBER))
|
|
*p++ = cm->letter;
|
|
*p = '\0';
|
|
|
|
/* type 3: 1 param to set, 0 params to unset (does not have .unset_with_param) */
|
|
p = extchmstr[2];
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if (cm->paracount && cm->letter && !cm->unset_with_param)
|
|
*p++ = cm->letter;
|
|
*p = '\0';
|
|
|
|
/* type 4: paramless modes */
|
|
p = extchmstr[3];
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if (!cm->paracount && cm->letter)
|
|
*p++ = cm->letter;
|
|
*p = '\0';
|
|
}
|
|
|
|
/** Create the string that is used in numeric 004 */
|
|
static void make_cmodestr(void)
|
|
{
|
|
Cmode *cm;
|
|
char *p = &cmodestring[0];
|
|
CoreChannelModeTable *tab = &corechannelmodetable[0];
|
|
int i;
|
|
while (tab->mode != 0x0)
|
|
{
|
|
*p = tab->flag;
|
|
p++;
|
|
tab++;
|
|
}
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if (cm->letter)
|
|
*p++ = cm->letter;
|
|
*p = '\0';
|
|
}
|
|
|
|
/** Check for changes - if any are detected, we broadcast the change */
|
|
void extcmodes_check_for_changed_channel_modes(void)
|
|
{
|
|
char chanmodes[256];
|
|
ISupport *isup;
|
|
|
|
//sort_cmodes();
|
|
make_cmodestr();
|
|
make_extcmodestr();
|
|
|
|
snprintf(chanmodes, sizeof(chanmodes), "%s%s", CHPAR1, EXPAR1);
|
|
safe_strdup(me.server->features.chanmodes[0], chanmodes);
|
|
snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR2);
|
|
safe_strdup(me.server->features.chanmodes[1], chanmodes);
|
|
snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR3);
|
|
safe_strdup(me.server->features.chanmodes[2], chanmodes);
|
|
snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR4);
|
|
safe_strdup(me.server->features.chanmodes[3], chanmodes);
|
|
|
|
ircsnprintf(chanmodes, sizeof(chanmodes), "%s,%s,%s,%s",
|
|
me.server->features.chanmodes[0],
|
|
me.server->features.chanmodes[1],
|
|
me.server->features.chanmodes[2],
|
|
me.server->features.chanmodes[3]);
|
|
|
|
isup = ISupportFind("CHANMODES");
|
|
if (!isup)
|
|
{
|
|
strlcpy(previous_chanmodes, chanmodes, sizeof(previous_chanmodes));
|
|
return; /* not booted yet. then we are done here. */
|
|
}
|
|
|
|
ISupportSetValue(isup, chanmodes);
|
|
|
|
if (*previous_chanmodes && strcmp(chanmodes, previous_chanmodes))
|
|
{
|
|
unreal_log(ULOG_INFO, "mode", "CHANNEL_MODES_CHANGED", NULL,
|
|
"Channel modes changed at runtime: $old_channel_modes -> $new_channel_modes",
|
|
log_data_string("old_channel_modes", previous_chanmodes),
|
|
log_data_string("new_channel_modes", chanmodes));
|
|
/* Broadcast change to all (locally connected) servers */
|
|
sendto_server(NULL, 0, 0, NULL, "PROTOCTL CHANMODES=%s", chanmodes);
|
|
}
|
|
|
|
strlcpy(previous_chanmodes, chanmodes, sizeof(previous_chanmodes));
|
|
}
|
|
|
|
void make_prefix(char **isupport_prefix, char **isupport_statusmsg)
|
|
{
|
|
static char prefix[256];
|
|
static char prefix_prefix[256];
|
|
char prefix_modes[256];
|
|
int rank[256];
|
|
Cmode *cm;
|
|
int n;
|
|
|
|
*prefix = *prefix_prefix = *prefix_modes = '\0';
|
|
|
|
for (n=0, cm=channelmodes; cm && n < ARRAY_SIZEOF(rank)-1; cm = cm->next)
|
|
{
|
|
if ((cm->type == CMODE_MEMBER) && cm->letter)
|
|
{
|
|
strlcat_letter(prefix_prefix, cm->prefix, sizeof(prefix_prefix));
|
|
strlcat_letter(prefix_modes, cm->letter, sizeof(prefix_modes));
|
|
rank[n] = cm->rank;
|
|
n++;
|
|
}
|
|
}
|
|
|
|
if (*prefix_prefix)
|
|
{
|
|
int i, j;
|
|
/* Now sort the damn thing */
|
|
for (i=0; i < n; i++)
|
|
{
|
|
for (j=i+1; j < n; j++)
|
|
{
|
|
if (rank[i] < rank[j])
|
|
{
|
|
/* swap */
|
|
char save;
|
|
int save_rank;
|
|
save = prefix_prefix[i];
|
|
prefix_prefix[i] = prefix_prefix[j];
|
|
prefix_prefix[j] = save;
|
|
save = prefix_modes[i];
|
|
prefix_modes[i] = prefix_modes[j];
|
|
prefix_modes[j] = save;
|
|
save_rank = rank[i];
|
|
rank[i] = rank[j];
|
|
rank[j] = save_rank;
|
|
}
|
|
}
|
|
}
|
|
snprintf(prefix, sizeof(prefix), "(%s)%s", prefix_modes, prefix_prefix);
|
|
}
|
|
|
|
*isupport_prefix = prefix;
|
|
*isupport_statusmsg = prefix_prefix;
|
|
}
|
|
|
|
void extcmodes_check_for_changed_prefixes(void)
|
|
{
|
|
ISupport *isup;
|
|
char *prefix, *statusmsg;
|
|
|
|
make_prefix(&prefix, &statusmsg);
|
|
ISupportSet(NULL, "PREFIX", prefix);
|
|
ISupportSet(NULL, "STATUSMSG", statusmsg);
|
|
|
|
if (*previous_prefix && strcmp(prefix, previous_prefix))
|
|
{
|
|
unreal_log(ULOG_INFO, "mode", "PREFIX_CHANGED", NULL,
|
|
"Prefix changed at runtime: $old_prefix -> $new_prefix",
|
|
log_data_string("old_prefix", previous_prefix),
|
|
log_data_string("new_prefix", prefix));
|
|
/* Broadcast change to all (locally connected) servers */
|
|
sendto_server(NULL, 0, 0, NULL, "PROTOCTL PREFIX=%s", prefix);
|
|
}
|
|
|
|
strlcpy(previous_prefix, prefix, sizeof(previous_prefix));
|
|
}
|
|
|
|
/** Check for changes - if any are detected, we broadcast the change */
|
|
void extcmodes_check_for_changes(void)
|
|
{
|
|
extcmodes_check_for_changed_channel_modes();
|
|
extcmodes_check_for_changed_prefixes();
|
|
}
|
|
|
|
/** Initialize the extended channel modes system */
|
|
void extcmode_init(void)
|
|
{
|
|
memset(&extchmstr, 0, sizeof(extchmstr));
|
|
memset(¶m_to_slot_mapping, 0, sizeof(param_to_slot_mapping));
|
|
*previous_chanmodes = '\0';
|
|
*previous_prefix = '\0';
|
|
}
|
|
|
|
/** Update letter->slot mapping and slot->handler mapping */
|
|
void extcmode_para_addslot(Cmode *cm, int slot)
|
|
{
|
|
if ((slot < 0) || (slot > MAXPARAMMODES))
|
|
abort();
|
|
cm->param_slot = slot;
|
|
ParamTable[slot] = cm;
|
|
param_to_slot_mapping[cm->letter] = slot;
|
|
}
|
|
|
|
/** Update letter->slot mapping and slot->handler mapping */
|
|
void extcmode_para_delslot(Cmode *cm, int slot)
|
|
{
|
|
if ((slot < 0) || (slot > MAXPARAMMODES))
|
|
abort();
|
|
ParamTable[slot] = NULL;
|
|
param_to_slot_mapping[cm->letter] = 0;
|
|
}
|
|
|
|
void channelmode_add_sorted(Cmode *n)
|
|
{
|
|
Cmode *m;
|
|
|
|
if (channelmodes == NULL)
|
|
{
|
|
channelmodes = n;
|
|
return;
|
|
}
|
|
|
|
for (m = channelmodes; m; m = m->next)
|
|
{
|
|
if (m->letter == '\0')
|
|
abort();
|
|
if (sort_character_lowercase_before_uppercase(n->letter, m->letter))
|
|
{
|
|
/* Insert us before */
|
|
if (m->prev)
|
|
m->prev->next = n;
|
|
else
|
|
channelmodes = n; /* new head */
|
|
n->prev = m->prev;
|
|
|
|
n->next = m;
|
|
m->prev = n;
|
|
return;
|
|
}
|
|
if (!m->next)
|
|
{
|
|
/* Append us at end */
|
|
m->next = n;
|
|
n->prev = m;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @defgroup ChannelModeAPI Channel mode API
|
|
* @{
|
|
*/
|
|
|
|
/** Register a new channel mode (Channel mode API).
|
|
* @param module The module requesting this channel mode (usually: modinfo->handle)
|
|
* @param req Details of the channel mode request
|
|
* @param mode Store the mode value (bit) here on success
|
|
* @returns the newly created channel mode, or NULL in case of error.
|
|
*/
|
|
Cmode *CmodeAdd(Module *module, CmodeInfo req, Cmode_t *mode)
|
|
{
|
|
int paraslot = -1;
|
|
int existing = 0;
|
|
Cmode *cm;
|
|
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
{
|
|
if (cm->letter == req.letter)
|
|
{
|
|
if (cm->unloaded)
|
|
{
|
|
cm->unloaded = 0;
|
|
existing = 1;
|
|
break;
|
|
} else {
|
|
if (module)
|
|
module->errorcode = MODERR_EXISTS;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cm)
|
|
{
|
|
long l, found = 0;
|
|
|
|
if (req.type == CMODE_NORMAL)
|
|
{
|
|
for (l = 1; l < LONG_MAX/2; l *= 2)
|
|
{
|
|
found = 0;
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
{
|
|
if (cm->mode == l)
|
|
{
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
break;
|
|
}
|
|
/* If 'found' is still true, then we are out of space */
|
|
if (found)
|
|
{
|
|
unreal_log(ULOG_ERROR, "module", "CHANNEL_MODE_OUT_OF_SPACE", NULL,
|
|
"CmodeAdd: out of space!!!");
|
|
if (module)
|
|
module->errorcode = MODERR_NOSPACE;
|
|
return NULL;
|
|
}
|
|
cm = safe_alloc(sizeof(Cmode));
|
|
cm->letter = req.letter;
|
|
cm->mode = l;
|
|
*mode = cm->mode;
|
|
} else if (req.type == CMODE_MEMBER)
|
|
{
|
|
if (!req.prefix || !req.sjoin_prefix || !req.paracount ||
|
|
!req.unset_with_param || !req.rank)
|
|
{
|
|
unreal_log(ULOG_ERROR, "module", "CMODEADD_API_ERROR", NULL,
|
|
"CmodeAdd(): module is missing required information. "
|
|
"Module: $module_name",
|
|
log_data_string("module_name", module->header->name));
|
|
module->errorcode = MODERR_INVALID;
|
|
return NULL;
|
|
}
|
|
cm = safe_alloc(sizeof(Cmode));
|
|
cm->letter = req.letter;
|
|
} else {
|
|
abort();
|
|
}
|
|
channelmode_add_sorted(cm);
|
|
}
|
|
|
|
if ((req.paracount == 1) && (req.type == CMODE_NORMAL))
|
|
{
|
|
if (existing)
|
|
{
|
|
/* Re-use parameter slot of the module with the same modechar that is unloading */
|
|
paraslot = cm->param_slot;
|
|
}
|
|
else
|
|
{
|
|
/* Allocate a new one */
|
|
for (paraslot = 0; ParamTable[paraslot]; paraslot++)
|
|
{
|
|
if (paraslot == MAXPARAMMODES - 1)
|
|
{
|
|
unreal_log(ULOG_ERROR, "module", "CHANNEL_MODE_OUT_OF_SPACE", NULL,
|
|
"CmodeAdd: out of space!!! Place 2.");
|
|
if (module)
|
|
module->errorcode = MODERR_NOSPACE;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cm->letter = req.letter;
|
|
cm->type = req.type;
|
|
cm->prefix = req.prefix;
|
|
cm->sjoin_prefix = req.sjoin_prefix;
|
|
cm->rank = req.rank;
|
|
cm->paracount = req.paracount;
|
|
cm->is_ok = req.is_ok;
|
|
cm->put_param = req.put_param;
|
|
cm->get_param = req.get_param;
|
|
cm->conv_param = req.conv_param;
|
|
cm->free_param = req.free_param;
|
|
cm->dup_struct = req.dup_struct;
|
|
cm->sjoin_check = req.sjoin_check;
|
|
cm->local = req.local;
|
|
cm->unset_with_param = req.unset_with_param;
|
|
cm->owner = module;
|
|
cm->unloaded = 0;
|
|
|
|
if (cm->type == CMODE_NORMAL)
|
|
{
|
|
*mode = cm->mode;
|
|
if (cm->paracount == 1)
|
|
extcmode_para_addslot(cm, paraslot);
|
|
}
|
|
|
|
if (module)
|
|
{
|
|
ModuleObject *cmodeobj = safe_alloc(sizeof(ModuleObject));
|
|
cmodeobj->object.cmode = cm;
|
|
cmodeobj->type = MOBJ_CMODE;
|
|
AddListItem(cmodeobj, module->objects);
|
|
module->errorcode = MODERR_NOERROR;
|
|
}
|
|
return cm;
|
|
}
|
|
|
|
/** Delete a previously registered channel mode - not called by modules.
|
|
* For modules this is done automatically on unload, no need to call this explicitly.
|
|
*/
|
|
void CmodeDel(Cmode *cmode)
|
|
{
|
|
if (cmode->owner)
|
|
{
|
|
ModuleObject *cmodeobj;
|
|
for (cmodeobj = cmode->owner->objects; cmodeobj; cmodeobj = cmodeobj->next) {
|
|
if (cmodeobj->type == MOBJ_CMODE && cmodeobj->object.cmode == cmode) {
|
|
DelListItem(cmodeobj, cmode->owner->objects);
|
|
safe_free(cmodeobj);
|
|
break;
|
|
}
|
|
}
|
|
cmode->owner = NULL;
|
|
}
|
|
if (loop.rehashing)
|
|
cmode->unloaded = 1;
|
|
else
|
|
unload_extcmode_commit(cmode);
|
|
|
|
}
|
|
|
|
/** @} */
|
|
|
|
/** After a channel mode is deregistered for sure, unload it completely.
|
|
* This is done after a REHASH when no new module has registered the mode.
|
|
* Then we can unload it for good. This also sends MODE -.. out etc.
|
|
*/
|
|
static void unload_extcmode_commit(Cmode *cmode)
|
|
{
|
|
Channel *channel;
|
|
|
|
if (!cmode)
|
|
return;
|
|
|
|
if (cmode->type == CMODE_NORMAL)
|
|
{
|
|
/* Unset channel mode and send MODE to everyone */
|
|
if (cmode->paracount == 0)
|
|
{
|
|
/* Paramless mode, easy */
|
|
for (channel = channels; channel; channel = channel->nextch)
|
|
{
|
|
if (channel->mode.mode & cmode->mode)
|
|
{
|
|
MessageTag *mtags = NULL;
|
|
|
|
new_message(&me, NULL, &mtags);
|
|
sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
|
|
":%s MODE %s -%c",
|
|
me.name, channel->name, cmode->letter);
|
|
sendto_server(NULL, 0, 0, mtags,
|
|
":%s MODE %s -%c 0",
|
|
me.id, channel->name, cmode->letter);
|
|
free_message_tags(mtags);
|
|
|
|
channel->mode.mode &= ~cmode->mode;
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
/* Parameter mode, more complicated */
|
|
for (channel = channels; channel; channel = channel->nextch)
|
|
{
|
|
if (channel->mode.mode & cmode->mode)
|
|
{
|
|
MessageTag *mtags = NULL;
|
|
|
|
new_message(&me, NULL, &mtags);
|
|
if (cmode->unset_with_param)
|
|
{
|
|
const char *param = cmode->get_param(GETPARASTRUCT(channel, cmode->letter));
|
|
sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
|
|
":%s MODE %s -%c %s",
|
|
me.name, channel->name, cmode->letter, param);
|
|
sendto_server(NULL, 0, 0, mtags,
|
|
":%s MODE %s -%c %s 0",
|
|
me.id, channel->name, cmode->letter, param);
|
|
} else {
|
|
sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
|
|
":%s MODE %s -%c",
|
|
me.name, channel->name, cmode->letter);
|
|
sendto_server(NULL, 0, 0, mtags,
|
|
":%s MODE %s -%c 0",
|
|
me.id, channel->name, cmode->letter);
|
|
}
|
|
free_message_tags(mtags);
|
|
|
|
cmode->free_param(GETPARASTRUCT(channel, cmode->letter));
|
|
channel->mode.mode &= ~cmode->mode;
|
|
}
|
|
}
|
|
extcmode_para_delslot(cmode, cmode->param_slot);
|
|
}
|
|
} else
|
|
if (cmode->type == CMODE_MEMBER)
|
|
{
|
|
for (channel = channels; channel; channel = channel->nextch)
|
|
{
|
|
Member *m;
|
|
for (m = channel->members; m; m = m->next)
|
|
{
|
|
if (strchr(m->member_modes, cmode->letter))
|
|
{
|
|
MessageTag *mtags = NULL;
|
|
|
|
new_message(&me, NULL, &mtags);
|
|
sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
|
|
":%s MODE %s -%c %s",
|
|
me.name, channel->name, cmode->letter, m->client->name);
|
|
sendto_server(NULL, 0, 0, mtags,
|
|
":%s MODE %s -%c %s 0",
|
|
me.id, channel->name, cmode->letter, m->client->id);
|
|
free_message_tags(mtags);
|
|
del_member_mode(m->client, channel, cmode->letter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DelListItem(cmode, channelmodes);
|
|
safe_free(cmode);
|
|
}
|
|
|
|
/** Unload all unused channel modes after a REHASH */
|
|
void unload_all_unused_extcmodes(void)
|
|
{
|
|
Cmode *cm, *cm_next;
|
|
|
|
for (cm=channelmodes; cm; cm = cm_next)
|
|
{
|
|
cm_next = cm->next;
|
|
if (cm->letter && cm->unloaded)
|
|
{
|
|
unload_extcmode_commit(cm);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/** @defgroup ChannelModeAPI Channel mode API
|
|
* @{
|
|
*/
|
|
|
|
/** Get parameter for a channel mode.
|
|
* @param channel The channel
|
|
* @param mode The mode character (eg: 'f')
|
|
*/
|
|
const char *cm_getparameter(Channel *channel, char mode)
|
|
{
|
|
return GETPARAMHANDLERBYLETTER(mode)->get_param(GETPARASTRUCT(channel, mode));
|
|
}
|
|
|
|
/** Get parameter for a channel mode - special version for SJOIN.
|
|
* This version doesn't take a channel, but a mode.mode_params.
|
|
* It is only used by SJOIN and should not be used in 3rd party modules.
|
|
* @param p The list, eg oldmode.mode_params
|
|
* @param mode The mode letter
|
|
*/
|
|
const char *cm_getparameter_ex(void **p, char mode)
|
|
{
|
|
return GETPARAMHANDLERBYLETTER(mode)->get_param(GETPARASTRUCTEX(p, mode));
|
|
}
|
|
|
|
/** Set parameter for a channel mode.
|
|
* @param channel The channel
|
|
* @param mode The mode character (eg: 'f')
|
|
* @param str The parameter string
|
|
* @note Module users should not use this function directly, it is only used by MODE and SJOIN.
|
|
*/
|
|
void cm_putparameter(Channel *channel, char mode, const char *str)
|
|
{
|
|
GETPARASTRUCT(channel, mode) = GETPARAMHANDLERBYLETTER(mode)->put_param(GETPARASTRUCT(channel, mode), str);
|
|
}
|
|
|
|
/** Free a channel mode parameter.
|
|
* @param channel The channel
|
|
* @param mode The mode character (eg: 'f')
|
|
*/
|
|
void cm_freeparameter(Channel *channel, char mode)
|
|
{
|
|
GETPARAMHANDLERBYLETTER(mode)->free_param(GETPARASTRUCT(channel, mode));
|
|
GETPARASTRUCT(channel, mode) = NULL;
|
|
}
|
|
|
|
|
|
/** Set parameter for a channel mode - special version for SJOIN.
|
|
* This version doesn't take a channel, but a mode.mode_params.
|
|
* It is only used by SJOIN and should not be used in 3rd party modules.
|
|
* @param p The list, eg oldmode.mode_params
|
|
* @param mode The mode letter
|
|
* @param str The mode parameter string to set
|
|
*/
|
|
void cm_putparameter_ex(void **p, char mode, const char *str)
|
|
{
|
|
GETPARASTRUCTEX(p, mode) = GETPARAMHANDLERBYLETTER(mode)->put_param(GETPARASTRUCTEX(p, mode), str);
|
|
}
|
|
|
|
/** Default handler for - require channel operator or higher (+o/+a/+q)
|
|
* @param client The client issueing the MODE
|
|
* @param channel The channel
|
|
* @param mode The mode letter (eg: 'f')
|
|
* @param para The parameter, if any (can be NULL)
|
|
* @param checkt The check type, one of .....
|
|
* @param what MODE_ADD / MODE_DEL (???)
|
|
* @returns EX_ALLOW or EX_DENY
|
|
*/
|
|
int extcmode_default_requirechop(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
|
|
{
|
|
if (IsUser(client) && check_channel_access(client, channel, "oaq"))
|
|
return EX_ALLOW;
|
|
if (checkt == EXCHK_ACCESS_ERR) /* can only be due to being halfop */
|
|
sendnumeric(client, ERR_NOTFORHALFOPS, mode);
|
|
return EX_DENY;
|
|
}
|
|
|
|
/** Default handler for - require halfop or higher (+h/+o/+a/+q)
|
|
* @param client The client issueing the MODE
|
|
* @param channel The channel
|
|
* @param mode The mode letter (eg: 'f')
|
|
* @param para The parameter, if any (can be NULL)
|
|
* @param checkt The check type, one of .....
|
|
* @param what MODE_ADD / MODE_DEL (???)
|
|
* @returns EX_ALLOW or EX_DENY
|
|
*/
|
|
int extcmode_default_requirehalfop(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
|
|
{
|
|
if (IsUser(client) && (check_channel_access(client, channel, "oaq") || check_channel_access(client, channel, "h")))
|
|
return EX_ALLOW;
|
|
return EX_DENY;
|
|
}
|
|
|
|
/** Duplicate all channel mode parameters - only used by SJOIN.
|
|
* @param xi Input list
|
|
* @param xi Output list
|
|
*/
|
|
void extcmode_duplicate_paramlist(void **xi, void **xo)
|
|
{
|
|
int i;
|
|
Cmode *handler;
|
|
void *inx;
|
|
|
|
for (i = 0; i < MAXPARAMMODES; i++)
|
|
{
|
|
handler = CMP_GETHANDLERBYSLOT(i);
|
|
if (!handler)
|
|
continue; /* nothing there.. */
|
|
inx = xi[handler->param_slot]; /* paramter data of input is here */
|
|
if (!inx)
|
|
continue; /* not set */
|
|
xo[handler->param_slot] = handler->dup_struct(inx); /* call dup_struct with that input and set the output param to that */
|
|
}
|
|
}
|
|
|
|
/** Free all channel mode parameters - only used by SJOIN.
|
|
* @param ar The list
|
|
*/
|
|
void extcmode_free_paramlist(void **ar)
|
|
{
|
|
int i;
|
|
Cmode *handler;
|
|
|
|
for (i = 0; i < MAXPARAMMODES; i++)
|
|
{
|
|
handler = GETPARAMHANDLERBYSLOT(i);
|
|
if (!handler)
|
|
continue; /* nothing here... */
|
|
handler->free_param(ar[handler->param_slot]);
|
|
ar[handler->param_slot] = NULL;
|
|
}
|
|
}
|
|
|
|
/** @} */
|
|
|
|
/** Internal function: returns 1 if the specified module has 1 or more extended channel modes registered */
|
|
int module_has_extcmode_param_mode(Module *mod)
|
|
{
|
|
Cmode *cm;
|
|
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->letter) && (cm->owner == mod) && (cm->paracount))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Channel member privileges - getting, setting, checking vhoaq status, etc.
|
|
* These functions get or set the access rights of channel members, such as +vhoaq.
|
|
* They can also convert between modes (vhoaq), prefixes and sjoin prefixes.
|
|
* @defgroup ChannelMember Channel members access privileges
|
|
* @{
|
|
*/
|
|
|
|
/** Retrieve channel access for a user on a channel, returns modes eg "qao".
|
|
* @param client The client
|
|
* @param channel The channel
|
|
* @returns The modes, sorted by high ranking to lower ranking, eg "qao".
|
|
* An empty string ("") is returned when not in the channel or no modes.
|
|
*/
|
|
const char *get_channel_access(Client *client, Channel *channel)
|
|
{
|
|
Membership *mb;
|
|
|
|
mb = find_membership_link(client->user->channel, channel);
|
|
if (!mb)
|
|
return "";
|
|
return mb->member_modes;
|
|
}
|
|
|
|
/** Check channel access for user.
|
|
* @param client The client to check
|
|
* @param channel The channel to check
|
|
* @param modes Which mode(s) to check for
|
|
* @returns If the client in channel has any of the modes set, 1 is returned.
|
|
* Otherwise 0 is returned, which is also the case if the user is
|
|
* not a user or is not in the channel at all.
|
|
*/
|
|
int check_channel_access(Client *client, Channel *channel, const char *modes)
|
|
{
|
|
Membership *mb;
|
|
const char *p;
|
|
|
|
if (!IsUser(client))
|
|
return 0; /* eg server */
|
|
|
|
mb = find_membership_link(client->user->channel, channel);
|
|
if (!mb)
|
|
return 0; /* not a member */
|
|
|
|
for (p = mb->member_modes; *p; p++)
|
|
if (strchr(modes, *p))
|
|
return 1; /* match new style */
|
|
|
|
return 0; /* nomatch */
|
|
}
|
|
|
|
/** Check channel access for user.
|
|
* @param client The client to check
|
|
* @param channel The channel to check
|
|
* @param modes Which mode(s) to check for
|
|
* @returns If the client in channel has any of the modes set, 1 is returned.
|
|
* Otherwise 0 is returned, which is also the case if the user is
|
|
* not a user or is not in the channel at all.
|
|
*/
|
|
int check_channel_access_membership(Membership *mb, const char *modes)
|
|
{
|
|
const char *p;
|
|
|
|
if (!mb)
|
|
return 0;
|
|
|
|
for (p = mb->member_modes; *p; p++)
|
|
if (strchr(modes, *p))
|
|
return 1; /* match new style */
|
|
|
|
return 0; /* nomatch */
|
|
}
|
|
|
|
/** Check channel access for user.
|
|
* @param client The client to check
|
|
* @param channel The channel to check
|
|
* @param modes Which mode(s) to check for
|
|
* @returns If the client in channel has any of the modes set, 1 is returned.
|
|
* Otherwise 0 is returned, which is also the case if the user is
|
|
* not a user or is not in the channel at all.
|
|
*/
|
|
int check_channel_access_member(Member *mb, const char *modes)
|
|
{
|
|
const char *p;
|
|
|
|
if (!mb)
|
|
return 0;
|
|
|
|
for (p = mb->member_modes; *p; p++)
|
|
if (strchr(modes, *p))
|
|
return 1; /* match new style */
|
|
|
|
return 0; /* nomatch */
|
|
}
|
|
|
|
/** Check channel access for user.
|
|
* @param current Flags currently set on the client (eg mb->member_modes)
|
|
* @param modes Which mode(s) to check for
|
|
* @returns If the client in channel has any of the modes set, 1 is returned.
|
|
* Otherwise 0 is returned.
|
|
*/
|
|
int check_channel_access_string(const char *current_modes, const char *modes)
|
|
{
|
|
const char *p;
|
|
|
|
for (p = current_modes; *p; p++)
|
|
if (strchr(modes, *p))
|
|
return 1;
|
|
|
|
return 0; /* nomatch */
|
|
}
|
|
|
|
/** Check channel access for user.
|
|
* @param current Flags currently set on the client (eg mb->member_modes)
|
|
* @param letter Which mode letter to check for
|
|
* @returns If the client in channel has any of the modes set, 1 is returned.
|
|
* Otherwise 0 is returned.
|
|
*/
|
|
int check_channel_access_letter(const char *current_modes, const char letter)
|
|
{
|
|
return strchr(current_modes, letter) ? 1 : 0;
|
|
}
|
|
|
|
Cmode *find_channel_mode_handler(char letter)
|
|
{
|
|
Cmode *cm;
|
|
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if (cm->letter == letter)
|
|
return cm;
|
|
return NULL;
|
|
}
|
|
|
|
/** Is 'letter' a valid mode used for access/levels/ranks? (vhoaq and such)
|
|
* @param letter The channel mode letter to check, eg 'v'
|
|
* @returns 1 if valid, 0 if the channel mode does not exist or is not a level mode.
|
|
*/
|
|
int valid_channel_access_mode_letter(char letter)
|
|
{
|
|
Cmode *cm;
|
|
|
|
if ((cm = find_channel_mode_handler(letter)) && (cm->type == CMODE_MEMBER))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void addlettertomstring(char *str, char letter)
|
|
{
|
|
Cmode *cm;
|
|
int n;
|
|
int my_rank;
|
|
char *p;
|
|
|
|
if (!(cm = find_channel_mode_handler(letter)) || (cm->type != CMODE_MEMBER))
|
|
return; // should we BUG on this? if something makes it this far, it can never be good right?
|
|
|
|
my_rank = cm->rank;
|
|
|
|
n = strlen(str);
|
|
if (n >= MEMBERMODESLEN-1)
|
|
return; // panic!
|
|
|
|
for (p = str; *p; p++)
|
|
{
|
|
cm = find_channel_mode_handler(*p);
|
|
if (!cm)
|
|
continue; /* wtf */
|
|
if (cm->rank < my_rank)
|
|
{
|
|
/* We need to insert us here */
|
|
n = strlen(p);
|
|
memmove(p+1, p, n+1); // +1 for NUL byte
|
|
*p = letter;
|
|
return;
|
|
}
|
|
}
|
|
/* We should be at the end */
|
|
str[n] = letter;
|
|
str[n+1] = '\0';
|
|
}
|
|
|
|
void add_member_mode_fast(Member *mb, Membership *mbs, char letter)
|
|
{
|
|
addlettertomstring(mb->member_modes, letter);
|
|
addlettertomstring(mbs->member_modes, letter);
|
|
}
|
|
|
|
void del_member_mode_fast(Member *mb, Membership *mbs, char letter)
|
|
{
|
|
delletterfromstring(mb->member_modes, letter);
|
|
delletterfromstring(mbs->member_modes, letter);
|
|
}
|
|
|
|
int find_mbs(Client *client, Channel *channel, Member **mb, Membership **mbs)
|
|
{
|
|
*mbs = NULL;
|
|
|
|
if (!(*mb = find_member_link(channel->members, client)))
|
|
return 0;
|
|
|
|
if (!(*mbs = find_membership_link(client->user->channel, channel)))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void add_member_mode(Client *client, Channel *channel, char letter)
|
|
{
|
|
Member *mb;
|
|
Membership *mbs;
|
|
|
|
if (!find_mbs(client, channel, &mb, &mbs))
|
|
return;
|
|
|
|
add_member_mode_fast(mb, mbs, letter);
|
|
}
|
|
|
|
void del_member_mode(Client *client, Channel *channel, char letter)
|
|
{
|
|
Member *mb;
|
|
Membership *mbs;
|
|
|
|
if (!find_mbs(client, channel, &mb, &mbs))
|
|
return;
|
|
|
|
del_member_mode_fast(mb, mbs, letter);
|
|
}
|
|
|
|
char sjoin_prefix_to_mode(char s)
|
|
{
|
|
Cmode *cm;
|
|
|
|
/* Filter this out early to avoid spurious results */
|
|
if (s == '\0')
|
|
return '\0';
|
|
|
|
/* First the hardcoded list modes: */
|
|
if (s == '&')
|
|
return 'b';
|
|
if (s == '"')
|
|
return 'e';
|
|
if (s == '\'')
|
|
return 'I';
|
|
|
|
/* Now the dynamic ones (+vhoaq): */
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->sjoin_prefix == s) && (cm->type == CMODE_MEMBER))
|
|
return cm->letter;
|
|
|
|
/* Not found */
|
|
return '\0';
|
|
}
|
|
|
|
char mode_to_sjoin_prefix(char s)
|
|
{
|
|
Cmode *cm;
|
|
|
|
/* Filter this out early to avoid spurious results */
|
|
if (s == '\0')
|
|
return '\0';
|
|
|
|
/* First the hardcoded list modes: */
|
|
if (s == 'b')
|
|
return '&';
|
|
if (s == 'e')
|
|
return '"';
|
|
if (s == 'I')
|
|
return '\'';
|
|
|
|
/* Now the dynamic ones (+vhoaq): */
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->letter == s) && (cm->type == CMODE_MEMBER))
|
|
return cm->sjoin_prefix;
|
|
|
|
/* Not found */
|
|
return '\0';
|
|
}
|
|
|
|
const char *modes_to_sjoin_prefix(const char *modes)
|
|
{
|
|
static char buf[MEMBERMODESLEN];
|
|
const char *m;
|
|
char f;
|
|
|
|
*buf = '\0';
|
|
for (m = modes; *m; m++)
|
|
{
|
|
f = mode_to_sjoin_prefix(*m);
|
|
if (f)
|
|
strlcat_letter(buf, f, sizeof(buf));
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
char mode_to_prefix(char s)
|
|
{
|
|
Cmode *cm;
|
|
|
|
/* Filter this out early to avoid spurious results */
|
|
if (s == '\0')
|
|
return '\0';
|
|
|
|
/* Now the dynamic ones (+vhoaq): */
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->letter == s) && (cm->type == CMODE_MEMBER))
|
|
return cm->prefix;
|
|
|
|
/* Not found */
|
|
return '\0';
|
|
}
|
|
|
|
const char *modes_to_prefix(const char *modes)
|
|
{
|
|
static char buf[MEMBERMODESLEN];
|
|
const char *m;
|
|
char f;
|
|
|
|
*buf = '\0';
|
|
for (m = modes; *m; m++)
|
|
{
|
|
f = mode_to_prefix(*m);
|
|
if (f)
|
|
strlcat_letter(buf, f, sizeof(buf));
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
char prefix_to_mode(char s)
|
|
{
|
|
Cmode *cm;
|
|
|
|
/* Filter this out early to avoid spurious results */
|
|
if (s == '\0')
|
|
return '\0';
|
|
|
|
/* Now the dynamic ones (+vhoaq): */
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->prefix == s) && (cm->type == CMODE_MEMBER))
|
|
return cm->letter;
|
|
|
|
/* Not found */
|
|
return '\0';
|
|
}
|
|
|
|
char rank_to_mode(int rank)
|
|
{
|
|
Cmode *cm;
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->type == CMODE_MEMBER) && (cm->rank == rank))
|
|
return cm->letter;
|
|
return '\0';
|
|
}
|
|
|
|
int mode_to_rank(char mode)
|
|
{
|
|
Cmode *cm;
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->type == CMODE_MEMBER) && (cm->letter == mode))
|
|
return cm->rank;
|
|
return '\0';
|
|
}
|
|
|
|
int prefix_to_rank(char prefix)
|
|
{
|
|
Cmode *cm;
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->type == CMODE_MEMBER) && (cm->prefix == prefix))
|
|
return cm->rank;
|
|
return '\0';
|
|
}
|
|
|
|
char rank_to_prefix(int rank)
|
|
{
|
|
Cmode *cm;
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->type == CMODE_MEMBER) && (cm->rank == rank))
|
|
return cm->prefix;
|
|
return '\0';
|
|
}
|
|
|
|
char lowest_ranking_prefix(const char *prefix)
|
|
{
|
|
const char *p;
|
|
int winning_rank = INT_MAX;
|
|
|
|
for (p = prefix; *p; p++)
|
|
{
|
|
int rank = prefix_to_rank(*p);
|
|
if (rank < winning_rank)
|
|
winning_rank = rank;
|
|
}
|
|
if (winning_rank == INT_MAX)
|
|
return '\0'; /* No result */
|
|
return rank_to_prefix(winning_rank);
|
|
}
|
|
|
|
char lowest_ranking_mode(const char *mode)
|
|
{
|
|
const char *p;
|
|
int winning_rank = INT_MAX;
|
|
|
|
for (p = mode; *p; p++)
|
|
{
|
|
int rank = mode_to_rank(*p);
|
|
if (rank < winning_rank)
|
|
winning_rank = rank;
|
|
}
|
|
if (winning_rank == INT_MAX)
|
|
return '\0'; /* No result */
|
|
return rank_to_mode(winning_rank);
|
|
}
|
|
|
|
/** Generate all member modes that are equal or greater than 'modes'.
|
|
* Eg calling this with "o" would generate "oaq" with the default loaded modules.
|
|
* This is used in sendto_channel() to make multiple check_channel_access_member()
|
|
* calls more easy / faster.
|
|
*/
|
|
void channel_member_modes_generate_equal_or_greater(const char *modes, char *buf, size_t buflen)
|
|
{
|
|
const char *p;
|
|
int rank;
|
|
Cmode *cm;
|
|
|
|
*buf = '\0';
|
|
|
|
/* First we must grab the lowest ranking mode, eg 'vhoaq' results in rank for 'v' */
|
|
rank = lowest_ranking_mode(modes);
|
|
if (!rank)
|
|
return; /* zero matches */
|
|
|
|
for (cm=channelmodes; cm; cm = cm->next)
|
|
if ((cm->type == CMODE_MEMBER) && (cm->rank >= rank))
|
|
strlcat_letter(buf, cm->letter, buflen);
|
|
}
|
|
|
|
/** @} */
|