/* Nickname mail address authentication module. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include "modules.h" #include "conffile.h" #include "commands.h" #include "language.h" #include "modules/mail/mail.h" #include "modules/operserv/operserv.h" #include "nickserv.h" #include "ns-local.h" /*************************************************************************/ /* * This module provides the ability to verify the accuracy of E-mail * addresses associated with nicknames. Upon registering a new nickname * or changing the E-mail address of a nickname, a random authentication * code (see below) is generated and mailed to the user, with instructions * to use the AUTH command (provided by this module) to verify the nick * registration or address change. Until this is done, the nick may not * be identified for, essentially preventing use of the nick. * * The access code generated is a random 9-digit number, lower-bounded to * 100,000,000 to avoid potential difficulties with leading zeroes. */ /*************************************************************************/ /*************************** Local variables *****************************/ /*************************************************************************/ static Module *module; static Module *module_nickserv; static Module *module_mail; static time_t NSNoAuthExpire = 0; static time_t NSSendauthDelay = 0; static int cb_authed = -1; static void do_auth(User *u); static void do_sendauth(User *u); static void do_setauth(User *u); static void do_getauth(User *u); static void do_clearauth(User *u); static Command commands[] = { { "AUTH", do_auth, NULL, NICK_HELP_AUTH, -1,-1 }, { "SENDAUTH", do_sendauth, NULL, NICK_HELP_SENDAUTH, -1,-1 }, { "SETAUTH", do_setauth, is_services_admin, -1,-1,NICK_OPER_HELP_SETAUTH }, { "GETAUTH", do_getauth, is_services_admin, -1,-1,NICK_OPER_HELP_GETAUTH }, { "CLEARAUTH",do_clearauth,is_services_admin, -1,-1,NICK_OPER_HELP_CLEARAUTH }, { NULL } }; /*************************************************************************/ /**************************** Local routines *****************************/ /*************************************************************************/ /* Create an authentication code for the given nickname group and store it * in the NickGroupInfo structure. `reason' is the NICKAUTH_* value to * store in the `authreason' field. */ static void make_auth(NickGroupInfo *ngi, int16 reason) { ngi->authcode = rand()%900000000 + 100000000; ngi->authset = time(NULL); ngi->authreason = reason; put_nickgroupinfo(ngi); } /*************************************************************************/ /* Send mail to a nick's E-mail address containing the authentication code. * `what' is one of the IS_* constants defined below. `caller_line' is * filled in by the macro below. Returns the result of sendmail(). */ #define IS_REGISTRATION NICK_AUTH_MAIL_TEXT_REG #define IS_EMAIL_CHANGE NICK_AUTH_MAIL_TEXT_EMAIL #define IS_SENDAUTH NICK_AUTH_MAIL_TEXT_SENDAUTH #define IS_SETAUTH -1 static int send_auth(User *u, NickGroupInfo *ngi, const char *nick, int what, int caller_line) { char subject[BUFSIZE], body[BUFSIZE]; const char *text; if (!u || !ngi || !ngi->email) { module_log("send_auth() with %s! (called from line %d)", !u ? "null User" : ngi ? "NickGroupInfo with no E-mail" : "null NickGroupInfo", caller_line); return -1; } text = what<0 ? "" : getstring(ngi, what); snprintf(subject, sizeof(subject), getstring(ngi,NICK_AUTH_MAIL_SUBJECT), nick); if (what == IS_SETAUTH) { snprintf(body, sizeof(body), getstring(ngi,NICK_AUTH_MAIL_BODY_SETAUTH), nick, ngi->authcode, s_NickServ, s_NickServ, ngi->authcode); } else { snprintf(body, sizeof(body), getstring(ngi,NICK_AUTH_MAIL_BODY), nick, ngi->authcode, s_NickServ, s_NickServ, ngi->authcode, s_NickServ, text, u->username, u->host); } return sendmail(ngi->email, subject, body); } #define send_auth(u,ngi,nick,what) send_auth(u,ngi,nick,what,__LINE__) /*************************************************************************/ /*************************** Command handlers ****************************/ /*************************************************************************/ /* Handle the AUTH command. */ static void do_auth(User *u) { char *s = strtok(NULL, " "); int32 code; NickInfo *ni; NickGroupInfo *ngi; if (!s || !*s) { syntax_error(s_NickServ, u, "AUTH", NICK_AUTH_SYNTAX); } else if (readonly) { notice_lang(s_NickServ, u, NICK_AUTH_DISABLED); } else if (!(ni = u->ni)) { notice_lang(s_NickServ, u, NICK_NOT_REGISTERED); } else if (ni->status & NS_VERBOTEN) { notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u->nick); } else if (!(ngi = u->ngi) || ngi == NICKGROUPINFO_INVALID) { notice_lang(s_NickServ, u, INTERNAL_ERROR); } else if (!ngi->authcode) { notice_lang(s_NickServ, u, NICK_AUTH_NOT_NEEDED, s_NickServ); } else if (!ngi->email) { module_log("BUG: do_auth() for %s[%u]: authcode set but no email!", ni->nick, ngi->id); notice_lang(s_NickServ, u, INTERNAL_ERROR); } else { const char *what = "(unknown)"; int16 authreason = ngi->authreason; errno = 0; code = strtol(s, &s, 10); if (errno == ERANGE || *s || code != ngi->authcode) { char buf[BUFSIZE]; snprintf(buf, sizeof(buf), "AUTH for %s", ni->nick); notice_lang(s_NickServ, u, NICK_AUTH_FAILED); if (bad_password(NULL, u, buf) == 1) notice_lang(s_NickServ, u, PASSWORD_WARNING_FOR_AUTH); ngi->bad_auths++; if (BadPassWarning && ngi->bad_auths >= BadPassWarning) { wallops(s_NickServ, "\2Warning:\2 Repeated bad AUTH attempts" " for nick %s", ni->nick); } return; } ngi->authcode = 0; ngi->authset = 0; ngi->authreason = 0; ngi->bad_auths = 0; if (authreason == NICKAUTH_REGISTER) ngi->flags = NSDefFlags; put_nickgroupinfo(ngi); set_identified(u, ni, ngi); switch (authreason) { case NICKAUTH_REGISTER: notice_lang(s_NickServ, u, NICK_AUTH_SUCCEEDED_REGISTER); what = "REGISTER"; break; case NICKAUTH_SET_EMAIL: notice_lang(s_NickServ, u, NICK_AUTH_SUCCEEDED_SET_EMAIL); what = "SET EMAIL"; break; case NICKAUTH_SETAUTH: what = "SETAUTH"; /* fall through */ default: /* "you may now continue using your nick", good for a default */ notice_lang(s_NickServ, u, NICK_AUTH_SUCCEEDED_SETAUTH); break; } module_log("%s@%s authenticated %s for %s", u->username, u->host, what, ni->nick); call_callback_4(module, cb_authed, u, ni, ngi, authreason); } } /*************************************************************************/ /* Handle the SENDAUTH command. To prevent abuse the command is limited to * one use per nick per day (reset when Services starts up). */ static void do_sendauth(User *u) { char *s = strtok(NULL, " "); NickInfo *ni; NickGroupInfo *ngi; time_t now = time(NULL); if (s) { syntax_error(s_NickServ, u, "SENDAUTH", NICK_SENDAUTH_SYNTAX); } else if (!(ni = u->ni)) { notice_lang(s_NickServ, u, NICK_NOT_REGISTERED); } else if (ni->status & NS_VERBOTEN) { notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u->nick); } else if (!(ngi = u->ngi) || ngi == NICKGROUPINFO_INVALID) { notice_lang(s_NickServ, u, INTERNAL_ERROR); } else if (!ngi->authcode) { notice_lang(s_NickServ, u, NICK_AUTH_NOT_NEEDED); } else if (ngi->last_sendauth && now - ngi->last_sendauth < NSSendauthDelay) { notice_lang(s_NickServ, u, NICK_SENDAUTH_TOO_SOON, maketime(ngi,NSSendauthDelay-(now-ngi->last_sendauth),0)); } else if (!ngi->email) { module_log("BUG: do_sendauth() for %s[%u]: authcode set but no email!", ni->nick, ngi->id); notice_lang(s_NickServ, u, INTERNAL_ERROR); } else { int res = send_auth(u, ngi, ni->nick, IS_SENDAUTH); if (res == 0) { ngi->last_sendauth = time(NULL); notice_lang(s_NickServ, u, NICK_AUTH_SENT, ngi->email); } else if (res == 1) { notice_lang(s_NickServ, u, SENDMAIL_NO_RESOURCES); } else { module_log("Valid SENDAUTH by %s!%s@%s failed", u->nick, u->username, u->host); notice_lang(s_NickServ, u, NICK_SENDAUTH_FAILED); } } } /*************************************************************************/ /* Handle the SETAUTH command (Services admins only). */ static void do_setauth(User *u) { char *nick = strtok(NULL, " "); NickInfo *ni; NickGroupInfo *ngi; if (!nick) { syntax_error(s_NickServ, u, "SETAUTH", NICK_SETAUTH_SYNTAX); } else if (!(ni = get_nickinfo(nick))) { notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick); } else if (ni->status & NS_VERBOTEN) { notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick); } else if (!(ngi = get_ngi(ni))) { notice_lang(s_NickServ, u, INTERNAL_ERROR); } else if (ngi->authcode) { notice_lang(s_NickServ, u, NICK_AUTH_HAS_AUTHCODE, ni->nick); } else if (!ngi->email) { notice_lang(s_NickServ, u, NICK_SETAUTH_NO_EMAIL, ni->nick); } else { int i; make_auth(ngi, NICKAUTH_SETAUTH); notice_lang(s_NickServ, u, NICK_SETAUTH_AUTHCODE_SET, ngi->authcode, ni->nick); i = send_auth(u, ngi, ni->nick, IS_SETAUTH); if (i != 0) { module_log("send_auth() failed%s for SETAUTH on %s by %s", i==1 ? " temporarily" : "", nick, u->nick); notice_lang(s_NickServ, u, i==1 ? NICK_SETAUTH_SEND_TEMPFAIL : NICK_SETAUTH_SEND_FAILED, ngi->email); } ngi->last_sendauth = 0; ARRAY_FOREACH (i, ngi->nicks) { NickInfo *ni2; if (irc_stricmp(nick, ngi->nicks[i]) == 0) ni2 = ni; else ni2 = get_nickinfo_noexpire(ngi->nicks[i]); if (!ni2) { module_log("BUG: missing NickInfo for nick %d (%s) of" " nickgroup %u", i, ngi->nicks[i], ngi->id); continue; } ni2->authstat &= ~NA_IDENTIFIED; if (ni2->user) { notice_lang(s_NickServ, ni2->user, NICK_SETAUTH_USER_NOTICE, ngi->email, s_NickServ); } } } } /*************************************************************************/ /* Handle the GETAUTH command (Services admins only). */ static void do_getauth(User *u) { char *nick = strtok(NULL, " "); NickInfo *ni; NickGroupInfo *ngi; if (!nick) { syntax_error(s_NickServ, u, "GETAUTH", NICK_GETAUTH_SYNTAX); } else if (!(ni = get_nickinfo(nick))) { notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick); } else if (ni->status & NS_VERBOTEN) { notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick); } else if (!(ngi = get_ngi(ni))) { notice_lang(s_NickServ, u, INTERNAL_ERROR); } else if (!ngi->authcode) { notice_lang(s_NickServ, u, NICK_AUTH_NO_AUTHCODE, ni->nick); } else { notice_lang(s_NickServ, u, NICK_GETAUTH_AUTHCODE_IS, ni->nick, ngi->authcode); } } /*************************************************************************/ /* Handle the CLEARAUTH command (Services admins only). */ static void do_clearauth(User *u) { char *nick = strtok(NULL, " "); NickInfo *ni; NickGroupInfo *ngi; if (!nick) { syntax_error(s_NickServ, u, "CLEARAUTH", NICK_CLEARAUTH_SYNTAX); } else if (!(ni = get_nickinfo(nick))) { notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick); } else if (ni->status & NS_VERBOTEN) { notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick); } else if (!(ngi = get_ngi(ni))) { notice_lang(s_NickServ, u, INTERNAL_ERROR); } else if (!ngi->authcode) { notice_lang(s_NickServ, u, NICK_AUTH_NO_AUTHCODE, ni->nick); } else { ngi->authcode = 0; put_nickgroupinfo(ngi); notice_lang(s_NickServ, u, NICK_CLEARAUTH_CLEARED, ni->nick); if (readonly) notice_lang(s_NickServ, u, READ_ONLY_MODE); } } /*************************************************************************/ /*************************** Callback routines ***************************/ /*************************************************************************/ /* Nick-registration callback: clear IDENTIFIED flag, set nick flags * appropriately (no kill, secure), send authcode mail. */ static int do_registered(User *u, NickInfo *ni, NickGroupInfo *ngi, int *replied) { int i; make_auth(ngi, NICKAUTH_REGISTER); if ((i = send_auth(u, ngi, ni->nick, IS_REGISTRATION)) != 0) { module_log("send_auth() failed%s for registration (%s)", i==1 ? " temporarily" : "", u->nick); } ni->authstat &= ~NA_IDENTIFIED; ngi->last_sendauth = 0; ngi->flags &= ~(NF_KILLPROTECT | NF_KILL_QUICK | NF_KILL_IMMED); ngi->flags |= NF_SECURE; if (!*replied) { notice_lang(s_NickServ, u, NICK_REGISTERED, u->nick); *replied = 1; } notice_lang(s_NickServ, u, NICK_AUTH_SENT, ngi->email); notice_lang(s_NickServ, u, NICK_AUTH_FOR_REGISTER, s_NickServ); return 0; } /*************************************************************************/ /* SET EMAIL callback: clear IDENTIFIED flag, send authcode mail. */ static int do_set_email(User *u, NickGroupInfo *ngi) { int i; /* Note: we assume here that if it's not a servadmin doing the change, * it must be the user him/herself (and thus use u->nick and u->ni). * See also set.c:do_set_email(). */ if (ngi->email && !is_services_admin(u)) { make_auth(ngi, NICKAUTH_SET_EMAIL); if ((i = send_auth(u, ngi, u->nick, IS_EMAIL_CHANGE)) != 0) { module_log("send_auth() failed%s for E-mail change (%s)", i==1 ? " temporarily" : "", u->nick); } u->ni->authstat &= ~NA_IDENTIFIED; ngi->last_sendauth = 0; notice_lang(s_NickServ, u, NICK_AUTH_SENT, ngi->email); notice_lang(s_NickServ, u, NICK_AUTH_FOR_SET_EMAIL, s_NickServ); } return 0; } /*************************************************************************/ /* IDENTIFY check: do not allow identification if nick not authenticated. */ static int do_identify_check(const User *u, const char *pass) { if (u->ngi && u->ngi != NICKGROUPINFO_INVALID && u->ngi->authcode) { notice_lang(s_NickServ, u, NICK_PLEASE_AUTH, u->ngi->email); notice_lang(s_NickServ, u, MORE_INFO, s_NickServ, "AUTH"); return 1; } return 0; } /*************************************************************************/ /* Expiration check callback. */ static int do_check_expire(NickInfo *ni, NickGroupInfo *ngi) { time_t now = time(NULL); if (!NSNoAuthExpire) return 0; if (ngi && ngi->authcode && ngi->authreason != NICKAUTH_SET_EMAIL && now - ngi->authset >= NSNoAuthExpire ) { module_log("Expiring unauthenticated nick %s", ni->nick); return 1; } return 0; } /*************************************************************************/ /***************************** Module stuff ******************************/ /*************************************************************************/ const int32 module_version = MODULE_VERSION_CODE; ConfigDirective module_config[] = { { "NSNoAuthExpire", { { CD_TIME, 0, &NSNoAuthExpire } } }, { "NSSendauthDelay", { { CD_TIME, 0, &NSSendauthDelay } } }, { NULL } }; /* Old message number */ static int old_LIST_OPER_SYNTAX = -1; static int old_HELP_REGISTER_EMAIL = -1; static int old_OPER_HELP_LIST = -1; static int old_OPER_HELP_LISTEMAIL = -1; /*************************************************************************/ int init_module(Module *module_) { module = module_; module_nickserv = find_module("nickserv/main"); if (!module_nickserv) { module_log("Main NickServ module not loaded"); return 0; } use_module(module_nickserv); module_mail = find_module("mail/main"); if (!module_mail) { module_log("Mail module not loaded"); return 0; } use_module(module_mail); if (!NSRequireEmail) { module_log("NSRequireEmail must be set to use nickname" " authentication"); return 0; } if (!register_commands(module_nickserv, commands)) { module_log("Unable to register commands"); exit_module(0); return 0; } cb_authed = register_callback(module, "authed"); if (cb_authed < 0) { module_log("Unable to register callback"); exit_module(0); return 0; } if (!add_callback(module_nickserv, "registered", do_registered) || !add_callback(module_nickserv, "SET EMAIL", do_set_email) || !add_callback(module_nickserv, "IDENTIFY check", do_identify_check) || !add_callback(module_nickserv, "check_expire", do_check_expire) ) { module_log("Unable to add callbacks"); exit_module(0); return 0; } old_LIST_OPER_SYNTAX = setstring(NICK_LIST_OPER_SYNTAX, NICK_LIST_OPER_SYNTAX_AUTH); old_HELP_REGISTER_EMAIL = setstring(NICK_HELP_REGISTER_EMAIL, NICK_HELP_REGISTER_EMAIL_AUTH); old_OPER_HELP_LIST = setstring(NICK_OPER_HELP_LIST, NICK_OPER_HELP_LIST_AUTH); old_OPER_HELP_LISTEMAIL = setstring(NICK_OPER_HELP_LISTEMAIL, NICK_OPER_HELP_LISTEMAIL_AUTH); return 1; } /*************************************************************************/ int exit_module(int shutdown_unused) { #ifdef CLEAN_COMPILE shutdown_unused = shutdown_unused; #endif if (old_OPER_HELP_LISTEMAIL >= 0) { setstring(NICK_OPER_HELP_LISTEMAIL, old_OPER_HELP_LISTEMAIL); old_OPER_HELP_LISTEMAIL = -1; } if (old_OPER_HELP_LIST >= 0) { setstring(NICK_OPER_HELP_LIST, old_OPER_HELP_LIST); old_OPER_HELP_LIST = -1; } if (old_HELP_REGISTER_EMAIL >= 0) { setstring(NICK_HELP_REGISTER_EMAIL, old_HELP_REGISTER_EMAIL); old_HELP_REGISTER_EMAIL = -1; } if (old_LIST_OPER_SYNTAX >= 0) { setstring(NICK_LIST_OPER_SYNTAX, old_LIST_OPER_SYNTAX); old_LIST_OPER_SYNTAX = -1; } if (module_mail) { unuse_module(module_mail); module_mail = NULL; } if (module_nickserv) { remove_callback(module_nickserv, "check_expire", do_check_expire); remove_callback(module_nickserv, "IDENTIFY check", do_identify_check); remove_callback(module_nickserv, "SET EMAIL", do_set_email); remove_callback(module_nickserv, "registered", do_registered); unregister_commands(module_nickserv, commands); unuse_module(module_nickserv); module_nickserv = NULL; } unregister_callback(module, cb_authed); return 1; } /*************************************************************************/