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