1 /*
2  * Copyright (c) 2005-2007 William Pitcock, et al.
3  * The rights to this code are as documented under doc/LICENSE.
4  *
5  * Copyright (C) 2003-2007 Lee Hardy <leeh@leeh.co.uk>
6  * Copyright (C) 2003-2007 ircd-ratbox development team
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are
10  * met:
11  *
12  * 1.Redistributions of source code must retain the above copyright notice,
13  *   this list of conditions and the following disclaimer.
14  * 2.Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in the
16  *   documentation and/or other materials provided with the distribution.
17  * 3.The name of the author may not be used to endorse or promote products
18  *   derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
29  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  * ALIS, based on the ratbox-services implementation.
33  *
34  */
35 
36 #include "atheme.h"
37 #include <limits.h>
38 
39 DECLARE_MODULE_V1
40 (
41 	"alis/main", false, _modinit, _moddeinit,
42 	PACKAGE_STRING,
43 	"William Pitcock <nenolod -at- nenolod.net>"
44 );
45 
46 #define ALIS_MAX_PARC	10
47 #define ALIS_MAX_MATCH	60
48 
49 #define DIR_NONE	-1
50 #define DIR_UNSET	0
51 #define DIR_SET		1
52 #define DIR_EQUAL	2
53 
54 service_t *alis;
55 
56 static void alis_cmd_list(sourceinfo_t *si, int parc, char *parv[]);
57 static void alis_cmd_help(sourceinfo_t *si, int parc, char *parv[]);
58 
59 command_t alis_list = { "LIST", "Lists channels matching given parameters.",
60 				AC_NONE, ALIS_MAX_PARC, alis_cmd_list, { .path = "alis/list" } };
61 command_t alis_help = { "HELP", "Displays contextual help information.",
62 				AC_NONE, 1, alis_cmd_help, { .path = "help" } };
63 
64 struct alis_query
65 {
66 	char *mask;
67 	char *topic;
68 	int min;
69 	int max;
70 	int show_mode;
71 	int show_topicwho;
72 	unsigned int mode;
73 	int mode_dir;
74 	int mode_key;
75 	int mode_limit;
76 	int mode_ext[256];
77 	int skip;
78 	int maxmatches;
79 	int showsecret;
80 };
81 
_modinit(module_t * m)82 void _modinit(module_t *m)
83 {
84 	alis = service_add("alis", NULL);
85 	service_bind_command(alis, &alis_list);
86 	service_bind_command(alis, &alis_help);
87 }
88 
_moddeinit(module_unload_intent_t intent)89 void _moddeinit(module_unload_intent_t intent)
90 {
91 	service_unbind_command(alis, &alis_list);
92 	service_unbind_command(alis, &alis_help);
93 
94 	service_delete(alis);
95 }
96 
alis_parse_mode(const char * text,int * key,int * limit,int * ext)97 static int alis_parse_mode(const char *text, int *key, int *limit, int *ext)
98 {
99 	int mode = 0, i;
100 
101 	if(!text)
102 		return 0;
103 
104 	while(*text)
105 	{
106 		switch(*text)
107 		{
108 			case 'l':
109 				*limit = 1;
110 				break;
111 			case 'k':
112 				*key = 1;
113 				break;
114 			default:
115 				mode |= mode_to_flag(*text);
116 				for (i = 0; ignore_mode_list[i].mode != '\0'; i++)
117 					if (*text == ignore_mode_list[i].mode)
118 						ext[i] = 1;
119 				break;
120 		}
121 
122 		text++;
123 	}
124 
125 	return mode;
126 }
127 
parse_alis(sourceinfo_t * si,int parc,char * parv[],struct alis_query * query)128 static int parse_alis(sourceinfo_t *si, int parc, char *parv[], struct alis_query *query)
129 {
130 	int i = 1;
131 	char *opt = NULL, *arg = NULL;
132 
133 	if (parc < 1)
134 		query->mask = sstrdup("*");
135 	else if (!VALID_GLOBAL_CHANNEL_PFX(parv[0]) && strchr(parv[0], '*') == NULL && strchr(parv[0], '?') == NULL)
136 	{
137 		size_t max = 1 + strlen(parv[0]) + 2;
138 		query->mask = smalloc(max);
139 		snprintf(query->mask, max, "*%s*", parv[0]);
140 	}
141 	else
142 		query->mask = sstrdup(parv[0]);
143 
144 	query->mode_dir = DIR_NONE;
145 	while ((opt = parv[i++]))
146 	{
147 		if(!strcasecmp(opt, "-min"))
148 		{
149 			if((arg = parv[i++]) == NULL || (query->min = atoi(arg)) < 1)
150 			{
151 				command_fail(si, fault_badparams, "Invalid -min option");
152 				return 0;
153 			}
154 		}
155 		else if(!strcasecmp(opt, "-max"))
156 		{
157 			if((arg = parv[i++]) == NULL || (query->max = atoi(arg)) < 1)
158 			{
159 				command_fail(si, fault_badparams, "Invalid -max option");
160 				return 0;
161 			}
162 		}
163 		else if(!strcasecmp(opt, "-maxmatches"))
164 		{
165 			if((arg = parv[i++]) == NULL || (query->maxmatches = atoi(arg)) == 0)
166 			{
167 				command_fail(si, fault_badparams, "Invalid -maxmatches option");
168 				return 0;
169 			}
170 			if(si->su != NULL && !has_priv(si, PRIV_CHAN_AUSPEX))
171 			{
172 				if(query->maxmatches > ALIS_MAX_MATCH)
173 				{
174 					command_fail(si, fault_badparams, "Invalid -maxmatches option");
175 					return 0;
176 				}
177 				if(query->maxmatches <= 0)
178 					query->maxmatches = ALIS_MAX_MATCH;
179 			}
180 			else
181 			{
182 				if(query->maxmatches <= 0)
183 					query->maxmatches = INT_MAX;
184 			}
185 		}
186 		else if(!strcasecmp(opt, "-skip"))
187 		{
188 			if((arg = parv[i++]) == NULL || (query->skip = atoi(arg)) < 1)
189 			{
190 				command_fail(si, fault_badparams, "Invalid -skip option");
191 				return 0;
192 			}
193 		}
194 		else if(!strcasecmp(opt, "-topic"))
195 		{
196 			arg = parv[i++];
197 
198 			if (arg == NULL)
199 			{
200 				command_fail(si, fault_badparams, "Invalid -topic option");
201 				return 0;
202 			}
203 
204 			if (strchr(arg, '*') == NULL)
205 			{
206 				size_t max = 1 + strlen(arg) + 2;
207 				query->topic = smalloc(max);
208 				snprintf(query->topic, max, "*%s*", arg);
209 			}
210 			else
211 				query->topic = sstrdup(arg);
212 		}
213 		else if(!strcasecmp(opt, "-show"))
214 		{
215 			arg = parv[i++];
216 
217 			if (arg == NULL)
218 			{
219 				command_fail(si, fault_badparams, "Invalid -show option");
220 				return 0;
221 			}
222 
223 			if(arg[0] == 'm')
224 			{
225 				query->show_mode = 1;
226 
227 				if(arg[1] == 't')
228 					query->show_topicwho = 1;
229 			}
230 			else if(arg[0] == 't')
231 			{
232 				query->show_topicwho = 1;
233 
234 				if(arg[1] == 'm')
235 					query->show_mode = 1;
236 			}
237 		}
238 		else if(!strcasecmp(opt, "-mode"))
239 		{
240 			arg = parv[i++];
241 
242 			if (arg == NULL)
243 			{
244 				command_fail(si, fault_badparams, "Invalid -mode option");
245 				return 0;
246 			}
247 
248 			switch(*arg)
249 			{
250 				case '+':
251 					query->mode_dir = DIR_SET;
252 					break;
253 				case '-':
254 					query->mode_dir = DIR_UNSET;
255 					break;
256 				case '=':
257 					query->mode_dir = DIR_EQUAL;
258 					break;
259 				default:
260 					command_fail(si, fault_badparams, "Invalid -mode option");
261 					return 0;
262 			}
263 
264 			query->mode = alis_parse_mode(arg+1,
265 					&query->mode_key,
266 					&query->mode_limit,
267 					query->mode_ext);
268 		}
269 		else if (!strcasecmp(opt, "-showsecret"))
270 		{
271 			if (!has_priv(si, PRIV_CHAN_AUSPEX))
272 			{
273 				command_fail(si, fault_noprivs, _("You are not authorized to perform this operation."));
274 				return 0;
275 			}
276 
277 			query->showsecret = 1;
278 		}
279 		else
280 		{
281 			command_fail(si, fault_badparams, "Invalid option %s", opt);
282 			return 0;
283 		}
284 	}
285 
286 	return 1;
287 }
288 
free_alis(struct alis_query * query)289 static void free_alis(struct alis_query *query)
290 {
291 	return_if_fail(query != NULL);
292 
293 	if (query->mask)
294 		free(query->mask);
295 
296 	if (query->topic)
297 		free(query->topic);
298 }
299 
print_channel(sourceinfo_t * si,channel_t * chptr,struct alis_query * query)300 static void print_channel(sourceinfo_t *si, channel_t *chptr, struct alis_query *query)
301 {
302 	int show_topicwho = query->show_topicwho;
303 	int show_topic = 1;
304 	char topic[BUFSIZE];
305 
306 	/* cant show a topicwho, when a channel has no topic. */
307 	if(!chptr->topic)
308 	{
309 		show_topicwho = 0;
310 		show_topic = 0;
311 	}
312 	if(show_topic)
313 	{
314 		mowgli_strlcpy(topic, chptr->topic, sizeof topic);
315 		strip_ctrl(topic);
316 	}
317 
318 	if(query->show_mode && show_topicwho && show_topic)
319 		command_success_nodata(si, "%-50s %-8s %3zu :%s (%s)",
320 			chptr->name, channel_modes(chptr, false),
321 			MOWGLI_LIST_LENGTH(&chptr->members),
322 			topic, chptr->topic_setter);
323 	else if(query->show_mode && show_topic)
324 		command_success_nodata(si, "%-50s %-8s %3zu :%s",
325 			chptr->name, channel_modes(chptr, false),
326 			MOWGLI_LIST_LENGTH(&chptr->members),
327 			topic);
328 	else if(query->show_mode)
329 		command_success_nodata(si, "%-50s %-8s %3zu",
330 			chptr->name, channel_modes(chptr, false),
331 			MOWGLI_LIST_LENGTH(&chptr->members));
332 	else if(show_topicwho && show_topic)
333 		command_success_nodata(si, "%-50s %3zu :%s (%s)",
334 			chptr->name, MOWGLI_LIST_LENGTH(&chptr->members),
335 			topic, chptr->topic_setter);
336 	else if(show_topic)
337 		command_success_nodata(si, "%-50s %3zu :%s",
338 			chptr->name, MOWGLI_LIST_LENGTH(&chptr->members),
339 			topic);
340 	else
341 		command_success_nodata(si, "%-50s %3zu",
342 			chptr->name, MOWGLI_LIST_LENGTH(&chptr->members));
343 }
344 
show_channel(channel_t * chptr,struct alis_query * query)345 static int show_channel(channel_t *chptr, struct alis_query *query)
346 {
347 	int i;
348 
349 	/* skip +s channels unless -showsecret is used */
350 	if(chptr->modes & CMODE_SEC && !query->showsecret)
351 		return 0;
352 
353 	if((int)MOWGLI_LIST_LENGTH(&chptr->members) < query->min ||
354 	   (query->max && (int)MOWGLI_LIST_LENGTH(&chptr->members) > query->max))
355 		return 0;
356 
357 	if(query->mode_dir == DIR_SET)
358 	{
359 		if(((chptr->modes & query->mode) != query->mode) ||
360 		   (query->mode_key && chptr->key == NULL) ||
361 		   (query->mode_limit && !chptr->limit))
362 			return 0;
363 		for (i = 0; ignore_mode_list[i].mode != '\0'; i++)
364 			if (query->mode_ext[i] && !chptr->extmodes[i])
365 				return 0;
366 	}
367 	else if(query->mode_dir == DIR_UNSET)
368 	{
369 		if((chptr->modes & query->mode) ||
370 		   (query->mode_key && chptr->key != NULL) ||
371 		   (query->mode_limit && chptr->limit))
372 			return 0;
373 		for (i = 0; ignore_mode_list[i].mode != '\0'; i++)
374 			if (query->mode_ext[i] && chptr->extmodes[i])
375 				return 0;
376 	}
377 	else if(query->mode_dir == DIR_EQUAL)
378 	{
379 		if(((chptr->modes & ~(CMODE_LIMIT | CMODE_KEY)) != query->mode) ||
380 		   (query->mode_key && chptr->key == NULL) ||
381 		   (query->mode_limit && !chptr->limit))
382 			return 0;
383 		for (i = 0; ignore_mode_list[i].mode != '\0'; i++)
384 			if (query->mode_ext[i] && !chptr->extmodes[i])
385 				return 0;
386 	}
387 
388 	if(match(query->mask, chptr->name))
389 		return 0;
390 
391 	if(query->topic != NULL && match(query->topic, chptr->topic))
392 		return 0;
393 
394 	if(query->skip)
395 	{
396 		query->skip--;
397 		return 0;
398 	}
399 
400 	return 1;
401 }
402 
alis_cmd_list(sourceinfo_t * si,int parc,char * parv[])403 static void alis_cmd_list(sourceinfo_t *si, int parc, char *parv[])
404 {
405 	channel_t *chptr;
406 	struct alis_query query;
407 	mowgli_patricia_iteration_state_t state;
408 	int maxmatch;
409 
410 	memset(&query, 0, sizeof(struct alis_query));
411 	query.maxmatches = ALIS_MAX_MATCH;
412 
413 	if (!parse_alis(si, parc, parv, &query))
414 	{
415 		free_alis(&query);
416 		return;
417 	}
418 
419 	logcommand(si, CMDLOG_GET, "LIST: \2%s\2", query.mask);
420 
421 	maxmatch = query.maxmatches;
422 	command_success_nodata(si,
423 		"Returning maximum of %d channel names matching '\2%s\2'",
424 		query.maxmatches, query.mask);
425 
426 	/* hunting for one channel.. */
427 	if(strchr(query.mask, '*') == NULL && strchr(query.mask, '?') == NULL)
428 	{
429 		if((chptr = channel_find(query.mask)) != NULL)
430 		{
431 			if(!(chptr->modes & CMODE_SEC) ||
432 					(si->su != NULL &&
433 					 chanuser_find(chptr, si->su)))
434 				print_channel(si, chptr, &query);
435 		}
436 
437 		command_success_nodata(si, "End of output");
438 		free_alis(&query);
439 		return;
440 	}
441 
442 	MOWGLI_PATRICIA_FOREACH(chptr, &state, chanlist)
443 	{
444 		/* matches, so show it */
445 		if(show_channel(chptr, &query))
446 		{
447 			print_channel(si, chptr, &query);
448 
449 			if(--maxmatch == 0)
450 			{
451 				command_success_nodata(si, "Maximum channel output reached");
452 				break;
453 			}
454 		}
455 	}
456 
457 	command_success_nodata(si, "End of output");
458 	free_alis(&query);
459 	return;
460 }
461 
alis_cmd_help(sourceinfo_t * si,int parc,char * parv[])462 static void alis_cmd_help(sourceinfo_t *si, int parc, char *parv[])
463 {
464 	const char *command = parv[0];
465 
466 	if (command == NULL)
467 	{
468 		command_success_nodata(si, _("***** \2%s Help\2 *****"), alis->nick);
469 		command_success_nodata(si, _("\2%s\2 allows searching for channels with more\n"
470 					"flexibility than the /list command."),
471 				alis->nick);
472 		command_success_nodata(si, " ");
473 		command_success_nodata(si, _("For more information on a command, type:"));
474 		command_success_nodata(si, "\2/%s%s help <command>\2", (ircd->uses_rcommand == false) ? "msg " : "", alis->disp);
475 		command_success_nodata(si, " ");
476 		command_help(si, alis->commands);
477 		command_success_nodata(si, _("***** \2End of Help\2 *****"));
478 		return;
479 	}
480 
481 	help_display(si, si->service, command, alis->commands);
482 }
483