/* NeoStats - IRC Statistical Services ** Copyright (c) 1999-2006 Adam Rutter, Justin Hammond, Mark Hetherington ** http://www.neostats.net/ ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ** USA ** ** NeoStats CVS Identification ** $Id$ */ /* TODO: * - Akill support. * - Buffer sizes for name and domain should not need to be so big * and should be made a more appropriate size. * - If a remove akill command is added, it must check whether an akill * was added by blsb before removing it otherwise blsb becomes a way * for opers to remove any akill on the network including those they * may not normally have access to. * - Do we need cache support?. */ #include "neostats.h" #ifdef HAVE_ARPA_INET_H #include #endif #ifdef HAVE_ARPA_NAMESER_H #include #endif #include "blsb.h" static int event_nickip( const CmdParams *cmdparams ); static int blsb_cmd_list( const CmdParams *cmdparams ); static int blsb_cmd_add( const CmdParams *cmdparams ); static int blsb_cmd_del( const CmdParams *cmdparams ); static int blsb_cmd_check( const CmdParams *cmdparams ); static int blsb_set_exclusions_cb( const CmdParams *cmdparams, SET_REASON reason ); static struct blsb { int akilltime; int doakill; int verbose; int exclusions; list_t *domains; } blsb; static Bot *blsb_bot; static dom_list stddomlist[] = { {"Secure-IRC", "bl.irc-chat.net", BL_LOOKUP_TXT_RECORD, "Insecure Host - See http://secure.irc-chat.net/ipinfo.php?ip=%s", 0}, {"Tor_Exit_Server", "exitnodes.tor.dnsbl.sectoor.de", BL_LOOKUP_TXT_RECORD, "Your Host is a Tor Exit Server", 0}, {"IRC Abusive Hosts", "ircbl.ahbl.org", BL_LOOKUP_A_RECORD, "Abusive Host - See http://www.ahbl.org/ for more info and/or removal (your IP is %s)", 0}, {"Drone BlackList", "dnsbl.dronebl.org", BL_LOOKUP_A_RECORD, "Your host is listed in DroneBL. See http://www.dronebl.org/lookup?ip=%s for more info", 0}, {"", "", 0} }; /** Copyright info */ static const char *blsb_copyright[] = { "Copyright (c) 1999-2006, NeoStats", "http://www.neostats.net/", NULL }; /** Module Info definition * This describes the module to the NeoStats core and provides information * to end users when modules are queried. * The structure is required but some fields are optional. */ ModuleInfo module_info = { "BLSB", "Black List Scanning Bot", blsb_copyright, blsb_about, NEOSTATS_VERSION, MODULE_VERSION, __DATE__, __TIME__, MODULE_FLAG_LOCAL_EXCLUDES, 0, 0, }; static bot_cmd blsb_commands[]= { {"ADD", blsb_cmd_add, 4, NS_ULEVEL_ADMIN, blsb_help_add, 0, NULL, NULL}, {"DEL", blsb_cmd_del, 1, NS_ULEVEL_ADMIN, blsb_help_del, 0, NULL, NULL}, {"LIST", blsb_cmd_list, 0, NS_ULEVEL_ADMIN, blsb_help_list, 0, NULL, NULL}, {"CHECK", blsb_cmd_check, 1, NS_ULEVEL_OPER, blsb_help_check, 0, NULL, NULL}, NS_CMD_END() }; static bot_setting blsb_settings[]= { {"AKILL", &blsb.doakill, SET_TYPE_BOOLEAN, 0, 0, NS_ULEVEL_ADMIN, NULL, blsb_help_set_akill, NULL, (void*)1 }, {"AKILLTIME", &blsb.akilltime, SET_TYPE_INT, 0, 20736000, NS_ULEVEL_ADMIN, NULL, blsb_help_set_akilltime, NULL, (void*)TS_ONE_DAY }, {"VERBOSE", &blsb.verbose, SET_TYPE_BOOLEAN, 0, 0, NS_ULEVEL_ADMIN, NULL, blsb_help_set_verbose, NULL, (void*)1 }, {"EXCLUSIONS", &blsb.exclusions, SET_TYPE_BOOLEAN, 0, 0, NS_ULEVEL_ADMIN, NULL, blsb_help_set_exclusions, blsb_set_exclusions_cb, (void *)0 }, NS_SETTING_END() }; /** BotInfo */ static BotInfo blsb_botinfo = { "blsb", "blsb1", "blsb", BOT_COMMON_HOST, "BlackList Scanning Bot", BOT_FLAG_SERVICEBOT|BOT_FLAG_RESTRICT_OPERS|BOT_FLAG_DEAF, blsb_commands, blsb_settings, }; /** Module event list * What events we will act on */ ModuleEvent module_events[] = { { EVENT_NICKIP, event_nickip, EVENT_FLAG_EXCLUDE_ME}, NS_EVENT_END() }; /** @brief new_bldomain * * Allocate a new blacklist domain entry and add to the list * * @param name of service * @param domain of service * @param type of service * * @return pointer to newly allocated entry */ static dom_list *new_bldomain( const char *name, const char *domain, BL_LOOKUP_TYPE type, const char *msg , int noban) { dom_list *dl; dl = ns_calloc( sizeof( dom_list ) ); strlcpy( dl->name, name, BUFSIZE ); strlcpy( dl->domain, domain, BUFSIZE ); strlcpy( dl->msg, msg, BUFSIZE ); dl->type = type; dl->noban = noban; lnode_create_append( blsb.domains, dl ); DBAStore( "domains", dl->name, (void *)dl, sizeof( dom_list ) ); return dl; } /** @brief dnsbl_callback * * DNS callback * * @param data * @param a * * @return NS_SUCCESS if suceeds else result of command */ static void dnsbl_callback(void *data, adns_answer *a) { scanclient *sc = (scanclient *)data; int i; char *show = NULL; struct in_addr inp; Client *u=NULL; if (a && (a->nrrs > 0) && (a->status == adns_s_ok)) { for (i = 0; i < a->nrrs; i++) { if (a->type == adns_r_a) { /* here we should actually check the return IP address and see if we really want to do something with it */ inp = *((struct in_addr*)&a->rrs.inaddr[i]); show = ns_malloc(BUFSIZE); ircsnprintf(show, BUFSIZE, sc->domain->msg, sc->ip); } if (a->type == adns_r_txt) { show = a->rrs.manyistr[i]->str; } irc_chanalert( blsb_bot, "%s (%s) exists in %s blacklist: %s %s", sc->usernick, sc->ip, sc->domain->name, show, sc->domain->noban ? "(NOBAN)" : "" ); if( sc->checknick[0] != '\0' ) u = FindUser( sc->checknick ); if( u ) irc_prefmsg(blsb_bot, u, "%s (%s) exists in %s blacklist: %s %s", sc->usernick, sc->ip, sc->domain->name, show, sc->domain->noban ? "(NOBAN)" : "" ); if (sc->banned == 0 && !sc->domain->noban) { sc->banned = 1; /* only ban/msg the user once */ u = FindUser(sc->usernick); if( u ) irc_prefmsg(blsb_bot, u, "Your Host is listed as a inscure host at %s: %s", sc->domain->name, show); if (blsb.doakill) { if( sc->exclude != 1 ) { irc_akill (blsb_bot, sc->ip, "*", blsb.akilltime, "Your Host is listed as a insecure host at %s: %s", sc->domain->name, show); irc_chanalert( blsb_bot, "Akilling %s!%s@%s", sc->usernick, sc->username, sc->hostname); } } } if (a->type == adns_r_a) ns_free(show); } } else if (a && ((a->status == adns_s_nxdomain) || (a->status == adns_s_nodata))) { if (blsb.verbose) irc_chanalert( blsb_bot, "%s (%s) does not exist in %s blacklist", sc->usernick, sc->ip, sc->domain->name); if( sc->checknick[0] != '\0' ) u = FindUser( sc->checknick ); if( u ) irc_prefmsg(blsb_bot, u, "%s (%s) does not exist in %s blacklist", sc->usernick, sc->ip, sc->domain->name); } else if (a->status != adns_s_ok) { nlog(LOG_WARNING, "DNS error %s", adns_strerror(a->status)); } ns_free(sc->lookup); ns_free(sc); } /** @brief do_lookup * * trigger lookups * * @param Client *lookupuser * @param Client *reportuser * * @return NS_SUCCESS if suceeds else result of command */ static scanclient *do_lookup( Client *lookupuser, Client *reportuser ) { static char ip[HOSTIPLEN]; static char reverseip[HOSTIPLEN]; lnode_t *node; dom_list *dl; scanclient *sc = NULL; unsigned char a, b, c, d; unsigned int buflen; d = (unsigned char) ( lookupuser->ip.s_addr >> 24 ) & 0xFF; c = (unsigned char) ( lookupuser->ip.s_addr >> 16 ) & 0xFF; b = (unsigned char) ( lookupuser->ip.s_addr >> 8 ) & 0xFF; a = (unsigned char) ( lookupuser->ip.s_addr & 0xFF ); ircsnprintf( ip, HOSTIPLEN, "%d.%d.%d.%d", a, b, c, d ); ircsnprintf( reverseip, HOSTIPLEN, "%d.%d.%d.%d", d, c, b, a ); node = list_first( blsb.domains ); while( node ) { dl = lnode_get( node); /* Allocate enough for domain, ip address, additional period and NULL */ buflen = strlen( dl->domain ) + HOSTIPLEN + 1 + 1; sc = ns_malloc( sizeof( scanclient ) ); if( reportuser == NULL ) sc->checknick[0] = '\0'; else strlcpy( sc->checknick, reportuser->name, MAXNICK ); strlcpy( sc->usernick, lookupuser->name, MAXNICK ); strlcpy( sc->username, lookupuser->user->username, MAXUSER ); strlcpy( sc->hostname, lookupuser->user->hostname, MAXHOST ); if( ModIsUserExcluded( lookupuser ) == NS_FALSE ) sc->exclude = 0; else sc->exclude = 1; sc->domain = dl; sc->banned = 0; sc->lookup = ns_malloc( buflen ); strlcpy( sc->ip, ip, HOSTIPLEN ); strlcpy( sc->reverseip, reverseip, HOSTIPLEN ); ircsnprintf( sc->lookup, buflen, "%s.%s", reverseip, dl->domain ); switch (dl->type) { case BL_LOOKUP_TXT_RECORD: /* TXT record */ dns_lookup( sc->lookup, adns_r_txt, dnsbl_callback, sc ); break; case BL_LOOKUP_A_RECORD: /* A record */ dns_lookup( sc->lookup, adns_r_a, dnsbl_callback, sc ); break; default: nlog( LOG_WARNING, "Unknown Type for DNS BL %s", dl->name ); break; } node = list_next( blsb.domains, node ); } return sc; } /** @brief blsb_cmd_list * * LIST command handler * List entries in the blacklist domain list * * @param cmdparam struct * * @return NS_SUCCESS if suceeds else result of command */ int blsb_cmd_list( const CmdParams *cmdparams ) { dom_list *dl; lnode_t *lnode; lnode = list_first(blsb.domains); irc_prefmsg (blsb_bot, cmdparams->source, "BlackList domains:"); while (lnode) { dl = lnode_get(lnode); irc_prefmsg (blsb_bot, cmdparams->source, "%s: %s (type %d) %s", dl->domain, dl->name, dl->type, dl->noban ? "NOBAN" : ""); lnode = list_next(blsb.domains, lnode); } irc_prefmsg (blsb_bot, cmdparams->source, "End of list."); CommandReport(blsb_bot, "%s requested blacklist domain list", cmdparams->source->name); return NS_SUCCESS; } /** @brief blsb_cmd_add * * ADD command handler * Add an entry to the blacklist domain list * * @param cmdparam struct * cmdparams->av[0] = domain * cmdparams->av[1] = type * cmdparams->av[2] = name * * @return NS_SUCCESS if suceeds else result of command */ int blsb_cmd_add( const CmdParams *cmdparams ) { dom_list *dl; lnode_t *lnode; int type; char *msg; if( list_isfull( blsb.domains ) ) { irc_prefmsg( blsb_bot, cmdparams->source, "Error, domain list is full" ); return NS_FAILURE; } type = atoi( cmdparams->av[1] ); if( type <= BL_LOOKUP_TYPE_MIN || type >= BL_LOOKUP_TYPE_MAX ) { irc_prefmsg( blsb_bot, cmdparams->source, "type field does not contain a valid type" ); return NS_FAILURE; } msg = joinbuf( cmdparams->av, cmdparams->ac, 3 ); /* XXX do a initial lookup on the domain to check it exists? */ /* check for duplicates */ lnode = list_first( blsb.domains ); while( lnode ) { dl = lnode_get( lnode ); if( ( ircstrcasecmp( dl->name, cmdparams->av[2] ) == 0 ) || ( ircstrcasecmp(dl->domain, cmdparams->av[0] ) == 0 ) ) { irc_prefmsg( blsb_bot, cmdparams->source, "%s already has an entry", cmdparams->av[0] ); return NS_SUCCESS; } lnode = list_next(blsb.domains, lnode); } dl = new_bldomain( cmdparams->av[2], cmdparams->av[0], type, msg , ircstrcasecmp( cmdparams->av[3], "NOBAN" ) ? 0 : 1); irc_prefmsg( blsb_bot, cmdparams->source, "Added domain %s (%s) as type %d", dl->name, dl->domain, dl->type ); CommandReport( blsb_bot, "%s added domain %s (%s) as type %d", cmdparams->source->name, dl->name, dl->domain, dl->type ); return NS_SUCCESS; } /** @brief blsb_cmd_del * * DEL command handler * deletes an entry from the blacklist domain list * * @param cmdparam struct * cmdparams->av[0] = domain * * @return NS_SUCCESS if suceeds else result of command */ int blsb_cmd_del( const CmdParams *cmdparams ) { dom_list *dl; lnode_t *lnode; lnode = list_first(blsb.domains); while (lnode) { dl = lnode_get(lnode); if( ircstrcasecmp( dl->domain, cmdparams->av[0] ) == 0 ) { list_delete(blsb.domains, lnode); lnode_destroy(lnode); irc_prefmsg (blsb_bot, cmdparams->source, "Deleted %s (%s) from blacklist domains", dl->name, dl->domain); CommandReport(blsb_bot, "%s deleted %s (%s) from blacklist domains", cmdparams->source->name, dl->name, dl->domain); DBADelete("domains", dl->name); ns_free(dl); return NS_SUCCESS; } lnode = list_next(blsb.domains, lnode); } /* if we get here, then we can't find the entry */ irc_prefmsg (blsb_bot, cmdparams->source, "Error, no entry for %s", cmdparams->av[0]); return NS_FAILURE; } /** @brief blsb_cmd_check * * CHECK command handler * * @param cmdparam struct * * @return NS_SUCCESS if suceeds else result of command */ int blsb_cmd_check( const CmdParams *cmdparams ) { scanclient *sc = NULL; Client *user; user = FindUser(cmdparams->av[0]); if (!user) { #if 0 /* XXX TODO: Lookup Hostname */ if (!ValidateHost(cmdparams->av[0]) { irc_prefmsg(blsb_bot, cmdparams->source, "Invalid Nick or Host"); return NS_FAILURE; } else { sc = ns_malloc(sizeof(scanclient)); strlcpy( sc->checknick, cmdparams->source->name, MAXNICK ); strlcpy( sc->usernick, cmdparams->source->name, MAXNICK ); strlcpy( sc->username, cmdparams->source->user->username, MAXUSER ); strlcpy( sc->hostname, cmdparams->source->user->hostname, MAXHOST ); if( ModIsUserExcluded( cmdparams->source ) == NS_FALSE ) sc->exclude = 0; else sc->exclude = 1; sc->domain = dl; sc->lookup = ns_malloc(buflen); ircsnprintf(sc->lookup, buflen, "%d.%d.%d.%d.%s", d, c, b, a, dl->domain); #endif irc_prefmsg(blsb_bot, cmdparams->source, "Can not find %s online", cmdparams->av[0]); return NS_ERR_SYNTAX_ERROR; } sc = do_lookup( user, cmdparams->source ); if( sc ) { irc_prefmsg( blsb_bot, cmdparams->source, "Checking %s (%s) against DNS Blacklists", sc->usernick, sc->ip ); CommandReport( blsb_bot, "%s is checking %s (%s) against DNS Blacklists", cmdparams->source->name, sc->usernick, sc->ip ); } return NS_SUCCESS; } /** @brief event_nickip * * NICKIP event handler * scan user that just signed on the network * * @cmdparams pointer to commands param struct * * @return NS_SUCCESS if suceeds else NS_FAILURE */ static int event_nickip( const CmdParams *cmdparams ) { SET_SEGV_LOCATION(); if (ModIsServerExcluded(cmdparams->source->uplink)) return NS_SUCCESS; if (IsNetSplit(cmdparams->source)) return NS_SUCCESS; (void)do_lookup( cmdparams->source, NULL ); return NS_SUCCESS; } /** @brief load_dom * * Database load domains row callback handler * * @param pointer to loaded data * @param size of loaded data * * @return NS_FALSE */ static int load_dom( void *data, int size ) { dom_list *dl; if( size == sizeof(dom_list) ) { dl = ns_calloc( sizeof(dom_list)); os_memcpy(dl, data, sizeof (dom_list)); lnode_create_append(blsb.domains, dl); } return NS_FALSE; } /** @brief load_default_bldomains * * Load default domain settings * * @param none * * @return none */ static void load_default_bldomains( void ) { dom_list *default_domains; default_domains = stddomlist; while( default_domains->type != BL_LOOKUP_TYPE_MIN ) { (void)new_bldomain( default_domains->name, default_domains->domain, default_domains->type, default_domains->msg , default_domains->noban ); default_domains++; } } /** @brief ModInit * * Init handler * * @param none * * @return NS_SUCCESS if suceeds else NS_FAILURE */ int ModInit( void ) { ModuleConfig( blsb_settings ); blsb.domains = list_create( LISTCOUNT_T_MAX ); me.want_nickip = 1; if( !blsb.domains ) { nlog( LOG_CRITICAL, "Unable to create domain list" ); return NS_FAILURE; } DBAFetchRows( "domains", load_dom ); /* If no domains, this must be our first run so load defaults */ if( list_count( blsb.domains ) == 0 ) load_default_bldomains(); return NS_SUCCESS; } /** @brief ModSynch * * Startup handler * * @param none * * @return NS_SUCCESS if suceeds else NS_FAILURE */ int ModSynch (void) { SET_SEGV_LOCATION(); blsb_bot = AddBot (&blsb_botinfo); if( !blsb_bot ) return NS_FAILURE; if( blsb.verbose ) irc_chanalert (blsb_bot, "Black List Scanning bot has started"); return NS_SUCCESS; } /** @brief ModFini * * Fini handler * * @param none * * @return NS_SUCCESS if suceeds else NS_FAILURE */ int ModFini( void ) { return NS_SUCCESS; } /** @brief blsb_set_exclusions_cb * * Set callback for exclusions * Enable or disable exclude event flag * * @cmdparams pointer to commands param struct * @cmdparams reason for SET * * @return NS_SUCCESS if suceeds else NS_FAILURE */ static int blsb_set_exclusions_cb( const CmdParams *cmdparams, SET_REASON reason ) { if( reason == SET_LOAD || reason == SET_CHANGE ) { SetAllEventFlags( EVENT_FLAG_USE_EXCLUDE, blsb.exclusions ); } return NS_SUCCESS; }