diff --git a/data/anope.example.conf b/data/anope.example.conf index 11dc07cc2..7bc4a2358 100644 --- a/data/anope.example.conf +++ b/data/anope.example.conf @@ -741,7 +741,8 @@ log * nickserv/alist - Can see the channel access list of other users * nickserv/auspex - Can see any information with /NICKSERV INFO * nickserv/cert - Can modify other users certificate lists - * nickserv/confirm - Can confirm other users nicknames + * nickserv/confirm/email - Can confirm other users email address change + * nickserv/confirm/register - Can confirm other users account registration * nickserv/drop - Can drop other users nicks * nickserv/drop/display - Allows dropping display nicks when preservedisplay is enabled * nickserv/drop/override - Allows dropping nicks without using a confirmation code @@ -813,7 +814,7 @@ opertype commands = "chanserv/list chanserv/suspend chanserv/topic memoserv/staff nickserv/list nickserv/suspend operserv/mode operserv/chankill operserv/akill operserv/session operserv/modinfo operserv/sqline operserv/oper operserv/kick operserv/ignore operserv/snline" /* What privs (see above) this opertype has */ - privs = "chanserv/auspex chanserv/no-register-limit memoserv/* nickserv/auspex nickserv/confirm" + privs = "chanserv/auspex chanserv/no-register-limit memoserv/* nickserv/auspex nickserv/confirm/*" /* * Modes to be set on users when they identify to accounts linked to this opertype. @@ -990,7 +991,7 @@ mail registration_message = "Hi, You have requested to register the nickname {nick} on {network}. - Please type \" /msg NickServ CONFIRM {code} \" to complete registration. + Please type \" /msg NickServ CONFIRM REGISTER {code} \" to complete registration. If you don't know why this mail was sent to you, please ignore it silently. @@ -1008,7 +1009,7 @@ mail reset_message = "Hi, You have requested to have the password for {nick} reset. - To reset your password, type \" /msg NickServ CONFIRM {nick} {code} \" + To reset your password, type \" /msg NickServ CONFIRM RESETPASS {nick} {code} \" If you don't know why this mail was sent to you, please ignore it silently. @@ -1028,7 +1029,7 @@ mail emailchange_message = "Hi, You have requested to change your email address from {old_email} to {new_email}. - Please type \" /msg NickServ CONFIRM {code} \" to confirm this change. + Please type \" /msg NickServ CONFIRM EMAIL {code} \" to confirm this change. If you don't know why this mail was sent to you, please ignore it silently. diff --git a/data/nickserv.example.conf b/data/nickserv.example.conf index ffd704e3c..92839c444 100644 --- a/data/nickserv.example.conf +++ b/data/nickserv.example.conf @@ -335,6 +335,16 @@ module } command { service = "NickServ"; name = "CERT"; command = "nickserv/cert"; } +/* + * ns_confirm + * + * Provides the command nickserv/confirm. + * + * Used for confirming previous account actions. + */ +module { name = "ns_confirm" } +command { service = "NickServ"; name = "CONFIRM"; command = "nickserv/confirm"; } + /* * ns_drop * @@ -351,6 +361,7 @@ command { service = "NickServ"; name = "DROP"; command = "nickserv/drop"; } * Provides various functionality relating to email addresses. This includes the * following commands: * + * - nickserv/confirm/email: Used for confirming email changes. * - nickserv/getemail: Used for getting accounts by searching for emails. * - nickserv/set/email, nickserv/saset/email: Used for setting an account's * emailvaddress. @@ -359,6 +370,12 @@ module { name = "ns_email" + /* + * The amount of time a user has after requesting a change of email address + * before it expires. Defaults to 1 day. + */ + #changeexpire = 1d + /* * The limit to how many registered accounts can use the same email address. * If set to 0 or left commented there will be no limit enforced when @@ -373,7 +390,7 @@ module */ #remove_aliases = yes } - +command { service = "NickServ"; name = "CONFIRM EMAIL"; command = "nickserv/confirm/email"; } command { service = "NickServ"; name = "GETEMAIL"; command = "nickserv/getemail"; permission = "nickserv/getemail"; group = "nickserv/admin"; } command { service = "NickServ"; name = "SET EMAIL"; command = "nickserv/set/email"; } command { service = "NickServ"; name = "SASET EMAIL"; command = "nickserv/saset/email"; permission = "nickserv/saset/email"; } @@ -504,7 +521,7 @@ command { service = "NickServ"; name = "RELEASE"; command = "nickserv/recover"; /* * ns_register * - * Provides the commands nickserv/confirm, nickserv/register, and nickserv/resend. + * Provides the commands nickserv/confirm/register, nickserv/register, and nickserv/resend. * * Used for registering accounts. */ @@ -544,18 +561,28 @@ module */ #unconfirmedexpire = 1d } -command { service = "NickServ"; name = "CONFIRM"; command = "nickserv/confirm"; } +command { service = "NickServ"; name = "CONFIRM REGISTER"; command = "nickserv/confirm/register"; } command { service = "NickServ"; name = "REGISTER"; command = "nickserv/register"; } command { service = "NickServ"; name = "RESEND"; command = "nickserv/resend"; } /* * ns_resetpass * - * Provides the command nickserv/resetpass. + * Provides the command nickserv/confirm/resetpass and nickserv/resetpass. * * Used for resetting passwords by emailing users a temporary one. */ -module { name = "ns_resetpass" } +module +{ + name = "ns_resetpass" + + /* + * The amount of time a user has after requesting a password reset before it + * expires. Defaults to 1 day. + */ + #resetexpire = 1d +} +command { service = "NickServ"; name = "CONFIRM RESETPASS"; command = "nickserv/confirm/resetpass"; } command { service = "NickServ"; name = "RESETPASS"; command = "nickserv/resetpass"; } /* diff --git a/language/anope.en_US.po b/language/anope.en_US.po index a1f099782..0825b9f79 100644 --- a/language/anope.en_US.po +++ b/language/anope.en_US.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Anope\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-15 11:49+0100\n" -"PO-Revision-Date: 2025-06-10 17:31+0100\n" +"POT-Creation-Date: 2025-06-16 10:00+0100\n" +"PO-Revision-Date: 2025-06-16 10:00+0100\n" "Last-Translator: Sadie Powell \n" "Language-Team: English\n" "Language: en_US\n" @@ -519,6 +519,9 @@ msgstr "" msgid "channel {ON | OFF}" msgstr "" +msgid "code" +msgstr "" + msgid "email" msgstr "" @@ -621,9 +624,6 @@ msgstr "" msgid "option setting" msgstr "" -msgid "passcode" -msgstr "" - msgid "password" msgstr "" @@ -645,6 +645,9 @@ msgstr "" msgid "server [reason]" msgstr "" +msgid "type parameters" +msgstr "" + msgid "user modes" msgstr "" @@ -1155,6 +1158,9 @@ msgstr "" msgid "" msgstr "" +msgid "@nickname" +msgstr "" + #, c-format msgid "A confirmation email has been sent to %s. Follow the instructions in it to change your email address." msgstr "" @@ -1339,7 +1345,10 @@ msgstr "" msgid "Additionally, Services Operators with the chanserv/drop/override permission can replace code with OVERRIDE to drop without a confirmation code." msgstr "" -msgid "Additionally, Services Operators with the nickserv/confirm permission can replace passcode with a users nick to force validate them." +msgid "Additionally, Services Operators with the nickserv/confirm/email permission can specify @nickname instead of code to force confirm another user's change of email address." +msgstr "" + +msgid "Additionally, Services Operators with the nickserv/confirm/register permission can specify @nickname instead of code to force confirm another user's account registration." msgstr "" msgid "Additionally, Services Operators with the nickserv/drop/override permission can replace code with OVERRIDE to drop without a confirmation code." @@ -2213,7 +2222,28 @@ msgstr "" msgid "Configures underlines kicker" msgstr "" -msgid "Confirm a passcode" +msgid "Confirm a previous account registration" +msgstr "" + +msgid "Confirm a previous action" +msgstr "" + +msgid "Confirm a previous change of email address" +msgstr "" + +msgid "Confirm a previous password reset" +msgstr "" + +#, c-format +msgid "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." +msgstr "" + +#, c-format +msgid "Confirms an account registration. You have %s after registration to do this before your registration expires." +msgstr "" + +#, c-format +msgid "Confirms an change of email address. You have %s after requesting an email change to do this before your request expires." msgstr "" msgid "Control modes and mode locks on a channel" @@ -3047,9 +3077,6 @@ msgstr "" msgid "Invalid passcode has been entered, please check the email again, and retry." msgstr "" -msgid "Invalid passcode." -msgstr "" - #, c-format msgid "Invalid session limit. It must be a valid integer greater than or equal to zero and less than %d." msgstr "" @@ -3684,10 +3711,6 @@ msgstr "" msgid "Nick %s is already an operator." msgstr "" -#, c-format -msgid "Nick %s is already confirmed." -msgstr "" - #, c-format msgid "Nick %s is an illegal nickname and cannot be used." msgstr "" @@ -5200,6 +5223,14 @@ msgid_plural "The email address %s has reached its usage limit of %u users." msgstr[0] "" msgstr[1] "" +#, c-format +msgid "The email address change confirmation code you specified for %s is incorrect." +msgstr "" + +#, c-format +msgid "The email address change request for %s has expired." +msgstr "" + #, c-format msgid "The email address of %s will now be hidden from %s INFO displays." msgstr "" @@ -5208,6 +5239,10 @@ msgstr "" msgid "The email address of %s will now be shown in %s INFO displays." msgstr "" +#, c-format +msgid "The email address of %s has been changed from %s to %s." +msgstr "" + #, c-format msgid "The entry message list for %s is full." msgstr "" @@ -5288,6 +5323,18 @@ msgstr "" msgid "The oper info list for %s is full." msgstr "" +#, c-format +msgid "The password reset code you specified for %s is incorrect." +msgstr "" + +#, c-format +msgid "The password reset request for %s has expired." +msgstr "" + +#, c-format +msgid "The registration confirmation code you specified for %s is incorrect." +msgstr "" + #, c-format msgid "The services access status of %s will now be hidden from %s INFO displays." msgstr "" @@ -5335,15 +5382,27 @@ msgstr "" msgid "There is no bot assigned to %s anymore." msgstr "" +#, c-format +msgid "There is no email address change confirmation pending for %s." +msgstr "" + msgid "There is no logon news." msgstr "" msgid "There is no oper news." msgstr "" +#, c-format +msgid "There is no password reset confirmation pending for %s." +msgstr "" + msgid "There is no random news." msgstr "" +#, c-format +msgid "There is no registration confirmation pending for %s." +msgstr "" + #, c-format msgid "There is no such configuration block %s." msgstr "" @@ -5390,12 +5449,7 @@ msgstr "" msgid "This command is an alias to the command %s." msgstr "" -msgid "" -"This command is used by several commands as a way to confirm changes made to your account.\n" -"\n" -"This is most commonly used to confirm your email address once you register or change it.\n" -"\n" -"This is also used after the RESETPASS command has been used to force identify you to your nick so you may change your password." +msgid "This command is used by several commands as a way to actions changes made to your account. type can be one of:" msgstr "" msgid "This command lists information about the specified loaded module." @@ -5876,7 +5930,8 @@ msgstr "" msgid "You are now a super admin." msgstr "" -msgid "You are now identified for your nick. Change your password now." +#, c-format +msgid "You are now identified as %s. Change your password now using %s." msgstr "" #, c-format @@ -6192,17 +6247,9 @@ msgstr "" msgid "Your account will expire, if not confirmed, in %s." msgstr "" -#, c-format -msgid "Your email address has been changed to %s." -msgstr "" - msgid "Your email address is not allowed, choose a different one." msgstr "" -#, c-format -msgid "Your email address of %s has been confirmed." -msgstr "" - #, c-format msgid "Your email has been updated to %s" msgstr "" @@ -6269,9 +6316,6 @@ msgstr "" msgid "Your password is too short. It must be longer than %u characters." msgstr "" -msgid "Your password reset request has expired." -msgstr "" - msgid "Your requested vhost has been approved." msgstr "" @@ -6379,6 +6423,9 @@ msgstr "" msgid "[nickname]" msgstr "" +msgid "[nickname] code" +msgstr "" + msgid "[parameter]" msgstr "" diff --git a/modules/nickserv/ns_confirm.cpp b/modules/nickserv/ns_confirm.cpp new file mode 100644 index 000000000..ed45cd3b0 --- /dev/null +++ b/modules/nickserv/ns_confirm.cpp @@ -0,0 +1,90 @@ +/* NickServ core functions + * + * (C) 2003-2025 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandNSConfirm final + : public Command +{ +public: + CommandNSConfirm(Module *creator) + : Command(creator, "nickserv/confirm", 1, 3) + { + this->AllowUnregistered(true); + this->SetDesc(_("Confirm a previous action")); + this->SetSyntax(_("\037type\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->OnSyntaxError(source, ""); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_( + "This command is used by several commands as a way to actions changes made to " + "your account. \037type\037 can be one of:" + )); + + auto this_name = source.command; + auto hide_privileged_commands = Config->GetBlock("options").Get("hideprivilegedcommands"); + auto hide_registered_commands = Config->GetBlock("options").Get("hideregisteredcommands"); + + HelpWrapper help; + for (const auto &[c_name, info] : source.service->commands) + { + if (c_name.find_ci(this_name + " ") == 0) + { + if (info.hide) + continue; + + ServiceReference c("Command", info.name); + if (!c) + continue; + + else if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount()) + continue; + + else if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) + continue; + + source.command = c_name; + c->OnServHelp(source, help); + } + } + help.SendTo(source); + + source.Reply(_("Type \002%s\032\037option\037\002 for more information on a specific option."), + source.service->GetQueryCommand("generic/help", this_name).c_str()); + + return true; + } +}; + +class NSConfirm final + : public Module +{ +private: + CommandNSConfirm commandnsconfirm; + +public: + NSConfirm(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, VENDOR) + , commandnsconfirm(this) + { + } +}; + +MODULE_INIT(NSConfirm) diff --git a/modules/nickserv/ns_email.cpp b/modules/nickserv/ns_email.cpp index 0946f3b6f..c7a662484 100644 --- a/modules/nickserv/ns_email.cpp +++ b/modules/nickserv/ns_email.cpp @@ -68,6 +68,133 @@ namespace } } +struct EmailChange final +{ + Anope::string code; + Anope::string email; + time_t requested = Anope::CurTime; +}; + +class CommandNSConfirmEmail final + : public Command +{ +private: + PrimitiveExtensibleItem &ns_set_email; + +public: + CommandNSConfirmEmail(Module *creator, PrimitiveExtensibleItem &nse) + : Command(creator, "nickserv/confirm/email", 1, 2) + , ns_set_email(nse) + { + this->SetDesc(_("Confirm a previous change of email address")); + this->SetSyntax(_("\037code\037")); + this->SetSyntax(_("@\037nickname\037"), [](auto &source) { return source.HasPriv("nickserv/confirm/email"); }); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + auto has_priv = source.HasPriv("nickserv/confirm/email"); + + Anope::string code; + NickAlias *na; + if (params[0] == '@') + { + if (!has_priv) + { + source.Reply(ACCESS_DENIED); + return; + } + + auto nick = params[0].substr(0); + 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 *nse = ns_set_email.Get(nc); + if (!nse) + { + source.Reply(_("There is no email address change confirmation pending for %s."), + na->nick.c_str()); + return; + } + + if (!has_priv) + { + if (!code.equals_cs(nse->code)) + { + source.Reply(_("The email address change confirmation code you specified for %s is incorrect."), + na->nick.c_str()); + return; + } + + auto changeexpire = Config->GetModule(owner).Get("changeexpire", "1d"); + if (nse->requested < Anope::CurTime - changeexpire) + { + ns_set_email.Unset(nc); + source.Reply(_("The email address change request for %s has expired."), + na->nick.c_str()); + return; + } + + } + if (!CheckLimitReached(source, nse->email, true)) + { + ns_set_email.Unset(nc); + return; + } + + auto old_email = nc->email; + nc->email = nse->email; + ns_set_email.Unset(nc); + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to confirm the email change of " + << nc->display << " from " << old_email << " to " << nc->email; + + source.Reply(_("The email address of %s has been changed from \002%s\002 to \002%s\002."), + na->nick.c_str(), old_email.c_str(), nc->email.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + auto changeexpire = Config->GetModule(owner).Get("changeexpire", "1d"); + + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_( + "Confirms an change of email address. You have %s after requesting an email " + "change to do this before your request expires." + ), + Anope::Duration(changeexpire, source.GetAccount()).c_str()); + + if (source.HasPriv("nickserv/confirm/email")) + { + source.Reply(" "); + source.Reply(_( + "Additionally, Services Operators with the \037nickserv/confirm/email\037 " + "permission can specify @\037nickname\037 instead of \037code\037 to force " + "confirm another user's change of email address." + )); + } + return true; + } +}; + class CommandNSGetEmail final : public Command { @@ -118,18 +245,16 @@ class CommandNSSetEmail { static bool SendConfirmMail(User *u, NickCore *nc, BotInfo *bi, const Anope::string &new_email) { - Anope::string code = Anope::Random(Config->GetBlock("options").Get("codelength", "15")); - - std::pair *n = nc->Extend >("ns_set_email"); - n->first = new_email; - n->second = code; + auto *nse = nc->Extend("ns_set_email"); + nse->code = Anope::Random(Config->GetBlock("options").Get("codelength", "15")); + nse->email = new_email; Anope::map vars = { { "old_email", nc->email }, { "new_email", new_email }, { "account", nc->display }, { "network", Config->GetBlock("networkinfo").Get("networkname") }, - { "code", code }, + { "code", nse->code }, }; auto subject = Anope::Template(Config->GetBlock("mail").Get("emailchange_subject"), vars); @@ -266,16 +391,18 @@ class NSEmail final : public Module { private: + CommandNSConfirmEmail commandnsconfirmemail; CommandNSGetEmail commandnsgetemail; CommandNSSetEmail commandnssetemail; CommandNSSASetEmail commandnssasetemail; /* email, passcode */ - PrimitiveExtensibleItem> ns_set_email; + PrimitiveExtensibleItem ns_set_email; public: NSEmail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsconfirmemail(this, ns_set_email) , commandnsgetemail(this) , commandnssetemail(this) , commandnssasetemail(this) @@ -292,23 +419,6 @@ public: EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override { - NickCore *uac = source.nc; - - if (command->name == "nickserv/confirm" && !params.empty() && uac) - { - std::pair *n = ns_set_email.Get(uac); - if (n) - { - if (params[0] == n->second) - { - uac->email = n->first; - Log(LOG_COMMAND, source, command) << "to confirm their email address change to " << uac->email; - source.Reply(_("Your email address has been changed to \002%s\002."), uac->email.c_str()); - ns_set_email.Unset(uac); - return EVENT_STOP; - } - } - } if (!source.IsOper() && command->name == "nickserv/register") { if (CheckLimitReached(source, params.size() > 1 ? params[1] : "", false)) diff --git a/modules/nickserv/ns_register.cpp b/modules/nickserv/ns_register.cpp index be9793277..51a46b9dc 100644 --- a/modules/nickserv/ns_register.cpp +++ b/modules/nickserv/ns_register.cpp @@ -29,115 +29,6 @@ static bool SendRegmail(User *u, const NickAlias *na, BotInfo *bi); static ServiceReference nickserv("NickServService", "NickServ"); -class CommandNSConfirm final - : public Command -{ -public: - CommandNSConfirm(Module *creator) : Command(creator, "nickserv/confirm", 1, 2) - { - this->SetDesc(_("Confirm a passcode")); - this->SetSyntax(_("\037passcode\037")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - Anope::string *code = source.nc ? source.nc->GetExt("passcode") : NULL; - bool confirming_other = !code || *code != params[0]; - - if (source.nc && (!source.nc->HasExt("UNCONFIRMED") || (source.IsOper() && confirming_other)) && source.HasPriv("nickserv/confirm")) - { - const Anope::string &nick = params[0]; - NickAlias *na = NickAlias::Find(nick); - if (na == NULL) - source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); - else if (!na->nc->HasExt("UNCONFIRMED")) - source.Reply(_("Nick \002%s\002 is already confirmed."), na->nick.c_str()); - else - { - na->nc->Shrink("UNCONFIRMED"); - FOREACH_MOD(OnNickConfirm, (source.GetUser(), na->nc)); - Log(LOG_ADMIN, source, this) << "to confirm nick " << na->nick << " (" << na->nc->display << ")"; - source.Reply(_("Nick \002%s\002 has been confirmed."), na->nick.c_str()); - - /* Login the users online already */ - for (std::list::iterator it = na->nc->users.begin(); it != na->nc->users.end(); ++it) - { - User *u = *it; - - IRCD->SendLogin(u, na); - - NickAlias *u_na = NickAlias::Find(u->nick); - - /* Set +r if they're on a nick in the group */ - if (!Config->GetModule("nickserv").Get("nonicknameownership") && u_na && *u_na->nc == *na->nc) - u->SetMode(source.service, "REGISTERED"); - } - } - } - else if (source.nc) - { - const Anope::string &passcode = params[0]; - if (code != NULL && *code == passcode) - { - NickCore *nc = source.nc; - nc->Shrink("passcode"); - Log(LOG_COMMAND, source, this) << "to confirm their email"; - source.Reply(_("Your email address of \002%s\002 has been confirmed."), source.nc->email.c_str()); - nc->Shrink("UNCONFIRMED"); - FOREACH_MOD(OnNickConfirm, (source.GetUser(), nc)); - - if (source.GetUser()) - { - NickAlias *na = NickAlias::Find(source.GetNick()); - if (na) - { - IRCD->SendLogin(source.GetUser(), na); - if (!Config->GetModule("nickserv").Get("nonicknameownership") && na->nc == source.GetAccount() && !na->nc->HasExt("UNCONFIRMED")) - source.GetUser()->SetMode(source.service, "REGISTERED"); - } - } - } - else - source.Reply(_("Invalid passcode.")); - } - else - source.Reply(NICK_IDENTIFY_REQUIRED); - - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_( - "This command is used by several commands as a way to confirm " - "changes made to your account." - "\n\n" - "This is most commonly used to confirm your email address once " - "you register or change it." - "\n\n" - "This is also used after the RESETPASS command has been used to " - "force identify you to your nick so you may change your password." - )); - - if (source.HasPriv("nickserv/confirm")) - { - source.Reply(_( - "Additionally, Services Operators with the \037nickserv/confirm\037 permission can " - "replace \037passcode\037 with a users nick to force validate them." - )); - } - return true; - } - - void OnSyntaxError(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(NICK_CONFIRM_INVALID); - } -}; - class CommandNSRegister final : public Command { @@ -288,7 +179,7 @@ public: { const auto *code = GetCode(na->nc); source.Reply(_("Your account is not confirmed. To confirm it, type \002%s\002."), - source.service->GetQueryCommand("nickserv/confirm", *code).c_str()); + source.service->GetQueryCommand("nickserv/confirm/register", *code).c_str()); } else if (nsregister.equals_ci("mail")) source.Reply(_("Your account is not confirmed. To confirm it, follow the instructions that were emailed to you.")); @@ -345,6 +236,115 @@ public: } }; +class CommandNSConfirmRegister final + : public Command +{ +public: + CommandNSConfirmRegister(Module *creator) + : Command(creator, "nickserv/confirm/register", 1, 2) + { + 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 ¶ms) override + { + auto has_priv = source.HasPriv("nickserv/confirm/register"); + + Anope::string code; + NickAlias *na; + if (params[0] == '@') + { + if (!has_priv) + { + source.Reply(ACCESS_DENIED); + return; + } + + auto nick = params[0].substr(0); + 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("passcode"); + if (!passcode) + { + source.Reply(_("There is no registration confirmation pending for %s."), + na->nick.c_str()); + return; + } + if (has_priv || !code.equals_cs(*passcode)) + { + source.Reply(_("The registration confirmation code you specified for %s is incorrect."), + na->nick.c_str()); + return; + } + + na->nc->Shrink("UNCONFIRMED"); + FOREACH_MOD(OnNickConfirm, (source.GetUser(), nc)); + + auto nonicknameownership = Config->GetModule("nickserv").Get("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("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 { @@ -407,16 +407,20 @@ class NSRegister final : public Module { CommandNSRegister commandnsregister; - CommandNSConfirm commandnsconfirm; - CommandNSResend commandnsrsend; + CommandNSConfirmRegister commandnsconfirmregister; + CommandNSResend commandnsresend; SerializableExtensibleItem unconfirmed; SerializableExtensibleItem passcode; public: - NSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), - commandnsregister(this), commandnsconfirm(this), commandnsrsend(this), unconfirmed(this, "UNCONFIRMED"), - passcode(this, "passcode") + 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("registration").equals_ci("disable")) throw ModuleException("Module " + this->name + " will not load with registration disabled."); @@ -432,9 +436,9 @@ public: u->SendMessage(NickServ, _("All new accounts must be validated by an administrator. Please wait for your registration to be confirmed.")); else if (nsregister.equals_ci("code")) { - const auto *code = GetCode(u->Account() ); + const auto *code = GetCode(u->Account()); u->SendMessage(NickServ, _("Your account is not confirmed. To confirm it, type \002%s\002."), - NickServ->GetQueryCommand("nickserv/confirm", *code).c_str()); + NickServ->GetQueryCommand("nickserv/confirm/register", *code).c_str()); } else if (nsregister.equals_ci("mail")) u->SendMessage(NickServ, _("Your account is not confirmed. To confirm it, follow the instructions that were emailed to you.")); diff --git a/modules/nickserv/ns_resetpass.cpp b/modules/nickserv/ns_resetpass.cpp index e6bdc8391..227b7420a 100644 --- a/modules/nickserv/ns_resetpass.cpp +++ b/modules/nickserv/ns_resetpass.cpp @@ -65,71 +65,119 @@ struct ResetInfo final 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); + 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), - commandnsresetpass(this), reset(this, "reset") + 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."); } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (command->name == "nickserv/confirm" && params.size() > 1) - { - if (Anope::ReadOnly) - { - source.Reply(READ_ONLY_MODE); - return EVENT_STOP; - } - - NickAlias *na = NickAlias::Find(params[0]); - - ResetInfo *ri = na ? reset.Get(na->nc) : NULL; - if (na && ri) - { - NickCore *nc = na->nc; - if (nc->HasExt("NS_SUSPENDED")) - { - source.Reply(NICK_X_SUSPENDED, nc->display.c_str()); - return EVENT_STOP; - } - - const Anope::string &passcode = params[1]; - if (ri->time < Anope::CurTime - 3600) - { - reset.Unset(nc); - source.Reply(_("Your password reset request has expired.")); - } - else if (passcode.equals_cs(ri->code)) - { - reset.Unset(nc); - nc->Shrink("UNCONFIRMED"); - - Log(LOG_COMMAND, source, &commandnsresetpass) << "to confirm RESETPASS and forcefully identify as " << na->nick; - - if (source.GetUser()) - { - source.GetUser()->Identify(na); - } - - source.Reply(_("You are now identified for your nick. Change your password now.")); - } - else - return EVENT_CONTINUE; - - return EVENT_STOP; - } - } - - return EVENT_CONTINUE; - } }; static bool SendResetEmail(User *u, const NickAlias *na, BotInfo *bi)