/** * clones.C: This file contains functionality implementing clone detection. * * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team * Rights to this code are as documented in COPYING. * * * Portions of this file were derived from sources bearing the following license: * Copyright © 2006 Atheme Development Group * Rights to this code are documented in doc/LICENCE. * * $Id: clones.C,v 1.7 2007/09/22 14:27:28 pippijn Exp $ */ #include #include "atheme.h" #include #include #include static char const rcsid[] = "$Id: clones.C,v 1.7 2007/09/22 14:27:28 pippijn Exp $"; REGISTER_MODULE ("operserv/clones", false, "The Ermyth Team "); #define DEFAULT_WARN_CLONES 3 /* IPs with more than this are warned about */ #define EXEMPT_GRACE 10 /* exempt IPs exceeding their allowance by this are banned */ static void os_cmd_clones (sourceinfo_t *si, int parc, char *parv[]); static void os_cmd_clones_kline (sourceinfo_t *si, int parc, char *parv[]); static void os_cmd_clones_list (sourceinfo_t *si, int parc, char *parv[]); static void os_cmd_clones_addexempt (sourceinfo_t *si, int parc, char *parv[]); static void os_cmd_clones_delexempt (sourceinfo_t *si, int parc, char *parv[]); static void os_cmd_clones_listexempt (sourceinfo_t *si, int parc, char *parv[]); static void write_exemptdb (void); static void load_exemptdb (void); E cmdvec os_cmdtree; E helpvec os_helptree; cmdvec os_clones_cmds; bool kline_enabled; struct cexcept_t { typedef std::vector vector_type; char *ip; int clones; char *reason; }; struct hostentry_t { char ip[HOSTIPLEN]; user_t::vector_type clients; time_t lastaction; unsigned lastaction_clones; }; static cexcept_t::vector_type clone_exempts; typedef std::pair host_pair; typedef std::map host_map; static host_map hostlist; command_t const os_clones = { "CLONES", N_("Manages network wide clones."), PRIV_AKILL, 4, os_cmd_clones }; command_t const os_clones_kline = { "KLINE", N_("Enables/disables klines for excessive clones."), AC_NONE, 1, os_cmd_clones_kline }; command_t const os_clones_list = { "LIST", N_("Lists clones on the network."), AC_NONE, 0, os_cmd_clones_list }; command_t const os_clones_addexempt = { "ADDEXEMPT", N_("Adds a clones exemption."), AC_NONE, 3, os_cmd_clones_addexempt }; command_t const os_clones_delexempt = { "DELEXEMPT", N_("Deletes a clones exemption."), AC_NONE, 1, os_cmd_clones_delexempt }; command_t const os_clones_listexempt = { "LISTEXEMPT", N_("Lists clones exemptions."), AC_NONE, 0, os_cmd_clones_listexempt }; static void write_exemptdb (void) { FILE *f; if (!(f = fopen (DATADIR "/exempts.db.new", "w"))) { slog (LG_ERROR, "write_exemptdb(): cannot write exempt database: %s", strerror (errno)); return; } fprintf (f, "CK %d\n", kline_enabled ? 1 : 0); foreach (cexcept_t *c, clone_exempts) fprintf (f, "EX %s %d %s\n", c->ip, c->clones, c->reason); fclose (f); if ((rename (DATADIR "/exempts.db.new", DATADIR "/exempts.db")) < 0) { slog (LG_ERROR, "write_exemptdb(): couldn't rename exempts database."); return; } } static void load_exemptdb (void) { FILE *f; char *item, rBuf[BUFSIZE * 2], *p; if (!(f = fopen (DATADIR "/exempts.db", "r"))) { slog (LG_DEBUG, "load_exemptdb(): cannot open exempt database: %s", strerror (errno)); return; } while (fgets (rBuf, BUFSIZE * 2, f)) { item = strtok (rBuf, " "); strip (item); if (!strcmp (item, "EX")) { char *ip = strtok (NULL, " "); int clones = atoi (strtok (NULL, " ")); char *reason = strtok (NULL, ""); if (!ip || clones <= 0 || !reason) ; /* erroneous, don't add */ else { cexcept_t *c = new cexcept_t; c->ip = sstrdup (ip); c->clones = clones; p = strchr (reason, '\n'); if (p != NULL) *p = '\0'; c->reason = sstrdup (reason); clone_exempts.push_back (c); } } else if (!strcmp (item, "CK")) { char *enable = strtok (NULL, " "); if (enable != NULL) kline_enabled = atoi (enable) != 0; } } fclose (f); } static int is_exempt (char const * const ip) { /* first check for an exact match */ foreach (cexcept_t *c, clone_exempts) if (!strcmp (ip, c->ip)) return c->clones; /* then look for cidr */ foreach (cexcept_t *c, clone_exempts) if (!match_ips (c->ip, ip)) return c->clones; return 0; } static void os_cmd_clones (sourceinfo_t *si, int parc, char *parv[]) { command_t const *c; char *cmd = parv[0]; /* Bad/missing arg */ if (!cmd) { command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "CLONES"); command_fail (si, fault::needmoreparams, _("Syntax: CLONES KLINE|LIST|ADDEXEMPT|DELEXEMPT|LISTEXEMPT [parameters]")); return; } c = os_clones_cmds.find (cmd); if (c == NULL) { command_fail (si, fault::badparams, _("Invalid command. Use \2/%s%s help\2 for a command listing."), (ircd->uses_rcommand == false) ? "msg " : "", opersvs.me->disp); return; } c->exec (si->service, si, parc + 1, parv + 1); } static void os_cmd_clones_kline (sourceinfo_t *si, int parc, char *parv[]) { char const *arg = parv[0]; if (arg == NULL) arg = ""; if (!strcasecmp (arg, "ON")) { if (kline_enabled) { command_fail (si, fault::nochange, _("CLONES klines are already enabled.")); return; } kline_enabled = true; command_success_nodata (si, _("Enabled CLONES klines.")); wallops ("\2%s\2 enabled CLONES klines", get_oper_name (si)); snoop ("CLONES:KLINE:ON: \2%s\2", get_oper_name (si)); logcommand (si, CMDLOG_ADMIN, "CLONES KLINE ON"); write_exemptdb (); } else if (!strcasecmp (arg, "OFF")) { if (!kline_enabled) { command_fail (si, fault::nochange, _("CLONES klines are already disabled.")); return; } kline_enabled = false; command_success_nodata (si, _("Disabled CLONES klines.")); wallops ("\2%s\2 disabled CLONES klines", get_oper_name (si)); snoop ("CLONES:KLINE:OFF: \2%s\2", get_oper_name (si)); logcommand (si, CMDLOG_ADMIN, "CLONES KLINE OFF"); write_exemptdb (); } else { if (kline_enabled) command_success_string (si, "ON", _("CLONES klines are currently enabled.")); else command_success_string (si, "OFF", _("CLONES klines are currently disabled.")); } } static void os_cmd_clones_list (sourceinfo_t *si, int parc, char *parv[]) { hostentry_t *he; int k = 0; int allowed = 0; foreach (host_pair &hp, hostlist) { he = hp.second; k = he->clients.size (); if (k > 3) { if ((allowed = is_exempt (he->ip))) command_success_nodata (si, _("%d from %s (\2EXEMPT\2; allowed %d)"), k, he->ip, allowed); else command_success_nodata (si, _("%d from %s"), k, he->ip); } } command_success_nodata (si, _("End of CLONES LIST")); logcommand (si, CMDLOG_ADMIN, "CLONES LIST"); } static void os_cmd_clones_addexempt (sourceinfo_t *si, int parc, char *parv[]) { char *ip = parv[0]; char *clonesstr = parv[1]; int clones; char *reason = parv[2]; cexcept_t *c = NULL; if (!ip || !clonesstr) { command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "CLONES ADDEXEMPT"); command_fail (si, fault::needmoreparams, _("Syntax: CLONES ADDEXEMPT ")); return; } clones = atoi (clonesstr); if (clones <= DEFAULT_WARN_CLONES) { command_fail (si, fault::badparams, _("Allowed clones count must be more than %d"), DEFAULT_WARN_CLONES); return; } foreach (cexcept_t *t, clone_exempts) if (!strcmp (ip, t->ip)) c = t; if (c == NULL) { if (!reason) { command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "CLONES ADDEXEMPT"); command_fail (si, fault::needmoreparams, _("Syntax: CLONES ADDEXEMPT ")); return; } c = new cexcept_t; c->ip = sstrdup (ip); c->reason = sstrdup (reason); clone_exempts.push_back (c); command_success_nodata (si, _("Added \2%s\2 to clone exempt list."), ip); } else { if (reason) { sfree (c->reason); c->reason = sstrdup (reason); } command_success_nodata (si, _("Updated \2%s\2 in clone exempt list."), ip); } c->clones = clones; snoop ("CLONES:ADDEXEMPT: \2%s\2 \2%d\2 (%s) by \2%s\2", ip, clones, c->reason, get_oper_name (si)); logcommand (si, CMDLOG_ADMIN, "CLONES ADDEXEMPT %s %d %s", ip, clones, c->reason); write_exemptdb (); } static void os_cmd_clones_delexempt (sourceinfo_t *si, int parc, char *parv[]) { cexcept_t::vector_type::iterator it = clone_exempts.begin (); cexcept_t::vector_type::iterator et = clone_exempts.end (); char *arg = parv[0]; if (!arg) return; while (it != et) { cexcept_t *c = *it; if (!strcmp (c->ip, arg)) { sfree (c->ip); sfree (c->reason); delete c; clone_exempts.erase (it); command_success_nodata (si, _("Removed \2%s\2 from clone exempt list."), arg); snoop ("CLONES:DELEXEMPT: \2%s\2 by \2%s\2", arg, get_oper_name (si)); logcommand (si, CMDLOG_ADMIN, "CLONES DELEXEMPT %s", arg); write_exemptdb (); return; } ++it; } command_fail (si, fault::nosuch_target, _("\2%s\2 not found in clone exempt list."), arg); } static void os_cmd_clones_listexempt (sourceinfo_t *si, int parc, char *parv[]) { foreach (cexcept_t *c, clone_exempts) command_success_nodata (si, "%s (%d, %s)", c->ip, c->clones, c->reason); command_success_nodata (si, _("End of CLONES LISTEXEMPT")); logcommand (si, CMDLOG_ADMIN, "CLONES LISTEXEMPT"); } static void clones_newuser (user_t *u) { unsigned i = 0; hostentry_t *he; host_map::iterator heit; unsigned allowed = 0; /* User has no IP, ignore him */ if (is_internal_client (u) || *u->ip == '\0') return; heit = hostlist.find (u->ip); if (heit == hostlist.end ()) { he = new hostentry_t; strlcpy (he->ip, u->ip, sizeof he->ip); hostlist[he->ip] = he; } else he = heit->second; he->clients.push_back (u); i = he->clients.size (); if (i > DEFAULT_WARN_CLONES) { allowed = is_exempt (u->ip); if (allowed == 0 || i > allowed) { if (he->lastaction + 300 < NOW) he->lastaction_clones = i; else if (i <= he->lastaction_clones) return; else he->lastaction_clones = i; he->lastaction = NOW; if (allowed == 0 && i < config_options.default_clone_limit) snoop ("CLONES: %d clones on %s (%s!%s@%s)", i, u->ip, u->nick, u->user, u->host); else if (allowed != 0 && i < allowed + EXEMPT_GRACE) snoop ("CLONES: %d clones on %s (%s!%s@%s) (%d allowed)", i, u->ip, u->nick, u->user, u->host, allowed); else if (!kline_enabled) snoop ("CLONES: %d clones on %s (%s!%s@%s) (TKLINE disabled)", i, u->ip, u->nick, u->user, u->host); else { snoop ("CLONES: %d clones on %s (%s!%s@%s) (TKLINE due to excess clones)", i, u->ip, u->nick, u->user, u->host); slog (LG_INFO, "clones_newuser(): klining *@%s (user %s!%s@%s)", u->ip, u->nick, u->user, u->host); phandler->kline_sts ("*", "*", u->ip, 3600, "Excessive clones"); } } } } static void clones_userquit (user_t *u) { host_map::iterator heit; hostentry_t *he; /* User has no IP, ignore him */ if (is_internal_client (u) || *u->ip == '\0') return; heit = hostlist.find (u->ip); if (heit == hostlist.end ()) { slog (LG_DEBUG, "clones_userquit(): hostentry for %s not found??", u->ip); return; } he = heit->second; user_t::vector_type::iterator it = find_element (u, he->clients); if (it != he->clients.end ()) { he->clients.erase (it); if (he->clients.empty ()) { hostlist.erase (he->ip); delete he; } } } static inline void clones_newuser_pair (user_t::pair_type &up) { clones_newuser (up.second); } bool _modinit (module *m) { os_cmdtree << os_clones; os_clones_cmds << os_clones_kline; os_clones_cmds << os_clones_list; os_clones_cmds << os_clones_addexempt; os_clones_cmds << os_clones_delexempt; os_clones_cmds << os_clones_listexempt; help_addentry (os_helptree, "CLONES", "help/operserv/clones", NULL); user_t::callback.add.attach (clones_newuser); user_t::callback.remove.attach (clones_userquit); load_exemptdb (); /* add everyone to host hash */ std::for_each (user_t::map.begin (), user_t::map.end (), clones_newuser_pair); return true; } void _moddeinit (void) { foreach (host_pair &hp, hostlist) delete hp.second; while (!clone_exempts.empty ()) { cexcept_t *c = clone_exempts.back (); sfree (c->ip); sfree (c->reason); delete c; clone_exempts.pop_back (); } os_cmdtree >> os_clones; os_clones_cmds >> os_clones_kline; os_clones_cmds >> os_clones_list; os_clones_cmds >> os_clones_addexempt; os_clones_cmds >> os_clones_delexempt; os_clones_cmds >> os_clones_listexempt; help_delentry (os_helptree, "CLONES"); user_t::callback.add.detach (clones_newuser); user_t::callback.remove.detach (clones_userquit); }