]> jfr.im git - irc/evilnet/x3.git/blob - src/shun.c
shun support.. just the gline code with adjustments
[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 * srvx 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 2 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_ISSUER "issuer"
44 #define KEY_ISSUED "issued"
45
46 static heap_t shun_heap; /* key: expiry time, data: struct shun_entry* */
47 static dict_t shun_dict; /* key: target, data: struct shun_entry* */
48
49 static int
50 shun_comparator(const void *a, const void *b)
51 {
52 const struct shun *ga=a, *gb=b;
53 return ga->expires - gb->expires;
54 }
55
56 static void
57 free_shun_from_dict(void *data)
58 {
59 struct shun *ent = data;
60 free(ent->issuer);
61 free(ent->target);
62 free(ent->reason);
63 free(ent);
64 }
65
66 static void
67 free_shun(struct shun *ent)
68 {
69 dict_remove(shun_dict, ent->target);
70 }
71
72 static int
73 shun_for_p(UNUSED_ARG(void *key), void *data, void *extra)
74 {
75 struct shun *ge = data;
76 return !irccasecmp(ge->target, extra);
77 }
78
79 static int
80 delete_shun_for_p(UNUSED_ARG(void *key), void *data, void *extra)
81 {
82 struct shun *ge = data;
83
84 if (!irccasecmp(ge->target, extra)) {
85 free_shun(ge);
86 return 1;
87 } else {
88 return 0;
89 }
90 }
91
92 static void
93 shun_expire(UNUSED_ARG(void *data))
94 {
95 time_t stopped;
96 void *wraa;
97
98 stopped = 0;
99 while (heap_size(shun_heap)) {
100 heap_peek(shun_heap, 0, &wraa);
101 stopped = ((struct shun*)wraa)->expires;
102 if (stopped > now)
103 break;
104 heap_pop(shun_heap);
105 free_shun(wraa);
106 }
107 if (heap_size(shun_heap))
108 timeq_add(stopped, shun_expire, NULL);
109 }
110
111 int
112 shun_remove(const char *target, int announce)
113 {
114 int res = dict_find(shun_dict, target, NULL) ? 1 : 0;
115 if (heap_remove_pred(shun_heap, delete_shun_for_p, (char*)target)) {
116 void *argh;
117 struct shun *new_first;
118 heap_peek(shun_heap, 0, &argh);
119 if (argh) {
120 new_first = argh;
121 timeq_del(0, shun_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
122 timeq_add(new_first->expires, shun_expire, 0);
123 }
124 }
125 #ifdef WITH_PROTOCOL_BAHAMUT
126 /* Bahamut is sort of lame: It permanently remembers any AKILLs
127 * with durations longer than a day, and will never auto-expire
128 * them. So when the time comes, we'd better remind it. */
129 announce = 1;
130 #endif
131 if (announce)
132 irc_unshun(target);
133 return res;
134 }
135
136 struct shun *
137 shun_add(const char *issuer, const char *target, unsigned long duration, const char *reason, time_t issued, int announce)
138 {
139 struct shun *ent;
140 struct shun *prev_first;
141 void *argh;
142
143 heap_peek(shun_heap, 0, &argh);
144 prev_first = argh;
145 ent = dict_find(shun_dict, target, NULL);
146 if (ent) {
147 heap_remove_pred(shun_heap, shun_for_p, (char*)target);
148 if (ent->expires < (time_t)(now + duration))
149 ent->expires = now + duration;
150 } else {
151 ent = malloc(sizeof(*ent));
152 ent->issued = issued;
153 ent->issuer = strdup(issuer);
154 ent->target = strdup(target);
155 ent->expires = now + duration;
156 ent->reason = strdup(reason);
157 dict_insert(shun_dict, ent->target, ent);
158 }
159 heap_insert(shun_heap, ent, ent);
160 if (!prev_first || (ent->expires < prev_first->expires)) {
161 timeq_del(0, shun_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
162 timeq_add(ent->expires, shun_expire, 0);
163 }
164 if (announce)
165 irc_shun(NULL, ent);
166 return ent;
167 }
168
169 static char *
170 shun_alternate_target(const char *target)
171 {
172 const char *hostname;
173 unsigned long ip;
174 char *res;
175
176 /* If no host part, bail. */
177 if (!(hostname = strchr(target, '@')))
178 return NULL;
179 /* If host part contains wildcards, bail. */
180 if (hostname[strcspn(hostname, "*?/")])
181 return NULL;
182 /* If host part looks like an IP, parse it that way. */
183 if (!hostname[strspn(hostname+1, "0123456789.")+1]) {
184 struct in_addr in;
185 struct hostent *he;
186 if (inet_aton(hostname+1, &in)
187 && (he = gethostbyaddr((char*)&in, sizeof(in), AF_INET))) {
188 res = malloc((hostname - target) + 2 + strlen(he->h_name));
189 sprintf(res, "%.*s@%s", hostname - target, target, he->h_name);
190 return res;
191 } else
192 return NULL;
193 } else if (getipbyname(hostname+1, &ip)) {
194 res = malloc((hostname - target) + 18);
195 sprintf(res, "%.*s@%lu.%lu.%lu.%lu", hostname - target, target, ip & 255, (ip >> 8) & 255, (ip >> 16) & 255, (ip >> 24) & 255);
196 return res;
197 } else
198 return NULL;
199 }
200
201 struct shun *
202 shun_find(const char *target)
203 {
204 struct shun *res;
205 dict_iterator_t it;
206 char *alt_target;
207
208 res = dict_find(shun_dict, target, NULL);
209 if (res)
210 return res;
211 /* Stock ircu requires BADCHANs to match exactly. */
212 if ((target[0] == '#') || (target[0] == '&'))
213 return NULL;
214 else if (target[strcspn(target, "*?")]) {
215 /* Wildcard: do an obnoxiously long search. */
216 for (it = dict_first(shun_dict); it; it = iter_next(it)) {
217 res = iter_data(it);
218 if (match_ircglob(target, res->target))
219 return res;
220 }
221 }
222 /* See if we can resolve the hostname part of the mask. */
223 if ((alt_target = shun_alternate_target(target))) {
224 res = shun_find(alt_target);
225 free(alt_target);
226 return res;
227 }
228 return NULL;
229 }
230
231 static int
232 shun_refresh_helper(UNUSED_ARG(void *key), void *data, void *extra)
233 {
234 struct shun *ge = data;
235 irc_shun(extra, ge);
236 return 0;
237 }
238
239 void
240 shun_refresh_server(struct server *srv)
241 {
242 heap_remove_pred(shun_heap, shun_refresh_helper, srv);
243 }
244
245 void
246 shun_refresh_all(void)
247 {
248 heap_remove_pred(shun_heap, shun_refresh_helper, 0);
249 }
250
251 unsigned int
252 shun_count(void)
253 {
254 return dict_size(shun_dict);
255 }
256
257 static int
258 shun_add_record(const char *key, void *data, UNUSED_ARG(void *extra))
259 {
260 struct record_data *rd = data;
261 const char *issuer, *reason, *dstr;
262 time_t issued, expiration;
263
264 if (!(reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING))) {
265 log_module(MAIN_LOG, LOG_ERROR, "Missing reason for shun %s", key);
266 return 0;
267 }
268 if (!(dstr = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING))) {
269 log_module(MAIN_LOG, LOG_ERROR, "Missing expiration for shun %s", key);
270 return 0;
271 }
272 expiration = strtoul(dstr, NULL, 0);
273 if ((dstr = database_get_data(rd->d.object, KEY_ISSUED, RECDB_QSTRING))) {
274 issued = strtoul(dstr, NULL, 0);
275 } else {
276 issued = now;
277 }
278 if (!(issuer = database_get_data(rd->d.object, KEY_ISSUER, RECDB_QSTRING))) {
279 issuer = "<unknown>";
280 }
281 if (expiration > now)
282 shun_add(issuer, key, expiration - now, reason, issued, 0);
283 return 0;
284 }
285
286 static int
287 shun_saxdb_read(struct dict *db)
288 {
289 return dict_foreach(db, shun_add_record, 0) != NULL;
290 }
291
292 static int
293 shun_write_entry(UNUSED_ARG(void *key), void *data, void *extra)
294 {
295 struct shun *ent = data;
296 struct saxdb_context *ctx = extra;
297
298 saxdb_start_record(ctx, ent->target, 0);
299 saxdb_write_int(ctx, KEY_EXPIRES, ent->expires);
300 saxdb_write_int(ctx, KEY_ISSUED, ent->issued);
301 saxdb_write_string(ctx, KEY_REASON, ent->reason);
302 saxdb_write_string(ctx, KEY_ISSUER, ent->issuer);
303 saxdb_end_record(ctx);
304 return 0;
305 }
306
307 static int
308 shun_saxdb_write(struct saxdb_context *ctx)
309 {
310 heap_remove_pred(shun_heap, shun_write_entry, ctx);
311 return 0;
312 }
313
314 static void
315 shun_db_cleanup(void)
316 {
317 heap_delete(shun_heap);
318 dict_delete(shun_dict);
319 }
320
321 void
322 shun_init(void)
323 {
324 shun_heap = heap_new(shun_comparator);
325 shun_dict = dict_new();
326 dict_set_free_data(shun_dict, free_shun_from_dict);
327 saxdb_register("shun", shun_saxdb_read, shun_saxdb_write);
328 reg_exit_func(shun_db_cleanup);
329 }
330
331 struct shun_discrim *
332 shun_discrim_create(struct userNode *user, struct userNode *src, unsigned int argc, char *argv[])
333 {
334 unsigned int i;
335 struct shun_discrim *discrim;
336
337 discrim = calloc(1, sizeof(*discrim));
338 discrim->max_issued = now;
339 discrim->limit = 50;
340
341 for (i=0; i<argc; i++) {
342 if (i + 2 > argc) {
343 send_message(user, src, "MSG_MISSING_PARAMS", argv[i]);
344 goto fail;
345 } else if (!irccasecmp(argv[i], "mask") || !irccasecmp(argv[i], "host")) {
346 if (!irccasecmp(argv[++i], "exact"))
347 discrim->target_mask_type = SEXACT;
348 else if (!irccasecmp(argv[i], "subset"))
349 discrim->target_mask_type = SSUBSET;
350 else if (!irccasecmp(argv[i], "superset"))
351 discrim->target_mask_type = SSUPERSET;
352 else
353 discrim->target_mask_type = SSUBSET, i--;
354 if (++i == argc) {
355 send_message(user, src, "MSG_MISSING_PARAMS", argv[i-1]);
356 goto fail;
357 }
358 if (!is_shun(argv[i]) && !IsChannelName(argv[i])) {
359 send_message(user, src, "MSG_INVALID_SHUN", argv[i]);
360 goto fail;
361 }
362 discrim->target_mask = argv[i];
363 discrim->alt_target_mask = shun_alternate_target(discrim->target_mask);
364 } else if (!irccasecmp(argv[i], "limit"))
365 discrim->limit = strtoul(argv[++i], NULL, 0);
366 else if (!irccasecmp(argv[i], "reason"))
367 discrim->reason_mask = argv[++i];
368 else if (!irccasecmp(argv[i], "issuer"))
369 discrim->issuer_mask = argv[++i];
370 else if (!irccasecmp(argv[i], "after"))
371 discrim->min_expire = now + ParseInterval(argv[++i]);
372 else if (!irccasecmp(argv[i], "before"))
373 discrim->max_issued = now - ParseInterval(argv[++i]);
374 else {
375 send_message(user, src, "MSG_INVALID_CRITERIA", argv[i]);
376 goto fail;
377 }
378 }
379 return discrim;
380 fail:
381 free(discrim->alt_target_mask);
382 free(discrim);
383 return NULL;
384 }
385
386 struct shun_search {
387 struct shun_discrim *discrim;
388 shun_search_func func;
389 void *data;
390 unsigned int hits;
391 };
392
393 static int
394 shun_discrim_match(struct shun *shun, struct shun_discrim *discrim)
395 {
396 if ((discrim->issuer_mask && !match_ircglob(shun->issuer, discrim->issuer_mask))
397 || (discrim->reason_mask && !match_ircglob(shun->reason, discrim->reason_mask))
398 || (discrim->target_mask
399 && (((discrim->target_mask_type == SSUBSET)
400 && !match_ircglobs(discrim->target_mask, shun->target)
401 && (!discrim->alt_target_mask
402 || !match_ircglobs(discrim->alt_target_mask, shun->target)))
403 || ((discrim->target_mask_type == SEXACT)
404 && irccasecmp(discrim->target_mask, shun->target)
405 && (!discrim->alt_target_mask
406 || !irccasecmp(discrim->alt_target_mask, shun->target)))
407 || ((discrim->target_mask_type == SSUPERSET)
408 && !match_ircglobs(shun->target, discrim->target_mask)
409 && (!discrim->alt_target_mask
410 || !match_ircglobs(discrim->alt_target_mask, shun->target)))))
411 || (discrim->max_issued < shun->issued)
412 || (discrim->min_expire > shun->expires)) {
413 return 0;
414 }
415 return 1;
416 }
417
418 static int
419 shun_search_helper(UNUSED_ARG(void *key), void *data, void *extra)
420 {
421 struct shun *shun = data;
422 struct shun_search *search = extra;
423
424 if (shun_discrim_match(shun, search->discrim)
425 && (search->hits++ < search->discrim->limit)) {
426 search->func(shun, search->data);
427 }
428 return 0;
429 }
430
431 unsigned int
432 shun_discrim_search(struct shun_discrim *discrim, shun_search_func gsf, void *data)
433 {
434 struct shun_search search;
435 search.discrim = discrim;
436 search.func = gsf;
437 search.data = data;
438 search.hits = 0;
439 heap_remove_pred(shun_heap, shun_search_helper, &search);
440 return search.hits;
441 }