]> jfr.im git - irc/evilnet/x3.git/blob - src/mod-blacklist.c
Minor typo in previous commit where returning 0 when it should have been 1 from opser...
[irc/evilnet/x3.git] / src / mod-blacklist.c
1 /* Blacklist module for srvx 1.x
2 * Copyright 2007 Michael Poole <mdpoole@troilus.org>
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 3 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 "conf.h"
22 #include "gline.h"
23 #include "modcmd.h"
24 #include "proto.h"
25 #include "sar.h"
26
27 const char *blacklist_module_deps[] = { NULL };
28
29 struct dnsbl_zone {
30 struct string_list reasons;
31 const char *description;
32 const char *reason;
33 unsigned int duration;
34 unsigned int mask;
35 unsigned int debug : 1;
36 char zone[1];
37 };
38
39 struct dnsbl_data {
40 char client_ip[IRC_NTOP_MAX_SIZE];
41 char zone_name[1];
42 };
43
44 static struct log_type *bl_log;
45 static dict_t blacklist_zones; /* contains struct dnsbl_zone */
46 static dict_t blacklist_hosts; /* maps IPs or hostnames to reasons from blacklist_reasons */
47 static dict_t blacklist_reasons; /* maps strings to themselves (poor man's data sharing) */
48
49 static struct {
50 struct userNode *debug_bot;
51 struct chanNode *debug_channel;
52 unsigned long gline_duration;
53 } conf;
54
55 #if defined(GCC_VARMACROS)
56 # define blacklist_debug(ARGS...) do { if (conf.debug_bot && conf.debug_channel) send_channel_notice(conf.debug_channel, conf.debug_bot, ARGS); } while (0)
57 #elif defined(C99_VARMACROS)
58 # define blacklist_debug(...) do { if (conf.debug_bot && conf.debug_channel) send_channel_notice(conf.debug_channel, conf.debug_bot, __VA_ARGS__); } while (0)
59 #endif
60
61 static void
62 do_expandos(char *output, unsigned int out_len, const char *input, ...)
63 {
64 va_list args;
65 const char *key;
66 const char *datum;
67 char *found;
68 unsigned int klen;
69 unsigned int dlen;
70 unsigned int rlen;
71
72 safestrncpy(output, input, out_len);
73 va_start(args, input);
74 while ((key = va_arg(args, const char*)) != NULL) {
75 datum = va_arg(args, const char *);
76 klen = strlen(key);
77 dlen = strlen(datum);
78 for (found = output; (found = strstr(output, key)) != NULL; found += dlen) {
79 rlen = strlen(found + klen);
80 if ((dlen > klen) && ((unsigned)(found + dlen + rlen - output) > out_len))
81 rlen = output + out_len - found - dlen;
82 memmove(found + dlen, found + klen, rlen);
83 memcpy(found, datum, dlen + 1);
84 }
85 }
86 va_end(args);
87 }
88
89 static void
90 dnsbl_hit(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size)
91 {
92 struct dnsbl_data *data;
93 struct dnsbl_zone *zone;
94 const char *message;
95 char *txt;
96 unsigned int mask;
97 unsigned int pos;
98 unsigned int len;
99 unsigned int ii;
100 char reason[MAXLEN];
101 char target[IRC_NTOP_MAX_SIZE + 2];
102
103 /* Get the DNSBL zone (to make sure it has not disappeared in a rehash). */
104 data = (struct dnsbl_data*)(req + 1);
105 zone = dict_find(blacklist_zones, data->zone_name, NULL);
106 if (!zone)
107 return;
108
109 /* Scan the results. */
110 for (mask = 0, ii = 0, txt = NULL; ii < hdr->ancount; ++ii) {
111 pos = rr[ii].rd_start;
112 switch (rr[ii].type) {
113 case REQ_TYPE_A:
114 if (rr[ii].rdlength != 4)
115 break;
116 if (pos + 3 < raw_size)
117 mask |= (1 << raw[pos + 3]);
118 break;
119 case REQ_TYPE_TXT:
120 len = raw[pos];
121 txt = malloc(len + 1);
122 memcpy(txt, raw + pos + 1, len);
123 txt[len] = '\0';
124 break;
125 }
126 }
127
128 /* Do we care about one of the masks we found? */
129 if (mask & zone->mask) {
130 /* See if a per-result message was provided. */
131 for (ii = 0, message = NULL; mask && (ii < zone->reasons.used); ++ii, mask >>= 1) {
132 if (0 == (mask & 1))
133 continue;
134 if (NULL != (message = zone->reasons.list[ii]))
135 break;
136 }
137
138 /* If not, use a standard fallback. */
139 if (message == NULL) {
140 message = zone->reason;
141 if (message == NULL)
142 message = "client is blacklisted";
143 }
144
145 /* Expand elements of the message as necessary. */
146 do_expandos(reason, sizeof(reason), message, "%txt%", (txt ? txt : "(no-txt)"), "%ip%", data->client_ip, NULL);
147
148 if (zone->debug) {
149 blacklist_debug("DNSBL match: [%s] %s (%s)", zone->zone, data->client_ip, reason);
150 } else {
151 /* Now generate the G-line. */
152 target[0] = '*';
153 target[1] = '@';
154 strcpy(target + 2, data->client_ip);
155 gline_add(self->name, target, zone->duration, reason, now, 1, 0);
156 }
157 }
158 free(txt);
159 }
160
161 static int
162 blacklist_check_user(struct userNode *user)
163 {
164 static const char *hexdigits = "0123456789abcdef";
165 dict_iterator_t it;
166 const char *reason;
167 const char *host;
168 unsigned int dnsbl_len;
169 unsigned int ii;
170 char ip[IRC_NTOP_MAX_SIZE];
171 char dnsbl_target[128];
172
173 /* Users added during burst should not be checked. */
174 if (user->uplink->burst)
175 return 0;
176
177 /* Users with bogus IPs are probably service bots. */
178 if (!irc_in_addr_is_valid(user->ip))
179 return 0;
180
181 /* Check local file-based blacklist. */
182 irc_ntop(ip, sizeof(ip), &user->ip);
183 reason = dict_find(blacklist_hosts, host = ip, NULL);
184 if (reason == NULL) {
185 reason = dict_find(blacklist_hosts, host = user->hostname, NULL);
186 }
187 if (reason != NULL) {
188 char *target;
189 target = alloca(strlen(host) + 3);
190 target[0] = '*';
191 target[1] = '@';
192 strcpy(target + 2, host);
193 gline_add(self->name, target, conf.gline_duration, reason, now, 1, 0);
194 }
195
196 /* Figure out the base part of a DNS blacklist hostname. */
197 if (irc_in_addr_is_ipv4(user->ip)) {
198 dnsbl_len = snprintf(dnsbl_target, sizeof(dnsbl_target), "%d.%d.%d.%d.", user->ip.in6_8[15], user->ip.in6_8[14], user->ip.in6_8[13], user->ip.in6_8[12]);
199 } else if (irc_in_addr_is_ipv6(user->ip)) {
200 for (ii = 0; ii < 16; ++ii) {
201 dnsbl_target[ii * 4 + 0] = hexdigits[user->ip.in6_8[15 - ii] & 15];
202 dnsbl_target[ii * 4 + 1] = '.';
203 dnsbl_target[ii * 4 + 2] = hexdigits[user->ip.in6_8[15 - ii] >> 4];
204 dnsbl_target[ii * 4 + 3] = '.';
205 }
206 dnsbl_len = 48;
207 } else {
208 return 0;
209 }
210
211 /* Start a lookup for the appropriate hostname in each DNSBL. */
212 for (it = dict_first(blacklist_zones); it; it = iter_next(it)) {
213 struct dnsbl_data *data;
214 struct sar_request *req;
215 const char *zone;
216
217 zone = iter_key(it);
218 safestrncpy(dnsbl_target + dnsbl_len, zone, sizeof(dnsbl_target) - dnsbl_len);
219 req = sar_request_simple(sizeof(*data) + strlen(zone), dnsbl_hit, NULL, dnsbl_target, REQ_QTYPE_ALL, NULL);
220 if (req) {
221 data = (struct dnsbl_data*)(req + 1);
222 strcpy(data->client_ip, ip);
223 strcpy(data->zone_name, zone);
224 }
225 }
226
227 return 0;
228 }
229
230 static void
231 blacklist_load_file(const char *filename, const char *default_reason)
232 {
233 FILE *file;
234 const char *reason;
235 char *mapped_reason;
236 char *sep;
237 size_t len;
238 char linebuf[MAXLEN];
239
240 if (!filename)
241 return;
242 if (!default_reason)
243 default_reason = "client is blacklisted";
244 file = fopen(filename, "r");
245 if (!file) {
246 log_module(bl_log, LOG_ERROR, "Unable to open %s for reading: %s", filename, strerror(errno));
247 return;
248 }
249 log_module(bl_log, LOG_DEBUG, "Loading blacklist from %s.", filename);
250 while (fgets(linebuf, sizeof(linebuf), file)) {
251 /* Trim whitespace from end of line. */
252 len = strlen(linebuf);
253 while (isspace(linebuf[len-1]))
254 linebuf[--len] = '\0';
255
256 /* Figure out which reason string we should use. */
257 reason = default_reason;
258 sep = strchr(linebuf, ' ');
259 if (sep) {
260 *sep++ = '\0';
261 while (isspace(*sep))
262 sep++;
263 if (*sep != '\0')
264 reason = sep;
265 }
266
267 /* See if the reason string is already known. */
268 mapped_reason = dict_find(blacklist_reasons, reason, NULL);
269 if (!mapped_reason) {
270 mapped_reason = strdup(reason);
271 dict_insert(blacklist_reasons, mapped_reason, (char*)mapped_reason);
272 }
273
274 /* Store the blacklist entry. */
275 dict_insert(blacklist_hosts, strdup(linebuf), mapped_reason);
276 }
277 fclose(file);
278 }
279
280 static void
281 dnsbl_zone_free(void *pointer)
282 {
283 struct dnsbl_zone *zone;
284 zone = pointer;
285 free(zone->reasons.list);
286 free(zone);
287 }
288
289 static void
290 blacklist_conf_read(void)
291 {
292 dict_t node;
293 dict_t subnode;
294 const char *str1;
295 const char *str2;
296
297 dict_delete(blacklist_zones);
298 blacklist_zones = dict_new();
299 dict_set_free_data(blacklist_zones, dnsbl_zone_free);
300
301 dict_delete(blacklist_hosts);
302 blacklist_hosts = dict_new();
303 dict_set_free_keys(blacklist_hosts, free);
304
305 dict_delete(blacklist_reasons);
306 blacklist_reasons = dict_new();
307 dict_set_free_keys(blacklist_reasons, free);
308
309 node = conf_get_data("modules/blacklist", RECDB_OBJECT);
310 if (node == NULL)
311 return;
312
313 str1 = database_get_data(node, "debug_bot", RECDB_QSTRING);
314 if (str1)
315 conf.debug_bot = GetUserH(str1);
316
317 str1 = database_get_data(node, "debug_channel", RECDB_QSTRING);
318 if (conf.debug_bot && str1) {
319 str2 = database_get_data(node, "debug_channel_modes", RECDB_QSTRING);
320 if (!str2)
321 str2 = "+tinms";
322 conf.debug_channel = AddChannel(str1, now, str2, NULL, NULL);
323 AddChannelUser(conf.debug_bot, conf.debug_channel)->modes |= MODE_CHANOP;
324 } else {
325 conf.debug_channel = NULL;
326 }
327
328 str1 = database_get_data(node, "file", RECDB_QSTRING);
329 str2 = database_get_data(node, "file_reason", RECDB_QSTRING);
330 blacklist_load_file(str1, str2);
331
332 str1 = database_get_data(node, "gline_duration", RECDB_QSTRING);
333 if (str1 == NULL)
334 str1 = "1h";
335 conf.gline_duration = ParseInterval(str1);
336
337 subnode = database_get_data(node, "dnsbl", RECDB_OBJECT);
338 if (subnode) {
339 static const char *reason_prefix = "reason_";
340 static const unsigned int max_id = 255;
341 struct dnsbl_zone *zone;
342 dict_iterator_t it;
343 dict_iterator_t it2;
344 dict_t dnsbl;
345 unsigned int id;
346
347 for (it = dict_first(subnode); it; it = iter_next(it)) {
348 dnsbl = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
349 if (!dnsbl)
350 continue;
351
352 zone = malloc(sizeof(*zone) + strlen(iter_key(it)));
353 strcpy(zone->zone, iter_key(it));
354 zone->description = database_get_data(dnsbl, "description", RECDB_QSTRING);
355 zone->reason = database_get_data(dnsbl, "reason", RECDB_QSTRING);
356 str1 = database_get_data(dnsbl, "duration", RECDB_QSTRING);
357 zone->duration = str1 ? ParseInterval(str1) : 3600;
358 str1 = database_get_data(dnsbl, "mask", RECDB_QSTRING);
359 zone->mask = str1 ? strtoul(str1, NULL, 0) : ~0u;
360 str1 = database_get_data(dnsbl, "debug", RECDB_QSTRING);
361 zone->debug = str1 ? enabled_string(str1) : 0;
362 zone->reasons.used = 0;
363 zone->reasons.size = 0;
364 zone->reasons.list = NULL;
365 dict_insert(blacklist_zones, zone->zone, zone);
366
367 for (it2 = dict_first(dnsbl); it2; it2 = iter_next(it2)) {
368 str1 = GET_RECORD_QSTRING((struct record_data*)(iter_data(it2)));
369 if (!str1 || memcmp(iter_key(it2), reason_prefix, strlen(reason_prefix)))
370 continue;
371 id = strtoul(iter_key(it2) + strlen(reason_prefix), NULL, 0);
372 if (id > max_id) {
373 log_module(bl_log, LOG_ERROR, "Invalid code for DNSBL %s %s -- only %d responses supported.", iter_key(it), iter_key(it2), max_id);
374 continue;
375 }
376 if (zone->reasons.size < id + 1) {
377 zone->reasons.size = id + 1;
378 zone->reasons.list = realloc(zone->reasons.list, zone->reasons.size * sizeof(zone->reasons.list[0]));
379 }
380 zone->reasons.list[id] = (char*)str1;
381 if (zone->reasons.used < id + 1)
382 zone->reasons.used = id + 1;
383 }
384 }
385 }
386 }
387
388 static void
389 blacklist_cleanup(void)
390 {
391 dict_delete(blacklist_zones);
392 dict_delete(blacklist_hosts);
393 dict_delete(blacklist_reasons);
394 }
395
396 int
397 blacklist_init(void)
398 {
399 bl_log = log_register_type("blacklist", "file:blacklist.log");
400 conf_register_reload(blacklist_conf_read);
401 reg_new_user_func(blacklist_check_user);
402 reg_exit_func(blacklist_cleanup);
403 return 1;
404 }
405
406 int
407 blacklist_finalize(void)
408 {
409 return 1;
410 }