Initial import (again)
[srvx.git] / src / gline.c
1 /* gline.c - Gline database
2  * Copyright 2000-2004 srvx Development Team
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.  Important limitations are
8  * listed in the COPYING file that accompanies this software.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, email srvx-maintainers@srvx.net.
17  */
18
19 #include "heap.h"
20 #include "helpfile.h"
21 #include "log.h"
22 #include "saxdb.h"
23 #include "timeq.h"
24 #include "gline.h"
25
26 #ifdef HAVE_SYS_SOCKET_H
27 #include <sys/socket.h>
28 #endif
29 #ifdef HAVE_NETINET_IN_H
30 #include <netinet/in.h>
31 #endif
32 #ifdef HAVE_ARPA_INET_H
33 #include <arpa/inet.h>
34 #endif
35 #ifdef HAVE_NETDB_H
36 #include <netdb.h>
37 #endif
38
39 #define KEY_REASON "reason"
40 #define KEY_EXPIRES "expires"
41 #define KEY_ISSUER "issuer"
42 #define KEY_ISSUED "issued"
43
44 static heap_t gline_heap; /* key: expiry time, data: struct gline_entry* */
45 static dict_t gline_dict; /* key: target, data: struct gline_entry* */
46
47 static int
48 gline_comparator(const void *a, const void *b)
49 {
50     const struct gline *ga=a, *gb=b;
51     return ga->expires - gb->expires;
52 }
53
54 static void
55 free_gline_from_dict(void *data)
56 {
57     struct gline *ent = data;
58     free(ent->issuer);
59     free(ent->target);
60     free(ent->reason);
61     free(ent);
62 }
63
64 static void
65 free_gline(struct gline *ent)
66 {
67     dict_remove(gline_dict, ent->target);
68 }
69
70 static int
71 gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
72 {
73     struct gline *ge = data;
74     return !irccasecmp(ge->target, extra);
75 }
76
77 static int
78 delete_gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
79 {
80     struct gline *ge = data;
81
82     if (!irccasecmp(ge->target, extra)) {
83         free_gline(ge);
84         return 1;
85     } else {
86         return 0;
87     }
88 }
89
90 static void
91 gline_expire(UNUSED_ARG(void *data))
92 {
93     time_t stopped;
94     void *wraa;
95
96     stopped = 0;
97     while (heap_size(gline_heap)) {
98         heap_peek(gline_heap, 0, &wraa);
99         stopped = ((struct gline*)wraa)->expires;
100         if (stopped > now)
101             break;
102         heap_pop(gline_heap);
103         free_gline(wraa);
104     }
105     if (heap_size(gline_heap))
106         timeq_add(stopped, gline_expire, NULL);
107 }
108
109 int
110 gline_remove(const char *target, int announce)
111 {
112     int res = dict_find(gline_dict, target, NULL) ? 1 : 0;
113     if (heap_remove_pred(gline_heap, delete_gline_for_p, (char*)target)) {
114         void *argh;
115         struct gline *new_first;
116         heap_peek(gline_heap, 0, &argh);
117         if (argh) {
118             new_first = argh;
119             timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
120             timeq_add(new_first->expires, gline_expire, 0);
121         }
122     }
123 #ifdef WITH_PROTOCOL_BAHAMUT
124     /* Bahamut is sort of lame: It permanently remembers any AKILLs
125      * with durations longer than a day, and will never auto-expire
126      * them.  So when the time comes, we'd better remind it.  */
127     announce = 1;
128 #endif
129     if (announce)
130         irc_ungline(target);
131     return res;
132 }
133
134 struct gline *
135 gline_add(const char *issuer, const char *target, unsigned long duration, const char *reason, time_t issued, int announce)
136 {
137     struct gline *ent;
138     struct gline *prev_first;
139     void *argh;
140
141     heap_peek(gline_heap, 0, &argh);
142     prev_first = argh;
143     ent = dict_find(gline_dict, target, NULL);
144     if (ent) {
145         heap_remove_pred(gline_heap, gline_for_p, (char*)target);
146         if (ent->expires < (time_t)(now + duration))
147             ent->expires = now + duration;
148     } else {
149         ent = malloc(sizeof(*ent));
150         ent->issued = issued;
151         ent->issuer = strdup(issuer);
152         ent->target = strdup(target);
153         ent->expires = now + duration;
154         ent->reason = strdup(reason);
155         dict_insert(gline_dict, ent->target, ent);
156     }
157     heap_insert(gline_heap, ent, ent);
158     if (!prev_first || (ent->expires < prev_first->expires)) {
159         timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
160         timeq_add(ent->expires, gline_expire, 0);
161     }
162     if (announce)
163         irc_gline(NULL, ent);
164     return ent;
165 }
166
167 static char *
168 gline_alternate_target(const char *target)
169 {
170     const char *hostname;
171     unsigned long ip;
172     char *res;
173
174     /* If no host part, bail. */
175     if (!(hostname = strchr(target, '@')))
176         return NULL;
177     /* If host part contains wildcards, bail. */
178     if (hostname[strcspn(hostname, "*?/")])
179         return NULL;
180     /* If host part looks like an IP, parse it that way. */
181     if (!hostname[strspn(hostname+1, "0123456789.")+1]) {
182         struct in_addr in;
183         struct hostent *he;
184         if (inet_aton(hostname+1, &in)
185             && (he = gethostbyaddr(&in, sizeof(in), AF_INET))) {
186             res = malloc((hostname - target) + 2 + strlen(he->h_name));
187             sprintf(res, "%.*s@%s", hostname - target, target, he->h_name);
188             return res;
189         } else
190             return NULL;
191     } else if (getipbyname(hostname+1, &ip)) {
192         res = malloc((hostname - target) + 18);
193         sprintf(res, "%.*s@%lu.%lu.%lu.%lu", hostname - target, target, ip & 255, (ip >> 8) & 255, (ip >> 16) & 255, (ip >> 24) & 255);
194         return res;
195     } else
196         return NULL;
197 }
198
199 struct gline *
200 gline_find(const char *target)
201 {
202     struct gline *res;
203     dict_iterator_t it;
204     char *alt_target;
205
206     res = dict_find(gline_dict, target, NULL);
207     if (res)
208         return res;
209     /* Stock ircu requires BADCHANs to match exactly. */
210     if ((target[0] == '#') || (target[0] == '&'))
211         return NULL;
212     else if (target[strcspn(target, "*?")]) {
213         /* Wildcard: do an obnoxiously long search. */
214         for (it = dict_first(gline_dict); it; it = iter_next(it)) {
215             res = iter_data(it);
216             if (match_ircglob(target, res->target))
217                 return res;
218         }
219     }
220     /* See if we can resolve the hostname part of the mask. */
221     if ((alt_target = gline_alternate_target(target))) {
222         res = gline_find(alt_target);
223         free(alt_target);
224         return res;
225     }
226     return NULL;
227 }
228
229 static int
230 gline_refresh_helper(UNUSED_ARG(void *key), void *data, void *extra)
231 {
232     struct gline *ge = data;
233     irc_gline(extra, ge);
234     return 0;
235 }
236
237 void
238 gline_refresh_server(struct server *srv)
239 {
240     heap_remove_pred(gline_heap, gline_refresh_helper, srv);
241 }
242
243 void
244 gline_refresh_all(void)
245 {
246     heap_remove_pred(gline_heap, gline_refresh_helper, 0);
247 }
248
249 unsigned int
250 gline_count(void)
251 {
252     return dict_size(gline_dict);
253 }
254
255 static int
256 gline_add_record(const char *key, void *data, UNUSED_ARG(void *extra))
257 {
258     struct record_data *rd = data;
259     const char *issuer, *reason, *dstr;
260     time_t issued, expiration;
261
262     if (!(reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING))) {
263         log_module(MAIN_LOG, LOG_ERROR, "Missing reason for gline %s", key);
264         return 0;
265     }
266     if (!(dstr = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING))) {
267         log_module(MAIN_LOG, LOG_ERROR, "Missing expiration for gline %s", key);
268         return 0;
269     }
270     expiration = strtoul(dstr, NULL, 0);
271     if ((dstr = database_get_data(rd->d.object, KEY_ISSUED, RECDB_QSTRING))) {
272         issued = strtoul(dstr, NULL, 0);
273     } else {
274         issued = now;
275     }
276     if (!(issuer = database_get_data(rd->d.object, KEY_ISSUER, RECDB_QSTRING))) {
277         issuer = "<unknown>";
278     }
279     if (expiration > now)
280         gline_add(issuer, key, expiration - now, reason, issued, 0);
281     return 0;
282 }
283
284 static int
285 gline_saxdb_read(struct dict *db)
286 {
287     return dict_foreach(db, gline_add_record, 0) != NULL;
288 }
289
290 static int
291 gline_write_entry(UNUSED_ARG(void *key), void *data, void *extra)
292 {
293     struct gline *ent = data;
294     struct saxdb_context *ctx = extra;
295
296     saxdb_start_record(ctx, ent->target, 0);
297     saxdb_write_int(ctx, KEY_EXPIRES, ent->expires);
298     saxdb_write_int(ctx, KEY_ISSUED, ent->issued);
299     saxdb_write_string(ctx, KEY_REASON, ent->reason);
300     saxdb_write_string(ctx, KEY_ISSUER, ent->issuer);
301     saxdb_end_record(ctx);
302     return 0;
303 }
304
305 static int
306 gline_saxdb_write(struct saxdb_context *ctx)
307 {
308     heap_remove_pred(gline_heap, gline_write_entry, ctx);
309     return 0;
310 }
311
312 static void
313 gline_db_cleanup(void)
314 {
315     heap_delete(gline_heap);
316     dict_delete(gline_dict);
317 }
318
319 void
320 gline_init(void)
321 {
322     gline_heap = heap_new(gline_comparator);
323     gline_dict = dict_new();
324     dict_set_free_data(gline_dict, free_gline_from_dict);
325     saxdb_register("gline", gline_saxdb_read, gline_saxdb_write);
326     reg_exit_func(gline_db_cleanup);
327 }
328
329 struct gline_discrim *
330 gline_discrim_create(struct userNode *user, struct userNode *src, unsigned int argc, char *argv[])
331 {
332     unsigned int i;
333     struct gline_discrim *discrim;
334
335     discrim = calloc(1, sizeof(*discrim));
336     discrim->max_issued = now;
337     discrim->limit = 50;
338
339     for (i=0; i<argc; i++) {
340         if (i + 2 > argc) {
341             send_message(user, src, "MSG_MISSING_PARAMS", argv[i]);
342             goto fail;
343         } else if (!irccasecmp(argv[i], "mask") || !irccasecmp(argv[i], "host")) {
344             if (!irccasecmp(argv[++i], "exact"))
345                 discrim->target_mask_type = EXACT;
346             else if (!irccasecmp(argv[i], "subset"))
347                 discrim->target_mask_type = SUBSET;
348             else if (!irccasecmp(argv[i], "superset"))
349                 discrim->target_mask_type = SUPERSET;
350             else
351                 discrim->target_mask_type = SUBSET, i--;
352             if (++i == argc) {
353                 send_message(user, src, "MSG_MISSING_PARAMS", argv[i-1]);
354                 goto fail;
355             }
356             if (!is_gline(argv[i]) && !IsChannelName(argv[i])) {
357                 send_message(user, src, "MSG_INVALID_GLINE", argv[i]);
358                 goto fail;
359             }
360             discrim->target_mask = argv[i];
361             discrim->alt_target_mask = gline_alternate_target(discrim->target_mask);
362         } else if (!irccasecmp(argv[i], "limit"))
363             discrim->limit = strtoul(argv[++i], NULL, 0);
364         else if (!irccasecmp(argv[i], "reason"))
365             discrim->reason_mask = argv[++i];
366         else if (!irccasecmp(argv[i], "issuer"))
367             discrim->issuer_mask = argv[++i];
368         else if (!irccasecmp(argv[i], "after"))
369             discrim->min_expire = now + ParseInterval(argv[++i]);
370         else if (!irccasecmp(argv[i], "before"))
371             discrim->max_issued = now - ParseInterval(argv[++i]);
372         else {
373             send_message(user, src, "MSG_INVALID_CRITERIA", argv[i]);
374             goto fail;
375         }
376     }
377     return discrim;
378   fail:
379     free(discrim->alt_target_mask);
380     free(discrim);
381     return NULL;
382 }
383
384 struct gline_search {
385     struct gline_discrim *discrim;
386     gline_search_func func;
387     void *data;
388     unsigned int hits;
389 };
390
391 static int
392 gline_discrim_match(struct gline *gline, struct gline_discrim *discrim)
393 {
394     if ((discrim->issuer_mask && !match_ircglob(gline->issuer, discrim->issuer_mask))
395         || (discrim->reason_mask && !match_ircglob(gline->reason, discrim->reason_mask))
396         || (discrim->target_mask
397             && (((discrim->target_mask_type == SUBSET)
398                  && !match_ircglobs(discrim->target_mask, gline->target)
399                  && (!discrim->alt_target_mask
400                      || !match_ircglobs(discrim->alt_target_mask, gline->target)))
401                 || ((discrim->target_mask_type == EXACT)
402                     && irccasecmp(discrim->target_mask, gline->target)
403                     && (!discrim->alt_target_mask
404                         || !irccasecmp(discrim->alt_target_mask, gline->target)))
405                 || ((discrim->target_mask_type == SUPERSET)
406                     && !match_ircglobs(gline->target, discrim->target_mask)
407                     && (!discrim->alt_target_mask
408                         || !match_ircglobs(discrim->alt_target_mask, gline->target)))))
409         || (discrim->max_issued < gline->issued)
410         || (discrim->min_expire > gline->expires)) {
411         return 0;
412     }
413     return 1;
414 }
415
416 static int
417 gline_search_helper(UNUSED_ARG(void *key), void *data, void *extra)
418 {
419     struct gline *gline = data;
420     struct gline_search *search = extra;
421
422     if (gline_discrim_match(gline, search->discrim)
423         && (search->hits++ < search->discrim->limit)) {
424         search->func(gline, search->data);
425     }
426     return 0;
427 }
428
429 unsigned int
430 gline_discrim_search(struct gline_discrim *discrim, gline_search_func gsf, void *data)
431 {
432     struct gline_search search;
433     search.discrim = discrim;
434     search.func = gsf;
435     search.data = data;
436     search.hits = 0;
437     heap_remove_pred(gline_heap, gline_search_helper, &search);
438     return search.hits;
439 }