unrealircd

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

list.c (13172B)

      1 /*
      2  *   IRC - Internet Relay Chat, src/modules/list.c
      3  *   (C) 2004 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 CMD_FUNC(cmd_list);
     26 int send_list(Client *client);
     27 
     28 #define MSG_LIST 	"LIST"	
     29 
     30 ModuleHeader MOD_HEADER
     31   = {
     32 	"list",
     33 	"5.0",
     34 	"command /LIST",
     35 	"UnrealIRCd Team",
     36 	"unrealircd-6",
     37     };
     38 
     39 typedef struct ChannelListOptions ChannelListOptions;
     40 struct ChannelListOptions {
     41 	NameList *yeslist;
     42 	NameList *nolist;
     43 	unsigned int starthash;
     44 	short int showall;
     45 	unsigned short usermin;
     46 	int  usermax;
     47 	time_t currenttime;
     48 	time_t chantimemin;
     49 	time_t chantimemax;
     50 	time_t topictimemin;
     51 	time_t topictimemax;
     52 	void *lr_context;
     53 };
     54 
     55 /* Global variables */
     56 ModDataInfo *list_md = NULL;
     57 char modebuf[BUFSIZE], parabuf[BUFSIZE];
     58 
     59 /* Macros */
     60 #define CHANNELLISTOPTIONS(x)       ((ChannelListOptions *)moddata_local_client(x, list_md).ptr)
     61 #define ALLOCATE_CHANNELLISTOPTIONS(client)	do { moddata_local_client(client, list_md).ptr = safe_alloc(sizeof(ChannelListOptions)); } while(0)
     62 #define free_list_options(client)		list_md_free(&moddata_local_client(client, list_md))
     63 
     64 #define DoList(x)               (MyUser((x)) && CHANNELLISTOPTIONS((x)))
     65 #define IsSendable(x)		(DBufLength(&x->local->sendQ) < 2048)
     66 
     67 /* Forward declarations */
     68 EVENT(send_queued_list_data);
     69 void list_md_free(ModData *md);
     70 
     71 MOD_TEST()
     72 {
     73 	MARK_AS_OFFICIAL_MODULE(modinfo);
     74 	return MOD_SUCCESS;
     75 }
     76 
     77 MOD_INIT()
     78 {
     79 	ModDataInfo mreq;
     80 
     81 	MARK_AS_OFFICIAL_MODULE(modinfo);
     82 
     83 	memset(&mreq, 0, sizeof(mreq));
     84 	mreq.name = "list";
     85 	mreq.type = MODDATATYPE_LOCAL_CLIENT;
     86 	mreq.free = list_md_free;
     87 	list_md = ModDataAdd(modinfo->handle, mreq);
     88 	if (!list_md)
     89 	{
     90 		config_error("could not register list moddata");
     91 		return MOD_FAILED;
     92 	}
     93 
     94 	CommandAdd(modinfo->handle, MSG_LIST, cmd_list, MAXPARA, CMD_USER);
     95 	EventAdd(modinfo->handle, "send_queued_list_data", send_queued_list_data, NULL, 1500, 0);
     96 
     97 	return MOD_SUCCESS;
     98 }
     99 
    100 MOD_LOAD()
    101 {
    102 	return MOD_SUCCESS;
    103 }
    104 
    105 MOD_UNLOAD()
    106 {
    107 	return MOD_SUCCESS;
    108 }
    109 
    110 /* Originally from bahamut, modified a bit for UnrealIRCd by codemastr
    111  * also Opers can now see +s channels -- codemastr */
    112 
    113 /*
    114  * parv[1] = channel
    115  */
    116 CMD_FUNC(cmd_list)
    117 {
    118 	Channel *channel;
    119 	time_t currenttime = TStime();
    120 	char *name, *p = NULL;
    121 	ChannelListOptions *lopt = NULL;
    122 	int usermax, usermin, error = 0, doall = 0;
    123 	time_t chantimemin, chantimemax;
    124 	time_t topictimemin, topictimemax;
    125 	NameList *yeslist = NULL;
    126 	NameList *nolist = NULL;
    127 	int ntargets = 0;
    128 	int maxtargets = max_targets_for_command("LIST");
    129 	char request[BUFSIZE];
    130 
    131 	static char *usage[] = {
    132 		"   Usage: /LIST <options>",
    133 		"",
    134 		"If you don't include any options, the default is to send you the",
    135 		"entire unfiltered list of channels. Below are the options you can",
    136 		"use, and what channels LIST will return when you use them.",
    137 		">number  List channels with more than <number> people.",
    138 		"<number  List channels with less than <number> people.",
    139 		"C>number List channels created more than <number> minutes ago.",
    140 		"C<number List channels created less than <number> minutes ago.",
    141 		"T>number List channels whose topics are older than <number> minutes",
    142 		"         (Ie, they have not changed in the last <number> minutes.",
    143 		"T<number List channels whose topics are not older than <number> minutes.",
    144 		"*mask*   List channels that match *mask*",
    145 		"!*mask*  List channels that do not match *mask*",
    146 		NULL
    147 	};
    148 
    149 	/* Remote /LIST is not supported */
    150 	if (!MyUser(client))
    151 		return;
    152 
    153 	/* If a /LIST is in progress then a new one will cancel it */
    154 	if (CHANNELLISTOPTIONS(client))
    155 	{
    156 		sendnumeric(client, RPL_LISTEND);
    157 		free_list_options(client);
    158 		return;
    159 	}
    160 
    161 	if (parc < 2 || BadPtr(parv[1]))
    162 	{
    163 		sendnumeric(client, RPL_LISTSTART);
    164 		ALLOCATE_CHANNELLISTOPTIONS(client);
    165 		CHANNELLISTOPTIONS(client)->showall = 1;
    166 
    167 		if (send_list(client))
    168 		{
    169 			/* Save context since there is more to be sent */
    170 			CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context();
    171 			labeled_response_inhibit_end = 1;
    172 		}
    173 
    174 		return;
    175 	}
    176 
    177 	if ((parc == 2) && (parv[1][0] == '?') && (parv[1][1] == '\0'))
    178 	{
    179 		char **ptr = usage;
    180 		for (; *ptr; ptr++)
    181 			sendnumeric(client, RPL_LISTSYNTAX, *ptr);
    182 		return;
    183 	}
    184 
    185 	sendnumeric(client, RPL_LISTSTART);
    186 
    187 	chantimemax = topictimemax = currenttime + 86400;
    188 	chantimemin = topictimemin = 0;
    189 	usermin = 0;		/* Minimum of 0 */
    190 	usermax = -1;		/* No maximum */
    191 
    192 	strlcpy(request, parv[1], sizeof(request));
    193 	for (name = strtoken(&p, request, ","); name && !error; name = strtoken(&p, NULL, ","))
    194 	{
    195 		if (MyUser(client) && (++ntargets > maxtargets))
    196 		{
    197 			sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "LIST");
    198 			break;
    199 		}
    200 		switch (*name)
    201 		{
    202 			case '<':
    203 				usermax = atoi(name + 1) - 1;
    204 				doall = 1;
    205 				break;
    206 			case '>':
    207 				usermin = atoi(name + 1) + 1;
    208 				doall = 1;
    209 				break;
    210 			case 'C':
    211 			case 'c':	/* Channel time -- creation time? */
    212 				++name;
    213 				switch (*name++)
    214 				{
    215 					case '<':
    216 						chantimemin = currenttime - 60 * atoi(name);
    217 						doall = 1;
    218 						break;
    219 					case '>':
    220 						chantimemax = currenttime - 60 * atoi(name);
    221 						doall = 1;
    222 						break;
    223 					default:
    224 						sendnumeric(client, ERR_LISTSYNTAX);
    225 						error = 1;
    226 				}
    227 				break;
    228 			case 'T':
    229 			case 't':
    230 				++name;
    231 				switch (*name++)
    232 				{
    233 					case '<':
    234 						topictimemin = currenttime - 60 * atoi(name);
    235 						doall = 1;
    236 						break;
    237 					case '>':
    238 						topictimemax = currenttime - 60 * atoi(name);
    239 						doall = 1;
    240 						break;
    241 					default:
    242 						sendnumeric(client, ERR_LISTSYNTAX);
    243 						error = 1;
    244 				}
    245 				break;
    246 			default:
    247 				/* A channel, possibly with wildcards.
    248 				 * Thought for the future: Consider turning wildcard
    249 				 * processing on the fly.
    250 				 * new syntax: !channelmask will tell ircd to ignore
    251 				 * any channels matching that mask, and then
    252 				 * channelmask will tell ircd to send us a list of
    253 				 * channels only masking channelmask. Note: Specifying
    254 				 * a channel without wildcards will return that
    255 				 * channel even if any of the !channelmask masks
    256 				 * matches it.
    257 				 */
    258 				if (*name == '!')
    259 				{
    260 					/* Negative matching by name */
    261 					doall = 1;
    262 					add_name_list(nolist, name + 1);
    263 				}
    264 				else if (strchr(name, '*') || strchr(name, '?'))
    265 				{
    266 					/* Channel with wildcards */
    267 					doall = 1;
    268 					add_name_list(yeslist, name);
    269 				}
    270 				else
    271 				{
    272 					/* A specific channel name without wildcards */
    273 					channel = find_channel(name);
    274 					if (channel && (ShowChannel(client, channel) || ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL)))
    275 					{
    276 						modebuf[0] = '[';
    277 						channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
    278 
    279 						if (modebuf[2] == '\0')
    280 							modebuf[0] = '\0';
    281 						else
    282 							strlcat(modebuf, "]", sizeof modebuf);
    283 
    284 						sendnumeric(client, RPL_LIST, name, channel->users, modebuf,
    285 							    channel->topic ? channel->topic : "");
    286 					}
    287 				}
    288 		} /* switch */
    289 	} /* for */
    290 
    291 	if (doall)
    292 	{
    293 		ALLOCATE_CHANNELLISTOPTIONS(client);
    294 		CHANNELLISTOPTIONS(client)->usermin = usermin;
    295 		CHANNELLISTOPTIONS(client)->usermax = usermax;
    296 		CHANNELLISTOPTIONS(client)->topictimemax = topictimemax;
    297 		CHANNELLISTOPTIONS(client)->topictimemin = topictimemin;
    298 		CHANNELLISTOPTIONS(client)->chantimemax = chantimemax;
    299 		CHANNELLISTOPTIONS(client)->chantimemin = chantimemin;
    300 		CHANNELLISTOPTIONS(client)->nolist = nolist;
    301 		CHANNELLISTOPTIONS(client)->yeslist = yeslist;
    302 
    303 		if (send_list(client))
    304 		{
    305 			/* Save context since there is more to be sent */
    306 			CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context();
    307 			labeled_response_inhibit_end = 1;
    308 		}
    309 		return;
    310 	}
    311 
    312 	sendnumeric(client, RPL_LISTEND);
    313 }
    314 /*
    315  * The function which sends the actual channel list back to the user.
    316  * Operates by stepping through the hashtable, sending the entries back if
    317  * they match the criteria.
    318  * client = Local client to send the output back to.
    319  * Taken from bahamut, modified for UnrealIRCd by codemastr.
    320  */
    321 int send_list(Client *client)
    322 {
    323 	Channel *channel;
    324 	ChannelListOptions *lopt = CHANNELLISTOPTIONS(client);
    325 	unsigned int  hashnum;
    326 	int numsend = (get_sendq(client) / 768) + 1; /* (was previously hard-coded) */
    327 	/* ^
    328 	 * numsend = Number (roughly) of lines to send back. Once this number has
    329 	 * been exceeded, send_list will finish with the current hash bucket,
    330 	 * and record that number as the number to start next time send_list
    331 	 * is called for this user. So, this function will almost always send
    332 	 * back more lines than specified by numsend (though not by much,
    333 	 * assuming the hashing algorithm works well). Be conservative in your
    334 	 * choice of numsend. -Rak
    335 	 */	
    336 
    337 	/* Begin of /LIST? then send official channels first. */
    338 	if ((lopt->starthash == 0) && conf_offchans)
    339 	{
    340 		ConfigItem_offchans *x;
    341 		for (x = conf_offchans; x; x = x->next)
    342 		{
    343 			if (find_channel(x->name))
    344 				continue; /* exists, >0 users.. will be sent later */
    345 			sendnumeric(client, RPL_LIST, x->name, 0, "",
    346 			            x->topic ? x->topic : "");
    347 		}
    348 	}
    349 
    350 	for (hashnum = lopt->starthash; hashnum < CHAN_HASH_TABLE_SIZE; hashnum++)
    351 	{
    352 		if (numsend > 0)
    353 			for (channel = hash_get_chan_bucket(hashnum); channel; channel = channel->hnextch)
    354 			{
    355 				if (SecretChannel(channel)
    356 				    && !IsMember(client, channel)
    357 				    && !ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))
    358 					continue;
    359 
    360 				/* set::hide-list { deny-channel } */
    361 				if (!IsOper(client) && iConf.hide_list && find_channel_allowed(client, channel->name))
    362 					continue;
    363 
    364 				/* Similarly, hide unjoinable channels for non-ircops since it would be confusing */
    365 				if (!IsOper(client) && !valid_channelname(channel->name))
    366 					continue;
    367 
    368 				/* Much more readable like this -- codemastr */
    369 				if ((!lopt->showall))
    370 				{
    371 					/* User count must be in range */
    372 					if ((channel->users < lopt->usermin) ||
    373 					    ((lopt->usermax >= 0) && (channel->users > lopt->usermax)))
    374 						continue;
    375 
    376 					/* Creation time must be in range */
    377 					if ((channel->creationtime && (channel->creationtime < lopt->chantimemin)) ||
    378 					    (channel->creationtime > lopt->chantimemax))
    379 						continue;
    380 
    381 					/* Topic time must be in range */
    382 					if ((channel->topic_time < lopt->topictimemin) ||
    383 					    (channel->topic_time > lopt->topictimemax))
    384 						continue;
    385 
    386 					/* Must not be on nolist (if it exists) */
    387 					if (lopt->nolist && find_name_list_match(lopt->nolist, channel->name))
    388 						continue;
    389 
    390 					/* Must be on yeslist (if it exists) */
    391 					if (lopt->yeslist && !find_name_list_match(lopt->yeslist, channel->name))
    392 						continue;
    393 				}
    394 				modebuf[0] = '[';
    395 				channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
    396 				if (modebuf[2] == '\0')
    397 					modebuf[0] = '\0';
    398 				else
    399 					strlcat(modebuf, "]", sizeof modebuf);
    400 				if (!ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))
    401 					sendnumeric(client, RPL_LIST,
    402 					    ShowChannel(client,
    403 					    channel) ? channel->name :
    404 					    "*", channel->users,
    405 					    ShowChannel(client, channel) ?
    406 					    modebuf : "",
    407 					    ShowChannel(client,
    408 					    channel) ? (channel->topic ?
    409 					    channel->topic : "") : "");
    410 				else
    411 					sendnumeric(client, RPL_LIST, channel->name,
    412 					    channel->users,
    413 					    modebuf,
    414 					    (channel->topic ? channel->topic : ""));
    415 				numsend--;
    416 			}
    417 		else
    418 			break;
    419 	}
    420 
    421 	/* All done */
    422 	if (hashnum == CHAN_HASH_TABLE_SIZE)
    423 	{
    424 		sendnumeric(client, RPL_LISTEND);
    425 		free_list_options(client);
    426 		return 0;
    427 	}
    428 
    429 	/*
    430 	 * We've exceeded the limit on the number of channels to send back
    431 	 * at once.
    432 	 */
    433 	lopt->starthash = hashnum;
    434 	return 1;
    435 }
    436 
    437 EVENT(send_queued_list_data)
    438 {
    439 	Client *client, *saved;
    440 	list_for_each_entry_safe(client, saved, &lclient_list, lclient_node)
    441 	{
    442 		if (DoList(client) && IsSendable(client))
    443 		{
    444 			labeled_response_set_context(CHANNELLISTOPTIONS(client)->lr_context);
    445 			if (!send_list(client))
    446 			{
    447 				/* We are done! */
    448 				labeled_response_force_end();
    449 			}
    450 			labeled_response_set_context(NULL);
    451 		}
    452 	}
    453 }
    454 
    455 /** Called on client exit: free the channel list options of this user */
    456 void list_md_free(ModData *md)
    457 {
    458 	ChannelListOptions *lopt = (ChannelListOptions *)md->ptr;
    459 
    460 	if (!lopt)
    461 		return;
    462 
    463 	free_entire_name_list(lopt->yeslist);
    464 	free_entire_name_list(lopt->nolist);
    465 	safe_free(lopt->lr_context);
    466 
    467 	safe_free(md->ptr);
    468 }