// Anope IRC Services // // Copyright (C) 2003-2025 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" static bool SendResetEmail(User *u, const NickAlias *na, BotInfo *bi); class CommandNSResetPass final : public Command { public: CommandNSResetPass(Module *creator) : Command(creator, "nickserv/resetpass", 2, 2) { this->SetDesc(_("Helps you reset lost passwords")); this->SetSyntax(_("\037nickname\037 \037email\037")); this->AllowUnregistered(true); } void Execute(CommandSource &source, const std::vector ¶ms) override { const NickAlias *na; if (!(na = NickAlias::Find(params[0]))) source.Reply(NICK_X_NOT_REGISTERED, params[0].c_str()); else if (na->nc->HasExt("NS_SUSPENDED")) source.Reply(NICK_X_SUSPENDED, na->nc->display.c_str()); else if (!na->nc->email.equals_ci(params[1])) source.Reply(_("Incorrect email address.")); else { if (SendResetEmail(source.GetUser(), na, source.service)) { Log(LOG_COMMAND, source, this) << "for " << na->nick << " (account: " << na->nc->display << ")"; source.Reply(_("Password reset email for \002%s\002 has been sent."), na->nick.c_str()); } } } bool OnHelp(CommandSource &source, const Anope::string &subcommand) override { this->SendSyntax(source); source.Reply(" "); source.Reply(_( "Sends a confirmation code to the nickname's email address with instructions on " "how to reset their password. \037email\037 must be the email address associated " "with the account." )); return true; } }; struct ResetInfo final { Anope::string code; time_t time; }; class CommandNSConfirmResetPass final : public Command { private: PrimitiveExtensibleItem &reset; public: CommandNSConfirmResetPass(Module *creator, PrimitiveExtensibleItem &r) : Command(creator, "nickserv/confirm/resetpass", 1, 2) , reset(r) { this->AllowUnregistered(true); this->SetDesc(_("Confirm a previous password reset")); this->SetSyntax(_("[\037nickname\037] \037code\037")); } void Execute(CommandSource &source, const std::vector ¶ms) override { Anope::string code, nick; if (params.size() > 1) { nick = params[0]; code = params[1]; } else { code = params[0]; nick = source.GetNick(); } auto *na = NickAlias::Find(nick); if (!na) { source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); return; } NickCore *nc = na->nc; if (nc->HasExt("NS_SUSPENDED")) { source.Reply(NICK_X_SUSPENDED, na->nick.c_str()); return; } auto *ri = reset.Get(nc); if (!ri) { source.Reply(_("There is no password reset confirmation pending for %s."), na->nick.c_str()); return; } if (!code.equals_cs(ri->code)) { source.Reply(_("The password reset code you specified for %s is incorrect."), na->nick.c_str()); return; } auto resetexpire = Config->GetModule(owner).Get("resetexpire", "1d"); if (ri->time < Anope::CurTime - resetexpire) { reset.Unset(nc); source.Reply(_("The password reset request for %s has expired."), na->nick.c_str()); return; } reset.Unset(nc); if (Config->GetModule("nickserv").Get("registration").equals_ci("mail")) nc->Shrink("UNCONFIRMED"); if (source.GetUser()) source.GetUser()->Identify(na); Log(LOG_COMMAND, source, this) << "to reset their password and forcibly identify as " << na->nick; source.Reply(_("You are now identified as %s. Change your password now using %s."), na->nick.c_str(), source.service->GetQueryCommand("nickserv/set/password").c_str()); } bool OnHelp(CommandSource &source, const Anope::string &) override { auto resetexpire = Config->GetModule(owner).Get("resetexpire", "1d"); this->SendSyntax(source); source.Reply(" "); source.Reply(_( "Confirms a password reset and identifies you to the specified account. You have " "%s after requesting a reset to do this before your request expires. Once you " "have done this you can set the password using %s." ), Anope::Duration(resetexpire, source.GetAccount()).c_str(), source.service->GetQueryCommand("nickserv/set/password").c_str() ); return true; } }; class NSResetPass final : public Module { private: CommandNSConfirmResetPass commandnsconfirmpassword; CommandNSResetPass commandnsresetpass; PrimitiveExtensibleItem reset; public: NSResetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) , commandnsconfirmpassword(this, reset) , commandnsresetpass(this) , reset(this, "reset") { if (!Config->GetBlock("mail").Get("usemail")) throw ModuleException("Not using mail."); } }; static bool SendResetEmail(User *u, const NickAlias *na, BotInfo *bi) { auto *ri = na->nc->Extend("reset"); ri->code = Anope::Random(Config->GetBlock("options").Get("codelength", "15")); ri->time = Anope::CurTime; Anope::map vars = { { "nick", na->nick }, { "network", Config->GetBlock("networkinfo").Get("networkname") }, { "code", ri->code }, }; auto subject = Anope::Template(Language::Translate(na->nc, Config->GetBlock("mail").Get("reset_subject").c_str()), vars); auto message = Anope::Template(Language::Translate(na->nc, Config->GetBlock("mail").Get("reset_message").c_str()), vars); return Mail::Send(u, na->nc, bi, subject, message); } MODULE_INIT(NSResetPass)