]> jfr.im git - irc/rqf/shadowircd.git/blob - extensions/hurt.c
[svn] - the new plan:
[irc/rqf/shadowircd.git] / extensions / hurt.c
1 /*
2 * charybdis: an advanced Internet Relay Chat Daemon(ircd).
3 *
4 * Copyright (C) 2006 charybdis development team
5 * All rights reserved
6 *
7 * $Id: hurt.c 1905 2006-08-29 14:51:31Z jilles $
8 */
9 #include "stdinc.h"
10 #include "modules.h"
11 #include "hook.h"
12 #include "client.h"
13 #include "ircd.h"
14 #include "send.h"
15 #include "numeric.h"
16 #include "hostmask.h"
17 #include "event.h"
18 #include "s_conf.h"
19 #include "s_newconf.h"
20 #include "hash.h"
21
22 /* {{{ Structures */
23 #define HURT_CUTOFF (10) /* protocol messages. */
24 #define HURT_DEFAULT_EXPIRE (7 * 24 * 60) /* minutes. */
25 #define HURT_EXIT_REASON "Hurt: Failed to identify to services"
26
27 enum {
28 HEAL_NICK = 0,
29 HEAL_IP
30 };
31
32 typedef struct _hurt_state {
33 time_t start_time;
34 uint32_t n_hurts;
35 dlink_list hurt_clients;
36 uint16_t cutoff;
37 time_t default_expire;
38 const char *exit_reason;
39 } hurt_state_t;
40
41 typedef struct _hurt {
42 const char *ip;
43 struct sockaddr *saddr;
44 int saddr_bits;
45 const char *reason;
46 time_t expire;
47 } hurt_t;
48 /* }}} */
49
50 /* {{{ Prototypes */
51 static int mo_hurt(struct Client *, struct Client *, int, const char **);
52 static int me_hurt(struct Client *, struct Client *, int, const char **);
53 static int mo_heal(struct Client *, struct Client *, int, const char **);
54 static int me_heal(struct Client *, struct Client *, int, const char **);
55
56 static int modinit(void);
57 static void modfini(void);
58
59 static void client_exit_hook(hook_data_client_exit *);
60 static void new_local_user_hook(struct Client *);
61 static void doing_stats_hook(hook_data_int *hdata);
62
63 static void hurt_check_event(void *);
64 static void hurt_expire_event(void *);
65
66 static hurt_t *hurt_new(time_t, const char *, const char *);
67 static void hurt_add(hurt_t *);
68 static void hurt_propagate(struct Client *, struct Client *, hurt_t *);
69 static hurt_t *hurt_find(const char *ip);
70 static hurt_t *hurt_find_exact(const char *ip);
71 static void hurt_remove(const char *ip);
72 static void hurt_destroy(void *hurt);
73
74 static int heal_nick(struct Client *, struct Client *);
75
76 static int nick_is_valid(const char *);
77 /* }}} */
78
79 /* {{{ State containers */
80
81 dlink_list hurt_confs = { NULL, NULL, 0 };
82
83 /* }}} */
84
85 /* {{{ Messages */
86 struct Message hurt_msgtab = {
87 "HURT", 0, 0, 0, MFLG_SLOW, {
88 mg_ignore, mg_ignore, mg_ignore,
89 mg_ignore, {me_hurt, 0}, {mo_hurt, 3}
90 }
91 };
92
93 struct Message heal_msgtab = {
94 "HEAL", 0, 0, 0, MFLG_SLOW, {
95 mg_ignore, mg_ignore, mg_ignore,
96 mg_ignore, {me_heal, 0}, {mo_heal, 2}
97 }
98 };
99 /* }}} */
100
101 /* {{{ Misc module stuff */
102 mapi_hfn_list_av1 hurt_hfnlist[] = {
103 {"client_exit", (hookfn) client_exit_hook},
104 {"new_local_user", (hookfn) new_local_user_hook},
105 {"doing_stats", (hookfn) doing_stats_hook},
106 {NULL, NULL},
107 };
108
109 mapi_clist_av1 hurt_clist[] = { &hurt_msgtab, &heal_msgtab, NULL };
110
111 DECLARE_MODULE_AV1(
112 hurt,
113 modinit,
114 modfini,
115 hurt_clist,
116 NULL,
117 hurt_hfnlist,
118 "$Revision: 1905 $"
119 );
120 /* }}} */
121
122 hurt_state_t hurt_state = {
123 .cutoff = HURT_CUTOFF,
124 .default_expire = HURT_DEFAULT_EXPIRE,
125 .exit_reason = HURT_EXIT_REASON,
126 };
127
128 /*
129 * Module constructor/destructor.
130 */
131
132 /* {{{ static int modinit() */
133 static int
134 modinit(void)
135 {
136 /* set-up hurt_state. */
137 hurt_state.start_time = CurrentTime;
138
139 /* add our event handlers. */
140 eventAdd("hurt_expire", hurt_expire_event, NULL, 60);
141 eventAdd("hurt_check", hurt_check_event, NULL, 5);
142
143 return 0;
144 }
145 /* }}} */
146
147 /* {{{ static void modfini() */
148 static void
149 modfini(void)
150 {
151 dlink_node *ptr, *next_ptr;
152
153 /* and delete our events. */
154 eventDelete(hurt_expire_event, NULL);
155 eventDelete(hurt_check_event, NULL);
156
157 DLINK_FOREACH_SAFE (ptr, next_ptr, hurt_state.hurt_clients.head)
158 {
159 dlinkDestroy(ptr, &hurt_state.hurt_clients);
160 }
161 }
162 /* }}} */
163
164 /*
165 * Message handlers.
166 */
167
168 /* {{{ static int mo_hurt()
169 *
170 * HURT [<expire>] <ip> <reason>
171 *
172 * parv[1] - expire or ip
173 * parv[2] - ip or reason
174 * parv[3] - reason or NULL
175 */
176 static int
177 mo_hurt(struct Client *client_p, struct Client *source_p,
178 int parc, const char **parv)
179 {
180 const char *ip, *expire, *reason;
181 int expire_time;
182 hurt_t *hurt;
183 struct Client *target_p;
184
185 if (!IsOperK(source_p)) {
186 sendto_one(source_p, form_str(ERR_NOPRIVS), me.name,
187 source_p->name, "kline");
188 return 0;
189 }
190
191 if (parc == 3)
192 expire = NULL, ip = parv[1], reason = parv[2];
193 else
194 expire = parv[1], ip = parv[2], reason = parv[3];
195
196 if (!expire)
197 expire_time = HURT_DEFAULT_EXPIRE;
198 if (expire && (expire_time = valid_temp_time(expire)) < 1) {
199 sendto_one(source_p,
200 ":%s NOTICE %s :Permanent HURTs are not supported",
201 me.name, source_p->name);
202 return 0;
203 }
204 if (EmptyString(reason)) {
205 sendto_one(source_p,
206 ":%s NOTICE %s :Empty HURT reasons are bad for business",
207 me.name, source_p->name);
208 return 0;
209 }
210
211 /* Is this a client? */
212 if (strchr(ip, '.') == NULL && strchr(ip, ':') == NULL)
213 {
214 target_p = find_named_person(ip);
215 if (target_p == NULL)
216 {
217 sendto_one_numeric(source_p, ERR_NOSUCHNICK,
218 form_str(ERR_NOSUCHNICK), ip);
219 return 0;
220 }
221 ip = target_p->orighost;
222 }
223 else
224 {
225 if (!strncmp(ip, "*@", 2))
226 ip += 2;
227 if (strchr(ip, '!') || strchr(ip, '@'))
228 {
229 sendto_one_notice(source_p, ":Invalid HURT mask [%s]",
230 ip);
231 return 0;
232 }
233 }
234
235 if (hurt_find(ip) != NULL) {
236 sendto_one(source_p,
237 ":%s NOTICE %s :[%s] already HURT",
238 me.name, source_p->name, ip);
239 return 0;
240 }
241
242 /*
243 * okay, we've got this far, now it's time to add the the HURT locally
244 * and propagate it to other servers on the network.
245 */
246 sendto_realops_snomask(SNO_GENERAL, L_ALL,
247 "%s added HURT on [%s] for %ld minutes with reason [%s]",
248 get_oper_name(source_p), ip, (long) expire_time / 60, reason);
249
250 hurt = hurt_new(expire_time, ip, reason);
251 hurt_add(hurt);
252 hurt_propagate(NULL, source_p, hurt);
253
254 return 0;
255 }
256 /* }}} */
257
258 /* {{{ static int me_hurt()
259 *
260 * [ENCAP mask] HURT <target> <expire> <ip> <reason>
261 *
262 * parv[1] - expire
263 * parv[2] - ip
264 * parv[3] - reason
265 */
266 static int
267 me_hurt(struct Client *client_p, struct Client *source_p,
268 int parc, const char **parv)
269 {
270 time_t expire_time;
271 hurt_t *hurt;
272
273 /*
274 * right... if we don't get enough arguments, or if we get any invalid
275 * arguments, just ignore this request - shit happens, and it's not worth
276 * dropping a server over.
277 */
278 if (parc < 4 || !IsPerson(source_p))
279 return 0;
280 if ((expire_time = atoi(parv[1])) < 1)
281 return 0;
282 if (hurt_find(parv[2]) != NULL)
283 return 0;
284 if (EmptyString(parv[3]))
285 return 0;
286
287 sendto_realops_snomask(SNO_GENERAL, L_ALL,
288 "%s added HURT on [%s] for %ld minutes with reason [%s]",
289 get_oper_name(source_p), parv[2], (long) expire_time / 60, parv[3]);
290 hurt = hurt_new(expire_time, parv[2], parv[3]);
291 hurt_add(hurt);
292
293 return 0;
294 }
295 /* }}} */
296
297 /* {{{ static int mo_heal()
298 *
299 * HURT <nick>|<ip>
300 *
301 * parv[1] - nick or ip
302 */
303 static int
304 mo_heal(struct Client *client_p, struct Client *source_p,
305 int parc, const char **parv)
306 {
307 struct Client *target_p;
308
309 if (!IsOperUnkline(source_p))
310 {
311 sendto_one(source_p, form_str(ERR_NOPRIVS),
312 me.name, source_p->name, "unkline");
313 return 0;
314 }
315
316 if (nick_is_valid(parv[1]))
317 {
318 target_p = find_named_person(parv[1]);
319 if (target_p == NULL)
320 {
321 sendto_one_numeric(source_p, ERR_NOSUCHNICK,
322 form_str(ERR_NOSUCHNICK), parv[1]);
323 return 0;
324 }
325 if (MyConnect(target_p))
326 heal_nick(source_p, target_p);
327 else
328 sendto_one(target_p, ":%s ENCAP %s HEAL %s",
329 get_id(source_p, target_p),
330 target_p->servptr->name,
331 get_id(target_p, target_p));
332 }
333 else if (strchr(parv[1], '.'))
334 {
335 if (hurt_find_exact(parv[1]) == NULL)
336 {
337 sendto_one(source_p, ":%s NOTICE %s :Mask [%s] is not HURT",
338 me.name, source_p->name, parv[1]);
339 return 0;
340 }
341 hurt_remove(parv[1]);
342 sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s removed HURT on %s",
343 get_oper_name(source_p), parv[1]);
344 sendto_server(NULL, NULL, NOCAPS, NOCAPS, ":%s ENCAP * HEAL %s",
345 source_p->name, parv[1]);
346 }
347 else
348 {
349 sendto_one(source_p,
350 ":%s NOTICE %s :[%s] is not a valid IP address/nick",
351 me.name, source_p->name, parv[1]);
352 return 0;
353 }
354
355 return 0;
356 }
357 /* }}} */
358
359 static int
360 me_heal(struct Client *client_p, struct Client *source_p,
361 int parc, const char **parv)
362 {
363 struct Client *target_p;
364
365 /* as noted in me_hurt(), if we don't get sufficient arguments...
366 * *poof*, it's dropped...
367 */
368 if (parc < 2)
369 return 0;
370
371 if (nick_is_valid(parv[1]))
372 {
373 target_p = find_person(parv[1]);
374 if (target_p != NULL && MyConnect(target_p))
375 heal_nick(source_p, target_p);
376 }
377 else if (strchr(parv[1], '.')) /* host or mask to remove ban for */
378 {
379 if (hurt_find_exact(parv[1]) == NULL)
380 return 0;
381
382 hurt_remove(parv[1]);
383 sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s removed HURT on %s",
384 get_oper_name(source_p), parv[1]);
385 }
386 else
387 return 0;
388
389 return 0;
390 }
391
392 /*
393 * Event handlers.
394 */
395
396 /* {{{ static void hurt_check_event() */
397 static void
398 hurt_check_event(void *arg)
399 {
400 dlink_node *ptr, *next_ptr;
401 struct Client *client_p;
402
403 DLINK_FOREACH_SAFE (ptr, next_ptr, hurt_state.hurt_clients.head) {
404 client_p = ptr->data;
405 if (!EmptyString(client_p->user->suser))
406 {
407 dlinkDestroy(ptr, &hurt_state.hurt_clients);
408 sendto_one_notice(client_p, ":HURT restriction removed for this session");
409 USED_TARGETS(client_p) = 0;
410 client_p->localClient->target_last = CurrentTime; /* don't ask --nenolod */
411 }
412 else if (client_p->localClient->receiveM > hurt_state.cutoff)
413 exit_client(NULL, client_p, &me, hurt_state.exit_reason);
414 }
415 }
416 /* }}} */
417
418 /* {{{ static void hurt_expire_event() */
419 static void
420 hurt_expire_event(void *unused)
421 {
422 dlink_node *ptr, *next_ptr;
423 hurt_t *hurt;
424
425 DLINK_FOREACH_SAFE (ptr, next_ptr, hurt_confs.head)
426 {
427 hurt = (hurt_t *) ptr->data;
428
429 if (hurt->expire <= CurrentTime)
430 {
431 dlinkFindDestroy(hurt, &hurt_confs);
432 hurt_destroy(hurt);
433 }
434 }
435 }
436 /* }}} */
437
438 /*
439 * Hook functions.
440 */
441
442 /* {{{ static void client_exit_hook() */
443 static void
444 client_exit_hook(hook_data_client_exit *data)
445 {
446 s_assert(data != NULL);
447 s_assert(data->target != NULL);
448
449 dlinkFindDestroy(data->target, &hurt_state.hurt_clients);
450 }
451 /* }}} */
452
453 /* {{{ static void new_local_user_hook() */
454 static void
455 new_local_user_hook(struct Client *source_p)
456 {
457 if (IsAnyDead(source_p) || !EmptyString(source_p->user->suser) ||
458 IsExemptKline(source_p))
459 return;
460
461 if (hurt_find(source_p->sockhost) || hurt_find(source_p->orighost))
462 {
463 USED_TARGETS(source_p) = 10;
464 source_p->localClient->target_last = CurrentTime + 600; /* don't ask --nenolod */
465 SetTGChange(source_p);
466 dlinkAddAlloc(source_p, &hurt_state.hurt_clients);
467 sendto_one_notice(source_p, ":You are hurt. Please identify to services immediately, or use /stats p for assistance.");
468 }
469 }
470 /* }}} */
471
472 /* {{{ static void doing_stats_hook() */
473 static void
474 doing_stats_hook(hook_data_int *hdata)
475 {
476 dlink_node *ptr;
477 hurt_t *hurt;
478 struct Client *source_p;
479
480 s_assert(hdata);
481 s_assert(hdata->client);
482
483 source_p = hdata->client;
484 if(hdata->arg2 != (int) 's')
485 return;
486 if((ConfigFileEntry.stats_k_oper_only == 2) && !IsOper(source_p))
487 return;
488 if ((ConfigFileEntry.stats_k_oper_only == 1) && !IsOper(source_p))
489 {
490 hurt = hurt_find(source_p->sockhost);
491 if (hurt != NULL)
492 {
493 sendto_one_numeric(source_p, RPL_STATSKLINE,
494 form_str(RPL_STATSKLINE), 's',
495 "*", hurt->ip, hurt->reason, "", "");
496 return;
497 }
498
499 hurt = hurt_find(source_p->orighost);
500 if (hurt != NULL)
501 {
502 sendto_one_numeric(source_p, RPL_STATSKLINE,
503 form_str(RPL_STATSKLINE), 's',
504 "*", hurt->ip, hurt->reason, "", "");
505 return;
506 }
507 return;
508 }
509
510 DLINK_FOREACH(ptr, hurt_confs.head)
511 {
512 hurt = (hurt_t *) ptr->data;
513 sendto_one_numeric(source_p, RPL_STATSKLINE,
514 form_str(RPL_STATSKLINE), 's',
515 "*", hurt->ip, hurt->reason, "", "");
516 }
517 }
518 /* }}} */
519
520 /* {{{ static void hurt_propagate()
521 *
522 * client_p - specific server to propagate HURT to, or NULL to propagate to all
523 * servers.
524 * source_p - source (oper who added the HURT)
525 * hurt - HURT to be propagated
526 */
527 static void
528 hurt_propagate(struct Client *client_p, struct Client *source_p, hurt_t *hurt)
529 {
530 if (client_p)
531 sendto_one(client_p,
532 ":%s ENCAP %s HURT %ld %s :%s",
533 source_p->name, client_p->name,
534 (long)(hurt->expire - CurrentTime),
535 hurt->ip, hurt->reason);
536 else
537 sendto_server(&me, NULL, NOCAPS, NOCAPS,
538 ":%s ENCAP * HURT %ld %s :%s",
539 source_p->name,
540 (long)(hurt->expire - CurrentTime),
541 hurt->ip, hurt->reason);
542 }
543 /* }}} */
544
545 /* {{{ static hurt_t *hurt_new() */
546 static hurt_t *
547 hurt_new(time_t expire, const char *ip, const char *reason)
548 {
549 hurt_t *hurt;
550
551 hurt = MyMalloc(sizeof(hurt_t));
552
553 DupString(hurt->ip, ip);
554 DupString(hurt->reason, reason);
555 hurt->expire = CurrentTime + expire;
556
557 return hurt;
558 }
559 /* }}} */
560
561 /* {{{ static void hurt_destroy() */
562 static void
563 hurt_destroy(void *hurt)
564 {
565 hurt_t *h;
566
567 if (!hurt)
568 return;
569
570 h = (hurt_t *) hurt;
571 MyFree((char *) h->ip);
572 MyFree((char *) h->reason);
573 MyFree(h);
574 }
575 /* }}} */
576
577 static void
578 hurt_add(hurt_t *hurt)
579 {
580 dlinkAddAlloc(hurt, &hurt_confs);
581 }
582
583 static hurt_t *
584 hurt_find_exact(const char *ip)
585 {
586 dlink_node *ptr;
587 hurt_t *hurt;
588
589 DLINK_FOREACH(ptr, hurt_confs.head)
590 {
591 hurt = (hurt_t *) ptr->data;
592
593 if (!strcasecmp(ip, hurt->ip))
594 return hurt;
595 }
596
597 return NULL;
598 }
599
600 static hurt_t *
601 hurt_find(const char *ip)
602 {
603 dlink_node *ptr;
604 hurt_t *hurt;
605
606 DLINK_FOREACH(ptr, hurt_confs.head)
607 {
608 hurt = (hurt_t *) ptr->data;
609
610 if (match(hurt->ip, ip))
611 return hurt;
612 }
613
614 return NULL;
615 }
616
617 static void
618 hurt_remove(const char *ip)
619 {
620 hurt_t *hurt = hurt_find_exact(ip);
621
622 dlinkFindDestroy(hurt, &hurt_confs);
623 hurt_destroy(hurt);
624 }
625
626 /* {{{ static int heal_nick() */
627 static int
628 heal_nick(struct Client *source_p, struct Client *target_p)
629 {
630 if (dlinkFindDestroy(target_p, &hurt_state.hurt_clients))
631 {
632 sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s used HEAL on %s",
633 get_oper_name(source_p), get_client_name(target_p, HIDE_IP));
634 sendto_one_notice(target_p, ":HURT restriction temporarily removed by operator");
635 sendto_one_notice(source_p, ":HURT restriction on %s temporarily removed", target_p->name);
636 USED_TARGETS(target_p) = 0;
637 target_p->localClient->target_last = CurrentTime; /* don't ask --nenolod */
638 return 1;
639 }
640 else
641 {
642 sendto_one_notice(source_p, ":%s was not hurt", target_p->name);
643 return 0;
644 }
645 }
646 /* }}} */
647
648 /*
649 * Anything else...
650 */
651
652 /* {{{ static int nick_is_valid() */
653 static int
654 nick_is_valid(const char *nick)
655 {
656 const char *s = nick;
657
658 for (; *s != '\0'; s++) {
659 if (!IsNickChar(*s))
660 return 0;
661 }
662
663 return 1;
664 }
665 /* }}} */
666
667 /*
668 * vim: ts=8 sw=8 noet fdm=marker tw=80
669 */