]> jfr.im git - irc/evilnet/x3.git/blob - src/shun.c
Merged in srvx 1.4-RC1 changes. DNSBL parts are missing as it hasnt even been impleme...
[irc/evilnet/x3.git] / src / shun.c
1 /* shun.c - Shun database
2 * Copyright 2000-2004 srvx Development Team
3 *
4 * This file is part of x3.
5 *
6 * x3 is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 */
20
21 #include "heap.h"
22 #include "helpfile.h"
23 #include "log.h"
24 #include "saxdb.h"
25 #include "timeq.h"
26 #include "shun.h"
27
28 #ifdef HAVE_SYS_SOCKET_H
29 #include <sys/socket.h>
30 #endif
31 #ifdef HAVE_NETINET_IN_H
32 #include <netinet/in.h>
33 #endif
34 #ifdef HAVE_ARPA_INET_H
35 #include <arpa/inet.h>
36 #endif
37 #ifdef HAVE_NETDB_H
38 #include <netdb.h>
39 #endif
40
41 #define KEY_REASON "reason"
42 #define KEY_EXPIRES "expires"
43 #define KEY_LASTMOD "lastmod"
44 #define KEY_ISSUER "issuer"
45 #define KEY_ISSUED "issued"
46
47 static heap_t shun_heap; /* key: expiry time, data: struct shun_entry* */
48 static dict_t shun_dict; /* key: target, data: struct shun_entry* */
49
50 static int
51 shun_comparator(const void *a, const void *b)
52 {
53 const struct shun *ga=a, *gb=b;
54 return ga->expires - gb->expires;
55 }
56
57 static void
58 free_shun_from_dict(void *data)
59 {
60 struct shun *ent = data;
61 free(ent->issuer);
62 free(ent->target);
63 free(ent->reason);
64 free(ent);
65 }
66
67 static void
68 free_shun(struct shun *ent)
69 {
70 dict_remove(shun_dict, ent->target);
71 }
72
73 static int
74 shun_for_p(UNUSED_ARG(void *key), void *data, void *extra)
75 {
76 struct shun *ge = data;
77 return !irccasecmp(ge->target, extra);
78 }
79
80 static int
81 delete_shun_for_p(UNUSED_ARG(void *key), void *data, void *extra)
82 {
83 struct shun *ge = data;
84
85 if (!irccasecmp(ge->target, extra)) {
86 free_shun(ge);
87 return 1;
88 } else {
89 return 0;
90 }
91 }
92
93 static void
94 shun_expire(UNUSED_ARG(void *data))
95 {
96 time_t stopped;
97 void *wraa;
98
99 stopped = 0;
100 while (heap_size(shun_heap)) {
101 heap_peek(shun_heap, 0, &wraa);
102 stopped = ((struct shun*)wraa)->expires;
103 if (stopped > now)
104 break;
105 heap_pop(shun_heap);
106 free_shun(wraa);
107 }
108 if (heap_size(shun_heap))
109 timeq_add(stopped, shun_expire, NULL);
110 }
111
112 int
113 shun_remove(const char *target, int announce)
114 {
115 int res = dict_find(shun_dict, target, NULL) ? 1 : 0;
116 if (heap_remove_pred(shun_heap, delete_shun_for_p, (char*)target)) {
117 void *argh;
118 struct shun *new_first;
119 heap_peek(shun_heap, 0, &argh);
120 if (argh) {
121 new_first = argh;
122 timeq_del(0, shun_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
123 timeq_add(new_first->expires, shun_expire, 0);
124 }
125 }
126 if (announce)
127 irc_unshun(target);
128 return res;
129 }
130
131 struct shun *
132 shun_add(const char *issuer, const char *target, unsigned long duration, const char *reason, time_t issued, time_t lastmod, int announce)
133 {
134 struct shun *ent;
135 struct shun *prev_first;
136 void *argh;
137
138 heap_peek(shun_heap, 0, &argh);
139 prev_first = argh;
140 ent = dict_find(shun_dict, target, NULL);
141 if (ent) {
142 heap_remove_pred(shun_heap, shun_for_p, (char*)target);
143 if (ent->expires < (time_t)(now + duration))
144 ent->expires = now + duration;
145 if (ent->lastmod < lastmod)
146 ent->lastmod = lastmod;
147 } else {
148 ent = malloc(sizeof(*ent));
149 ent->issued = issued;
150 ent->lastmod = lastmod;
151 ent->issuer = strdup(issuer);
152 ent->target = strdup(target);
153 ent->expires = now + duration;
154 ent->reason = strdup(reason);
155 dict_insert(shun_dict, ent->target, ent);
156 }
157 heap_insert(shun_heap, ent, ent);
158 if (!prev_first || (ent->expires < prev_first->expires)) {
159 timeq_del(0, shun_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
160 timeq_add(ent->expires, shun_expire, 0);
161 }
162 if (announce)
163 irc_shun(NULL, ent);
164 return ent;
165 }
166
167 static char *
168 shun_alternate_target(const char *target)
169 {
170 const char *hostname;
171
172 /* If no host part, bail. */
173 if (!(hostname = strchr(target, '@')))
174 return NULL;
175 /* If host part contains wildcards, bail. */
176 if (hostname[strcspn(hostname, "*?/")])
177 return NULL;
178 /* Get parsed address and canonical name for host. */
179 #if 0
180 irc_in_addr_t in; /* move this to the right place */
181 if (irc_pton(&in, NULL, hostname+1)) {
182 if (getnameinfo(/*TODO*/))
183 return NULL;
184 } else if (!getaddrinfo(/*TODO*/)) {
185 } else return NULL;
186 #else
187 return NULL;
188 #endif
189 }
190
191 struct shun *
192 shun_find(const char *target)
193 {
194 struct shun *res;
195 dict_iterator_t it;
196 char *alt_target;
197
198 res = dict_find(shun_dict, target, NULL);
199 if (res)
200 return res;
201 /* Stock ircu requires BADCHANs to match exactly. */
202 if ((target[0] == '#') || (target[0] == '&'))
203 return NULL;
204 else if (target[strcspn(target, "*?")]) {
205 /* Wildcard: do an obnoxiously long search. */
206 for (it = dict_first(shun_dict); it; it = iter_next(it)) {
207 res = iter_data(it);
208 if (match_ircglob(target, res->target))
209 return res;
210 }
211 }
212 /* See if we can resolve the hostname part of the mask. */
213 if ((alt_target = shun_alternate_target(target))) {
214 res = shun_find(alt_target);
215 free(alt_target);
216 return res;
217 }
218 return NULL;
219 }
220
221 static int
222 shun_refresh_helper(UNUSED_ARG(void *key), void *data, void *extra)
223 {
224 struct shun *ge = data;
225 irc_shun(extra, ge);
226 return 0;
227 }
228
229 void
230 shun_refresh_server(struct server *srv)
231 {
232 heap_remove_pred(shun_heap, shun_refresh_helper, srv);
233 }
234
235 void
236 shun_refresh_all(void)
237 {
238 heap_remove_pred(shun_heap, shun_refresh_helper, 0);
239 }
240
241 unsigned int
242 shun_count(void)
243 {
244 return dict_size(shun_dict);
245 }
246
247 static int
248 shun_add_record(const char *key, void *data, UNUSED_ARG(void *extra))
249 {
250 struct record_data *rd = data;
251 const char *issuer, *reason, *dstr;
252 time_t issued, expiration, lastmod;
253
254 if (!(reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING))) {
255 log_module(MAIN_LOG, LOG_ERROR, "Missing reason for shun %s", key);
256 return 0;
257 }
258 if (!(dstr = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING))) {
259 log_module(MAIN_LOG, LOG_ERROR, "Missing expiration for shun %s", key);
260 return 0;
261 }
262 expiration = strtoul(dstr, NULL, 0);
263 dstr = database_get_data(rd->d.object, KEY_LASTMOD, RECDB_QSTRING);
264 lastmod = dstr ? strtoul(dstr, NULL, 0) : 0;
265 if ((dstr = database_get_data(rd->d.object, KEY_ISSUED, RECDB_QSTRING))) {
266 issued = strtoul(dstr, NULL, 0);
267 } else {
268 issued = now;
269 }
270 if (!(issuer = database_get_data(rd->d.object, KEY_ISSUER, RECDB_QSTRING))) {
271 issuer = "<unknown>";
272 }
273 if (expiration > now)
274 shun_add(issuer, key, expiration - now, reason, issued, lastmod, 0);
275 return 0;
276 }
277
278 static int
279 shun_saxdb_read(struct dict *db)
280 {
281 return dict_foreach(db, shun_add_record, 0) != NULL;
282 }
283
284 static int
285 shun_write_entry(UNUSED_ARG(void *key), void *data, void *extra)
286 {
287 struct shun *ent = data;
288 struct saxdb_context *ctx = extra;
289
290 saxdb_start_record(ctx, ent->target, 0);
291 saxdb_write_int(ctx, KEY_EXPIRES, ent->expires);
292 saxdb_write_int(ctx, KEY_ISSUED, ent->issued);
293 if (ent->lastmod)
294 saxdb_write_int(ctx, KEY_LASTMOD, ent->lastmod);
295 saxdb_write_string(ctx, KEY_REASON, ent->reason);
296 saxdb_write_string(ctx, KEY_ISSUER, ent->issuer);
297 saxdb_end_record(ctx);
298 return 0;
299 }
300
301 static int
302 shun_saxdb_write(struct saxdb_context *ctx)
303 {
304 heap_remove_pred(shun_heap, shun_write_entry, ctx);
305 return 0;
306 }
307
308 static void
309 shun_db_cleanup(void)
310 {
311 heap_delete(shun_heap);
312 dict_delete(shun_dict);
313 }
314
315 void
316 shun_init(void)
317 {
318 shun_heap = heap_new(shun_comparator);
319 shun_dict = dict_new();
320 dict_set_free_data(shun_dict, free_shun_from_dict);
321 saxdb_register("shun", shun_saxdb_read, shun_saxdb_write);
322 reg_exit_func(shun_db_cleanup);
323 }
324
325 struct shun_discrim *
326 shun_discrim_create(struct userNode *user, struct userNode *src, unsigned int argc, char *argv[])
327 {
328 unsigned int i;
329 struct shun_discrim *discrim;
330
331 discrim = calloc(1, sizeof(*discrim));
332 discrim->limit = 50;
333 discrim->max_issued = INT_MAX;
334 discrim->max_lastmod = INT_MAX;
335
336 for (i=0; i<argc; i++) {
337 if (i + 2 > argc) {
338 send_message(user, src, "MSG_MISSING_PARAMS", argv[i]);
339 goto fail;
340 } else if (!irccasecmp(argv[i], "mask") || !irccasecmp(argv[i], "host")) {
341 if (!irccasecmp(argv[++i], "exact"))
342 discrim->target_mask_type = SEXACT;
343 else if (!irccasecmp(argv[i], "subset"))
344 discrim->target_mask_type = SSUBSET;
345 else if (!irccasecmp(argv[i], "superset"))
346 discrim->target_mask_type = SSUPERSET;
347 else
348 discrim->target_mask_type = SSUBSET, i--;
349 if (++i == argc) {
350 send_message(user, src, "MSG_MISSING_PARAMS", argv[i-1]);
351 goto fail;
352 }
353 if (!is_shun(argv[i]) && !IsChannelName(argv[i])) {
354 send_message(user, src, "MSG_INVALID_SHUN", argv[i]);
355 goto fail;
356 }
357 discrim->target_mask = argv[i];
358 discrim->alt_target_mask = shun_alternate_target(discrim->target_mask);
359 } else if (!irccasecmp(argv[i], "limit"))
360 discrim->limit = strtoul(argv[++i], NULL, 0);
361 else if (!irccasecmp(argv[i], "reason"))
362 discrim->reason_mask = argv[++i];
363 else if (!irccasecmp(argv[i], "issuer"))
364 discrim->issuer_mask = argv[++i];
365 else if (!irccasecmp(argv[i], "after"))
366 discrim->min_expire = now + ParseInterval(argv[++i]);
367 else if (!irccasecmp(argv[i], "before"))
368 discrim->max_issued = now - ParseInterval(argv[++i]);
369 else if (!irccasecmp(argv[i], "lastmod")) {
370 const char *cmp = argv[++i];
371 if (cmp[0] == '<') {
372 if (cmp[1] == '=') {
373 discrim->min_lastmod = now - ParseInterval(cmp + 2);
374 } else {
375 discrim->min_lastmod = now - (ParseInterval(cmp + 1) - 1);
376 }
377 } else if (cmp[0] == '>') {
378 if (cmp[1] == '=') {
379 discrim->max_lastmod = now - ParseInterval(cmp + 2);
380 } else {
381 discrim->max_lastmod = now - (ParseInterval(cmp + 1) - 1);
382 }
383 } else {
384 discrim->min_lastmod = now - ParseInterval(cmp + 2);
385 }
386 } else {
387 send_message(user, src, "MSG_INVALID_CRITERIA", argv[i]);
388 goto fail;
389 }
390 }
391 return discrim;
392 fail:
393 free(discrim->alt_target_mask);
394 free(discrim);
395 return NULL;
396 }
397
398 struct shun_search {
399 struct shun_discrim *discrim;
400 shun_search_func func;
401 void *data;
402 unsigned int hits;
403 };
404
405 static int
406 shun_discrim_match(struct shun *shun, struct shun_discrim *discrim)
407 {
408 if ((discrim->issuer_mask && !match_ircglob(shun->issuer, discrim->issuer_mask))
409 || (discrim->reason_mask && !match_ircglob(shun->reason, discrim->reason_mask))
410 || (discrim->target_mask
411 && (((discrim->target_mask_type == SSUBSET)
412 && !match_ircglobs(discrim->target_mask, shun->target)
413 && (!discrim->alt_target_mask
414 || !match_ircglobs(discrim->alt_target_mask, shun->target)))
415 || ((discrim->target_mask_type == SEXACT)
416 && irccasecmp(discrim->target_mask, shun->target)
417 && (!discrim->alt_target_mask
418 || !irccasecmp(discrim->alt_target_mask, shun->target)))
419 || ((discrim->target_mask_type == SSUPERSET)
420 && !match_ircglobs(shun->target, discrim->target_mask)
421 && (!discrim->alt_target_mask
422 || !match_ircglobs(discrim->alt_target_mask, shun->target)))))
423 || (discrim->max_issued < shun->issued)
424 || (discrim->min_expire > shun->expires)
425 || (discrim->min_lastmod > shun->lastmod)
426 || (discrim->max_lastmod < shun->lastmod)) {
427 return 0;
428 }
429 return 1;
430 }
431
432 static int
433 shun_search_helper(UNUSED_ARG(void *key), void *data, void *extra)
434 {
435 struct shun *shun = data;
436 struct shun_search *search = extra;
437
438 if (shun_discrim_match(shun, search->discrim)
439 && (search->hits++ < search->discrim->limit)) {
440 search->func(shun, search->data);
441 }
442 return 0;
443 }
444
445 unsigned int
446 shun_discrim_search(struct shun_discrim *discrim, shun_search_func gsf, void *data)
447 {
448 struct shun_search search;
449 search.discrim = discrim;
450 search.func = gsf;
451 search.data = data;
452 search.hits = 0;
453 heap_remove_pred(shun_heap, shun_search_helper, &search);
454 return search.hits;
455 }