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