/* * charybdis: A slightly useful ircd. * blacklist.c: Manages DNS blacklist entries and lookups * * Copyright (C) 2006-2008 charybdis development team * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ /* To configure/use, add a block to the general{} section of your atheme.conf * like this: * * blacklists { * "dnsbl.dronebl.org"; * "rbl.efnetrbl.org"; * }; */ #include "atheme.h" #include "conf.h" DECLARE_MODULE_V1 ( "contrib/dnsbl", false, _modinit, _moddeinit, PACKAGE_STRING, "Atheme Development Group " ); mowgli_list_t blacklist_list = { NULL, NULL, 0 }; mowgli_patricia_t **os_set_cmdtree; static char *action = NULL; /* A configured DNSBL */ struct Blacklist { unsigned int status; /* If CONF_ILLEGAL, delete when no clients */ int refcount; char host[IRCD_RES_HOSTLEN + 1]; unsigned int hits; time_t lastwarning; }; /* A lookup in progress for a particular DNSBL for a particular client */ struct BlacklistClient { struct Blacklist *blacklist; user_t *u; dns_query_t dns_query; mowgli_node_t node; }; struct dnsbl_exempt_ { char *ip; time_t exempt_ts; char *creator; char *reason; }; typedef struct dnsbl_exempt_ dnsbl_exempt_t; mowgli_list_t dnsbl_elist; static void os_cmd_set_dnsblaction(sourceinfo_t *si, int parc, char *parv[]); static void dnsbl_hit(user_t *u, struct Blacklist *blptr); static void os_cmd_dnsblexempt(sourceinfo_t *si, int parc, char *parv[]); static void os_cmd_dnsblscan(sourceinfo_t *si, int parc, char *parv[]); static void write_dnsbl_exempt_db(database_handle_t *db); static void db_h_ble(database_handle_t *db, const char *type); static void lookup_blacklists(user_t *u); command_t os_set_dnsblaction = { "DNSBLACTION", N_("Changes what happens to a user when they hit a DNSBL."), PRIV_USER_ADMIN, 1, os_cmd_set_dnsblaction, { .path = "contrib/set_dnsblaction" } }; command_t os_dnsblexempt = { "DNSBLEXEMPT", N_("Manage the list of IP's exempt from DNSBL checking."), PRIV_USER_ADMIN, 3, os_cmd_dnsblexempt, { .path = "contrib/dnsblexempt" } }; command_t os_dnsblscan = { "DNSBLSCAN", N_("Manually scan if a user is in a DNSBL."), PRIV_USER_ADMIN, 1, os_cmd_dnsblscan, { .path = "contrib/dnsblscan" } }; static inline mowgli_list_t *dnsbl_queries(user_t *u) { mowgli_list_t *l; return_val_if_fail(u != NULL, NULL); l = privatedata_get(u, "dnsbl:queries"); if (l != NULL) return l; l = mowgli_list_create(); privatedata_set(u, "dnsbl:queries", l); return l; } static void os_cmd_set_dnsblaction(sourceinfo_t *si, int parc, char *parv[]) { char *act = parv[0]; if (!act) { command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "DNSBLACTION"); command_fail(si, fault_needmoreparams, _("Syntax: SET DNSBLACTION ")); return; } if (!strcasecmp("SNOOP", act) || !strcasecmp("KLINE", act) || !strcasecmp("NOTIFY", act)) { action = sstrdup(act); command_success_nodata(si, _("DNSBLACTION successfully set to \2%s\2"), act); logcommand(si, CMDLOG_ADMIN, "SET:DNSBLACTION: \2%s\2", act); return; } else if (!strcasecmp("NONE", act)) { action = NULL; command_success_nodata(si, _("DNSBLACTION successfully set to \2%s\2"), act); logcommand(si, CMDLOG_ADMIN, "SET:DNSBLACTION: \2%s\2", act); return; } else { command_fail(si, fault_badparams, _("Invalid action given.")); return; } } static void os_cmd_dnsblexempt(sourceinfo_t *si, int parc, char *parv[]) { char *command = parv[0]; char *ip = parv[1]; char *reason = parv[2]; mowgli_node_t *n, *tn; dnsbl_exempt_t *de; if (!command) { command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "DNSBLEXEMPT"); command_fail(si, fault_needmoreparams, _("Syntax: DNSBLEXEMPT ADD|DEL|LIST [ip] [reason]")); return; } if (!strcasecmp("ADD", command)) { if (!ip || !reason) { command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "DNSBLEXEMPT"); command_fail(si, fault_needmoreparams, _("Syntax: DNSBLEXEMPT ADD ")); return; } MOWGLI_ITER_FOREACH(n, dnsbl_elist.head) { de = n->data; if (!irccasecmp(de->ip, ip)) { command_success_nodata(si, _("\2%s\2 has already been entered into the DNSBL exempts list."), ip); return; } } de = smalloc(sizeof(dnsbl_exempt_t)); de->exempt_ts = CURRTIME;; de->creator = sstrdup(get_source_name(si)); de->reason = sstrdup(reason); de->ip = sstrdup(ip); mowgli_node_add(de, mowgli_node_create(), &dnsbl_elist); command_success_nodata(si, _("You have added \2%s\2 to the DNSBL exempts list."), ip); logcommand(si, CMDLOG_ADMIN, "DNSBL:EXEMPT:ADD: \2%s\2 \2%s\2", ip, reason); } else if (!strcasecmp("DEL", command)) { if (!ip) { command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "DNSBLEXEMPT"); command_fail(si, fault_needmoreparams, _("Syntax: DNSBLEXEMPT DEL ")); return; } MOWGLI_ITER_FOREACH_SAFE(n, tn, dnsbl_elist.head) { de = n->data; if (!irccasecmp(de->ip, ip)) { logcommand(si, CMDLOG_SET, "DNSBL:EXEMPT:DEL: \2%s\2", de->ip); command_success_nodata(si, _("DNSBL Exempt IP \2%s\2 has been deleted."), de->ip); mowgli_node_delete(n, &dnsbl_elist); free(de->creator); free(de->reason); free(de->ip); free(de); return; } } command_success_nodata(si, _("IP \2%s\2 not found in DNSBL Exempt database."), ip); } else if (!strcasecmp("LIST", command)) { char buf[BUFSIZE]; struct tm tm; MOWGLI_ITER_FOREACH(n, dnsbl_elist.head) { de = n->data; tm = *localtime(&de->exempt_ts); strftime(buf, BUFSIZE, TIME_FORMAT, &tm); command_success_nodata(si, "IP: \2%s\2 Reason: \2%s\2 (%s - %s)", de->ip, de->reason, de->creator, buf); } command_success_nodata(si, "End of list."); logcommand(si, CMDLOG_GET, "DNSBL:EXEMPT:LIST"); } else { command_fail(si, fault_needmoreparams, STR_INVALID_PARAMS, "DNSBLEXEMPT"); command_fail(si, fault_needmoreparams, _("Syntax: DNSBLEXEMPT ADD|DEL|LIST [ip] [reason]")); return; } } static void os_cmd_dnsblscan(sourceinfo_t *si, int parc, char *parv[]) { char *user = parv[0]; user_t *u; if (!user) { command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "DNSBLSCAN"); command_fail(si, fault_needmoreparams, _("Syntax: DNSBLSCAN ")); return; } if ((u = user_find_named(user))) { lookup_blacklists(u); logcommand(si, CMDLOG_ADMIN, "DNSBLSCAN: %s", user); command_success_nodata(si, "%s has been scanned.", user); return; } else { command_fail(si, fault_badparams, "User %s is not on the network, you can not scan them.", user); return; } } /* private interfaces */ static struct Blacklist *find_blacklist(char *name) { mowgli_node_t *n; MOWGLI_ITER_FOREACH(n, blacklist_list.head) { struct Blacklist *blptr = (struct Blacklist *) n->data; if (!strcasecmp(blptr->host, name)) return blptr; } return NULL; } static void blacklist_dns_callback(void *vptr, dns_reply_t *reply) { struct BlacklistClient *blcptr = (struct BlacklistClient *) vptr; int listed = 0; mowgli_list_t *l; if (blcptr == NULL) return; if (blcptr->u == NULL) { free(blcptr); return; } if (reply != NULL) { /* only accept 127.x.y.z as a listing */ if (reply->addr.saddr.sa.sa_family == AF_INET && !memcmp(&((struct sockaddr_in *)&reply->addr)->sin_addr, "\177", 1)) listed++; else if (blcptr->blacklist->lastwarning + 3600 < CURRTIME) { slog(LG_DEBUG, "Garbage reply from blacklist %s", blcptr->blacklist->host); blcptr->blacklist->lastwarning = CURRTIME; } } /* they have a blacklist entry for this client */ if (listed) { dnsbl_hit(blcptr->u, blcptr->blacklist); return; } l = dnsbl_queries(blcptr->u); mowgli_node_delete(&blcptr->node, l); free(blcptr); } /* XXX: no IPv6 implementation, not to concerned right now though. */ static void initiate_blacklist_dnsquery(struct Blacklist *blptr, user_t *u) { struct BlacklistClient *blcptr = malloc(sizeof(struct BlacklistClient)); char buf[IRCD_RES_HOSTLEN + 1]; int ip[4]; mowgli_list_t *l; blcptr->blacklist = blptr; blcptr->u = u; blcptr->dns_query.ptr = blcptr; blcptr->dns_query.callback = blacklist_dns_callback; /* A sscanf worked fine for chary for many years, it'll be fine here */ sscanf(u->ip, "%d.%d.%d.%d", &ip[3], &ip[2], &ip[1], &ip[0]); /* becomes 2.0.0.127.torbl.ahbl.org or whatever */ snprintf(buf, sizeof buf, "%d.%d.%d.%d.%s", ip[0], ip[1], ip[2], ip[3], blptr->host); gethost_byname_type(buf, &blcptr->dns_query, T_A); l = dnsbl_queries(u); mowgli_node_add(blcptr, &blcptr->node, l); blptr->refcount++; } /* public interfaces */ static struct Blacklist *new_blacklist(char *name) { struct Blacklist *blptr; if (name == NULL) return NULL; blptr = find_blacklist(name); if (blptr == NULL) { blptr = malloc(sizeof(struct Blacklist)); mowgli_node_add(blptr, mowgli_node_create(), &blacklist_list); } mowgli_strlcpy(blptr->host, name, IRCD_RES_HOSTLEN + 1); blptr->lastwarning = 0; return blptr; } static void lookup_blacklists(user_t *u) { mowgli_node_t *n; MOWGLI_ITER_FOREACH(n, blacklist_list.head) { struct Blacklist *blptr = (struct Blacklist *) n->data; blptr->status = 0; if (u == NULL) return; initiate_blacklist_dnsquery(blptr, u); } } /* This appears to be unnecessary on Atheme and only causes crashes so #if 0 * it out, at least for now. --jdhore */ #if 0 static void abort_blacklist_queries(user_t *u) { mowgli_node_t *n, *tn; mowgli_list_t *l; struct BlacklistClient *blcptr; if (u == NULL) return; l = dnsbl_queries(u); MOWGLI_ITER_FOREACH_SAFE(n, tn, l->head) { blcptr = n->data; mowgli_node_delete(&blcptr->node, l); unref_blacklist(blcptr->blacklist); delete_resolver_queries(&blcptr->dns_query); free(blcptr); } } #endif static void destroy_blacklists(void) { mowgli_node_t *n, *tn; struct Blacklist *blptr; MOWGLI_ITER_FOREACH_SAFE(n, tn, blacklist_list.head) { blptr = n->data; blptr->hits = 0; /* keep it simple and consistent */ free(n->data); mowgli_node_delete(n, &blacklist_list); mowgli_node_free(n); } } static int dnsbl_config_handler(mowgli_config_file_entry_t *ce) { mowgli_config_file_entry_t *cce; MOWGLI_ITER_FOREACH(cce, ce->entries) { char *line = sstrdup(cce->varname); new_blacklist(line); } return 0; } static void dnsbl_config_purge(void *unused) { destroy_blacklists(); } static void check_dnsbls(hook_user_nick_t *data) { user_t *u = data->u; mowgli_node_t *n; if (!u) return; if (is_internal_client(u)) return; if (!action) return; MOWGLI_ITER_FOREACH(n, dnsbl_elist.head) { dnsbl_exempt_t *de = n->data; if (!irccasecmp(de->ip, u->ip)) return; } lookup_blacklists(u); } static void dnsbl_hit(user_t *u, struct Blacklist *blptr) { service_t *svs; svs = service_find("operserv"); if (!strcasecmp("SNOOP", action)) { slog(LG_INFO, "DNSBL: \2%s\2!%s@%s [%s] is listed in DNS Blacklist %s.", u->nick, u->user, u->host, u->gecos, blptr->host); /* abort_blacklist_queries(u); */ return; } else if (!strcasecmp("NOTIFY", action)) { slog(LG_INFO, "DNSBL: \2%s\2!%s@%s [%s] is listed in DNS Blacklist %s.", u->nick, u->user, u->host, u->gecos, blptr->host); notice(svs->nick, u->nick, "Your IP address %s is listed in DNS Blacklist %s", u->ip, blptr->host); /* abort_blacklist_queries(u); */ return; } else if (!strcasecmp("KLINE", action)) { slog(LG_INFO, "DNSBL: k-lining \2%s\2!%s@%s [%s] who is listed in DNS Blacklist %s.", u->nick, u->user, u->host, u->gecos, blptr->host); /* abort_blacklist_queries(u); */ notice(svs->nick, u->nick, "Your IP address %s is listed in DNS Blacklist %s", u->ip, blptr->host); kline_sts("*", "*", u->host, 86400, "Banned (DNS Blacklist)"); return; } } static void osinfo_hook(sourceinfo_t *si) { mowgli_node_t *n; if (action) command_success_nodata(si, "Action taken when a user is an a DNSBL: %s", action); else command_success_nodata(si, "Action taken when a user is an a DNSBL: %s", "None"); MOWGLI_ITER_FOREACH(n, blacklist_list.head) { struct Blacklist *blptr = (struct Blacklist *) n->data; command_success_nodata(si, "Blacklist(s): %s", blptr->host); } } static void write_dnsbl_exempt_db(database_handle_t *db) { mowgli_node_t *n; MOWGLI_ITER_FOREACH(n, dnsbl_elist.head) { dnsbl_exempt_t *de = n->data; db_start_row(db, "BW"); db_write_word(db, de->ip); db_write_time(db, de->exempt_ts); db_write_word(db, de->creator); db_write_word(db, de->reason); db_commit_row(db); } } static void db_h_ble(database_handle_t *db, const char *type) { const char *ip = db_sread_word(db); time_t exempt_ts = db_sread_time(db); const char *creator = db_sread_word(db); const char *reason = db_sread_word(db); dnsbl_exempt_t *de = smalloc(sizeof(dnsbl_exempt_t)); de->ip = sstrdup(ip); de->exempt_ts = exempt_ts; de->creator = sstrdup(creator); de->reason = sstrdup(reason); mowgli_node_add(de, mowgli_node_create(), &dnsbl_elist); } void _modinit(module_t *m) { MODULE_TRY_REQUEST_SYMBOL(m, os_set_cmdtree, "operserv/set", "os_set_cmdtree"); if (!module_find_published("backend/opensex")) { slog(LG_INFO, "Module %s requires use of the OpenSEX database backend, refusing to load.", m->name); m->mflags = MODTYPE_FAIL; return; } hook_add_db_write(write_dnsbl_exempt_db); db_register_type_handler("BLE", db_h_ble); service_named_bind_command("operserv", &os_dnsblexempt); service_named_bind_command("operserv", &os_dnsblscan); hook_add_event("config_purge"); hook_add_config_purge(dnsbl_config_purge); hook_add_event("user_add"); hook_add_user_add(check_dnsbls); hook_add_event("operserv_info"); hook_add_operserv_info(osinfo_hook); add_dupstr_conf_item("dnsbl_action", &conf_gi_table, 0, &action, NULL); add_conf_item("BLACKLISTS", &conf_gi_table, dnsbl_config_handler); command_add(&os_set_dnsblaction, *os_set_cmdtree); } void _moddeinit(module_unload_intent_t intent) { hook_del_db_write(write_dnsbl_exempt_db); hook_del_user_add(check_dnsbls); hook_del_config_purge(dnsbl_config_purge); hook_del_operserv_info(osinfo_hook); db_unregister_type_handler("BLE"); del_conf_item("dnsbl_action", &conf_gi_table); del_conf_item("BLACKLISTS", &conf_gi_table); command_delete(&os_set_dnsblaction, *os_set_cmdtree); service_named_unbind_command("operserv", &os_dnsblexempt); service_named_unbind_command("operserv", &os_dnsblscan); } /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs * vim:ts=8 * vim:sw=8 * vim:noexpandtab */