/** * identify.C: This file contains code for the NickServ IDENTIFY and LOGIN functions. * * 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 © 2005-2006 William Pitcock, et al. * Rights to this code are as documented in doc/pod/license.pod. * * $Id: identify.C,v 1.10 2007/09/22 14:27:27 pippijn Exp $ */ #include "atheme.h" #include #include #include #include #include #include #include /* Check whether we are compiling IDENTIFY or LOGIN */ #ifdef NICKSERV_LOGIN #define COMMAND_UC "LOGIN" #define COMMAND_LC "login" #else #define COMMAND_UC "IDENTIFY" #define COMMAND_LC "identify" #endif static char const rcsid[] = "$Id: identify.C,v 1.10 2007/09/22 14:27:27 pippijn Exp $"; REGISTER_MODULE ("nickserv/" COMMAND_LC, false, "The Ermyth Team "); static void ns_cmd_login (sourceinfo_t *si, int parc, char *parv[]); #ifdef NICKSERV_LOGIN command_t const ns_login = { "LOGIN", N_("Authenticates to a services account."), AC_NONE, 2, ns_cmd_login }; #else command_t const ns_identify = { "IDENTIFY", N_("Identifies to services for a nickname."), AC_NONE, 2, ns_cmd_login }; command_t const ns_id = { "ID", N_("Alias for IDENTIFY"), AC_NONE, 2, ns_cmd_login }; #endif E cmdvec ns_cmdtree; E helpvec ns_helptree; bool _modinit (module *m) { #ifdef NICKSERV_LOGIN ns_cmdtree << ns_login; help_addentry (ns_helptree, "LOGIN", "help/nickserv/login", NULL); #else ns_cmdtree << ns_identify; ns_cmdtree << ns_id; help_addentry (ns_helptree, "IDENTIFY", "help/nickserv/identify", NULL); help_addentry (ns_helptree, "ID", "help/nickserv/identify", NULL); #endif return true; } void _moddeinit () { #ifdef NICKSERV_LOGIN ns_cmdtree >> ns_login; help_delentry (ns_helptree, "LOGIN"); #else ns_cmdtree >> ns_identify; ns_cmdtree >> ns_id; help_delentry (ns_helptree, "IDENTIFY"); help_delentry (ns_helptree, "ID"); #endif } static void ns_cmd_login (sourceinfo_t *si, int parc, char *parv[]) { user_t *u = si->su; myuser_t *mu; mynick_t *mn; chanuser_t *cu; chanacs_t *ca; node_t *n; char *target = parv[0]; char *password = parv[1]; char buf[BUFSIZE], strfbuf[32]; char lau[BUFSIZE], lao[BUFSIZE]; struct tm tm; metadata *md_failnum; if (si->su == NULL) { command_fail (si, fault::noprivs, _("\2%s\2 can only be executed via IRC."), COMMAND_UC); return; } #ifndef NICKSERV_LOGIN if (!nicksvs.no_nick_ownership && target && !password) { password = target; target = si->su->nick; } #endif if (!target || !password) { command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, COMMAND_UC); command_fail (si, fault::needmoreparams, nicksvs.no_nick_ownership ? "Syntax: " COMMAND_UC " " : "Syntax: " COMMAND_UC " [nick] "); return; } if (nicksvs.no_nick_ownership) mu = myuser_t::find (target); else { mn = mynick_t::find (target); mu = mn != NULL ? mn->owner : NULL; } if (!mu) { command_fail (si, fault::nosuch_target, _("\2%s\2 is not a registered nickname."), target); return; } if (mu->find_metadata ("private:freeze:freezer")) { command_fail (si, fault::authfail, nicksvs.no_nick_ownership ? "You cannot login as \2%s\2 because the account has been frozen." : "You cannot identify to \2%s\2 because the nickname has been frozen.", mu->name); logcommand (si, CMDLOG_LOGIN, "failed " COMMAND_UC " to %s (frozen)", mu->name); return; } if (u->myuser == mu) { command_fail (si, fault::nochange, _("You are already logged in as \2%s\2."), u->myuser->name); return; } else if (u->myuser != NULL && !si->service->cmdtree->find ("LOGOUT")) { command_fail (si, fault::alreadyexists, _("You are already logged in as \2%s\2."), u->myuser->name); return; } else if (u->myuser != NULL && phandler->ircd_on_logout (u->nick, u->myuser->name, NULL)) /* logout killed the user... */ return; /* we use this in both cases, so set it up here. may be NULL. */ md_failnum = mu->find_metadata ("private:loginfail:failnum"); if (mu->verify_password (password)) { if (mu->logins.size () >= me.maxlogins) { command_fail (si, fault::toomany, _("There are already \2%d\2 sessions logged in to \2%s\2 (maximum allowed: %d)."), mu->logins.size (), mu->name, me.maxlogins); logcommand (si, CMDLOG_LOGIN, "failed " COMMAND_UC " to %s (too many logins)", mu->name); return; } /* if they are identified to another account, nuke their session first */ if (u->myuser) { myuser_t::login_vector::iterator it, it_end; u->myuser->lastlogin = NOW; for (it = u->myuser->logins.begin (), it_end = u->myuser->logins.end (); it != it_end; ++it) { if (*it == u) { u->myuser->logins.erase (u); break; } } u->myuser = NULL; } if (is_soper (mu)) snoop ("SOPER: \2%s\2 as \2%s\2", u->nick, mu->name); mu->notice (nicksvs.nick, "%s!%s@%s has just authenticated as you (%s)", u->nick, u->user, u->vhost, mu->name); u->myuser = mu; mu->logins.insert (u); /* keep track of login address for users */ strlcpy (lau, u->user, BUFSIZE); strlcat (lau, "@", BUFSIZE); strlcat (lau, u->vhost, BUFSIZE); mu->add_metadata ("private:host:vhost", lau); /* and for opers */ strlcpy (lao, u->user, BUFSIZE); strlcat (lao, "@", BUFSIZE); strlcat (lao, u->host, BUFSIZE); mu->add_metadata ("private:host:actual", lao); logcommand (si, CMDLOG_LOGIN, COMMAND_UC); command_success_nodata (si, nicksvs.no_nick_ownership ? "You are now logged in as \2%s\2." : "You are now identified for \2%s\2.", u->myuser->name); /* check for failed attempts and let them know */ if (md_failnum && (atoi (md_failnum->value) > 0)) { metadata *md_failtime, *md_failaddr; time_t ts; tm = *localtime (&mu->lastlogin); strftime (strfbuf, sizeof (strfbuf) - 1, "%b %d %H:%M:%S %Y", &tm); command_success_nodata (si, _("\2%d\2 failed %s since %s."), atoi (md_failnum->value), (atoi (md_failnum->value) == 1) ? "login" : "logins", strfbuf); md_failtime = mu->find_metadata ("private:loginfail:lastfailtime"); ts = atol (md_failtime->value); md_failaddr = mu->find_metadata ("private:loginfail:lastfailaddr"); tm = *localtime (&ts); strftime (strfbuf, sizeof (strfbuf) - 1, "%b %d %H:%M:%S %Y", &tm); command_success_nodata (si, _("Last failed attempt from: \2%s\2 on %s."), md_failaddr->value, strfbuf); mu->del_metadata ("private:loginfail:failnum"); /* md_failnum now invalid */ mu->del_metadata ("private:loginfail:lastfailtime"); mu->del_metadata ("private:loginfail:lastfailaddr"); } mu->lastlogin = NOW; mn = mynick_t::find (u->nick); if (mn != NULL && mn->owner == mu) mn->lastseen = NOW; /* XXX: ircd_on_login supports hostmasking, we just dont have it yet. */ /* don't allow them to join regonly chans until their * email is verified */ if (!(mu->flags & MU_WAITAUTH)) phandler->ircd_on_login (si->su->nick, mu->name, NULL); u->callback.identify (u); /* now we get to check for xOP */ /* we don't check for host access yet (could match different * entries because of services cloaks) */ LIST_FOREACH (n, mu->chanacs.head) { ca = (chanacs_t *) n->data; cu = chanuser_find (ca->mychan->chan, u); if (cu && chansvs.me != NULL) { if (ca->level & CA_AKICK && !(ca->level & CA_REMOVE)) { /* Stay on channel if this would empty it -- jilles */ if (ca->mychan->chan->nummembers <= (ca->mychan->flags & MC_GUARD ? 2 : 1)) { ca->mychan->flags |= MC_INHABIT; if (!(ca->mychan->flags & MC_GUARD)) join (cu->chan->name, chansvs.nick); } ban (chansvs.me->me, ca->mychan->chan, u); remove_ban_exceptions (chansvs.me->me, ca->mychan->chan, u); phandler->kick (chansvs.nick, ca->mychan->name, u->nick, "User is banned from this channel"); continue; } if (ca->level & CA_USEDUPDATE) ca->mychan->used = NOW; if (ca->mychan->flags & MC_NOOP || mu->flags & MU_NOOP) continue; if (ircd->uses_owner && !(cu->modes & ircd->owner_mode) && ca->level & CA_AUTOOP && ca->myuser->should_owner (ca->mychan)) { modestack_mode_param (chansvs.nick, ca->mychan->chan, MTYPE_ADD, ircd->owner_mchar[1], CLIENT_NAME (u)); cu->modes |= ircd->owner_mode; } if (ircd->uses_protect && !(cu->modes & ircd->protect_mode) && ca->level & CA_AUTOOP && ca->myuser->should_protect (ca->mychan)) { modestack_mode_param (chansvs.nick, ca->mychan->chan, MTYPE_ADD, ircd->protect_mchar[1], CLIENT_NAME (u)); cu->modes |= ircd->protect_mode; } if (!(cu->modes & CMODE_OP) && ca->level & CA_AUTOOP) { modestack_mode_param (chansvs.nick, ca->mychan->chan, MTYPE_ADD, 'o', CLIENT_NAME (u)); cu->modes |= CMODE_OP; } if (ircd->uses_halfops && !(cu->modes & (CMODE_OP | ircd->halfops_mode)) && ca->level & CA_AUTOHALFOP) { modestack_mode_param (chansvs.nick, ca->mychan->chan, MTYPE_ADD, 'h', CLIENT_NAME (u)); cu->modes |= ircd->halfops_mode; } if (!(cu->modes & (CMODE_OP | ircd->halfops_mode | CMODE_VOICE)) && ca->level & CA_AUTOVOICE) { modestack_mode_param (chansvs.nick, ca->mychan->chan, MTYPE_ADD, 'v', CLIENT_NAME (u)); cu->modes |= CMODE_VOICE; } } } return; } logcommand (si, CMDLOG_LOGIN, "failed " COMMAND_UC " to %s (bad password)", mu->name); command_fail (si, fault::authfail, _("Invalid password for \2%s\2."), mu->name); /* record the failed attempts */ /* note that we reuse this buffer later when warning opers about failed logins */ snprintf (buf, sizeof buf, "%s!%s@%s", u->nick, u->user, u->vhost); /* increment fail count */ if (md_failnum && (atoi (md_failnum->value) > 0)) md_failnum = mu->add_metadata ("private:loginfail:failnum", itoa (atoi (md_failnum->value) + 1)); else md_failnum = mu->add_metadata ("private:loginfail:failnum", "1"); mu->add_metadata ("private:loginfail:lastfailaddr", buf); mu->add_metadata ("private:loginfail:lastfailtime", itoa (NOW)); if (atoi (md_failnum->value) == 10) { time_t ts = NOW; tm = *localtime (&ts); strftime (strfbuf, sizeof (strfbuf) - 1, "%b %d %H:%M:%S %Y", &tm); wallops ("Warning: Numerous failed login attempts to \2%s\2. Last attempt received from \2%s\2 on %s.", mu->name, buf, strfbuf); } }