unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

api-clicap.c (9670B)

      1 /************************************************************************
      2  *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/api-clicap.c
      3  *   (c) 2015- Bram Matthys and The UnrealIRCd team
      4  *
      5  *   See file AUTHORS in IRC package for additional names of
      6  *   the programmers. 
      7  *
      8  *   This program is free software; you can redistribute it and/or modify
      9  *   it under the terms of the GNU General Public License as published by
     10  *   the Free Software Foundation; either version 1, or (at your option)
     11  *   any later version.
     12  *
     13  *   This program is distributed in the hope that it will be useful,
     14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  *   GNU General Public License for more details.
     17  *
     18  *   You should have received a copy of the GNU General Public License
     19  *   along with this program; if not, write to the Free Software
     20  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     21  */
     22 
     23 #include "unrealircd.h"
     24 
     25 #define ADVERTISEONLYCAPS 16
     26 /* Advertise only caps are not counted anywhere, this only provides space in rehash temporary storage arrays.
     27  * If exceeded, the caps just won't be stored and will be re-added safely. --k4be
     28  */
     29 
     30 #define MAXCLICAPS ((int)(sizeof(long)*8 - 1 + ADVERTISEONLYCAPS)) /* how many cap bits will fit in `long`? */
     31 static char *old_caps[MAXCLICAPS]; /**< List of old CAP names - used for /rehash */
     32 int old_caps_proto[MAXCLICAPS]; /**< List of old CAP protocol values - used for /rehash */
     33 
     34 MODVAR ClientCapability *clicaps = NULL; /* List of client capabilities */
     35 
     36 void clicap_init(void)
     37 {
     38 	memset(&old_caps, 0, sizeof(old_caps));
     39 }
     40 
     41 /**
     42  * Returns an clicap handle based on the given token name.
     43  *
     44  * @param token The clicap token to search for.
     45  * @return Returns the handle to the clicap token if it was found,
     46  *         otherwise NULL is returned.
     47  */
     48 ClientCapability *ClientCapabilityFindReal(const char *token)
     49 {
     50 	ClientCapability *clicap;
     51 
     52 	for (clicap = clicaps; clicap; clicap = clicap->next)
     53 	{
     54 		if (!strcasecmp(token, clicap->name))
     55 			return clicap;
     56 	}
     57 
     58 	return NULL;
     59 }
     60 
     61 /**
     62  * Returns an clicap handle based on the given token name.
     63  *
     64  * @param token The clicap token to search for.
     65  * @return Returns the handle to the clicap token if it was found,
     66  *         otherwise NULL is returned.
     67  */
     68 ClientCapability *ClientCapabilityFind(const char *token, Client *client)
     69 {
     70 	ClientCapability *clicap;
     71 
     72 	for (clicap = clicaps; clicap; clicap = clicap->next)
     73 	{
     74 		if (!strcasecmp(token, clicap->name))
     75 		{
     76 			if (clicap->visible && !clicap->visible(client))
     77 				return NULL; /* hidden */
     78 			return clicap;
     79 		}
     80 	}
     81 	return NULL;
     82 }
     83 
     84 /** Find the bit that will be set if 'token' is enabled */
     85 long ClientCapabilityBit(const char *token)
     86 {
     87 	ClientCapability *clicap = ClientCapabilityFindReal(token);
     88 
     89 #ifdef DEBUGMODE
     90 	if (!clicap)
     91 	{
     92 		unreal_log(ULOG_WARNING, "main", "BUG_CLIENTCAPABILITYBIT_UNKNOWN_TOKEN", NULL,
     93 		           "[BUG] ClientCapabilityBit() check for unknown token: $token",
     94 		           log_data_string("token", token));
     95 	}
     96 #endif
     97 
     98 	return clicap ? clicap->cap : 0L;
     99 }
    100 
    101 void SetCapability(Client *client, const char *token)
    102 {
    103 	client->local->caps |= ClientCapabilityBit(token);
    104 }
    105 
    106 void ClearCapability(Client *client, const char *token)
    107 {
    108 	client->local->caps &= ~(ClientCapabilityBit(token));
    109 }
    110 
    111 long clicap_allocate_cap(void)
    112 {
    113 	long v;
    114 	ClientCapability *clicap;
    115 
    116 	/* The first bit (v=1) is used by the "invert" marker */
    117 	for (v=2; v; v <<= 1)
    118 	{
    119 		unsigned char found = 0;
    120 		for (clicap = clicaps; clicap; clicap = clicap->next)
    121 		{
    122 			if (clicap->cap == v)
    123 			{
    124 				found = 1;
    125 				break;
    126 			}
    127 		}
    128 		if (!found)
    129 			return v; /* free bit found */
    130 	}
    131 
    132 	return 0;
    133 }
    134 
    135 /**
    136  * Adds a new clicap token.
    137  *
    138  * @param module The module which owns this token.
    139  * @param clicap_request The details of the requested token, handlers, etc.
    140  * @param cap The assigned capability bit.
    141  * @return Returns the handle to the new token if successful, otherwise NULL.
    142  *         The module's error code contains specific information about the
    143  *         error.
    144  */
    145 ClientCapability *ClientCapabilityAdd(Module *module, ClientCapabilityInfo *clicap_request, long *cap)
    146 {
    147 	ClientCapability *clicap;
    148 	int exists = 0;
    149 
    150 	if (cap)
    151 		*cap = 0; /* Initialize early */
    152 
    153 	clicap = ClientCapabilityFindReal(clicap_request->name);
    154 	if (clicap)
    155 	{
    156 		exists = 1;
    157 		if (clicap->unloaded)
    158 		{
    159 			clicap->unloaded = 0;
    160 		} else {
    161 			if (module)
    162 				module->errorcode = MODERR_EXISTS;
    163 			return NULL;
    164 		}
    165 	} else {
    166 		long v = 0;
    167 
    168 		/* Allocate a bit, but only if the module needs it.
    169 		 * (some clicaps are advertise-only and never gets set,
    170 		 *  hence they don't need a bit allocated to them)
    171 		 */
    172 		if (cap != NULL)
    173 		{
    174 			v = clicap_allocate_cap();
    175 			if (v == 0)
    176 			{
    177 				unreal_log(ULOG_ERROR, "module", "CLIENTCAPABILITY_OUT_OF_SPACE", NULL,
    178 				           "ClientCapabilityAdd: out of space!!!");
    179 				if (module)
    180 					module->errorcode = MODERR_NOSPACE;
    181 				return NULL;
    182 			}
    183 		}
    184 		/* New client capability */
    185 		clicap = safe_alloc(sizeof(ClientCapability));
    186 		safe_strdup(clicap->name, clicap_request->name);
    187 		clicap->cap = v;
    188 	}
    189 	/* Add or update the following fields: */
    190 	clicap->owner = module;
    191 	clicap->flags = clicap_request->flags;
    192 	clicap->visible = clicap_request->visible;
    193 	clicap->parameter = clicap_request->parameter;
    194 
    195 	if (!exists)
    196 		AddListItem(clicap, clicaps);
    197 
    198 	if (clicap->cap && !cap)
    199 		abort(); /* module API call error */
    200 
    201 	if (cap)
    202 		*cap = clicap->cap;
    203 
    204 	if (module)
    205 	{
    206 		ModuleObject *clicapobj = safe_alloc(sizeof(ModuleObject));
    207 		clicapobj->object.clicap = clicap;
    208 		clicapobj->type = MOBJ_CLICAP;
    209 		AddListItem(clicapobj, module->objects);
    210 		module->errorcode = MODERR_NOERROR;
    211 	}
    212 
    213 	return clicap;
    214 }
    215 
    216 void unload_clicap_commit(ClientCapability *clicap)
    217 {
    218 	/* This is an unusual operation, I think we should log it. */
    219 	unreal_log(ULOG_INFO, "module", "UNLOAD_CLICAP", NULL,
    220 	           "Unloading client capability '$token'",
    221 	           log_data_string("token", clicap->name));
    222 
    223 	/* NOTE: Stripping the CAP from local clients is done
    224 	 * in clicap_check_for_changes(), so not here.
    225 	 */
    226 
    227 	/* A message tag handler may depend on us, remove it */
    228 	/* NOTE: This assumes there is a 0:1 or 1:1 relationship between
    229 	 *       the two, but in theory there could be multiple message tags
    230 	 *       introduced by 1 capability. Ah well, we'll cross that
    231 	 *       bridge when we come to it ;)
    232 	 */
    233 	if (clicap->mtag_handler)
    234 		clicap->mtag_handler->clicap_handler = NULL;
    235 
    236 	/* Destroy the capability */
    237 	DelListItem(clicap, clicaps);
    238 	safe_free(clicap->name);
    239 	safe_free(clicap);
    240 }
    241 /**
    242  * Removes the specified clicap token.
    243  *
    244  * @param clicap The token to remove.
    245  */
    246 void ClientCapabilityDel(ClientCapability *clicap)
    247 {
    248 	if (clicap->owner)
    249 	{
    250 		ModuleObject *mobj;
    251 		for (mobj = clicap->owner->objects; mobj; mobj = mobj->next) {
    252 			if (mobj->type == MOBJ_CLICAP && mobj->object.clicap == clicap) {
    253 				DelListItem(mobj, clicap->owner->objects);
    254 				safe_free(mobj);
    255 				break;
    256 			}
    257 		}
    258 		clicap->owner = NULL;
    259 	}
    260 
    261 	if (loop.rehashing)
    262 		clicap->unloaded = 1;
    263 	else
    264 		unload_clicap_commit(clicap);
    265 }
    266 
    267 void unload_all_unused_caps(void)
    268 {
    269 	ClientCapability *clicap, *clicap_next;
    270 
    271 	for (clicap = clicaps; clicap; clicap = clicap_next)
    272 	{
    273 		clicap_next = clicap->next;
    274 		if (clicap->unloaded)
    275 			unload_clicap_commit(clicap);
    276 	}
    277 }
    278 
    279 /** Called before REHASH. This saves the list of cap names and protocol values */
    280 void clicap_pre_rehash(void)
    281 {
    282 	ClientCapability *clicap;
    283 	int i = 0;
    284 
    285 	for (i=0; i < MAXCLICAPS; i++)
    286 	{
    287 		safe_free(old_caps[i]);
    288 		old_caps_proto[i] = 0;
    289 	}
    290 
    291 	for (i=0, clicap = clicaps; clicap; clicap = clicap->next)
    292 	{
    293 		if (i == MAXCLICAPS)
    294 		{
    295 			unreal_log(ULOG_ERROR, "module", "BUG_TOO_MANY_CLIENTCAPABILITIES", NULL,
    296 			           "[BUG] clicap_pre_rehash: More than $count caps loaded - this should never happen",
    297 			           log_data_integer("count", MAXCLICAPS));
    298 			break;
    299 		}
    300 		safe_strdup(old_caps[i], clicap->name);
    301 		old_caps_proto[i] = clicap->cap;
    302 		i++;
    303 	}
    304 }
    305 
    306 /** Clear 'proto' protocol for all users */
    307 void clear_cap_for_users(long cap)
    308 {
    309 	Client *client;
    310 
    311 	if (cap == 0)
    312 		return;
    313 
    314 	list_for_each_entry(client, &lclient_list, lclient_node)
    315 	{
    316 		client->local->caps &= ~cap;
    317 	}
    318 	list_for_each_entry(client, &unknown_list, lclient_node)
    319 	{
    320 		client->local->caps &= ~cap;
    321 	}
    322 }
    323 
    324 /** Called after REHASH. This will deal with:
    325  * 1. Clearing flags for caps that are deleted
    326  * 2. Sending any CAP DEL
    327  * 3. Sending any CAP NEW
    328  */
    329 void clicap_check_for_changes(void)
    330 {
    331 	ClientCapability *clicap;
    332 	char *name;
    333 	int i;
    334 	int found;
    335 
    336 	if (!loop.rehashing)
    337 		return; /* First boot */
    338 
    339 	/* Let's deal with CAP DEL first:
    340 	 * Go through the old caps and see what's missing now.
    341 	 */
    342 	for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
    343 	{
    344 		name = old_caps[i];
    345 		found = 0;
    346 		for (clicap = clicaps; clicap; clicap = clicap->next)
    347 		{
    348 			if (!strcmp(clicap->name, name))
    349 			{
    350 				found = 1;
    351 				break;
    352 			}
    353 		}
    354 		if (!found)
    355 		{
    356 			/* Broadcast CAP DEL to local users */
    357 			send_cap_notify(0, name);
    358 			clear_cap_for_users(old_caps_proto[i]);
    359 		}
    360 	}
    361 
    362 	/* Now deal with CAP ADD:
    363 	 * Go through the new caps and see if it was missing from old caps.
    364 	 */
    365 	for (clicap = clicaps; clicap; clicap = clicap->next)
    366 	{
    367 		name = clicap->name;
    368 		found = 0;
    369 		for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
    370 		{
    371 			if (!strcmp(old_caps[i], name))
    372 			{
    373 				found = 1;
    374 				break;
    375 			}
    376 		}
    377 
    378 		if (!found)
    379 		{
    380 			/* Broadcast CAP NEW to local users */
    381 			send_cap_notify(1, name);
    382 		}
    383 	}
    384 
    385 	/* Now free the old caps. */
    386 	for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
    387 		safe_free(old_caps[i]);
    388 }