mirror of
https://github.com/anope/anope.git
synced 2026-06-12 19:14:47 +02:00
439 lines
12 KiB
C++
439 lines
12 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"
|
|
|
|
namespace
|
|
{
|
|
bool clean = false;
|
|
unsigned maxemails = 0;
|
|
|
|
/* strip dots from username, and remove anything after the first + */
|
|
Anope::string CleanMail(const Anope::string &email)
|
|
{
|
|
size_t host = email.find('@');
|
|
if (host == Anope::string::npos)
|
|
return email;
|
|
|
|
Anope::string username = email.substr(0, host);
|
|
username = username.replace_all_cs(".", "");
|
|
|
|
size_t sz = username.find('+');
|
|
if (sz != Anope::string::npos)
|
|
username = username.substr(0, sz);
|
|
|
|
Anope::string cleaned = username + email.substr(host);
|
|
Log(LOG_DEBUG) << "cleaned " << email << " to " << cleaned;
|
|
return cleaned;
|
|
}
|
|
|
|
unsigned CountEmail(const Anope::string &email, NickCore *unc)
|
|
{
|
|
unsigned count = 0;
|
|
|
|
if (email.empty())
|
|
return 0;
|
|
|
|
Anope::string cleanemail = clean ? CleanMail(email) : email;
|
|
|
|
for (const auto &[_, nc] : *NickCoreList)
|
|
{
|
|
Anope::string cleannc = clean ? CleanMail(nc->email) : nc->email;
|
|
|
|
if (unc != nc && cleanemail.equals_ci(cleannc))
|
|
++count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
bool CheckLimitReached(CommandSource &source, const Anope::string &email, bool ignoreself)
|
|
{
|
|
if (!maxemails || email.empty())
|
|
return false;
|
|
|
|
if (CountEmail(email, ignoreself ? source.GetAccount() : NULL) < maxemails)
|
|
return false;
|
|
|
|
source.Reply(maxemails, N_("The email address \002%s\002 has reached its usage limit of %u user.", "The email address \002%s\002 has reached its usage limit of %u users."), email.c_str(), maxemails);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
struct EmailChange final
|
|
{
|
|
Anope::string code;
|
|
Anope::string email;
|
|
time_t requested = Anope::CurTime;
|
|
};
|
|
|
|
class CommandNSConfirmEmail final
|
|
: public Command
|
|
{
|
|
private:
|
|
PrimitiveExtensibleItem<EmailChange> &ns_set_email;
|
|
|
|
public:
|
|
CommandNSConfirmEmail(Module *creator, PrimitiveExtensibleItem<EmailChange> &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<Anope::string> ¶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<time_t>("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 address 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<time_t>("changeexpire", "1d");
|
|
|
|
this->SendSyntax(source);
|
|
source.Reply(" ");
|
|
source.Reply(_(
|
|
"Confirms an change of email address. You have %s after requesting an email "
|
|
"address 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
|
|
{
|
|
public:
|
|
CommandNSGetEmail(Module *creator)
|
|
: Command(creator, "nickserv/getemail", 1, 1)
|
|
{
|
|
this->SetDesc(_("Matches and returns all users that registered using given email address"));
|
|
this->SetSyntax(_("\037email\037"));
|
|
}
|
|
|
|
void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override
|
|
{
|
|
const Anope::string &email = params[0];
|
|
int j = 0;
|
|
|
|
Log(LOG_ADMIN, source, this) << "on " << email;
|
|
|
|
for (const auto &[_, nc] : *NickCoreList)
|
|
{
|
|
if (!nc->email.empty() && Anope::Match(nc->email, email))
|
|
{
|
|
++j;
|
|
source.Reply(_("Email matched: \002%s\002 (\002%s\002) to \002%s\002."), nc->display.c_str(), nc->email.c_str(), email.c_str());
|
|
}
|
|
}
|
|
|
|
if (j <= 0)
|
|
{
|
|
source.Reply(_("No registrations matching \002%s\002 were found."), email.c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
|
|
{
|
|
this->SendSyntax(source);
|
|
source.Reply(" ");
|
|
source.Reply(_("Returns the matching accounts that used given email."));
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class CommandNSSetEmail
|
|
: public Command
|
|
{
|
|
static bool SendConfirmMail(User *u, NickCore *nc, BotInfo *bi, const Anope::string &new_email)
|
|
{
|
|
auto *nse = nc->Extend<EmailChange>("ns_set_email");
|
|
nse->code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
|
|
nse->email = new_email;
|
|
|
|
Anope::map<Anope::string> vars = {
|
|
{ "old_email", nc->email },
|
|
{ "new_email", new_email },
|
|
{ "account", nc->display },
|
|
{ "network", Config->GetBlock("networkinfo").Get<const Anope::string>("networkname") },
|
|
{ "code", nse->code },
|
|
};
|
|
|
|
auto subject = Anope::Template(Config->GetBlock("mail").Get<const Anope::string>("emailchange_subject"), vars);
|
|
auto message = Anope::Template(Config->GetBlock("mail").Get<const Anope::string>("emailchange_message"), vars);
|
|
|
|
Anope::string old = nc->email;
|
|
nc->email = new_email;
|
|
bool b = Mail::Send(u, nc, bi, subject, message);
|
|
nc->email = old;
|
|
return b;
|
|
}
|
|
|
|
public:
|
|
CommandNSSetEmail(Module *creator, const Anope::string &cname = "nickserv/set/email", size_t min = 0) : Command(creator, cname, min, min + 1)
|
|
{
|
|
this->SetDesc(_("Associate an email address with your nickname"));
|
|
this->SetSyntax(_("\037address\037"));
|
|
}
|
|
|
|
void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m)
|
|
{
|
|
if (Anope::ReadOnly)
|
|
{
|
|
source.Reply(READ_ONLY_MODE);
|
|
return;
|
|
}
|
|
|
|
const NickAlias *na = NickAlias::Find(user);
|
|
if (!na)
|
|
{
|
|
source.Reply(NICK_X_NOT_REGISTERED, user.c_str());
|
|
return;
|
|
}
|
|
NickCore *nc = na->nc;
|
|
|
|
if (nc->HasExt("UNCONFIRMED"))
|
|
{
|
|
source.Reply(_("You may not change the email address of an unconfirmed account."));
|
|
return;
|
|
}
|
|
|
|
if (param.empty() && Config->GetModule("nickserv").Get<bool>("forceemail", "yes"))
|
|
{
|
|
source.Reply(_("You cannot unset the email address on this network."));
|
|
return;
|
|
}
|
|
else if (Config->GetModule("nickserv").Get<bool>("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper())
|
|
{
|
|
source.Reply(_("You may not change the email address of other Services Operators."));
|
|
return;
|
|
}
|
|
else if (!param.empty() && !Mail::Validate(param))
|
|
{
|
|
source.Reply(MAIL_X_INVALID, param.c_str());
|
|
return;
|
|
}
|
|
else if (CheckLimitReached(source, param, true))
|
|
return;
|
|
|
|
EventReturn MOD_RESULT;
|
|
FOREACH_RESULT(OnSetNickOption, MOD_RESULT, (source, this, nc, param));
|
|
if (MOD_RESULT == EVENT_STOP)
|
|
return;
|
|
|
|
const auto nsmailreg = Config->GetModule("ns_register").Get<const Anope::string>("registration").equals_ci("mail");
|
|
if (!param.empty() && Config->GetModule("nickserv").Get<bool>("confirmemailchanges", nsmailreg ? "yes" : "no") && source.GetAccount() == nc)
|
|
{
|
|
if (SendConfirmMail(source.GetUser(), source.GetAccount(), source.service, param))
|
|
{
|
|
Log(LOG_COMMAND, source, this) << "to request changing the email address of " << nc->display << " to " << param;
|
|
source.Reply(_("A confirmation email has been sent to \002%s\002. Follow the instructions in it to change your email address."), param.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!param.empty())
|
|
{
|
|
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the email address of " << nc->display << " to " << param;
|
|
nc->email = param;
|
|
source.Reply(_("Email address for \002%s\002 changed to \002%s\002."), nc->display.c_str(), param.c_str());
|
|
}
|
|
else
|
|
{
|
|
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the email address of " << nc->display;
|
|
nc->email.clear();
|
|
source.Reply(_("Email address for \002%s\002 unset."), nc->display.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override
|
|
{
|
|
this->Run(source, source.nc->display, params.size() ? params[0] : "");
|
|
}
|
|
|
|
bool OnHelp(CommandSource &source, const Anope::string &) override
|
|
{
|
|
this->SendSyntax(source);
|
|
source.Reply(" ");
|
|
source.Reply(_(
|
|
"Associates the given email address with your nickname. "
|
|
"This address will be displayed whenever someone requests "
|
|
"information on the nickname with the \002INFO\002 command."
|
|
));
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class CommandNSSASetEmail final
|
|
: public CommandNSSetEmail
|
|
{
|
|
public:
|
|
CommandNSSASetEmail(Module *creator) : CommandNSSetEmail(creator, "nickserv/saset/email", 2)
|
|
{
|
|
this->ClearSyntax();
|
|
this->SetSyntax(_("\037nickname\037 \037address\037"));
|
|
}
|
|
|
|
void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override
|
|
{
|
|
this->Run(source, params[0], params.size() > 1 ? params[1] : "");
|
|
}
|
|
|
|
bool OnHelp(CommandSource &source, const Anope::string &) override
|
|
{
|
|
this->SendSyntax(source);
|
|
source.Reply(" ");
|
|
source.Reply(_("Associates the given email address with the nickname."));
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class NSEmail final
|
|
: public Module
|
|
{
|
|
private:
|
|
CommandNSConfirmEmail commandnsconfirmemail;
|
|
CommandNSGetEmail commandnsgetemail;
|
|
CommandNSSetEmail commandnssetemail;
|
|
CommandNSSASetEmail commandnssasetemail;
|
|
|
|
/* email, passcode */
|
|
PrimitiveExtensibleItem<EmailChange> 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)
|
|
, ns_set_email(this, "ns_set_email")
|
|
{
|
|
}
|
|
|
|
void OnReload(Configuration::Conf &conf) override
|
|
{
|
|
const auto &modconf = conf.GetModule(this);
|
|
maxemails = modconf.Get<unsigned>("maxemails");
|
|
clean = modconf.Get<bool>("remove_aliases", "yes");
|
|
}
|
|
|
|
EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> ¶ms) override
|
|
{
|
|
if (!source.IsOper() && command->name == "nickserv/register")
|
|
{
|
|
if (CheckLimitReached(source, params.size() > 1 ? params[1] : "", false))
|
|
return EVENT_STOP;
|
|
}
|
|
else if (!source.IsOper() && command->name == "nickserv/ungroup" && source.GetAccount())
|
|
{
|
|
if (CheckLimitReached(source, source.GetAccount()->email, false))
|
|
return EVENT_STOP;
|
|
}
|
|
|
|
return EVENT_CONTINUE;
|
|
}
|
|
};
|
|
|
|
MODULE_INIT(NSEmail)
|