/** * enforce.C: This file contains code for the NickServ RELEASE/ENFORCE functions. * * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team * Rights to this code are as documented in COPYING. * * This does nickserv enforcement on registered nicks if the ENFORCE option * has been enabled. Users who do not identify within 30-60 seconds have * their nick changed to Guest. * If the ircd or protocol module do not support forced nick changes, * they are killed instead. * Enforcement of the nick is only supported for ircds that support * holdnick_sts(), currently bahamut, charybdis, hybrid, inspircd11, * solidircd, ratbox and unreal (i.e. making sure they can't change back * immediately). Consequently this module is of little use for other ircds. * Note: For hybrid and ratbox, and charybdis before 2.1, the * RELEASE command to remove an enforcer prematurely is not supported, * although it pretends to be successful. * * * Portions of this file were derived from sources bearing the following license: * Copyright © 2005-2007 Atheme Development Group * Rights to this code are as documented in doc/pod/license.pod. * * $Id: enforce.C,v 1.10 2007/09/22 14:27:27 pippijn Exp $ */ #include "atheme.h" #include #include #include #include #include static char const rcsid[] = "$Id: enforce.C,v 1.10 2007/09/22 14:27:27 pippijn Exp $"; REGISTER_MODULE ("nickserv/enforce", false, "The Ermyth Team "); #define SHOW_CORRECT_TIMEOUT_BUT_BE_SLOW 0 #define ENFORCE_TIMEOUT 30 #define ENFORCE_CHECK_FREQ 5 struct enforce_timeout_t : zero_initialised { char nick[NICKLEN]; char host[HOSTLEN]; time_t timelimit; node_t node; }; list_t enforce_list; static void guest_nickname (user_t *u); static void ns_cmd_set_enforce (sourceinfo_t *si, int parc, char *parv[]); static void ns_cmd_release (sourceinfo_t *si, int parc, char *parv[]); static void enforce_timeout_check (void *arg); command_t const ns_set_enforce = { "ENFORCE", N_("Enables or disables automatic protection of a nickname."), AC_NONE, 1, ns_cmd_set_enforce }; command_t const ns_release = { "RELEASE", N_("Releases a services enforcer."), AC_NONE, 2, ns_cmd_release }; E cmdvec ns_cmdtree; E cmdvec ns_set_cmdtree; E helpvec ns_helptree; /* sends an FNC for the given user */ static void guest_nickname (user_t *u) { char gnick[NICKLEN]; int tries; /* Generate a new guest nickname and check if it already exists * This will try to generate a new nickname 30 different times * if nicks are in use. If it runs into 30 nicks in use, maybe * you shouldn't use this module. */ for (tries = 0; tries < 30; tries++) { snprintf (gnick, sizeof gnick, "Guest%d", gen_rand32 () % 100000); if (!user_find_named (gnick)) break; } phandler->fnc_sts (nicksvs.me->me, u, gnick, FNC_FORCE); } static void ns_cmd_set_enforce (sourceinfo_t *si, int parc, char *parv[]) { char *setting = parv[0]; if (!setting) { command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "ENFORCE"); command_fail (si, fault::needmoreparams, _("Syntax: SET ENFORCE ON|OFF")); return; } if (!si->smu) { command_fail (si, fault::noprivs, _("You are not logged in.")); return; } if (strcasecmp (setting, "ON") == 0) { if (si->smu->find_metadata ("private:doenforce")) command_fail (si, fault::nochange, _("ENFORCE is already enabled.")); else { si->smu->add_metadata ("private:doenforce", "1"); command_success_nodata (si, _("ENFORCE is now enabled.")); } } else if (strcasecmp (setting, "OFF") == 0) { if (si->smu->del_metadata ("private:doenforce")) command_success_nodata (si, _("ENFORCE is now disabled.")); else command_fail (si, fault::nochange, _("ENFORCE is already disabled.")); } else command_fail (si, fault::badparams, _("Unknown value for ENFORCE. Expected values are ON or OFF.")); } static void ns_cmd_release (sourceinfo_t *si, int parc, char *parv[]) { mynick_t *mn; char *target = parv[0]; char *password = parv[1]; user_t *u; node_t *n, *tn; enforce_timeout_t *timeout; /* Absolutely do not do anything like this if nicks * are not considered owned */ if (nicksvs.no_nick_ownership) { command_fail (si, fault::noprivs, _("RELEASE is disabled.")); return; } if (!target && si->smu != NULL) target = si->smu->name; if (!target) { command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "RELEASE"); command_fail (si, fault::needmoreparams, _("Syntax: RELEASE [password]")); return; } u = user_find_named (target); mn = mynick_t::find (target); if (!mn) { command_fail (si, fault::nosuch_target, _("\2%s\2 is not a registered nickname."), target); return; } if (u == si->su) { command_fail (si, fault::noprivs, _("You cannot RELEASE yourself.")); return; } if ((si->smu == mn->owner) || mn->owner->verify_password (password)) { /* if this (nick, host) is waiting to be enforced, remove it */ LIST_FOREACH_SAFE (n, tn, enforce_list.head) { timeout = static_cast (n->data); if (!irccasecmp (mn->nick, timeout->nick) && (!strcmp (u->host, timeout->host) || !strcmp (u->vhost, timeout->host))) { node_del (&timeout->node, &enforce_list); delete timeout; } } if (u == NULL || is_internal_client (u)) { logcommand (si, CMDLOG_DO, "RELEASE %s", target); phandler->holdnick_sts (si->service->me, 0, target, mn->owner); command_success_nodata (si, _("\2%s\2 has been released."), target); } else { notice (nicksvs.nick, target, "%s has released your nickname.", get_source_mask (si)); guest_nickname (u); command_success_nodata (si, _("%s has been released."), target); logcommand (si, CMDLOG_DO, "RELEASE %s!%s@%s", u->nick, u->user, u->vhost); } return; } if (!password) { command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "RELEASE"); command_fail (si, fault::needmoreparams, _("Syntax: RELEASE [password]")); return; } else { logcommand (si, CMDLOG_DO, "failed RELEASE %s (bad password)", target); command_fail (si, fault::authfail, _("Invalid password for \2%s\2."), target); } } void enforce_timeout_check (void *arg) { node_t *n, *tn; enforce_timeout_t *timeout; user_t *u; mynick_t *mn; bool valid; LIST_FOREACH_SAFE (n, tn, enforce_list.head) { timeout = static_cast (n->data); if (timeout->timelimit > NOW) break; /* assume sorted list */ u = user_find_named (timeout->nick); mn = mynick_t::find (timeout->nick); valid = u != NULL && mn != NULL && (!strcmp (u->host, timeout->host) || !strcmp (u->vhost, timeout->host)); node_del (&timeout->node, &enforce_list); delete timeout; if (!valid) continue; if (is_internal_client (u)) continue; if (u->myuser == mn->owner) continue; if (mn->owner->access_verify (u)) continue; if (!mn->owner->find_metadata ("private:doenforce")) continue; notice (nicksvs.nick, u->nick, "You failed to identify in time for the nickname %s", mn->nick); guest_nickname (u); phandler->holdnick_sts (nicksvs.me->me, 3600, u->nick, mn->owner); } } static void show_enforce (mynick_t *mn, myuser_t *mu, sourceinfo_t *si) { if (!mu->find_metadata ("private:doenforce")) command_success_nodata (si, "%s has enabled nick protection", mu->name); } static bool check_registration (sourceinfo_t *si, char const * const account, char const * const email) { if (!strncasecmp (account, "Guest", 5) && isdigit (account[5])) { command_fail (si, fault::badparams, "The nick \2%s\2 is reserved and cannot be registered.", account); return false; } return true; } static void check_enforce (mynick_t *mn, user_t *u) { enforce_timeout_t *timeout; #if SHOW_CORRECT_TIMEOUT_BUT_BE_SLOW enforce_timeout_t *timeout2; #endif /* nick is a service, ignore it */ if (is_internal_client (u)) return; if (!mn->owner->find_metadata ("private:doenforce")) return; /* check if it's already in enforce_list */ timeout = NULL; #if SHOW_CORRECT_TIMEOUT_BUT_BE_SLOW /* don't do this now, it's O(n^2) in the number of users using * a nick without access at a time */ LIST_FOREACH (n, enforce_list.head) { timeout2 = n->data; if (!irccasecmp (mn->nick, timeout2->nick) && (!strcmp (u->host, timeout2->host) || !strcmp (u->vhost, timeout2->host))) { timeout = timeout2; break; } } #endif if (timeout == NULL) { timeout = new enforce_timeout_t; strlcpy (timeout->nick, mn->nick, sizeof timeout->nick); strlcpy (timeout->host, u->host, sizeof timeout->host); /* the following ENFORCE_TIMEOUT must be constant, * otherwise the timeouts will not be sorted and * enforce_timeout_check() will break */ timeout->timelimit = NOW + ENFORCE_TIMEOUT; node_add (timeout, &timeout->node, &enforce_list); } notice (nicksvs.nick, u->nick, "You have %d seconds to identify to your nickname before it is changed.", timeout->timelimit - NOW); } static void idcheck_foreach_cb (myuser_t::pair_type &it) { myuser_t *mu = it.second; mu->del_metadata ("private:idcheck"); mu->del_metadata ("private:enforcer"); } bool _modinit (module *m) { /* Leave this for compatibility with old versions of this code * -- jilles */ std::for_each (myuser_t::map.begin (), myuser_t::map.end (), idcheck_foreach_cb); /* Absolutely do not do anything like this if nicks * are not considered owned */ if (nicksvs.no_nick_ownership) { slog (LG_ERROR, "%s: nicks are not configured to be owned", m->name); return false; } event_add ("enforce_timeout_check", enforce_timeout_check, NULL, ENFORCE_CHECK_FREQ); /*event_add("manage_bots", manage_bots, NULL, 30); */ ns_cmdtree << ns_release; ns_set_cmdtree << ns_set_enforce; help_addentry (ns_helptree, "RELEASE", "help/nickserv/release", NULL); help_addentry (ns_helptree, "SET ENFORCE", "help/nickserv/set_enforce", NULL); mynick_t::callback.info.attach (show_enforce); user_t::callback.can_register.attach (check_registration); mynick_t::callback.enforce.attach (check_enforce); return true; } void _moddeinit () { event_delete (enforce_timeout_check, NULL); ns_cmdtree >> ns_release; ns_set_cmdtree >> ns_set_enforce; help_delentry (ns_helptree, "RELEASE"); help_delentry (ns_helptree, "SET ENFORCE"); mynick_t::callback.info.detach (show_enforce); user_t::callback.can_register.detach (check_registration); mynick_t::callback.enforce.detach (check_enforce); }