ae8600c94c81587a901138b6eec745de3a35159c
[srvx.git] / src / gline.c
1 /* gline.c - Gline database
2  * Copyright 2000-2004 srvx Development Team
3  *
4  * This file is part of srvx.
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 "gline.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 gline_heap; /* key: expiry time, data: struct gline_entry* */
47 static dict_t gline_dict; /* key: target, data: struct gline_entry* */
48
49 static int
50 gline_comparator(const void *a, const void *b)
51 {
52     const struct gline *ga=a, *gb=b;
53     return ga->expires - gb->expires;
54 }
55
56 static void
57 free_gline_from_dict(void *data)
58 {
59     struct gline *ent = data;
60     free(ent->issuer);
61     free(ent->target);
62     free(ent->reason);
63     free(ent);
64 }
65
66 static void
67 free_gline(struct gline *ent)
68 {
69     dict_remove(gline_dict, ent->target);
70 }
71
72 static int
73 gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
74 {
75     struct gline *ge = data;
76     return !irccasecmp(ge->target, extra);
77 }
78
79 static int
80 delete_gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
81 {
82     struct gline *ge = data;
83
84     if (!irccasecmp(ge->target, extra)) {
85         free_gline(ge);
86         return 1;
87     } else {
88         return 0;
89     }
90 }
91
92 static void
93 gline_expire(UNUSED_ARG(void *data))
94 {
95     time_t stopped;
96     void *wraa;
97
98     stopped = 0;
99     while (heap_size(gline_heap)) {
100         heap_peek(gline_heap, 0, &wraa);
101         stopped = ((struct gline*)wraa)->expires;
102         if (stopped > now)
103             break;
104         heap_pop(gline_heap);
105         free_gline(wraa);
106     }
107     if (heap_size(gline_heap))
108         timeq_add(stopped, gline_expire, NULL);
109 }
110
111 int
112 gline_remove(const char *target, int announce)
113 {
114     int res = dict_find(gline_dict, target, NULL) ? 1 : 0;
115     if (heap_remove_pred(gline_heap, delete_gline_for_p, (char*)target)) {
116         void *argh;
117         struct gline *new_first;
118         heap_peek(gline_heap, 0, &argh);
119         if (argh) {
120             new_first = argh;
121             timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
122             timeq_add(new_first->expires, gline_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_ungline(target);
133     return res;
134 }
135
136 struct gline *
137 gline_add(const char *issuer, const char *target, unsigned long duration, const char *reason, time_t issued, int announce)
138 {
139     struct gline *ent;
140     struct gline *prev_first;
141     void *argh;
142
143     heap_peek(gline_heap, 0, &argh);
144     prev_first = argh;
145     ent = dict_find(gline_dict, target, NULL);
146     if (ent) {
147         heap_remove_pred(gline_heap, gline_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(gline_dict, ent->target, ent);
158     }
159     heap_insert(gline_heap, ent, ent);
160     if (!prev_first || (ent->expires < prev_first->expires)) {
161         timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
162         timeq_add(ent->expires, gline_expire, 0);
163     }
164     if (announce)
165         irc_gline(NULL, ent);
166     return ent;
167 }
168
169 static char *
170 gline_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 gline *
202 gline_find(const char *target)
203 {
204     struct gline *res;
205     dict_iterator_t it;
206     char *alt_target;
207
208     res = dict_find(gline_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(gline_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 = gline_alternate_target(target))) {
224         res = gline_find(alt_target);
225         free(alt_target);
226         return res;
227     }
228     return NULL;
229 }
230
231 static int
232 gline_refresh_helper(UNUSED_ARG(void *key), void *data, void *extra)
233 {
234     struct gline *ge = data;
235     irc_gline(extra, ge);
236     return 0;
237 }
238
239 void
240 gline_refresh_server(struct server *srv)
241 {
242     heap_remove_pred(gline_heap, gline_refresh_helper, srv);
243 }
244
245 void
246 gline_refresh_all(void)
247 {
248     heap_remove_pred(gline_heap, gline_refresh_helper, 0);
249 }
250
251 unsigned int
252 gline_count(void)
253 {
254     return dict_size(gline_dict);
255 }
256
257 static int
258 gline_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 gline %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 gline %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         gline_add(issuer, key, expiration - now, reason, issued, 0);
283     return 0;
284 }
285
286 static int
287 gline_saxdb_read(struct dict *db)
288 {
289     return dict_foreach(db, gline_add_record, 0) != NULL;
290 }
291
292 static int
293 gline_write_entry(UNUSED_ARG(void *key), void *data, void *extra)
294 {
295     struct gline *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 gline_saxdb_write(struct saxdb_context *ctx)
309 {
310     heap_remove_pred(gline_heap, gline_write_entry, ctx);
311     return 0;
312 }
313
314 static void
315 gline_db_cleanup(void)
316 {
317     heap_delete(gline_heap);
318     dict_delete(gline_dict);
319 }
320
321 void
322 gline_init(void)
323 {
324     gline_heap = heap_new(gline_comparator);
325     gline_dict = dict_new();
326     dict_set_free_data(gline_dict, free_gline_from_dict);
327     saxdb_register("gline", gline_saxdb_read, gline_saxdb_write);
328     reg_exit_func(gline_db_cleanup);
329 }
330
331 struct gline_discrim *
332 gline_discrim_create(struct userNode *user, struct userNode *src, unsigned int argc, char *argv[])
333 {
334     unsigned int i;
335     struct gline_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 = EXACT;
348             else if (!irccasecmp(argv[i], "subset"))
349                 discrim->target_mask_type = SUBSET;
350             else if (!irccasecmp(argv[i], "superset"))
351                 discrim->target_mask_type = SUPERSET;
352             else
353                 discrim->target_mask_type = SUBSET, i--;
354             if (++i == argc) {
355                 send_message(user, src, "MSG_MISSING_PARAMS", argv[i-1]);
356                 goto fail;
357             }
358             if (!is_gline(argv[i]) && !IsChannelName(argv[i])) {
359                 send_message(user, src, "MSG_INVALID_GLINE", argv[i]);
360                 goto fail;
361             }
362             discrim->target_mask = argv[i];
363             discrim->alt_target_mask = gline_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 gline_search {
387     struct gline_discrim *discrim;
388     gline_search_func func;
389     void *data;
390     unsigned int hits;
391 };
392
393 static int
394 gline_discrim_match(struct gline *gline, struct gline_discrim *discrim)
395 {
396     if ((discrim->issuer_mask && !match_ircglob(gline->issuer, discrim->issuer_mask))
397         || (discrim->reason_mask && !match_ircglob(gline->reason, discrim->reason_mask))
398         || (discrim->target_mask
399             && (((discrim->target_mask_type == SUBSET)
400                  && !match_ircglobs(discrim->target_mask, gline->target)
401                  && (!discrim->alt_target_mask
402                      || !match_ircglobs(discrim->alt_target_mask, gline->target)))
403                 || ((discrim->target_mask_type == EXACT)
404                     && irccasecmp(discrim->target_mask, gline->target)
405                     && (!discrim->alt_target_mask
406                         || !irccasecmp(discrim->alt_target_mask, gline->target)))
407                 || ((discrim->target_mask_type == SUPERSET)
408                     && !match_ircglobs(gline->target, discrim->target_mask)
409                     && (!discrim->alt_target_mask
410                         || !match_ircglobs(discrim->alt_target_mask, gline->target)))))
411         || (discrim->max_issued < gline->issued)
412         || (discrim->min_expire > gline->expires)) {
413         return 0;
414     }
415     return 1;
416 }
417
418 static int
419 gline_search_helper(UNUSED_ARG(void *key), void *data, void *extra)
420 {
421     struct gline *gline = data;
422     struct gline_search *search = extra;
423
424     if (gline_discrim_match(gline, search->discrim)
425         && (search->hits++ < search->discrim->limit)) {
426         search->func(gline, search->data);
427     }
428     return 0;
429 }
430
431 unsigned int
432 gline_discrim_search(struct gline_discrim *discrim, gline_search_func gsf, void *data)
433 {
434     struct gline_search search;
435     search.discrim = discrim;
436     search.func = gsf;
437     search.data = data;
438     search.hits = 0;
439     heap_remove_pred(gline_heap, gline_search_helper, &search);
440     return search.hits;
441 }