Convert time-related variables to consistently use "unsigned long".
[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_LASTMOD "lastmod"
44 #define KEY_ISSUER "issuer"
45 #define KEY_ISSUED "issued"
46
47 static heap_t gline_heap; /* key: expiry time, data: struct gline_entry* */
48 static dict_t gline_dict; /* key: target, data: struct gline_entry* */
49
50 static int
51 gline_comparator(const void *a, const void *b)
52 {
53     const struct gline *ga=a, *gb=b;
54     return ga->expires - gb->expires;
55 }
56
57 static void
58 free_gline_from_dict(void *data)
59 {
60     struct gline *ent = data;
61     free(ent->issuer);
62     free(ent->target);
63     free(ent->reason);
64     free(ent);
65 }
66
67 static void
68 free_gline(struct gline *ent)
69 {
70     dict_remove(gline_dict, ent->target);
71 }
72
73 static int
74 gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
75 {
76     struct gline *ge = data;
77     return !irccasecmp(ge->target, extra);
78 }
79
80 static int
81 delete_gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
82 {
83     struct gline *ge = data;
84
85     if (!irccasecmp(ge->target, extra)) {
86         free_gline(ge);
87         return 1;
88     } else {
89         return 0;
90     }
91 }
92
93 static void
94 gline_expire(UNUSED_ARG(void *data))
95 {
96     unsigned long stopped;
97     void *wraa;
98
99     stopped = 0;
100     while (heap_size(gline_heap)) {
101         heap_peek(gline_heap, 0, &wraa);
102         stopped = ((struct gline*)wraa)->expires;
103         if (stopped > now)
104             break;
105         heap_pop(gline_heap);
106         free_gline(wraa);
107     }
108     if (heap_size(gline_heap))
109         timeq_add(stopped, gline_expire, NULL);
110 }
111
112 int
113 gline_remove(const char *target, int announce)
114 {
115     int res = dict_find(gline_dict, target, NULL) ? 1 : 0;
116     if (heap_remove_pred(gline_heap, delete_gline_for_p, (char*)target)) {
117         void *argh;
118         struct gline *new_first;
119         heap_peek(gline_heap, 0, &argh);
120         if (argh) {
121             new_first = argh;
122             timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
123             timeq_add(new_first->expires, gline_expire, 0);
124         }
125     }
126 #ifdef WITH_PROTOCOL_BAHAMUT
127     /* Bahamut is sort of lame: It permanently remembers any AKILLs
128      * with durations longer than a day, and will never auto-expire
129      * them.  So when the time comes, we'd better remind it.  */
130     announce = 1;
131 #endif
132     if (announce)
133         irc_ungline(target);
134     return res;
135 }
136
137 struct gline *
138 gline_add(const char *issuer, const char *target, unsigned long duration, const char *reason, unsigned long issued, unsigned long lastmod, int announce)
139 {
140     struct gline *ent;
141     struct gline *prev_first;
142     void *argh;
143
144     heap_peek(gline_heap, 0, &argh);
145     prev_first = argh;
146     ent = dict_find(gline_dict, target, NULL);
147     if (ent) {
148         heap_remove_pred(gline_heap, gline_for_p, (char*)target);
149         if (ent->expires < now + duration)
150             ent->expires = now + duration;
151         if (ent->lastmod < lastmod)
152             ent->lastmod = lastmod;
153     } else {
154         ent = malloc(sizeof(*ent));
155         ent->issued = issued;
156         ent->lastmod = lastmod;
157         ent->issuer = strdup(issuer);
158         ent->target = strdup(target);
159         ent->expires = now + duration;
160         ent->reason = strdup(reason);
161         dict_insert(gline_dict, ent->target, ent);
162     }
163     heap_insert(gline_heap, ent, ent);
164     if (!prev_first || (ent->expires < prev_first->expires)) {
165         timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
166         timeq_add(ent->expires, gline_expire, 0);
167     }
168     if (announce)
169         irc_gline(NULL, ent);
170     return ent;
171 }
172
173 static char *
174 gline_alternate_target(const char *target)
175 {
176     const char *hostname;
177
178     /* If no host part, bail. */
179     if (!(hostname = strchr(target, '@')))
180         return NULL;
181     /* If host part contains wildcards, bail. */
182     if (hostname[strcspn(hostname, "*?/")])
183         return NULL;
184     /* Get parsed address and canonical name for host. */
185 #if 0
186     irc_in_addr_t in; /* move this to the right place */
187     if (irc_pton(&in, NULL, hostname+1)) {
188         if (getnameinfo(/*TODO*/))
189               return NULL;
190     } else if (!getaddrinfo(/*TODO*/)) {
191     } else return NULL;
192 #else
193     return NULL;
194 #endif
195 }
196
197 struct gline *
198 gline_find(const char *target)
199 {
200     struct gline *res;
201     dict_iterator_t it;
202     char *alt_target;
203
204     res = dict_find(gline_dict, target, NULL);
205     if (res)
206         return res;
207     /* Stock ircu requires BADCHANs to match exactly. */
208     if ((target[0] == '#') || (target[0] == '&'))
209         return NULL;
210     else if (target[strcspn(target, "*?")]) {
211         /* Wildcard: do an obnoxiously long search. */
212         for (it = dict_first(gline_dict); it; it = iter_next(it)) {
213             res = iter_data(it);
214             if (match_ircglob(target, res->target))
215                 return res;
216         }
217     }
218     /* See if we can resolve the hostname part of the mask. */
219     if ((alt_target = gline_alternate_target(target))) {
220         res = gline_find(alt_target);
221         free(alt_target);
222         return res;
223     }
224     return NULL;
225 }
226
227 static int
228 gline_refresh_helper(UNUSED_ARG(void *key), void *data, void *extra)
229 {
230     struct gline *ge = data;
231     irc_gline(extra, ge);
232     return 0;
233 }
234
235 void
236 gline_refresh_server(struct server *srv)
237 {
238     heap_remove_pred(gline_heap, gline_refresh_helper, srv);
239 }
240
241 void
242 gline_refresh_all(void)
243 {
244     heap_remove_pred(gline_heap, gline_refresh_helper, 0);
245 }
246
247 unsigned int
248 gline_count(void)
249 {
250     return dict_size(gline_dict);
251 }
252
253 static int
254 gline_add_record(const char *key, void *data, UNUSED_ARG(void *extra))
255 {
256     struct record_data *rd = data;
257     const char *issuer, *reason, *dstr;
258     unsigned long issued, expiration, lastmod;
259
260     if (!(reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING))) {
261         log_module(MAIN_LOG, LOG_ERROR, "Missing reason for gline %s", key);
262         return 0;
263     }
264     if (!(dstr = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING))) {
265         log_module(MAIN_LOG, LOG_ERROR, "Missing expiration for gline %s", key);
266         return 0;
267     }
268     expiration = strtoul(dstr, NULL, 0);
269     dstr = database_get_data(rd->d.object, KEY_LASTMOD, RECDB_QSTRING);
270     lastmod = dstr ? strtoul(dstr, NULL, 0) : 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, lastmod, 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     if (ent->lastmod)
300         saxdb_write_int(ctx, KEY_LASTMOD, ent->lastmod);
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->limit = 50;
339     discrim->max_issued = INT_MAX;
340     discrim->max_lastmod = INT_MAX;
341
342     for (i=0; i<argc; i++) {
343         if (i + 2 > argc) {
344             send_message(user, src, "MSG_MISSING_PARAMS", argv[i]);
345             goto fail;
346         } else if (!irccasecmp(argv[i], "mask") || !irccasecmp(argv[i], "host")) {
347             if (!irccasecmp(argv[++i], "exact"))
348                 discrim->target_mask_type = EXACT;
349             else if (!irccasecmp(argv[i], "subset"))
350                 discrim->target_mask_type = SUBSET;
351             else if (!irccasecmp(argv[i], "superset"))
352                 discrim->target_mask_type = SUPERSET;
353             else
354                 discrim->target_mask_type = SUBSET, i--;
355             if (++i == argc) {
356                 send_message(user, src, "MSG_MISSING_PARAMS", argv[i-1]);
357                 goto fail;
358             }
359             if (!is_gline(argv[i]) && !IsChannelName(argv[i])) {
360                 send_message(user, src, "MSG_INVALID_GLINE", argv[i]);
361                 goto fail;
362             }
363             discrim->target_mask = argv[i];
364             discrim->alt_target_mask = gline_alternate_target(discrim->target_mask);
365         } else if (!irccasecmp(argv[i], "limit"))
366             discrim->limit = strtoul(argv[++i], NULL, 0);
367         else if (!irccasecmp(argv[i], "reason"))
368             discrim->reason_mask = argv[++i];
369         else if (!irccasecmp(argv[i], "issuer"))
370             discrim->issuer_mask = argv[++i];
371         else if (!irccasecmp(argv[i], "after"))
372             discrim->min_expire = now + ParseInterval(argv[++i]);
373         else if (!irccasecmp(argv[i], "before"))
374             discrim->max_issued = now - ParseInterval(argv[++i]);
375         else if (!irccasecmp(argv[i], "lastmod")) {
376             const char *cmp = argv[++i];
377             if (cmp[0] == '<') {
378                 if (cmp[1] == '=') {
379                     discrim->min_lastmod = now - ParseInterval(cmp + 2);
380                 } else {
381                     discrim->min_lastmod = now - (ParseInterval(cmp + 1) - 1);
382                 }
383             } else if (cmp[0] == '>') {
384                 if (cmp[1] == '=') {
385                     discrim->max_lastmod = now - ParseInterval(cmp + 2);
386                 } else {
387                     discrim->max_lastmod = now - (ParseInterval(cmp + 1) - 1);
388                 }
389             } else {
390                 discrim->min_lastmod = now - ParseInterval(cmp + 2);
391             }
392         } else {
393             send_message(user, src, "MSG_INVALID_CRITERIA", argv[i]);
394             goto fail;
395         }
396     }
397     return discrim;
398   fail:
399     free(discrim->alt_target_mask);
400     free(discrim);
401     return NULL;
402 }
403
404 struct gline_search {
405     struct gline_discrim *discrim;
406     gline_search_func func;
407     void *data;
408     unsigned int hits;
409 };
410
411 static int
412 gline_discrim_match(struct gline *gline, struct gline_discrim *discrim)
413 {
414     if ((discrim->issuer_mask && !match_ircglob(gline->issuer, discrim->issuer_mask))
415         || (discrim->reason_mask && !match_ircglob(gline->reason, discrim->reason_mask))
416         || (discrim->target_mask
417             && (((discrim->target_mask_type == SUBSET)
418                  && !match_ircglobs(discrim->target_mask, gline->target)
419                  && (!discrim->alt_target_mask
420                      || !match_ircglobs(discrim->alt_target_mask, gline->target)))
421                 || ((discrim->target_mask_type == EXACT)
422                     && irccasecmp(discrim->target_mask, gline->target)
423                     && (!discrim->alt_target_mask
424                         || !irccasecmp(discrim->alt_target_mask, gline->target)))
425                 || ((discrim->target_mask_type == SUPERSET)
426                     && !match_ircglobs(gline->target, discrim->target_mask)
427                     && (!discrim->alt_target_mask
428                         || !match_ircglobs(discrim->alt_target_mask, gline->target)))))
429         || (discrim->max_issued < gline->issued)
430         || (discrim->min_expire > gline->expires)
431         || (discrim->min_lastmod > gline->lastmod)
432         || (discrim->max_lastmod < gline->lastmod)) {
433         return 0;
434     }
435     return 1;
436 }
437
438 static int
439 gline_search_helper(UNUSED_ARG(void *key), void *data, void *extra)
440 {
441     struct gline *gline = data;
442     struct gline_search *search = extra;
443
444     if (gline_discrim_match(gline, search->discrim)
445         && (search->hits++ < search->discrim->limit)) {
446         search->func(gline, search->data);
447     }
448     return 0;
449 }
450
451 unsigned int
452 gline_discrim_search(struct gline_discrim *discrim, gline_search_func gsf, void *data)
453 {
454     struct gline_search search;
455     search.discrim = discrim;
456     search.func = gsf;
457     search.data = data;
458     search.hits = 0;
459     heap_remove_pred(gline_heap, gline_search_helper, &search);
460     return search.hits;
461 }