/* Nickname mail address authentication module.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* 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;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1