1
0
mirror of https://github.com/anope/anope.git synced 2026-06-27 04:16:36 +02:00
Files
anope/modules/nickserv/ns_register.cpp
T
Sadie Powell aef1925b1d Fix finding gettext on Windows.
Unfortunately we need to move our escape character from 0x1A to 0x1B
because newer versions of gettext treat the former as an EOF character.
2026-02-08 00:23:41 +00:00

510 lines
15 KiB
C++

// Anope IRC Services <https://www.anope.org/>
//
// Copyright (C) 2003-2026 Anope Contributors
//
// Anope is free software. You can use, modify, and/or distribute it under the
// terms of version 2 of the GNU General Public License. See docs/LICENSE.txt
// for the complete terms of this license and docs/AUTHORS.txt for a list of
// contributors.
//
// Based on the original code of Epona by Lara
// Based on the original code of Services by Andy Church
//
// SPDX-License-Identifier: GPL-2.0-only
#include "module.h"
#include "modules/nickserv/service.h"
namespace
{
Anope::string *GetCode(NickCore *nc)
{
auto *code = nc->GetExt<Anope::string>("passcode");
if (!code)
{
code = nc->Extend<Anope::string>("passcode");
*code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
}
return code;
}
}
static bool SendRegmail(User *u, const NickAlias *na, BotInfo *bi);
class CommandNSRegister final
: public Command
{
public:
CommandNSRegister(Module *creator) : Command(creator, "nickserv/register", 1, 2)
{
this->SetDesc(_("Register a nickname"));
if (Config->GetModule("nickserv").Get<bool>("forceemail", "yes"))
this->SetSyntax(_("\037password\037 \037email\037"));
else
this->SetSyntax(_("\037password\037 \037[email]\037"));
this->AllowUnregistered(true);
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
User *u = source.GetUser();
Anope::string u_nick = source.GetNick();
Anope::string pass = params[0];
Anope::string email = params.size() > 1 ? params[1] : "";
const Anope::string &nsregister = Config->GetModule(this->owner).Get<const Anope::string>("registration");
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
if (nsregister.equals_ci("disable"))
{
source.Reply(_("Registration is currently disabled."));
return;
}
time_t nickregdelay = Config->GetModule(this->owner).Get<time_t>("nickregdelay");
time_t reg_delay = Config->GetModule("nickserv").Get<time_t>("regdelay");
if (u && !u->HasMode("OPER") && nickregdelay && Anope::CurTime - u->timestamp < nickregdelay)
{
auto waitperiod = (u->timestamp + nickregdelay) - Anope::CurTime;
source.Reply(_("You must wait %s before registering your nick."),
Anope::Duration(waitperiod, source.GetAccount()).c_str());
return;
}
if (NickServ::service && NickServ::service->IsGuestNick(u_nick))
{
source.Reply(NICK_CANNOT_BE_REGISTERED, u_nick.c_str());
return;
}
if (!IRCD->IsNickValid(u_nick))
{
source.Reply(NICK_CANNOT_BE_REGISTERED, u_nick.c_str());
return;
}
if (BotInfo::Find(u_nick, true))
{
source.Reply(NICK_CANNOT_BE_REGISTERED, u_nick.c_str());
return;
}
if (Config->GetModule("nickserv").Get<bool>("restrictopernicks"))
{
for (auto *o : Oper::opers)
{
if (!source.IsOper() && u_nick.find_ci(o->name) != Anope::string::npos)
{
source.Reply(NICK_CANNOT_BE_REGISTERED, u_nick.c_str());
return;
}
}
}
if (Config->GetModule("nickserv").Get<bool>("forceemail", "yes") && email.empty())
this->OnSyntaxError(source, "");
else if (u && Anope::CurTime < u->lastnickreg + reg_delay)
{
auto waitperiod = (unsigned long)(u->lastnickreg + reg_delay) - Anope::CurTime;
source.Reply(_("Please wait %s before using the REGISTER command again."),
Anope::Duration(waitperiod, source.GetAccount()).c_str());
}
else if (NickAlias::Find(u_nick) != NULL)
source.Reply(NICK_ALREADY_REGISTERED, u_nick.c_str());
else if (!email.empty() && !Mail::Validate(email))
source.Reply(MAIL_X_INVALID, email.c_str());
else
{
EventReturn MOD_RESULT;
FOREACH_RESULT(OnPasswordValidate, MOD_RESULT, (source, nullptr, pass));
if (MOD_RESULT == EVENT_STOP)
return;
Anope::string encpass;
if (!Anope::Encrypt(pass, encpass))
{
source.Reply(TRY_AGAIN_LATER, source.command.nobreak().c_str());
return;
}
auto *nc = new NickCore(u_nick);
auto *na = new NickAlias(u_nick, nc);
if (!email.empty())
nc->email = email;
nc->pass = encpass;
if (u)
na->UpdateSeen(u);
Log(LOG_COMMAND, source, this) << "to register " << na->nick << " (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")";
source.Reply(_("Nickname \002%s\002 registered."), u_nick.c_str());
if (nsregister.equals_ci("admin") || nsregister.equals_ci("code"))
{
nc->Extend<bool>("UNCONFIRMED");
}
else if (nsregister.equals_ci("mail"))
{
if (!email.empty())
{
nc->Extend<bool>("UNCONFIRMED");
SendRegmail(NULL, na, source.service);
}
}
FOREACH_MOD(OnNickRegister, (source.GetUser(), na, pass));
if (u)
{
// This notifies the user that if their registration is unconfirmed
u->Identify(na);
u->lastnickreg = Anope::CurTime;
}
else if (nc->HasExt("UNCONFIRMED"))
{
if (nsregister.equals_ci("admin"))
source.Reply(CONFIRM_REGISTER_ADMIN);
else if (nsregister.equals_ci("code"))
{
const auto *code = GetCode(na->nc);
source.Reply(CONFIRM_REGISTER_CODE, source.service->GetQueryCommand("nickserv/confirm/register", *code).c_str());
}
else if (nsregister.equals_ci("mail"))
source.Reply(CONFIRM_REGISTER_MAIL);
}
}
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
{
unsigned int minpasslen = Config->GetModule("nickserv").Get<unsigned>("minpasslen", "10");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Registers your nickname in the %s database. Once "
"your nick is registered, you can use the \002SET\002 and \002ACCESS\002 "
"commands to configure your nick's settings as you like "
"them. Make sure you remember the password you use when "
"registering - you'll need it to make changes to your nick "
"later. (Note that \002case matters!\002 \037ANOPE\037, \037Anope\037, and "
"\037anope\037 are all different passwords!) "
"\n\n"
"Guidelines on choosing passwords:"
"\n\n"
"Passwords should not be easily guessable. For example, "
"using your real name as a password is a bad idea. Using "
"your nickname as a password is a much worse idea ;) and, "
"in fact, %s will not allow it. Also, short "
"passwords are vulnerable to trial-and-error searches, so "
"you should choose a password at least %u characters long. "
"Finally, the space character cannot be used in passwords."
),
source.service->nick.c_str(),
source.service->nick.c_str(),
minpasslen);
if (!Config->GetModule("nickserv").Get<bool>("forceemail", "yes"))
{
source.Reply(" ");
source.Reply(_(
"The \037email\037 parameter is optional and will set the email address for your "
"nick immediately. You may also wish to \002SET\033HIDE\002 it after registering "
"if it isn't the default setting already."
));
}
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership"))
{
source.Reply(" ");
source.Reply(_(
"You can associate multiple nicknames with your account. All nicknames will "
"share the same configuration, set of memos, and channel privileges."
));
}
return true;
}
};
class CommandNSConfirmRegister final
: public Command
{
public:
CommandNSConfirmRegister(Module *creator)
: Command(creator, "nickserv/confirm/register", 1, 1)
{
this->SetDesc(_("Confirm a previous account registration"));
this->SetSyntax(_("\037code\037"));
this->SetSyntax(_("@\037nickname\037"), [](auto &source) { return source.HasPriv("nickserv/confirm/register"); });
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
Anope::string code;
NickAlias *na;
if (params[0][0] == '@')
{
if (!source.HasPriv("nickserv/confirm/register"))
{
source.Reply(ACCESS_DENIED);
return;
}
auto nick = params[0].substr(1);
na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
}
else
{
code = params[0];
na = source.GetAccount()->na;
}
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, na->nick.c_str());
return;
}
auto *passcode = nc->GetExt<Anope::string>("passcode");
if (!passcode)
{
source.Reply(_("There is no registration confirmation pending for %s."),
na->nick.c_str());
return;
}
if (!code.empty() && !code.equals_cs(*passcode))
{
source.Reply(_("The registration confirmation code you specified for %s is incorrect."),
na->nick.c_str());
return;
}
nc->Shrink<Anope::string>("passcode");
nc->Shrink<bool>("UNCONFIRMED");
FOREACH_MOD(OnNickConfirm, (source.GetUser(), nc));
auto nonicknameownership = Config->GetModule("nickserv").Get<bool>("nonicknameownership");
for (auto *u : nc->users)
{
IRCD->SendLogin(u, na);
if (!nonicknameownership)
continue;
const auto &aliases = *nc->aliases;
auto it = std::find_if(aliases.begin(), aliases.end(), [&u](const auto *na) {
return na->nick.equals_ci(u->nick);
});
if (it != aliases.end())
u->SetMode(source.service, "REGISTERED"); // nick is in the group
}
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to confirm the registration of " << nc->display;
source.Reply(_("Nick \002%s\002 has been confirmed."), na->nick.c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
auto unconfirmedexpire = Config->GetModule(owner).Get<time_t>("unconfirmedexpire", "1d");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Confirms an account registration. You have %s after registration to do this "
"before your registration expires."
),
Anope::Duration(unconfirmedexpire, source.GetAccount()).c_str());
if (source.HasPriv("nickserv/confirm/register"))
{
source.Reply(" ");
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/confirm/register\037 "
"permission can specify @\037nickname\037 instead of \037code\037 to force "
"confirm another user's account registration."
));
}
return true;
}
};
class CommandNSResend final
: public Command
{
public:
CommandNSResend(Module *creator)
: Command(creator, "nickserv/resend", 0, 1)
{
this->SetDesc(_("Resend registration confirmation email"));
this->SetSyntax(_("[\037nickname\037]"), [](auto &source) { return source.HasCommand("nickserv/resend"); });
this->AllowUnregistered(true);
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (!Config->GetModule(this->owner).Get<const Anope::string>("registration").equals_ci("mail"))
{
source.Reply(ACCESS_DENIED);
return;
}
auto is_oper = false;
Anope::string nick;
if (!params.empty() && source.HasCommand("nickserv/resend"))
{
nick = params[0];
is_oper = true;
}
else if (source.nc)
nick = source.GetAccount()->display;
else
nick = source.GetNick();
const auto *na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
NickCore *nc = na->nc;
if (!nc->HasExt("UNCONFIRMED"))
{
source.Reply(_("Nick \002%s\002 is already confirmed."), na->nick.c_str());
return;
}
if (!is_oper && Anope::CurTime < nc->lastmail + Config->GetModule(this->owner).Get<time_t>("resenddelay"))
{
source.Reply(_("Cannot send mail now; please retry a little later."));
return;
}
if (!SendRegmail(source.GetUser(), na, source.service))
{
Log(this->owner) << "Unable to resend registration confirmation code for " << na->nick;
return;
}
nc->lastmail = Anope::CurTime;
source.Reply(_("The confirmation code for \002%s\002 has been re-sent to %s."),
na->nick.c_str(), nc->email.c_str());
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to resend the registration confirmation code for " << na->nick;
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
{
if (!Config->GetModule(this->owner).Get<const Anope::string>("registration").equals_ci("mail"))
return false;
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_("This command will resend a registration confirmation email."));
if (source.HasCommand("nickserv/resend"))
{
source.Reply(" ");
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/resend\037 permission "
"can specify a nickname to resend a confirmation email for another account."
));
}
return true;
}
void OnServHelp(CommandSource &source, HelpWrapper &help) override
{
if (Config->GetModule(this->owner).Get<const Anope::string>("registration").equals_ci("mail"))
Command::OnServHelp(source, help);
}
};
class NSRegister final
: public Module
{
CommandNSRegister commandnsregister;
CommandNSConfirmRegister commandnsconfirmregister;
CommandNSResend commandnsresend;
SerializableExtensibleItem<bool> unconfirmed;
SerializableExtensibleItem<Anope::string> passcode;
public:
NSRegister(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsregister(this)
, commandnsconfirmregister(this)
, commandnsresend(this)
, unconfirmed(this, "UNCONFIRMED")
, passcode(this, "passcode")
{
if (Config->GetModule(this).Get<const Anope::string>("registration").equals_ci("disable"))
throw ModuleException("Module " + this->name + " will not load with registration disabled.");
}
void OnNickIdentify(User *u) override
{
BotInfo *NickServ;
if (unconfirmed.HasExt(u->Account()) && (NickServ = Config->GetClient("NickServ")))
{
const Anope::string &nsregister = Config->GetModule(this).Get<const Anope::string>("registration");
if (nsregister.equals_ci("admin"))
u->SendMessage(NickServ, CONFIRM_REGISTER_ADMIN);
else if (nsregister.equals_ci("code"))
{
const auto *code = GetCode(u->Account());
u->SendMessage(NickServ, CONFIRM_REGISTER_CODE, NickServ->GetQueryCommand("nickserv/confirm/register", *code).c_str());
}
else if (nsregister.equals_ci("mail"))
u->SendMessage(NickServ, CONFIRM_REGISTER_MAIL);
const NickAlias *this_na = u->AccountNick();
time_t registered = Anope::CurTime - this_na->registered;
time_t unconfirmed_expire = Config->GetModule(this).Get<time_t>("unconfirmedexpire", "1d");
if (unconfirmed_expire > registered)
u->SendMessage(NickServ, _("Your account will expire, if not confirmed, in %s."), Anope::Duration(unconfirmed_expire - registered, u->Account()).c_str());
}
}
void OnPreNickExpire(NickAlias *na, bool &expire) override
{
if (unconfirmed.HasExt(na->nc))
{
time_t unconfirmed_expire = Config->GetModule(this).Get<time_t>("unconfirmedexpire", "1d");
if (unconfirmed_expire && Anope::CurTime - na->registered >= unconfirmed_expire)
expire = true;
}
}
};
static bool SendRegmail(User *u, const NickAlias *na, BotInfo *bi)
{
NickCore *nc = na->nc;
auto *code = GetCode(na->nc);
Anope::map<Anope::string> vars = {
{ "nick", na->nick },
{ "network", Config->GetBlock("networkinfo").Get<const Anope::string>("networkname") },
{ "code", *code },
};
auto subject = Anope::Template(Language::Translate(na->nc, Config->GetBlock("mail").Get<const Anope::string>("registration_subject").c_str()), vars);
auto message = Anope::Template(Language::Translate(na->nc, Config->GetBlock("mail").Get<const Anope::string>("registration_message").c_str()), vars);
return Mail::Send(u, nc, bi, subject, message);
}
MODULE_INIT(NSRegister)