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