1 /* Blacklist module for srvx 1.x
2 * Copyright 2007 Michael Poole <mdpoole@troilus.org>
4 * This file is part of srvx.
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.
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.
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.
27 const char *blacklist_module_deps[] = { NULL };
30 struct string_list reasons;
31 const char *description;
33 unsigned int duration;
39 char client_ip[IRC_NTOP_MAX_SIZE];
43 static struct log_type *bl_log;
44 static dict_t blacklist_zones; /* contains struct dnsbl_zone */
45 static dict_t blacklist_hosts; /* maps IPs or hostnames to reasons from blacklist_reasons */
46 static dict_t blacklist_reasons; /* maps strings to themselves (poor man's data sharing) */
49 unsigned long gline_duration;
53 do_expandos(char *output, unsigned int out_len, const char *input, ...)
63 safestrncpy(output, input, out_len);
64 va_start(args, input);
65 while ((key = va_arg(args, const char*)) != NULL) {
66 datum = va_arg(args, const char *);
69 for (found = output; (found = strstr(output, key)) != NULL; found += dlen) {
70 rlen = strlen(found + klen);
71 if ((dlen > klen) && (found + dlen + rlen - output > out_len))
72 rlen = output + out_len - found - dlen;
73 memmove(found + dlen, found + klen, rlen);
74 memcpy(found, datum, dlen + 1);
81 dnsbl_hit(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size)
83 struct dnsbl_data *data;
84 struct dnsbl_zone *zone;
92 char target[IRC_NTOP_MAX_SIZE + 2];
94 /* Get the DNSBL zone (to make sure it has not disappeared in a rehash). */
95 data = (struct dnsbl_data*)(req + 1);
96 zone = dict_find(blacklist_zones, data->zone_name, NULL);
100 /* Scan the results. */
101 for (mask = 0, ii = 0, txt = NULL; ii < hdr->ancount; ++ii) {
102 pos = rr[ii].rd_start;
103 switch (rr[ii].type) {
105 if (rr[ii].rdlength != 4)
107 if (pos + 3 < raw_size)
108 mask |= (1 << raw[pos + 3]);
112 txt = malloc(len + 1);
113 memcpy(txt, raw + pos + 1, len);
119 /* Do we care about one of the masks we found? */
120 if (mask & zone->mask) {
121 /* See if a per-result message was provided. */
122 for (ii = 0, message = NULL; mask && (ii < zone->reasons.used); ++ii, mask >>= 1) {
125 if (NULL != (message = zone->reasons.list[ii]))
129 /* If not, use a standard fallback. */
130 if (message == NULL) {
131 message = zone->reason;
133 message = "client is blacklisted";
136 /* Expand elements of the message as necessary. */
137 do_expandos(reason, sizeof(reason), message, "%txt%", (txt ? txt : "(no-txt)"), "%ip%", data->client_ip, NULL);
139 /* Now generate the G-line. */
142 strcpy(target + 2, data->client_ip);
143 gline_add(self->name, target, zone->duration, reason, now, now, 1);
149 blacklist_check_user(struct userNode *user)
151 static const char *hexdigits = "0123456789abcdef";
155 unsigned int dnsbl_len;
157 char ip[IRC_NTOP_MAX_SIZE];
158 char dnsbl_target[128];
160 /* Users with bogus IPs are probably service bots. */
161 if (!irc_in_addr_is_valid(user->ip))
164 /* Check local file-based blacklist. */
165 irc_ntop(ip, sizeof(ip), &user->ip);
166 reason = dict_find(blacklist_hosts, host = ip, NULL);
167 if (reason == NULL) {
168 reason = dict_find(blacklist_hosts, host = user->hostname, NULL);
170 if (reason != NULL) {
172 target = alloca(strlen(host) + 3);
175 strcpy(target + 2, host);
176 gline_add(self->name, target, conf.gline_duration, reason, now, now, 1);
179 /* Figure out the base part of a DNS blacklist hostname. */
180 if (irc_in_addr_is_ipv4(user->ip)) {
181 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]);
182 } else if (irc_in_addr_is_ipv6(user->ip)) {
183 for (ii = 0; ii < 16; ++ii) {
184 dnsbl_target[ii * 4 + 0] = hexdigits[user->ip.in6_8[15 - ii] & 15];
185 dnsbl_target[ii * 4 + 1] = '.';
186 dnsbl_target[ii * 4 + 2] = hexdigits[user->ip.in6_8[15 - ii] >> 4];
187 dnsbl_target[ii * 4 + 3] = '.';
194 /* Start a lookup for the appropriate hostname in each DNSBL. */
195 for (it = dict_first(blacklist_zones); it; it = iter_next(it)) {
196 struct dnsbl_data *data;
197 struct sar_request *req;
201 safestrncpy(dnsbl_target + dnsbl_len, zone, sizeof(dnsbl_target) - dnsbl_len);
202 req = sar_request_simple(sizeof(*data) + strlen(zone), dnsbl_hit, NULL, dnsbl_target, REQ_QTYPE_ALL, NULL);
204 data = (struct dnsbl_data*)(req + 1);
205 strcpy(data->client_ip, ip);
206 strcpy(data->zone_name, zone);
213 blacklist_load_file(const char *filename, const char *default_reason)
220 char linebuf[MAXLEN];
225 default_reason = "client is blacklisted";
226 file = fopen(filename, "r");
228 log_module(bl_log, LOG_ERROR, "Unable to open %s for reading: %s", filename, strerror(errno));
231 log_module(bl_log, LOG_DEBUG, "Loading blacklist from %s.", filename);
232 while (fgets(linebuf, sizeof(linebuf), file)) {
233 /* Trim whitespace from end of line. */
234 len = strlen(linebuf);
235 while (isspace(linebuf[len-1]))
236 linebuf[--len] = '\0';
238 /* Figure out which reason string we should use. */
239 reason = default_reason;
240 sep = strchr(linebuf, ' ');
243 while (isspace(*sep))
249 /* See if the reason string is already known. */
250 mapped_reason = dict_find(blacklist_reasons, reason, NULL);
251 if (!mapped_reason) {
252 mapped_reason = strdup(reason);
253 dict_insert(blacklist_reasons, mapped_reason, (char*)mapped_reason);
256 /* Store the blacklist entry. */
257 dict_insert(blacklist_hosts, strdup(linebuf), mapped_reason);
263 dnsbl_zone_free(void *pointer)
265 struct dnsbl_zone *zone;
267 free(zone->reasons.list);
272 blacklist_conf_read(void)
279 dict_delete(blacklist_zones);
280 blacklist_zones = dict_new();
281 dict_set_free_data(blacklist_zones, free);
283 dict_delete(blacklist_hosts);
284 blacklist_hosts = dict_new();
285 dict_set_free_keys(blacklist_hosts, free);
287 dict_delete(blacklist_reasons);
288 blacklist_reasons = dict_new();
289 dict_set_free_keys(blacklist_reasons, dnsbl_zone_free);
291 node = conf_get_data("modules/blacklist", RECDB_OBJECT);
295 str1 = database_get_data(node, "file", RECDB_QSTRING);
296 str2 = database_get_data(node, "file_reason", RECDB_QSTRING);
297 blacklist_load_file(str1, str2);
299 str1 = database_get_data(node, "gline_duration", RECDB_QSTRING);
302 conf.gline_duration = ParseInterval(str1);
304 subnode = database_get_data(node, "dnsbl", RECDB_OBJECT);
306 static const char *reason_prefix = "reason_";
307 static const unsigned int max_id = 255;
308 struct dnsbl_zone *zone;
314 for (it = dict_first(subnode); it; it = iter_next(it)) {
315 dnsbl = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
319 zone = malloc(sizeof(*zone) + strlen(iter_key(it)));
320 strcpy(zone->zone, iter_key(it));
321 zone->description = database_get_data(dnsbl, "description", RECDB_QSTRING);
322 zone->reason = database_get_data(dnsbl, "reason", RECDB_QSTRING);
323 str1 = database_get_data(dnsbl, "duration", RECDB_QSTRING);
324 zone->duration = str1 ? ParseInterval(str1) : 3600;
325 str1 = database_get_data(dnsbl, "mask", RECDB_QSTRING);
326 zone->mask = str1 ? strtoul(str1, NULL, 0) : ~0u;
327 zone->reasons.used = 0;
328 zone->reasons.size = 0;
329 zone->reasons.list = NULL;
330 dict_insert(blacklist_zones, zone->zone, zone);
332 for (it2 = dict_first(dnsbl); it2; it2 = iter_next(it2)) {
333 str1 = GET_RECORD_QSTRING((struct record_data*)(iter_data(it2)));
334 if (!str1 || memcmp(iter_key(it2), reason_prefix, strlen(reason_prefix)))
336 id = strtoul(iter_key(it2) + strlen(reason_prefix), NULL, 0);
338 log_module(bl_log, LOG_ERROR, "Invalid code for DNSBL %s %s -- only %d responses supported.", iter_key(it), iter_key(it2), max_id);
341 if (zone->reasons.size < id + 1) {
342 zone->reasons.size = id + 1;
343 zone->reasons.list = realloc(zone->reasons.list, zone->reasons.size * sizeof(zone->reasons.list[0]));
345 zone->reasons.list[id] = (char*)str1;
346 if (zone->reasons.used < id + 1)
347 zone->reasons.used = id + 1;
354 blacklist_cleanup(void)
356 dict_delete(blacklist_zones);
357 dict_delete(blacklist_hosts);
358 dict_delete(blacklist_reasons);
364 bl_log = log_register_type("blacklist", "file:blacklist.log");
365 conf_register_reload(blacklist_conf_read);
366 reg_new_user_func(blacklist_check_user);
367 reg_exit_func(blacklist_cleanup);
372 blacklist_finalize(void)