Support G-line lifetimes.
[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 #define KEY_LIFETIME "lifetime"
47
48 static heap_t gline_heap; /* key: expiry time, data: struct gline_entry* */
49 static dict_t gline_dict; /* key: target, data: struct gline_entry* */
50
51 static int
52 gline_comparator(const void *a, const void *b)
53 {
54     const struct gline *ga=a, *gb=b;
55     return ga->expires - gb->expires;
56 }
57
58 static void
59 free_gline_from_dict(void *data)
60 {
61     struct gline *ent = data;
62     free(ent->issuer);
63     free(ent->target);
64     free(ent->reason);
65     free(ent);
66 }
67
68 static void
69 free_gline(struct gline *ent)
70 {
71     dict_remove(gline_dict, ent->target);
72 }
73
74 static int
75 gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
76 {
77     struct gline *ge = data;
78     return !irccasecmp(ge->target, extra);
79 }
80
81 static void
82 gline_expire(UNUSED_ARG(void *data))
83 {
84     unsigned long stopped;
85     void *wraa;
86
87     stopped = 0;
88     while (heap_size(gline_heap)) {
89         heap_peek(gline_heap, 0, &wraa);
90         stopped = ((struct gline*)wraa)->lifetime;
91         if (stopped > now)
92             break;
93         heap_pop(gline_heap);
94         free_gline(wraa);
95     }
96     if (heap_size(gline_heap))
97         timeq_add(stopped, gline_expire, NULL);
98 }
99
100 int
101 gline_remove(const char *target, int announce)
102 {
103     struct gline *gl;
104
105     gl = dict_find(gline_dict, target, NULL);
106     if (gl != NULL)
107         gl->expires = now;
108     if (announce)
109         irc_ungline(target);
110     return gl != NULL;
111 }
112
113 struct gline *
114 gline_add(const char *issuer, const char *target, unsigned long duration, const char *reason, unsigned long issued, unsigned long lastmod, unsigned long lifetime, int announce)
115 {
116     struct gline *ent;
117     struct gline *prev_first;
118     void *argh;
119     unsigned long expires;
120
121     heap_peek(gline_heap, 0, &argh);
122     prev_first = argh;
123     expires = now + duration;
124     if (lifetime < expires)
125         lifetime = expires;
126     ent = dict_find(gline_dict, target, NULL);
127     if (ent) {
128         heap_remove_pred(gline_heap, gline_for_p, (char*)target);
129         if (ent->expires != expires)
130             ent->expires = expires;
131         if (ent->lifetime < lifetime)
132             ent->lifetime = lifetime;
133         if (ent->lastmod < lastmod)
134             ent->lastmod = lastmod;
135         if (strcmp(ent->issuer, issuer)) {
136             free(ent->issuer);
137             ent->issuer = strdup(issuer);
138         }
139         if (strcmp(ent->reason, reason)) {
140             free(ent->reason);
141             ent->reason = strdup(reason);
142         }
143     } else {
144         ent = malloc(sizeof(*ent));
145         ent->issued = issued;
146         ent->lastmod = lastmod;
147         ent->issuer = strdup(issuer);
148         ent->target = strdup(target);
149         ent->expires = expires;
150         ent->lifetime = lifetime;
151         ent->reason = strdup(reason);
152         dict_insert(gline_dict, ent->target, ent);
153     }
154     heap_insert(gline_heap, ent, ent);
155     if (!prev_first || (ent->lifetime < prev_first->lifetime)) {
156         timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
157         timeq_add(ent->lifetime, gline_expire, 0);
158     }
159     if (announce)
160         irc_gline(NULL, ent);
161     return ent;
162 }
163
164 static char *
165 gline_alternate_target(const char *target)
166 {
167     const char *hostname;
168
169     /* If no host part, bail. */
170     if (!(hostname = strchr(target, '@')))
171         return NULL;
172     /* If host part contains wildcards, bail. */
173     if (hostname[strcspn(hostname, "*?/")])
174         return NULL;
175     /* Get parsed address and canonical name for host. */
176 #if 0
177     irc_in_addr_t in; /* move this to the right place */
178     if (irc_pton(&in, NULL, hostname+1)) {
179         if (getnameinfo(/*TODO*/))
180               return NULL;
181     } else if (!getaddrinfo(/*TODO*/)) {
182     } else return NULL;
183 #else
184     return NULL;
185 #endif
186 }
187
188 struct gline *
189 gline_find(const char *target)
190 {
191     struct gline *res;
192     dict_iterator_t it;
193     char *alt_target;
194
195     res = dict_find(gline_dict, target, NULL);
196     if (res)
197         return res;
198     /* Stock ircu requires BADCHANs to match exactly. */
199     if ((target[0] == '#') || (target[0] == '&'))
200         return NULL;
201     else if (target[strcspn(target, "*?")]) {
202         /* Wildcard: do an obnoxiously long search. */
203         for (it = dict_first(gline_dict); it; it = iter_next(it)) {
204             res = iter_data(it);
205             if (match_ircglob(target, res->target))
206                 return res;
207         }
208     }
209     /* See if we can resolve the hostname part of the mask. */
210     if ((alt_target = gline_alternate_target(target))) {
211         res = gline_find(alt_target);
212         free(alt_target);
213         return res;
214     }
215     return NULL;
216 }
217
218 static int
219 gline_refresh_helper(UNUSED_ARG(void *key), void *data, void *extra)
220 {
221     struct gline *ge = data;
222     irc_gline(extra, ge);
223     return 0;
224 }
225
226 void
227 gline_refresh_server(struct server *srv)
228 {
229     heap_remove_pred(gline_heap, gline_refresh_helper, srv);
230 }
231
232 void
233 gline_refresh_all(void)
234 {
235     heap_remove_pred(gline_heap, gline_refresh_helper, 0);
236 }
237
238 unsigned int
239 gline_count(void)
240 {
241     return dict_size(gline_dict);
242 }
243
244 static int
245 gline_add_record(const char *key, void *data, UNUSED_ARG(void *extra))
246 {
247     struct record_data *rd = data;
248     const char *issuer, *reason, *dstr;
249     unsigned long issued, expiration, lastmod, lifetime;
250
251     if (!(reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING))) {
252         log_module(MAIN_LOG, LOG_ERROR, "Missing reason for gline %s", key);
253         return 0;
254     }
255     if (!(dstr = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING))) {
256         log_module(MAIN_LOG, LOG_ERROR, "Missing expiration for gline %s", key);
257         return 0;
258     }
259     expiration = strtoul(dstr, NULL, 0);
260     dstr = database_get_data(rd->d.object, KEY_LIFETIME, RECDB_QSTRING);
261     lifetime = dstr ? strtoul(dstr, NULL, 0) : expiration;
262     dstr = database_get_data(rd->d.object, KEY_LASTMOD, RECDB_QSTRING);
263     lastmod = dstr ? strtoul(dstr, NULL, 0) : 0;
264     dstr = database_get_data(rd->d.object, KEY_ISSUED, RECDB_QSTRING);
265     issued = dstr ? strtoul(dstr, NULL, 0) : now;
266     if (!(issuer = database_get_data(rd->d.object, KEY_ISSUER, RECDB_QSTRING)))
267         issuer = "<unknown>";
268     if (lifetime > now)
269         gline_add(issuer, key, expiration - now, reason, issued, lastmod, lifetime, 0);
270     return 0;
271 }
272
273 static int
274 gline_saxdb_read(struct dict *db)
275 {
276     return dict_foreach(db, gline_add_record, 0) != NULL;
277 }
278
279 static int
280 gline_write_entry(UNUSED_ARG(void *key), void *data, void *extra)
281 {
282     struct gline *ent = data;
283     struct saxdb_context *ctx = extra;
284
285     saxdb_start_record(ctx, ent->target, 0);
286     saxdb_write_int(ctx, KEY_EXPIRES, ent->expires);
287     saxdb_write_int(ctx, KEY_ISSUED, ent->issued);
288     saxdb_write_int(ctx, KEY_LIFETIME, ent->lifetime);
289     if (ent->lastmod)
290         saxdb_write_int(ctx, KEY_LASTMOD, ent->lastmod);
291     saxdb_write_string(ctx, KEY_REASON, ent->reason);
292     saxdb_write_string(ctx, KEY_ISSUER, ent->issuer);
293     saxdb_end_record(ctx);
294     return 0;
295 }
296
297 static int
298 gline_saxdb_write(struct saxdb_context *ctx)
299 {
300     heap_remove_pred(gline_heap, gline_write_entry, ctx);
301     return 0;
302 }
303
304 static void
305 gline_db_cleanup(void)
306 {
307     heap_delete(gline_heap);
308     dict_delete(gline_dict);
309 }
310
311 void
312 gline_init(void)
313 {
314     gline_heap = heap_new(gline_comparator);
315     gline_dict = dict_new();
316     dict_set_free_data(gline_dict, free_gline_from_dict);
317     saxdb_register("gline", gline_saxdb_read, gline_saxdb_write);
318     reg_exit_func(gline_db_cleanup);
319 }
320
321 struct gline_discrim *
322 gline_discrim_create(struct userNode *user, struct userNode *src, unsigned int argc, char *argv[])
323 {
324     unsigned int i;
325     struct gline_discrim *discrim;
326
327     discrim = calloc(1, sizeof(*discrim));
328     discrim->limit = 50;
329     discrim->max_issued = ULONG_MAX;
330     discrim->max_lastmod = ULONG_MAX;
331     discrim->max_lifetime = ULONG_MAX;
332
333     for (i=0; i<argc; i++) {
334         if (i + 2 > argc) {
335             send_message(user, src, "MSG_MISSING_PARAMS", argv[i]);
336             goto fail;
337         } else if (!irccasecmp(argv[i], "mask") || !irccasecmp(argv[i], "host")) {
338             if (!irccasecmp(argv[++i], "exact"))
339                 discrim->target_mask_type = EXACT;
340             else if (!irccasecmp(argv[i], "subset"))
341                 discrim->target_mask_type = SUBSET;
342             else if (!irccasecmp(argv[i], "superset"))
343                 discrim->target_mask_type = SUPERSET;
344             else
345                 discrim->target_mask_type = SUBSET, i--;
346             if (++i == argc) {
347                 send_message(user, src, "MSG_MISSING_PARAMS", argv[i-1]);
348                 goto fail;
349             }
350             if (!is_gline(argv[i]) && !IsChannelName(argv[i])) {
351                 send_message(user, src, "MSG_INVALID_GLINE", argv[i]);
352                 goto fail;
353             }
354             discrim->target_mask = argv[i];
355             discrim->alt_target_mask = gline_alternate_target(discrim->target_mask);
356         } else if (!irccasecmp(argv[i], "limit"))
357             discrim->limit = strtoul(argv[++i], NULL, 0);
358         else if (!irccasecmp(argv[i], "reason"))
359             discrim->reason_mask = argv[++i];
360         else if (!irccasecmp(argv[i], "issuer"))
361             discrim->issuer_mask = argv[++i];
362         else if (!irccasecmp(argv[i], "after"))
363             discrim->min_expire = now + ParseInterval(argv[++i]);
364         else if (!irccasecmp(argv[i], "before"))
365             discrim->max_issued = now - ParseInterval(argv[++i]);
366         else if (!irccasecmp(argv[i], "lastmod")) {
367             const char *cmp = argv[++i];
368             if (cmp[0] == '<') {
369                 if (cmp[1] == '=') {
370                     discrim->min_lastmod = now - ParseInterval(cmp + 2);
371                 } else {
372                     discrim->min_lastmod = now - (ParseInterval(cmp + 1) - 1);
373                 }
374             } else if (cmp[0] == '>') {
375                 if (cmp[1] == '=') {
376                     discrim->max_lastmod = now - ParseInterval(cmp + 2);
377                 } else {
378                     discrim->max_lastmod = now - (ParseInterval(cmp + 1) - 1);
379                 }
380             } else {
381                 discrim->min_lastmod = now - ParseInterval(cmp + 2);
382             }
383         } else if (!irccasecmp(argv[i], "lifetime")) {
384             const char *cmp = argv[++i];
385             if (cmp[0] == '<') {
386                 if (cmp[1] == '=') {
387                     discrim->min_lifetime = now - ParseInterval(cmp + 2);
388                 } else {
389                     discrim->min_lifetime = now - (ParseInterval(cmp + 1) - 1);
390                 }
391             } else if (cmp[0] == '>') {
392                 if (cmp[1] == '=') {
393                     discrim->max_lifetime = now - ParseInterval(cmp + 2);
394                 } else {
395                     discrim->max_lifetime = now - (ParseInterval(cmp + 1) - 1);
396                 }
397             } else {
398                 discrim->min_lifetime = now - ParseInterval(cmp + 2);
399             }
400         } else {
401             send_message(user, src, "MSG_INVALID_CRITERIA", argv[i]);
402             goto fail;
403         }
404     }
405     return discrim;
406   fail:
407     free(discrim->alt_target_mask);
408     free(discrim);
409     return NULL;
410 }
411
412 struct gline_search {
413     struct gline_discrim *discrim;
414     gline_search_func func;
415     void *data;
416     unsigned int hits;
417 };
418
419 static int
420 gline_discrim_match(struct gline *gline, struct gline_discrim *discrim)
421 {
422     if ((discrim->issuer_mask && !match_ircglob(gline->issuer, discrim->issuer_mask))
423         || (discrim->reason_mask && !match_ircglob(gline->reason, discrim->reason_mask))
424         || (discrim->target_mask
425             && (((discrim->target_mask_type == SUBSET)
426                  && !match_ircglobs(discrim->target_mask, gline->target)
427                  && (!discrim->alt_target_mask
428                      || !match_ircglobs(discrim->alt_target_mask, gline->target)))
429                 || ((discrim->target_mask_type == EXACT)
430                     && irccasecmp(discrim->target_mask, gline->target)
431                     && (!discrim->alt_target_mask
432                         || !irccasecmp(discrim->alt_target_mask, gline->target)))
433                 || ((discrim->target_mask_type == SUPERSET)
434                     && !match_ircglobs(gline->target, discrim->target_mask)
435                     && (!discrim->alt_target_mask
436                         || !match_ircglobs(discrim->alt_target_mask, gline->target)))))
437         || (discrim->max_issued < gline->issued)
438         || (discrim->min_expire > gline->expires)
439         || (discrim->min_lastmod > gline->lastmod)
440         || (discrim->max_lastmod < gline->lastmod)
441         || (discrim->min_lifetime > gline->lifetime)
442         || (discrim->max_lifetime < gline->lifetime)) {
443         return 0;
444     }
445     return 1;
446 }
447
448 static int
449 gline_search_helper(UNUSED_ARG(void *key), void *data, void *extra)
450 {
451     struct gline *gline = data;
452     struct gline_search *search = extra;
453
454     if (gline_discrim_match(gline, search->discrim)
455         && (search->hits++ < search->discrim->limit)) {
456         search->func(gline, search->data);
457     }
458     return 0;
459 }
460
461 unsigned int
462 gline_discrim_search(struct gline_discrim *discrim, gline_search_func gsf, void *data)
463 {
464     struct gline_search search;
465     search.discrim = discrim;
466     search.func = gsf;
467     search.data = data;
468     search.hits = 0;
469     heap_remove_pred(gline_heap, gline_search_helper, &search);
470     return search.hits;
471 }