559541340ea58aa1da4bec3540023910494ba1b1
[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
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     /* Get parsed address and canonical name for host. */
181 #if 0
182     irc_in_addr_t in; /* move this to the right place */
183     if (irc_pton(&in, NULL, hostname+1)) {
184         if (getnameinfo(/*TODO*/))
185               return NULL;
186     } else if (!getaddrinfo(/*TODO*/)) {
187     } else return NULL;
188 #else
189     return NULL;
190 #endif
191 }
192
193 struct gline *
194 gline_find(const char *target)
195 {
196     struct gline *res;
197     dict_iterator_t it;
198     char *alt_target;
199
200     res = dict_find(gline_dict, target, NULL);
201     if (res)
202         return res;
203     /* Stock ircu requires BADCHANs to match exactly. */
204     if ((target[0] == '#') || (target[0] == '&'))
205         return NULL;
206     else if (target[strcspn(target, "*?")]) {
207         /* Wildcard: do an obnoxiously long search. */
208         for (it = dict_first(gline_dict); it; it = iter_next(it)) {
209             res = iter_data(it);
210             if (match_ircglob(target, res->target))
211                 return res;
212         }
213     }
214     /* See if we can resolve the hostname part of the mask. */
215     if ((alt_target = gline_alternate_target(target))) {
216         res = gline_find(alt_target);
217         free(alt_target);
218         return res;
219     }
220     return NULL;
221 }
222
223 static int
224 gline_refresh_helper(UNUSED_ARG(void *key), void *data, void *extra)
225 {
226     struct gline *ge = data;
227     irc_gline(extra, ge);
228     return 0;
229 }
230
231 void
232 gline_refresh_server(struct server *srv)
233 {
234     heap_remove_pred(gline_heap, gline_refresh_helper, srv);
235 }
236
237 void
238 gline_refresh_all(void)
239 {
240     heap_remove_pred(gline_heap, gline_refresh_helper, 0);
241 }
242
243 unsigned int
244 gline_count(void)
245 {
246     return dict_size(gline_dict);
247 }
248
249 static int
250 gline_add_record(const char *key, void *data, UNUSED_ARG(void *extra))
251 {
252     struct record_data *rd = data;
253     const char *issuer, *reason, *dstr;
254     time_t issued, expiration;
255
256     if (!(reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING))) {
257         log_module(MAIN_LOG, LOG_ERROR, "Missing reason for gline %s", key);
258         return 0;
259     }
260     if (!(dstr = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING))) {
261         log_module(MAIN_LOG, LOG_ERROR, "Missing expiration for gline %s", key);
262         return 0;
263     }
264     expiration = strtoul(dstr, NULL, 0);
265     if ((dstr = database_get_data(rd->d.object, KEY_ISSUED, RECDB_QSTRING))) {
266         issued = strtoul(dstr, NULL, 0);
267     } else {
268         issued = now;
269     }
270     if (!(issuer = database_get_data(rd->d.object, KEY_ISSUER, RECDB_QSTRING))) {
271         issuer = "<unknown>";
272     }
273     if (expiration > now)
274         gline_add(issuer, key, expiration - now, reason, issued, 0);
275     return 0;
276 }
277
278 static int
279 gline_saxdb_read(struct dict *db)
280 {
281     return dict_foreach(db, gline_add_record, 0) != NULL;
282 }
283
284 static int
285 gline_write_entry(UNUSED_ARG(void *key), void *data, void *extra)
286 {
287     struct gline *ent = data;
288     struct saxdb_context *ctx = extra;
289
290     saxdb_start_record(ctx, ent->target, 0);
291     saxdb_write_int(ctx, KEY_EXPIRES, ent->expires);
292     saxdb_write_int(ctx, KEY_ISSUED, ent->issued);
293     saxdb_write_string(ctx, KEY_REASON, ent->reason);
294     saxdb_write_string(ctx, KEY_ISSUER, ent->issuer);
295     saxdb_end_record(ctx);
296     return 0;
297 }
298
299 static int
300 gline_saxdb_write(struct saxdb_context *ctx)
301 {
302     heap_remove_pred(gline_heap, gline_write_entry, ctx);
303     return 0;
304 }
305
306 static void
307 gline_db_cleanup(void)
308 {
309     heap_delete(gline_heap);
310     dict_delete(gline_dict);
311 }
312
313 void
314 gline_init(void)
315 {
316     gline_heap = heap_new(gline_comparator);
317     gline_dict = dict_new();
318     dict_set_free_data(gline_dict, free_gline_from_dict);
319     saxdb_register("gline", gline_saxdb_read, gline_saxdb_write);
320     reg_exit_func(gline_db_cleanup);
321 }
322
323 struct gline_discrim *
324 gline_discrim_create(struct userNode *user, struct userNode *src, unsigned int argc, char *argv[])
325 {
326     unsigned int i;
327     struct gline_discrim *discrim;
328
329     discrim = calloc(1, sizeof(*discrim));
330     discrim->max_issued = now;
331     discrim->limit = 50;
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 {
367             send_message(user, src, "MSG_INVALID_CRITERIA", argv[i]);
368             goto fail;
369         }
370     }
371     return discrim;
372   fail:
373     free(discrim->alt_target_mask);
374     free(discrim);
375     return NULL;
376 }
377
378 struct gline_search {
379     struct gline_discrim *discrim;
380     gline_search_func func;
381     void *data;
382     unsigned int hits;
383 };
384
385 static int
386 gline_discrim_match(struct gline *gline, struct gline_discrim *discrim)
387 {
388     if ((discrim->issuer_mask && !match_ircglob(gline->issuer, discrim->issuer_mask))
389         || (discrim->reason_mask && !match_ircglob(gline->reason, discrim->reason_mask))
390         || (discrim->target_mask
391             && (((discrim->target_mask_type == SUBSET)
392                  && !match_ircglobs(discrim->target_mask, gline->target)
393                  && (!discrim->alt_target_mask
394                      || !match_ircglobs(discrim->alt_target_mask, gline->target)))
395                 || ((discrim->target_mask_type == EXACT)
396                     && irccasecmp(discrim->target_mask, gline->target)
397                     && (!discrim->alt_target_mask
398                         || !irccasecmp(discrim->alt_target_mask, gline->target)))
399                 || ((discrim->target_mask_type == SUPERSET)
400                     && !match_ircglobs(gline->target, discrim->target_mask)
401                     && (!discrim->alt_target_mask
402                         || !match_ircglobs(discrim->alt_target_mask, gline->target)))))
403         || (discrim->max_issued < gline->issued)
404         || (discrim->min_expire > gline->expires)) {
405         return 0;
406     }
407     return 1;
408 }
409
410 static int
411 gline_search_helper(UNUSED_ARG(void *key), void *data, void *extra)
412 {
413     struct gline *gline = data;
414     struct gline_search *search = extra;
415
416     if (gline_discrim_match(gline, search->discrim)
417         && (search->hits++ < search->discrim->limit)) {
418         search->func(gline, search->data);
419     }
420     return 0;
421 }
422
423 unsigned int
424 gline_discrim_search(struct gline_discrim *discrim, gline_search_func gsf, void *data)
425 {
426     struct gline_search search;
427     search.discrim = discrim;
428     search.func = gsf;
429     search.data = data;
430     search.hits = 0;
431     heap_remove_pred(gline_heap, gline_search_helper, &search);
432     return search.hits;
433 }