pissircd/src/api-clicap.c

408 lines
10 KiB
C

/************************************************************************
* UnrealIRCd - Unreal Internet Relay Chat Daemon - src/api-clicap.c
* (c) 2015- Bram Matthys 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.
*/
#include "unrealircd.h"
#define ADVERTISEONLYCAPS 16
/* Advertise only caps are not counted anywhere, this only provides space in rehash temporary storage arrays.
* If exceeded, the caps just won't be stored and will be re-added safely. --k4be
*/
#define MAXCLICAPS ((int)(sizeof(long)*8 - 1 + ADVERTISEONLYCAPS)) /* how many cap bits will fit in `long`? */
static char *old_caps[MAXCLICAPS]; /**< List of old CAP names - used for /rehash */
int old_caps_proto[MAXCLICAPS]; /**< List of old CAP protocol values - used for /rehash */
MODVAR ClientCapability *clicaps = NULL; /* List of client capabilities */
MODVAR long clicaps_affecting_mtag = 0; /**< Bitmask of client capabilities that affect message tags (server-time, message-tags, label, etc.) */
void clicap_init(void)
{
memset(&old_caps, 0, sizeof(old_caps));
}
/**
* Returns an clicap handle based on the given token name.
*
* @param token The clicap token to search for.
* @return Returns the handle to the clicap token if it was found,
* otherwise NULL is returned.
*/
ClientCapability *ClientCapabilityFindReal(const char *token)
{
ClientCapability *clicap;
for (clicap = clicaps; clicap; clicap = clicap->next)
{
if (!strcasecmp(token, clicap->name))
return clicap;
}
return NULL;
}
/**
* Returns an clicap handle based on the given token name.
*
* @param token The clicap token to search for.
* @return Returns the handle to the clicap token if it was found,
* otherwise NULL is returned.
*/
ClientCapability *ClientCapabilityFind(const char *token, Client *client)
{
ClientCapability *clicap;
for (clicap = clicaps; clicap; clicap = clicap->next)
{
if (!strcasecmp(token, clicap->name))
{
if (clicap->visible && !clicap->visible(client))
return NULL; /* hidden */
return clicap;
}
}
return NULL;
}
/** Find the bit that will be set if 'token' is enabled */
long ClientCapabilityBit(const char *token)
{
ClientCapability *clicap = ClientCapabilityFindReal(token);
#ifdef DEBUGMODE
if (!clicap)
{
unreal_log(ULOG_WARNING, "main", "BUG_CLIENTCAPABILITYBIT_UNKNOWN_TOKEN", NULL,
"[BUG] ClientCapabilityBit() check for unknown token: $token",
log_data_string("token", token));
}
#endif
return clicap ? clicap->cap : 0L;
}
void SetCapability(Client *client, const char *token)
{
client->local->caps |= ClientCapabilityBit(token);
}
void ClearCapability(Client *client, const char *token)
{
client->local->caps &= ~(ClientCapabilityBit(token));
}
long clicap_allocate_cap(void)
{
long v;
ClientCapability *clicap;
/* The first bit (v=1) is used by the "invert" marker */
for (v=2; v; v <<= 1)
{
unsigned char found = 0;
for (clicap = clicaps; clicap; clicap = clicap->next)
{
if (clicap->cap == v)
{
found = 1;
break;
}
}
if (!found)
return v; /* free bit found */
}
return 0;
}
/** This updates 'clicaps_affecting_mtag' which is used in LineCache in src/send.c */
static void clicap_update_affecting(void)
{
ClientCapability *e;
long v = 0;
for (e = clicaps; e; e = e->next)
if (e->mtag_handler || (e->flags & CLICAP_FLAGS_AFFECTS_MTAGS))
v |= e->cap;
clicaps_affecting_mtag = v;
}
/**
* Adds a new clicap token.
*
* @param module The module which owns this token.
* @param clicap_request The details of the requested token, handlers, etc.
* @param cap The assigned capability bit.
* @return Returns the handle to the new token if successful, otherwise NULL.
* The module's error code contains specific information about the
* error.
*/
ClientCapability *ClientCapabilityAdd(Module *module, ClientCapabilityInfo *clicap_request, long *cap)
{
ClientCapability *clicap;
int exists = 0;
if (cap)
*cap = 0; /* Initialize early */
clicap = ClientCapabilityFindReal(clicap_request->name);
if (clicap)
{
exists = 1;
if (clicap->unloaded)
{
clicap->unloaded = 0;
} else {
if (module)
module->errorcode = MODERR_EXISTS;
return NULL;
}
} else {
long v = 0;
/* Allocate a bit, but only if the module needs it.
* (some clicaps are advertise-only and never gets set,
* hence they don't need a bit allocated to them)
*/
if (cap != NULL)
{
v = clicap_allocate_cap();
if (v == 0)
{
unreal_log(ULOG_ERROR, "module", "CLIENTCAPABILITY_OUT_OF_SPACE", NULL,
"ClientCapabilityAdd: out of space!!!");
if (module)
module->errorcode = MODERR_NOSPACE;
return NULL;
}
}
/* New client capability */
clicap = safe_alloc(sizeof(ClientCapability));
safe_strdup(clicap->name, clicap_request->name);
clicap->cap = v;
}
/* Add or update the following fields: */
clicap->owner = module;
clicap->flags = clicap_request->flags;
clicap->visible = clicap_request->visible;
clicap->parameter = clicap_request->parameter;
if (!exists)
AddListItem(clicap, clicaps);
if (clicap->cap && !cap)
abort(); /* module API call error */
if (cap)
*cap = clicap->cap;
if (module)
{
ModuleObject *clicapobj = safe_alloc(sizeof(ModuleObject));
clicapobj->object.clicap = clicap;
clicapobj->type = MOBJ_CLICAP;
AddListItem(clicapobj, module->objects);
module->errorcode = MODERR_NOERROR;
}
clicap_update_affecting();
return clicap;
}
void unload_clicap_commit(ClientCapability *clicap)
{
/* This is an unusual operation, I think we should log it. */
unreal_log(ULOG_INFO, "module", "UNLOAD_CLICAP", NULL,
"Unloading client capability '$token'",
log_data_string("token", clicap->name));
/* NOTE: Stripping the CAP from local clients is done
* in clicap_check_for_changes(), so not here.
*/
/* A message tag handler may depend on us, remove it */
/* NOTE: This assumes there is a 0:1 or 1:1 relationship between
* the two, but in theory there could be multiple message tags
* introduced by 1 capability. Ah well, we'll cross that
* bridge when we come to it ;)
*/
if (clicap->mtag_handler)
clicap->mtag_handler->clicap_handler = NULL;
/* Destroy the capability */
DelListItem(clicap, clicaps);
safe_free(clicap->name);
safe_free(clicap);
clicap_update_affecting();
}
/**
* Removes the specified clicap token.
*
* @param clicap The token to remove.
*/
void ClientCapabilityDel(ClientCapability *clicap)
{
if (clicap->owner)
{
ModuleObject *mobj;
for (mobj = clicap->owner->objects; mobj; mobj = mobj->next) {
if (mobj->type == MOBJ_CLICAP && mobj->object.clicap == clicap) {
DelListItem(mobj, clicap->owner->objects);
safe_free(mobj);
break;
}
}
clicap->owner = NULL;
}
if (loop.rehashing)
clicap->unloaded = 1;
else
unload_clicap_commit(clicap);
}
void unload_all_unused_caps(void)
{
ClientCapability *clicap, *clicap_next;
for (clicap = clicaps; clicap; clicap = clicap_next)
{
clicap_next = clicap->next;
if (clicap->unloaded)
unload_clicap_commit(clicap);
}
}
/** Called before REHASH. This saves the list of cap names and protocol values */
void clicap_pre_rehash(void)
{
ClientCapability *clicap;
int i = 0;
for (i=0; i < MAXCLICAPS; i++)
{
safe_free(old_caps[i]);
old_caps_proto[i] = 0;
}
for (i=0, clicap = clicaps; clicap; clicap = clicap->next)
{
if (i == MAXCLICAPS)
{
unreal_log(ULOG_ERROR, "module", "BUG_TOO_MANY_CLIENTCAPABILITIES", NULL,
"[BUG] clicap_pre_rehash: More than $count caps loaded - this should never happen",
log_data_integer("count", MAXCLICAPS));
break;
}
safe_strdup(old_caps[i], clicap->name);
old_caps_proto[i] = clicap->cap;
i++;
}
}
/** Clear 'proto' protocol for all users */
void clear_cap_for_users(long cap)
{
Client *client;
if (cap == 0)
return;
list_for_each_entry(client, &lclient_list, lclient_node)
{
client->local->caps &= ~cap;
}
list_for_each_entry(client, &unknown_list, lclient_node)
{
client->local->caps &= ~cap;
}
}
/** Called after REHASH. This will deal with:
* 1. Clearing flags for caps that are deleted
* 2. Sending any CAP DEL
* 3. Sending any CAP NEW
*/
void clicap_check_for_changes(void)
{
ClientCapability *clicap;
char *name;
int i;
int found;
if (!loop.rehashing)
return; /* First boot */
/* Let's deal with CAP DEL first:
* Go through the old caps and see what's missing now.
*/
for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
{
name = old_caps[i];
found = 0;
for (clicap = clicaps; clicap; clicap = clicap->next)
{
if (!strcmp(clicap->name, name))
{
found = 1;
break;
}
}
if (!found)
{
/* Broadcast CAP DEL to local users */
send_cap_notify(0, name);
clear_cap_for_users(old_caps_proto[i]);
}
}
/* Now deal with CAP ADD:
* Go through the new caps and see if it was missing from old caps.
*/
for (clicap = clicaps; clicap; clicap = clicap->next)
{
name = clicap->name;
found = 0;
for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
{
if (!strcmp(old_caps[i], name))
{
found = 1;
break;
}
}
if (!found)
{
/* Broadcast CAP NEW to local users */
send_cap_notify(1, name);
}
}
/* Now free the old caps. */
for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
safe_free(old_caps[i]);
}