1 /*
2  * Copyright (c) 2005 William Pitcock, et al.
3  * Rights to this code are as documented in doc/LICENSE.
4  *
5  * This file contains code for the CService AKICK functions.
6  *
7  */
8 
9 #include "atheme.h"
10 
11 static void cs_cmd_akick(sourceinfo_t *si, int parc, char *parv[]);
12 static void cs_cmd_akick_add(sourceinfo_t *si, int parc, char *parv[]);
13 static void cs_cmd_akick_del(sourceinfo_t *si, int parc, char *parv[]);
14 static void cs_cmd_akick_list(sourceinfo_t *si, int parc, char *parv[]);
15 
16 static void akick_timeout_check(void *arg);
17 static void akickdel_list_create(void *arg);
18 
19 DECLARE_MODULE_V1
20 (
21 	"chanserv/akick", false, _modinit, _moddeinit,
22 	PACKAGE_STRING,
23 	VENDOR_STRING
24 );
25 
26 command_t cs_akick = { "AKICK", N_("Manipulates a channel's AKICK list."),
27                         AC_NONE, 4, cs_cmd_akick, { .path = "cservice/akick" } };
28 command_t cs_akick_add = { "ADD", N_("Adds a channel AKICK."),
29                         AC_NONE, 4, cs_cmd_akick_add, { .path = "" } };
30 command_t cs_akick_del = { "DEL", N_("Deletes a channel AKICK."),
31                         AC_NONE, 3, cs_cmd_akick_del, { .path = "" } };
32 command_t cs_akick_list = { "LIST", N_("Displays a channel's AKICK list."),
33                         AC_NONE, 2, cs_cmd_akick_list, { .path = "" } };
34 
35 typedef struct {
36 	time_t expiration;
37 
38 	myentity_t *entity;
39 	mychan_t *chan;
40 
41 	char host[NICKLEN + USERLEN + HOSTLEN + 4];
42 
43 	mowgli_node_t node;
44 } akick_timeout_t;
45 
46 time_t akickdel_next;
47 mowgli_list_t akickdel_list;
48 mowgli_patricia_t *cs_akick_cmds;
49 mowgli_eventloop_timer_t *akick_timeout_check_timer = NULL;
50 
51 static akick_timeout_t *akick_add_timeout(mychan_t *mc, myentity_t *mt, const char *host, time_t expireson);
52 
53 mowgli_heap_t *akick_timeout_heap;
54 
_modinit(module_t * m)55 void _modinit(module_t *m)
56 {
57         service_named_bind_command("chanserv", &cs_akick);
58 
59 	cs_akick_cmds = mowgli_patricia_create(strcasecanon);
60 
61 	/* Add sub-commands */
62 	command_add(&cs_akick_add, cs_akick_cmds);
63 	command_add(&cs_akick_del, cs_akick_cmds);
64 	command_add(&cs_akick_list, cs_akick_cmds);
65 
66         akick_timeout_heap = mowgli_heap_create(sizeof(akick_timeout_t), 512, BH_NOW);
67 
68     	if (akick_timeout_heap == NULL)
69     	{
70     		m->mflags = MODTYPE_FAIL;
71     		return;
72     	}
73 
74 	mowgli_timer_add_once(base_eventloop, "akickdel_list_create", akickdel_list_create, NULL, 0);
75 }
76 
_moddeinit(module_unload_intent_t intent)77 void _moddeinit(module_unload_intent_t intent)
78 {
79 	service_named_unbind_command("chanserv", &cs_akick);
80 
81 	/* Delete sub-commands */
82 	command_delete(&cs_akick_add, cs_akick_cmds);
83 	command_delete(&cs_akick_del, cs_akick_cmds);
84 	command_delete(&cs_akick_list, cs_akick_cmds);
85 
86 	mowgli_heap_destroy(akick_timeout_heap);
87 	mowgli_patricia_destroy(cs_akick_cmds, NULL, NULL);
88 }
89 
clear_bans_matching_entity(mychan_t * mc,myentity_t * mt)90 static void clear_bans_matching_entity(mychan_t *mc, myentity_t *mt)
91 {
92 	mowgli_node_t *n;
93 	myuser_t *tmu;
94 
95 	if (mc->chan == NULL)
96 		return;
97 
98 	if (!isuser(mt))
99 		return;
100 
101 	tmu = user(mt);
102 
103 	MOWGLI_ITER_FOREACH(n, tmu->logins.head)
104 	{
105 		user_t *tu;
106 		mowgli_node_t *it, *itn;
107 
108 		char hostbuf2[BUFSIZE];
109 
110 		tu = n->data;
111 
112 		snprintf(hostbuf2, BUFSIZE, "%s!%s@%s", tu->nick, tu->user, tu->vhost);
113 		for (it = next_matching_ban(mc->chan, tu, 'b', mc->chan->bans.head); it != NULL; it = next_matching_ban(mc->chan, tu, 'b', itn))
114 		{
115 			chanban_t *cb;
116 
117 			itn = it->next;
118 			cb = it->data;
119 
120 			modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, cb->type, cb->mask);
121 			chanban_delete(cb);
122 		}
123 	}
124 
125 	modestack_flush_channel(mc->chan);
126 }
127 
cs_cmd_akick(sourceinfo_t * si,int parc,char * parv[])128 static void cs_cmd_akick(sourceinfo_t *si, int parc, char *parv[])
129 {
130 	char *chan;
131 	char *cmd;
132 	command_t *c;
133 
134 	if (parc < 2)
135 	{
136 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AKICK");
137 		command_fail(si, fault_needmoreparams, _("Syntax: AKICK <#channel> <ADD|DEL|LIST> [parameters]"));
138 		return;
139 	}
140 
141 	if (parv[0][0] == '#')
142 		chan = parv[0], cmd = parv[1];
143 	else if (parv[1][0] == '#')
144 		cmd = parv[0], chan = parv[1];
145 	else
146 	{
147 		command_fail(si, fault_badparams, STR_INVALID_PARAMS, "AKICK");
148 		command_fail(si, fault_badparams, _("Syntax: AKICK <#channel> <ADD|DEL|LIST> [parameters]"));
149 		return;
150 	}
151 
152 	c = command_find(cs_akick_cmds, cmd);
153 	if (c == NULL)
154 	{
155 		command_fail(si, fault_badparams, _("Invalid command. Use \2/%s%s help\2 for a command listing."), (ircd->uses_rcommand == false) ? "msg " : "", chansvs.me->disp);
156 		return;
157 	}
158 
159 	parv[1] = chan;
160 	command_exec(si->service, si, c, parc - 1, parv + 1);
161 }
162 
cs_cmd_akick_add(sourceinfo_t * si,int parc,char * parv[])163 void cs_cmd_akick_add(sourceinfo_t *si, int parc, char *parv[])
164 {
165 	myentity_t *mt;
166 	mychan_t *mc;
167 	hook_channel_acl_req_t req;
168 	chanacs_t *ca, *ca2;
169 	char *chan = parv[0];
170 	long duration;
171 	char expiry[512];
172 	char *s;
173 	char *target;
174 	char *uname;
175 	char *token;
176 	char *treason, reason[BUFSIZE];
177 
178 	target = parv[1];
179 	token = strtok(parv[2], " ");
180 
181 	if (!target)
182 	{
183 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AKICK");
184 		command_fail(si, fault_needmoreparams, _("Syntax: AKICK <#channel> ADD <nickname|hostmask> [!P|!T <minutes>] [reason]"));
185 		return;
186 	}
187 
188 	mc = mychan_find(chan);
189 	if (!mc)
190 	{
191 		command_fail(si, fault_nosuch_target, _("Channel \2%s\2 is not registered."), chan);
192 		return;
193 	}
194 
195 	if (metadata_find(mc, "private:close:closer"))
196 	{
197 		command_fail(si, fault_noprivs, _("\2%s\2 is closed."), chan);
198 		return;
199 	}
200 
201 	/* A duration, reason or duration and reason. */
202 	if (token)
203 	{
204 		if (!strcasecmp(token, "!P")) /* A duration [permanent] */
205 		{
206 			duration = 0;
207 			treason = strtok(NULL, "");
208 
209 			if (treason)
210 				mowgli_strlcpy(reason, treason, BUFSIZE);
211 			else
212 				reason[0] = 0;
213 		}
214 		else if (!strcasecmp(token, "!T")) /* A duration [temporary] */
215 		{
216 			s = strtok(NULL, " ");
217 			treason = strtok(NULL, "");
218 
219 			if (treason)
220 				mowgli_strlcpy(reason, treason, BUFSIZE);
221 			else
222 				reason[0] = 0;
223 
224 			if (s)
225 			{
226 				duration = (atol(s) * 60);
227 				while (isdigit((unsigned char)*s))
228 					s++;
229 				if (*s == 'h' || *s == 'H')
230 					duration *= 60;
231 				else if (*s == 'd' || *s == 'D')
232 					duration *= 1440;
233 				else if (*s == 'w' || *s == 'W')
234 					duration *= 10080;
235 				else if (*s == '\0')
236 					;
237 				else
238 					duration = 0;
239 
240 				if (duration == 0)
241 				{
242 					command_fail(si, fault_badparams, _("Invalid duration given."));
243 					command_fail(si, fault_badparams, _("Syntax: AKICK <#channel> ADD <nick|hostmask> [!P|!T <minutes>] [reason]"));
244 					return;
245 				}
246 			}
247 			else
248 			{
249 				command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AKICK ADD");
250 				command_fail(si, fault_needmoreparams, _("Syntax: AKICK <#channel> ADD <nick|hostmask> [!P|!T <minutes>] [reason]"));
251 				return;
252 			}
253 		}
254 		else
255 		{
256 			duration = chansvs.akick_time;
257 			mowgli_strlcpy(reason, token, BUFSIZE);
258 			treason = strtok(NULL, "");
259 
260 			if (treason)
261 			{
262 				mowgli_strlcat(reason, " ", BUFSIZE);
263 				mowgli_strlcat(reason, treason, BUFSIZE);
264 			}
265 		}
266 	}
267 	else
268 	{ /* No reason and no duration */
269 		duration = chansvs.akick_time;
270 		reason[0] = 0;
271 	}
272 
273 	if ((chanacs_source_flags(mc, si) & (CA_FLAGS | CA_REMOVE)) != (CA_FLAGS | CA_REMOVE))
274 	{
275 		command_fail(si, fault_noprivs, _("You are not authorized to perform this operation."));
276 		return;
277 	}
278 
279 	mt = myentity_find_ext(target);
280 	if (!mt)
281 	{
282 		uname = pretty_mask(target);
283 		if (uname == NULL)
284 			uname = target;
285 
286 		/* we might be adding a hostmask */
287 		if (!validhostmask(uname))
288 		{
289 			command_fail(si, fault_badparams, _("\2%s\2 is neither a nickname nor a hostmask."), uname);
290 			return;
291 		}
292 
293 		uname = collapse(uname);
294 
295 		ca = chanacs_find_host_literal(mc, uname, 0);
296 		if (ca != NULL)
297 		{
298 			if (ca->level & CA_AKICK)
299 				command_fail(si, fault_nochange, _("\2%s\2 is already on the AKICK list for \2%s\2"), uname, mc->name);
300 			else
301 				command_fail(si, fault_alreadyexists, _("\2%s\2 already has flags \2%s\2 on \2%s\2"), uname, bitmask_to_flags(ca->level), mc->name);
302 			return;
303 		}
304 
305 		ca = chanacs_find_host(mc, uname, CA_AKICK);
306 		if (ca != NULL)
307 		{
308 			command_fail(si, fault_nochange, _("The more general mask \2%s\2 is already on the AKICK list for \2%s\2"), ca->host, mc->name);
309 			return;
310 		}
311 
312 		/* new entry */
313 		ca2 = chanacs_open(mc, NULL, uname, true, entity(si->smu));
314 		if (chanacs_is_table_full(ca2))
315 		{
316 			command_fail(si, fault_toomany, _("Channel %s access list is full."), mc->name);
317 			chanacs_close(ca2);
318 			return;
319 		}
320 
321 		req.ca = ca2;
322 		req.oldlevel = ca2->level;
323 
324 		chanacs_modify_simple(ca2, CA_AKICK, 0);
325 
326 		req.newlevel = ca2->level;
327 
328 		if (reason[0])
329 			metadata_add(ca2, "reason", reason);
330 
331 		if (duration > 0)
332 		{
333 			akick_timeout_t *timeout;
334 			time_t expireson = ca2->tmodified+duration;
335 
336 			snprintf(expiry, sizeof expiry, "%ld", expireson);
337 			metadata_add(ca2, "expires", expiry);
338 
339 			verbose(mc, _("\2%s\2 added \2%s\2 to the AKICK list, expires in %s."), get_source_name(si), uname,timediff(duration));
340 			logcommand(si, CMDLOG_SET, "AKICK:ADD: \2%s\2 on \2%s\2, expires in %s.", uname, mc->name,timediff(duration));
341 			command_success_nodata(si, _("AKICK on \2%s\2 was successfully added for \2%s\2 and will expire in %s."), uname, mc->name,timediff(duration) );
342 
343 			timeout = akick_add_timeout(mc, NULL, uname, expireson);
344 
345 			if (akickdel_next == 0 || akickdel_next > timeout->expiration)
346 			{
347 				if (akickdel_next != 0)
348 					mowgli_timer_destroy(base_eventloop, akick_timeout_check_timer);
349 
350 				akickdel_next = timeout->expiration;
351 				akick_timeout_check_timer = mowgli_timer_add_once(base_eventloop, "akick_timeout_check", akick_timeout_check, NULL, akickdel_next - CURRTIME);
352 			}
353 		}
354 		else
355 		{
356 			verbose(mc, _("\2%s\2 added \2%s\2 to the AKICK list."), get_source_name(si), uname);
357 			logcommand(si, CMDLOG_SET, "AKICK:ADD: \2%s\2 on \2%s\2", uname, mc->name);
358 
359 			command_success_nodata(si, _("AKICK on \2%s\2 was successfully added to the AKICK list for \2%s\2."), uname, mc->name);
360 		}
361 
362 		hook_call_channel_acl_change(&req);
363 		chanacs_close(ca2);
364 
365 		return;
366 	}
367 	else
368 	{
369 		if ((ca = chanacs_find_literal(mc, mt, 0x0)))
370 		{
371 			if (ca->level & CA_AKICK)
372 				command_fail(si, fault_nochange, _("\2%s\2 is already on the AKICK list for \2%s\2"), mt->name, mc->name);
373 			else
374 				command_fail(si, fault_alreadyexists, _("\2%s\2 already has flags \2%s\2 on \2%s\2"), mt->name, bitmask_to_flags(ca->level), mc->name);
375 			return;
376 		}
377 
378 		/* new entry */
379 		ca2 = chanacs_open(mc, mt, NULL, true, entity(si->smu));
380 		if (chanacs_is_table_full(ca2))
381 		{
382 			command_fail(si, fault_toomany, _("Channel %s access list is full."), mc->name);
383 			chanacs_close(ca2);
384 			return;
385 		}
386 
387 		req.ca = ca2;
388 		req.oldlevel = ca2->level;
389 
390 		chanacs_modify_simple(ca2, CA_AKICK, 0);
391 
392 		req.newlevel = ca2->level;
393 
394 		if (reason[0])
395 			metadata_add(ca2, "reason", reason);
396 
397 		if (duration > 0)
398 		{
399 			akick_timeout_t *timeout;
400 			time_t expireson = ca2->tmodified+duration;
401 
402 			snprintf(expiry, sizeof expiry, "%ld", expireson);
403 			metadata_add(ca2, "expires", expiry);
404 
405 			command_success_nodata(si, _("AKICK on \2%s\2 was successfully added for \2%s\2 and will expire in %s."), mt->name, mc->name, timediff(duration));
406 			verbose(mc, _("\2%s\2 added \2%s\2 to the AKICK list, expires in %s."), get_source_name(si), mt->name, timediff(duration));
407 			logcommand(si, CMDLOG_SET, "AKICK:ADD: \2%s\2 on \2%s\2, expires in %s", mt->name, mc->name, timediff(duration));
408 
409 			timeout = akick_add_timeout(mc, mt, mt->name, expireson);
410 
411 			if (akickdel_next == 0 || akickdel_next > timeout->expiration)
412 			{
413 				if (akickdel_next != 0)
414 					mowgli_timer_destroy(base_eventloop, akick_timeout_check_timer);
415 
416 				akickdel_next = timeout->expiration;
417 				akick_timeout_check_timer = mowgli_timer_add_once(base_eventloop, "akick_timeout_check", akick_timeout_check, NULL, akickdel_next - CURRTIME);
418 			}
419 		}
420 		else
421 		{
422 			command_success_nodata(si, _("AKICK on \2%s\2 was successfully added to the AKICK list for \2%s\2."), mt->name, mc->name);
423 
424 			verbose(mc, _("\2%s\2 added \2%s\2 to the AKICK list."), get_source_name(si), mt->name);
425 			logcommand(si, CMDLOG_SET, "AKICK:ADD: \2%s\2 on \2%s\2", mt->name, mc->name);
426 		}
427 
428 		hook_call_channel_acl_change(&req);
429 		chanacs_close(ca2);
430 		return;
431 	}
432 }
433 
cs_cmd_akick_del(sourceinfo_t * si,int parc,char * parv[])434 void cs_cmd_akick_del(sourceinfo_t *si, int parc, char *parv[])
435 {
436 	myentity_t *mt;
437 	mychan_t *mc;
438 	hook_channel_acl_req_t req;
439 	chanacs_t *ca;
440 	mowgli_node_t *n, *tn;
441 	char *chan = parv[0];
442 	char *uname = parv[1];
443 
444 	if (!chan || !uname)
445 	{
446 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AKICK");
447 		command_fail(si, fault_needmoreparams, _("Syntax: AKICK <#channel> DEL <nickname|hostmask>"));
448 		return;
449 	}
450 
451 	mc = mychan_find(chan);
452 	if (!mc)
453 	{
454 		command_fail(si, fault_nosuch_target, _("Channel \2%s\2 is not registered."), chan);
455 		return;
456 	}
457 
458 	if (metadata_find(mc, "private:close:closer"))
459 	{
460 		command_fail(si, fault_noprivs, _("\2%s\2 is closed."), chan);
461 		return;
462 	}
463 
464 	akick_timeout_t *timeout;
465 	chanban_t *cb;
466 
467 	if ((chanacs_source_flags(mc, si) & (CA_FLAGS | CA_REMOVE)) != (CA_FLAGS | CA_REMOVE))
468 	{
469 		command_fail(si, fault_noprivs, _("You are not authorized to perform this operation."));
470 		return;
471 	}
472 
473 	mt = myentity_find_ext(uname);
474 	if (!mt)
475 	{
476 		/* we might be deleting a hostmask */
477 		ca = chanacs_find_host_literal(mc, uname, CA_AKICK);
478 		if (ca == NULL)
479 		{
480 			ca = chanacs_find_host(mc, uname, CA_AKICK);
481 			if (ca != NULL)
482 				command_fail(si, fault_nosuch_key, _("\2%s\2 is not on the AKICK list for \2%s\2, however \2%s\2 is."), uname, mc->name, ca->host);
483 			else
484 				command_fail(si, fault_nosuch_key, _("\2%s\2 is not on the AKICK list for \2%s\2."), uname, mc->name);
485 			return;
486 		}
487 
488 		req.ca = ca;
489 		req.oldlevel = ca->level;
490 
491 		chanacs_modify_simple(ca, 0, CA_AKICK);
492 
493 		req.newlevel = ca->level;
494 
495 		hook_call_channel_acl_change(&req);
496 		chanacs_close(ca);
497 
498 		verbose(mc, _("\2%s\2 removed \2%s\2 from the AKICK list."), get_source_name(si), uname);
499 		logcommand(si, CMDLOG_SET, "AKICK:DEL: \2%s\2 on \2%s\2", uname, mc->name);
500 		command_success_nodata(si, _("\2%s\2 has been removed from the AKICK list for \2%s\2."), uname, mc->name);
501 
502 		MOWGLI_ITER_FOREACH_SAFE(n, tn, akickdel_list.head)
503 		{
504 			timeout = n->data;
505 			if (!match(timeout->host, uname) && timeout->chan == mc)
506 			{
507 				mowgli_node_delete(&timeout->node, &akickdel_list);
508 				mowgli_heap_free(akick_timeout_heap, timeout);
509 			}
510 		}
511 
512 		if (mc->chan != NULL && (cb = chanban_find(mc->chan, uname, 'b')))
513 		{
514 			modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, cb->type, cb->mask);
515 			chanban_delete(cb);
516 		}
517 
518 		return;
519 	}
520 
521 	if (!(ca = chanacs_find_literal(mc, mt, CA_AKICK)))
522 	{
523 		command_fail(si, fault_nosuch_key, _("\2%s\2 is not on the AKICK list for \2%s\2."), mt->name, mc->name);
524 		return;
525 	}
526 
527 	clear_bans_matching_entity(mc, mt);
528 
529 	MOWGLI_ITER_FOREACH_SAFE(n, tn, akickdel_list.head)
530 	{
531 		timeout = n->data;
532 		if (timeout->entity == mt && timeout->chan == mc)
533 		{
534 			mowgli_node_delete(&timeout->node, &akickdel_list);
535 			mowgli_heap_free(akick_timeout_heap, timeout);
536 		}
537 	}
538 
539 	req.ca = ca;
540 	req.oldlevel = ca->level;
541 
542 	chanacs_modify_simple(ca, 0, CA_AKICK);
543 
544 	req.newlevel = ca->level;
545 
546 	hook_call_channel_acl_change(&req);
547 	chanacs_close(ca);
548 
549 	command_success_nodata(si, _("\2%s\2 has been removed from the AKICK list for \2%s\2."), mt->name, mc->name);
550 	logcommand(si, CMDLOG_SET, "AKICK:DEL: \2%s\2 on \2%s\2", mt->name, mc->name);
551 	verbose(mc, _("\2%s\2 removed \2%s\2 from the AKICK list."), get_source_name(si), mt->name);
552 
553 	return;
554 }
555 
cs_cmd_akick_list(sourceinfo_t * si,int parc,char * parv[])556 void cs_cmd_akick_list(sourceinfo_t *si, int parc, char *parv[])
557 {
558 	mychan_t *mc;
559 	chanacs_t *ca;
560 	metadata_t *md, *md2;
561 	mowgli_node_t *n, *tn;
562 	bool operoverride = false;
563 	char *chan = parv[0];
564 	char expiry[512];
565 
566 	if (!chan)
567 	{
568 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "AKICK");
569 		command_fail(si, fault_needmoreparams, _("Syntax: AKICK <#channel> LIST"));
570 		return;
571 	}
572 
573 	/* make sure they're registered, logged in
574 	 * and the founder of the channel before
575 	 * we go any further.
576 	 */
577 	if (!si->smu)
578 	{
579 		/* if they're opers and just want to LIST, they don't have to log in */
580 		if (!(has_priv(si, PRIV_CHAN_AUSPEX)))
581 		{
582 			command_fail(si, fault_noprivs, _("You are not logged in."));
583 			return;
584 		}
585 	}
586 
587 	mc = mychan_find(chan);
588 	if (!mc)
589 	{
590 		command_fail(si, fault_nosuch_target, _("Channel \2%s\2 is not registered."), chan);
591 		return;
592 	}
593 
594 	if (metadata_find(mc, "private:close:closer"))
595 	{
596 		command_fail(si, fault_noprivs, _("\2%s\2 is closed."), chan);
597 		return;
598 	}
599 
600 	int i = 0;
601 
602 	if (!chanacs_source_has_flag(mc, si, CA_ACLVIEW))
603 	{
604 		if (has_priv(si, PRIV_CHAN_AUSPEX))
605 			operoverride = true;
606 		else
607 		{
608 			command_fail(si, fault_noprivs, _("You are not authorized to perform this operation."));
609 			return;
610 		}
611 	}
612 	command_success_nodata(si, _("AKICK list for \2%s\2:"), mc->name);
613 
614 	MOWGLI_ITER_FOREACH_SAFE(n, tn, mc->chanacs.head)
615 	{
616 		time_t expires_on = 0;
617 		char *ago;
618 		long time_left = 0;
619 
620 		ca = (chanacs_t *)n->data;
621 
622 		if (ca->level == CA_AKICK)
623 		{
624 			char buf[BUFSIZE], *buf_iter;
625 
626 			md = metadata_find(ca, "reason");
627 
628 			/* check if it's a temporary akick */
629 			if ((md2 = metadata_find(ca, "expires")))
630 			{
631 				snprintf(expiry, sizeof expiry, "%s", md2->value);
632 				expires_on = (time_t)atol(expiry);
633 				time_left = difftime(expires_on, CURRTIME);
634 			}
635 			ago = ca->tmodified ? time_ago(ca->tmodified) : "?";
636 
637 			buf_iter = buf;
638 			buf_iter += snprintf(buf_iter, sizeof(buf) - (buf_iter - buf), _("%d: \2%s\2 (\2%s\2) ["),
639 					     ++i, ca->entity != NULL ? ca->entity->name : ca->host,
640 					     md != NULL ? md->value : _("no AKICK reason specified"));
641 
642 			if (ca->setter)
643 				buf_iter += snprintf(buf_iter, sizeof(buf) - (buf_iter - buf), _("setter: %s"),
644 						     ca->setter);
645 
646 			if (expires_on > 0)
647 				buf_iter += snprintf(buf_iter, sizeof(buf) - (buf_iter - buf), _("%sexpires: %s"),
648 						     ca->setter != NULL ? ", " : "", timediff(time_left));
649 
650 			if (ca->tmodified)
651 				buf_iter += snprintf(buf_iter, sizeof(buf) - (buf_iter - buf), _("%smodified: %s"),
652 						     expires_on > 0 || ca->setter != NULL ? ", " : "", ago);
653 
654 			mowgli_strlcat(buf, "]", sizeof buf);
655 
656 			command_success_nodata(si, "%s", buf);
657 		}
658 
659 	}
660 
661 	command_success_nodata(si, _("Total of \2%d\2 %s in \2%s\2's AKICK list."), i, (i == 1) ? "entry" : "entries", mc->name);
662 
663 	if (operoverride)
664 		logcommand(si, CMDLOG_ADMIN, "AKICK:LIST: \2%s\2 (oper override)", mc->name);
665 	else
666 		logcommand(si, CMDLOG_GET, "AKICK:LIST: \2%s\2", mc->name);
667 }
668 
akick_timeout_check(void * arg)669 void akick_timeout_check(void *arg)
670 {
671 	mowgli_node_t *n, *tn;
672 	akick_timeout_t *timeout;
673 	chanacs_t *ca;
674 	mychan_t *mc;
675 
676 	chanban_t *cb;
677 	akickdel_next = 0;
678 
679 	MOWGLI_ITER_FOREACH_SAFE(n, tn, akickdel_list.head)
680 	{
681 		timeout = n->data;
682 		mc = timeout->chan;
683 
684 		if (timeout->expiration > CURRTIME)
685 		{
686 			akickdel_next = timeout->expiration;
687 			akick_timeout_check_timer = mowgli_timer_add_once(base_eventloop, "akick_timeout_check", akick_timeout_check, NULL, akickdel_next - CURRTIME);
688 			break;
689 		}
690 
691 		ca = NULL;
692 
693 		if (timeout->entity == NULL)
694 		{
695 			if ((ca = chanacs_find_host_literal(mc, timeout->host, CA_AKICK)) && mc->chan != NULL && (cb = chanban_find(mc->chan, ca->host, 'b')))
696 			{
697 				modestack_mode_param(chansvs.nick, mc->chan, MTYPE_DEL, cb->type, cb->mask);
698 				chanban_delete(cb);
699 			}
700 		}
701 		else
702 		{
703 			ca = chanacs_find_literal(mc, timeout->entity, CA_AKICK);
704 			if (ca == NULL)
705 			{
706 				mowgli_node_delete(&timeout->node, &akickdel_list);
707 				mowgli_heap_free(akick_timeout_heap, timeout);
708 
709 				continue;
710 			}
711 
712 			clear_bans_matching_entity(mc, timeout->entity);
713 		}
714 
715 		if (ca)
716 		{
717 			chanacs_modify_simple(ca, 0, CA_AKICK);
718 			chanacs_close(ca);
719 		}
720 
721 		mowgli_node_delete(&timeout->node, &akickdel_list);
722 		mowgli_heap_free(akick_timeout_heap, timeout);
723 	}
724 }
725 
akick_add_timeout(mychan_t * mc,myentity_t * mt,const char * host,time_t expireson)726 static akick_timeout_t *akick_add_timeout(mychan_t *mc, myentity_t *mt, const char *host, time_t expireson)
727 {
728 	mowgli_node_t *n;
729 	akick_timeout_t *timeout, *timeout2;
730 
731 	timeout = mowgli_heap_alloc(akick_timeout_heap);
732 
733 	timeout->entity = mt;
734 	timeout->chan = mc;
735 	timeout->expiration = expireson;
736 
737 	mowgli_strlcpy(timeout->host, host, sizeof timeout->host);
738 
739 	MOWGLI_ITER_FOREACH_PREV(n, akickdel_list.tail)
740 	{
741 		timeout2 = n->data;
742 		if (timeout2->expiration <= timeout->expiration)
743 			break;
744 	}
745 	if (n == NULL)
746 		mowgli_node_add_head(timeout, &timeout->node, &akickdel_list);
747 	else if (n->next == NULL)
748 		mowgli_node_add(timeout, &timeout->node, &akickdel_list);
749 	else
750 		mowgli_node_add_before(timeout, &timeout->node, &akickdel_list, n->next);
751 
752 	return timeout;
753 }
754 
akickdel_list_create(void * arg)755 void akickdel_list_create(void *arg)
756 {
757 	mychan_t *mc;
758 	mowgli_node_t *n, *tn;
759 	chanacs_t *ca;
760 	metadata_t *md;
761 	time_t expireson;
762 
763 	mowgli_patricia_iteration_state_t state;
764 
765 	MOWGLI_PATRICIA_FOREACH(mc, &state, mclist)
766 	{
767 		MOWGLI_ITER_FOREACH_SAFE(n, tn, mc->chanacs.head)
768 		{
769 			ca = (chanacs_t *)n->data;
770 
771 			if (!(ca->level & CA_AKICK))
772 				continue;
773 
774 			md = metadata_find(ca, "expires");
775 
776 			if (!md)
777 				continue;
778 
779 			expireson = atol(md->value);
780 
781 			if (CURRTIME > expireson)
782 			{
783 				chanacs_modify_simple(ca, 0, CA_AKICK);
784 				chanacs_close(ca);
785 			}
786 			else
787 			{
788 				/* overcomplicate the logic here a tiny bit */
789 				if (ca->host == NULL && ca->entity != NULL)
790 					akick_add_timeout(mc, ca->entity, entity(ca->entity)->name, expireson);
791 				else if (ca->host != NULL && ca->entity == NULL)
792 					akick_add_timeout(mc, NULL, ca->host, expireson);
793 			}
794 		}
795 	}
796 }
797 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
798  * vim:ts=8
799  * vim:sw=8
800  * vim:noexpandtab
801  */
802