diff options
| author | Adam <Adam@anope.org> | 2015-12-14 16:52:24 -0500 |
|---|---|---|
| committer | Adam <Adam@anope.org> | 2015-12-14 16:52:24 -0500 |
| commit | 6302f980fe6dad83aab7d2dc1046dadea5ffd939 (patch) | |
| tree | 4c27d33f55fc32ce34827977932dac4b3e5ceedd /modules/nickserv | |
| parent | 64dac60071fab652745a6e7a06cf6b7bdbbd3625 (diff) | |
New source tree structure for modules. From commands/cs_access => chanserv/access etc.
Diffstat (limited to 'modules/nickserv')
37 files changed, 6766 insertions, 0 deletions
diff --git a/modules/nickserv/CMakeLists.txt b/modules/nickserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/nickserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/nickserv/access.cpp b/modules/nickserv/access.cpp new file mode 100644 index 000000000..ba2116ac9 --- /dev/null +++ b/modules/nickserv/access.cpp @@ -0,0 +1,257 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/nickserv.h" +#include "modules/ns_access.h" + +class NickAccessImpl : public NickAccess +{ + public: + NickAccessImpl(Serialize::TypeBase *type) : NickAccess(type) { } + NickAccessImpl(Serialize::TypeBase *type, Serialize::ID id) : NickAccess(type, id) { } + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *) override; + + Anope::string GetMask() override; + void SetMask(const Anope::string &) override; +}; + +class NickAccessType : public Serialize::Type<NickAccessImpl> +{ + public: + Serialize::ObjectField<NickAccessImpl, NickServ::Account *> account; + Serialize::Field<NickAccessImpl, Anope::string> mask; + + NickAccessType(Module *creator) : Serialize::Type<NickAccessImpl>(creator, "NSAccess") + , account(this, "account", true) + , mask(this, "mask") + { + } +}; + +NickServ::Account *NickAccessImpl::GetAccount() +{ + return Get(&NickAccessType::account); +} + +void NickAccessImpl::SetAccount(NickServ::Account *acc) +{ + Set(&NickAccessType::account, acc); +} + +Anope::string NickAccessImpl::GetMask() +{ + return Get(&NickAccessType::mask); +} + +void NickAccessImpl::SetMask(const Anope::string &m) +{ + Set(&NickAccessType::mask, m); +} + +class CommandNSAccess : public Command +{ + private: + void DoAdd(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) + { + if (mask.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + std::vector<NickAccess *> access = nc->GetRefs<NickAccess *>(nsaccess); + + if (access.size() >= Config->GetModule(this->owner)->Get<unsigned>("accessmax", "32")) + { + source.Reply(_("Sorry, the maximum of \002{0}\002 access entries has been reached."), Config->GetModule(this->owner)->Get<unsigned>("accessmax")); + return; + } + + for (NickAccess *a : access) + if (a->GetMask().equals_ci(mask)) + { + source.Reply(_("Mask \002{0}\002 already present on the access list of \002{1}\002."), mask, nc->GetDisplay()); + return; + } + + NickAccess *a = nsaccess.Create(); + a->SetAccount(nc); + a->SetMask(mask); + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD mask " << mask << " to " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 added to the access list of \002{1}\002."), mask, nc->GetDisplay()); + } + + void DoDel(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) + { + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + for (NickAccess *a : nc->GetRefs<NickAccess *>(nsaccess)) + if (a->GetMask().equals_ci(mask)) + { + a->Delete(); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE mask " << mask << " from " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), mask, nc->GetDisplay()); + return; + } + + + source.Reply(_("\002{0}\002 not found on the access list of \002{1}\002."), mask, nc->GetDisplay()); + } + + void DoList(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) + { + std::vector<NickAccess *> access = nc->GetRefs<NickAccess *>(nsaccess); + if (access.empty()) + { + source.Reply(_("The access list of \002{0}\002 is empty."), nc->GetDisplay()); + return; + } + + source.Reply(_("Access list for \002{0}\002:"), nc->GetDisplay()); + for (NickAccess *a : access) + { + if (!mask.empty() && !Anope::Match(a->GetMask(), mask)) + continue; + + source.Reply(" {0}", a->GetMask()); + } + } + public: + CommandNSAccess(Module *creator) : Command(creator, "nickserv/access", 1, 3) + { + this->SetDesc(_("Modify the list of authorized addresses")); + this->SetSyntax(_("ADD [\037nickname\037] \037mask\037")); + this->SetSyntax(_("DEL [\037nickname\037] \037mask\037")); + this->SetSyntax(_("LIST [\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &cmd = params[0]; + Anope::string nick, mask; + + if (cmd.equals_ci("LIST")) + nick = params.size() > 1 ? params[1] : ""; + else + { + nick = params.size() == 3 ? params[1] : ""; + mask = params.size() > 1 ? params[params.size() - 1] : ""; + } + + NickServ::Account *nc; + if (!nick.empty() && source.HasPriv("nickserv/access")) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("secureadmins", "yes") && source.GetAccount() != na->GetAccount() && na->GetAccount()->IsServicesOper() && !cmd.equals_ci("LIST")) + { + source.Reply(_("You may view but not modify the access list of other Services Operators.")); + return; + } + + nc = na->GetAccount(); + } + else + nc = source.nc; + + if (!mask.empty() && (mask.find('@') == Anope::string::npos || mask.find('!') != Anope::string::npos)) + { + source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); + source.Reply(_("\002%s%s HELP %s\002 for more information."), Config->StrictPrivmsg, source.service->nick, source.command); // XXX + } + else if (cmd.equals_ci("LIST")) + return this->DoList(source, nc, mask); + else if (nc->HasFieldS("NS_SUSPENDED")) + source.Reply(_("\002{0}\002 is suspended."), nc->GetDisplay()); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, nc, mask); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, nc, mask); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Modifies or displays the access list for your account." + " The access list is a list of addresses that {1} uses to recognize you." + " If you match one of the hosts on the access list, services will not force you to change your nickname if the \002KILL\002 option is set." + " Furthermore, if the \002SECURE\002 option is disabled, services will recognize you just based on your hostmask, without having to supply a password." + " To gain access to channels when only recognized by your hostmask, the channel must too have the \002SECURE\002 option off." + " Services Operators may provide \037nickname\037 to modify other user's access lists.\n" + "\n" + "Examples:\n" + " \n" + " {command} ADD anyone@*.bepeg.com\n" + " Allows access to user \"anyone\" from any machine in the \"bepeg.com\" domain.\n" + "\n" + " {command} DEL anyone@*.bepeg.com\n" + " Reverses the previous command.\n" + "\n" + " {command} LIST\n" + " Displays the current access list."), + source.command, source.service->nick); + return true; + } +}; + +class NSAccess : public Module + , public EventHook<NickServ::Event::NickRegister> +{ + CommandNSAccess commandnsaccess; + NickAccessType nick_type; + + public: + NSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsaccess(this) + , nick_type(this) + { + } + + void OnNickRegister(User *u, NickServ::Nick *na, const Anope::string &) override + { + if (u && Config->GetModule(this)->Get<bool>("addaccessonreg")) + { + NickAccess *a = nsaccess.Create(); + a->SetAccount(na->GetAccount()); + a->SetMask(u->Mask()); + //XXX? +// source.Reply(_("\002{0}\002 has been registered under your hostmask: \002{1}\002"), u_nick, na->GetAccount()->GetAccess(0)); + } + } +}; + +MODULE_INIT(NSAccess) diff --git a/modules/nickserv/ajoin.cpp b/modules/nickserv/ajoin.cpp new file mode 100644 index 000000000..135beb403 --- /dev/null +++ b/modules/nickserv/ajoin.cpp @@ -0,0 +1,381 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_ajoin.h" + +class AutoJoinImpl : public AutoJoin +{ + public: + AutoJoinImpl(Serialize::TypeBase *type) : AutoJoin(type) { } + AutoJoinImpl(Serialize::TypeBase *type, Serialize::ID id) : AutoJoin(type, id) { } + + NickServ::Account *GetOwner() override; + void SetOwner(NickServ::Account *acc) override; + + Anope::string GetChannel() override; + void SetChannel(const Anope::string &c) override; + + Anope::string GetKey() override; + void SetKey(const Anope::string &k) override; +}; + +class AutoJoinType : public Serialize::Type<AutoJoinImpl> +{ + public: + Serialize::ObjectField<AutoJoinImpl, NickServ::Account *> owner; + Serialize::Field<AutoJoinImpl, Anope::string> channel, key; + + AutoJoinType(Module *me) : Serialize::Type<AutoJoinImpl>(me, "AutoJoin") + , owner(this, "owner", true) + , channel(this, "channel") + , key(this, "key") + { + } +}; + +NickServ::Account *AutoJoinImpl::GetOwner() +{ + return Get(&AutoJoinType::owner); +} + +void AutoJoinImpl::SetOwner(NickServ::Account *acc) +{ + Set(&AutoJoinType::owner, acc); +} + +Anope::string AutoJoinImpl::GetChannel() +{ + return Get(&AutoJoinType::channel); +} + +void AutoJoinImpl::SetChannel(const Anope::string &c) +{ + Set(&AutoJoinType::channel, c); +} + +Anope::string AutoJoinImpl::GetKey() +{ + return Get(&AutoJoinType::key); +} + +void AutoJoinImpl::SetKey(const Anope::string &k) +{ + Set(&AutoJoinType::key, k); +} + +class CommandNSAJoin : public Command +{ + void DoList(CommandSource &source, NickServ::Account *nc) + { + std::vector<AutoJoin *> channels = nc->GetRefs<AutoJoin *>(autojoin); + + if (channels.empty()) + { + source.Reply(_("The auto join list of \002{0}\002 is empty."), nc->GetDisplay()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Channel")).AddColumn(_("Key")); + for (unsigned i = 0; i < channels.size(); ++i) + { + AutoJoin *aj = channels[i]; + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Channel"] = aj->GetChannel(); + entry["Key"] = aj->GetKey(); + list.AddEntry(entry); + } + + source.Reply(_("Auto join list of \002{0}\002:"), nc->GetDisplay()); + + std::vector<Anope::string> replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + void DoAdd(CommandSource &source, NickServ::Account *nc, const Anope::string &chans, const Anope::string &keys) + { + std::vector<AutoJoin *> channels = nc->GetRefs<AutoJoin *>(autojoin); + + Anope::string addedchans; + Anope::string alreadyadded; + Anope::string invalidkey; + commasepstream ksep(keys, true); + commasepstream csep(chans); + for (Anope::string chan, key; csep.GetToken(chan);) + { + ksep.GetToken(key); + + unsigned i = 0; + for (; i < channels.size(); ++i) + if (channels[i]->GetChannel().equals_ci(chan)) + break; + + if (channels.size() >= Config->GetModule(this->owner)->Get<unsigned>("ajoinmax")) + { + source.Reply(_("Sorry, the maximum of \002{0}\002 auto join entries has been reached."), Config->GetModule(this->owner)->Get<unsigned>("ajoinmax")); + return; + } + + if (i != channels.size()) + alreadyadded += chan + ", "; + else if (IRCD->IsChannelValid(chan) == false) + source.Reply(_("\002{0}\002 isn't a valid channel."), chan); + else + { + Channel *c = Channel::Find(chan); + Anope::string k; + if (c && c->GetParam("KEY", k) && key != k) + { + invalidkey += chan + ", "; + continue; + } + + AutoJoin *entry = autojoin.Create(); + entry->SetOwner(nc); + entry->SetChannel(chan); + entry->SetKey(key); + + addedchans += chan + ", "; + } + } + + if (!alreadyadded.empty()) + { + alreadyadded = alreadyadded.substr(0, alreadyadded.length() - 2); + source.Reply(_("\002{0}\002 is already on the auto join list of \002{1}\002."), alreadyadded, nc->GetDisplay()); + } + + if (!invalidkey.empty()) + { + invalidkey = invalidkey.substr(0, invalidkey.length() - 2); + source.Reply(_("\002{0}\002 had an invalid key specified, and was ignored."), invalidkey); + } + + if (addedchans.empty()) + return; + + addedchans = addedchans.substr(0, addedchans.length() - 2); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD channel " << addedchans << " to " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 added to the auto join list of \002{1}\002."), addedchans, nc->GetDisplay()); + } + + void DoDel(CommandSource &source, NickServ::Account *nc, const Anope::string &chans) + { + std::vector<AutoJoin *> channels = nc->GetRefs<AutoJoin *>(autojoin); + Anope::string delchans; + Anope::string notfoundchans; + commasepstream sep(chans); + + for (Anope::string chan; sep.GetToken(chan);) + { + unsigned i = 0; + for (; i < channels.size(); ++i) + if (channels[i]->GetChannel().equals_ci(chan)) + break; + + if (i == channels.size()) + notfoundchans += chan + ", "; + else + { + delete channels[i]; + delchans += chan + ", "; + } + } + + if (!notfoundchans.empty()) + { + notfoundchans = notfoundchans.substr(0, notfoundchans.length() - 2); + source.Reply(_("\002{0}\002 was not found on the auto join list of \002{1}\002."), notfoundchans, nc->GetDisplay()); + } + + if (delchans.empty()) + return; + + delchans = delchans.substr(0, delchans.length() - 2); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE channel " << delchans << " from " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 was removed from the auto join list of \002{1}\002."), delchans, nc->GetDisplay()); + } + + public: + CommandNSAJoin(Module *creator) : Command(creator, "nickserv/ajoin", 1, 4) + { + this->SetDesc(_("Manage your auto join list")); + this->SetSyntax(_("ADD [\037nickname\037] \037channel\037 [\037key\037]")); + this->SetSyntax(_("DEL [\037nickname\037] \037channel\037")); + this->SetSyntax(_("LIST [\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &cmd = params[0]; + Anope::string nick, param, param2; + + if (cmd.equals_ci("LIST")) + nick = params.size() > 1 ? params[1] : ""; + else + nick = (params.size() > 2 && IRCD->IsChannelValid(params[2])) ? params[1] : ""; + + NickServ::Account *nc; + if (!nick.empty() && !source.HasCommand("nickserv/ajoin")) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + nc = na->GetAccount(); + param = params.size() > 2 ? params[2] : ""; + param2 = params.size() > 3 ? params[3] : ""; + } + else + { + nc = source.nc; + param = params.size() > 1 ? params[1] : ""; + param2 = params.size() > 2 ? params[2] : ""; + } + + if (cmd.equals_ci("LIST")) + return this->DoList(source, nc); + else if (nc->HasFieldS("NS_SUSPENDED")) + source.Reply(_("\002{0}\002 isn't registered."), nc->GetDisplay()); + else if (param.empty()) + this->OnSyntaxError(source, ""); + else if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode.")); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, nc, param, param2); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, nc, param); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command manages your auto join list." + " When you identify you will automatically join the channels on your auto join list." + " Services Operators may provide \037nickname\037 to modify other users' auto join lists.")); + return true; + } +}; + +class NSAJoin : public Module + , public EventHook<Event::UserLogin> +{ + CommandNSAJoin commandnsajoin; + AutoJoinType ajtype; + + public: + NSAJoin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsajoin(this) + , ajtype(this) + { + + if (!IRCD || !IRCD->CanSVSJoin) + throw ModuleException("Your IRCd does not support SVSJOIN"); + + } + + void OnUserLogin(User *u) override + { + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (!NickServ) + return; + + std::vector<AutoJoin *> channels = u->Account()->GetRefs<AutoJoin *>(autojoin); + if (channels.empty()) + return; + + /* Set +r now, so we can ajoin users into +R channels */ + ModeManager::ProcessModes(); + + for (AutoJoin *entry : channels) + { + Channel *c = Channel::Find(entry->GetChannel()); + ChanServ::Channel *ci; + + if (c) + ci = c->ci; + else + ci = ChanServ::Find(entry->GetChannel()); + + bool need_invite = false; + Anope::string key = entry->GetKey(); + ChanServ::AccessGroup u_access; + + if (ci != NULL) + { + if (ci->HasFieldS("CS_SUSPENDED")) + continue; + u_access = ci->AccessFor(u); + } + if (c != NULL) + { + if (c->FindUser(u) != NULL) + continue; + else if (c->HasMode("OPERONLY") && !u->HasMode("OPER")) + continue; + else if (c->HasMode("ADMINONLY") && !u->HasMode("ADMIN")) + continue; + else if (c->HasMode("SSL") && !(u->HasMode("SSL") || u->HasExtOK("ssl"))) + continue; + else if (c->MatchesList(u, "BAN") == true && c->MatchesList(u, "EXCEPT") == false) + need_invite = true; + else if (c->HasMode("INVITE") && c->MatchesList(u, "INVITEOVERRIDE") == false) + need_invite = true; + + if (c->HasMode("KEY")) + { + Anope::string k; + if (c->GetParam("KEY", k)) + { + if (u_access.HasPriv("GETKEY")) + key = k; + else if (key != k) + need_invite = true; + } + } + if (c->HasMode("LIMIT")) + { + Anope::string l; + if (c->GetParam("LIMIT", l)) + { + try + { + unsigned limit = convertTo<unsigned>(l); + if (c->users.size() >= limit) + need_invite = true; + } + catch (const ConvertException &) { } + } + } + } + + if (need_invite && c != NULL) + { + if (!u_access.HasPriv("INVITE")) + continue; + IRCD->SendInvite(NickServ, c, u); + } + + IRCD->SendSVSJoin(NickServ, u, entry->GetChannel(), key); + } + } +}; + +MODULE_INIT(NSAJoin) diff --git a/modules/nickserv/alist.cpp b/modules/nickserv/alist.cpp new file mode 100644 index 000000000..e4aaff38b --- /dev/null +++ b/modules/nickserv/alist.cpp @@ -0,0 +1,133 @@ +/* NickServ core functions + * + * (C) 2003-2014 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 CommandNSAList : public Command +{ + static bool ChannelSort(ChanServ::Channel *ci1, ChanServ::Channel *ci2) + { + return ci::less()(ci1->GetName(), ci2->GetName()); + } + + public: + CommandNSAList(Module *creator) : Command(creator, "nickserv/alist", 0, 2) + { + this->SetDesc(_("List channels you have access on")); + this->SetSyntax(_("[\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + Anope::string nick = source.GetNick(); + NickServ::Account *nc = source.nc; + + if (params.size() && source.HasPriv("nickserv/alist")) + { + nick = params[0]; + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + nc = na->GetAccount(); + } + + ListFormatter list(source.GetAccount()); + int chan_count = 0; + + list.AddColumn(_("Number")).AddColumn(_("Channel")).AddColumn(_("Access")).AddColumn(_("Description")); + + std::vector<ChanServ::Channel *> chans = nc->GetRefs<ChanServ::Channel *>(ChanServ::channel); + std::sort(chans.begin(), chans.end(), ChannelSort); + for (ChanServ::Channel *ci : chans) + { + ListFormatter::ListEntry entry; + + if (ci->GetFounder() == nc) + { + ++chan_count; + entry["Number"] = stringify(chan_count); + entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); + entry["Access"] = Language::Translate(source.GetAccount(), _("Founder")); + entry["Description"] = ci->GetDesc(); + list.AddEntry(entry); + continue; + } + + if (ci->GetSuccessor() == nc) + { + ++chan_count; + entry["Number"] = stringify(chan_count); + entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); + entry["Access"] = Language::Translate(source.GetAccount(), _("Successor")); + entry["Description"] = ci->GetDesc(); + list.AddEntry(entry); + continue; + } + + ChanServ::AccessGroup access = ci->AccessFor(nc, false); + if (access.empty()) + continue; + + ++chan_count; + + entry["Number"] = stringify(chan_count); + entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); + for (unsigned j = 0; j < access.size(); ++j) + entry["Access"] = entry["Access"] + ", " + access[j]->AccessSerialize(); + entry["Access"] = entry["Access"].substr(2); + entry["Description"] = ci->GetDesc(); + list.AddEntry(entry); + } + + std::vector<Anope::string> replies; + list.Process(replies); + + if (!chan_count) + { + source.Reply(_("\002{0}\002 has no access in any channels."), nc->GetDisplay()); + } + else + { + source.Reply(_("Channels that \002{0}\002 has access on:"), nc->GetDisplay()); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of list - \002{0}\002 channels shown."), chan_count); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists all channels you have access on.\n" + " \n" + "Channels that have the \037NOEXPIRE\037 option set will be prefixed by an exclamation mark. The nickname parameter is limited to Services Operators")); + + return true; + } +}; + +class NSAList : public Module +{ + CommandNSAList commandnsalist; + + public: + NSAList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsalist(this) + { + + } +}; + +MODULE_INIT(NSAList) diff --git a/modules/nickserv/cert.cpp b/modules/nickserv/cert.cpp new file mode 100644 index 000000000..fc87f8cb0 --- /dev/null +++ b/modules/nickserv/cert.cpp @@ -0,0 +1,364 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_cert.h" +#include "modules/nickserv.h" + +static Anope::hash_map<NickServ::Account *> certmap; +static EventHandlers<Event::NickCertEvents> *events; + +class CertServiceImpl : public CertService +{ + public: + CertServiceImpl(Module *o) : CertService(o) { } + + NickServ::Account* FindAccountFromCert(const Anope::string &cert) override + { + Anope::hash_map<NickServ::Account *>::iterator it = certmap.find(cert); + if (it != certmap.end()) + return it->second; + return NULL; + } + + bool Matches(User *u, NickServ::Account *nc) override + { + std::vector<NSCertEntry *> cl = nc->GetRefs<NSCertEntry *>(certentry); + return !u->fingerprint.empty() && FindCert(cl, u->fingerprint); + } + + NSCertEntry *FindCert(const std::vector<NSCertEntry *> &cl, const Anope::string &certfp) override + { + for (NSCertEntry *e : cl) + if (e->GetCert() == certfp) + return e; + return nullptr; + } +}; + +class NSCertEntryImpl : public NSCertEntry +{ + public: + NSCertEntryImpl(Serialize::TypeBase *type) : NSCertEntry(type) { } + NSCertEntryImpl(Serialize::TypeBase *type, Serialize::ID id) : NSCertEntry(type, id) { } + ~NSCertEntryImpl(); + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *) override; + + Anope::string GetCert() override; + void SetCert(const Anope::string &) override; +}; + +class NSCertEntryType : public Serialize::Type<NSCertEntryImpl> +{ + public: + struct Account : Serialize::ObjectField<NSCertEntryImpl, NickServ::Account *> + { + using Serialize::ObjectField<NSCertEntryImpl, NickServ::Account *>::ObjectField; + + void SetField(NSCertEntryImpl *s, NickServ::Account *acc) override + { + const Anope::string &cert = s->GetCert(); + if (!cert.empty()) + certmap.erase(cert); + + Serialize::ObjectField<NSCertEntryImpl, NickServ::Account *>::SetField(s, acc); + + if (!cert.empty() && s->GetAccount()) + certmap[cert] = acc; + } + } nc; + + struct Mask : Serialize::Field<NSCertEntryImpl, Anope::string> + { + using Serialize::Field<NSCertEntryImpl, Anope::string>::Field; + + void SetField(NSCertEntryImpl *s, const Anope::string &m) override + { + const Anope::string &old = GetField(s); + if (!old.empty()) + certmap.erase(old); + + Serialize::Field<NSCertEntryImpl, Anope::string>::SetField(s, m); + + if (!m.empty() && s->GetAccount()) + certmap[m] = s->GetAccount(); + } + } mask; + + NSCertEntryType(Module *me) : Serialize::Type<NSCertEntryImpl>(me, "NSCertEntry") + , nc(this, "nc", true) + , mask(this, "mask") + { + } +}; + +NSCertEntryImpl::~NSCertEntryImpl() +{ + const Anope::string &old = GetCert(); + if (!old.empty()) + certmap.erase(old); +} + +NickServ::Account *NSCertEntryImpl::GetAccount() +{ + return Get<NickServ::Account *>(&NSCertEntryType::nc); +} + +void NSCertEntryImpl::SetAccount(NickServ::Account *nc) +{ + Set(&NSCertEntryType::nc, nc); +} + +Anope::string NSCertEntryImpl::GetCert() +{ + return Get<Anope::string>(&NSCertEntryType::mask); +} + +void NSCertEntryImpl::SetCert(const Anope::string &mask) +{ + Set(&NSCertEntryType::mask, mask); +} + +class CommandNSCert : public Command +{ + NSCertEntry *FindCert(const std::vector<NSCertEntry *> &cl, const Anope::string &certfp) + { + for (NSCertEntry *e : cl) + if (e->GetCert() == certfp) + return e; + return nullptr; + } + + void DoAdd(CommandSource &source, NickServ::Account *nc, Anope::string certfp) + { + std::vector<NSCertEntry *> cl = nc->GetRefs<NSCertEntry *>(certentry); + unsigned max = Config->GetModule(this->owner)->Get<unsigned>("max", "5"); + + if (cl.size() >= max) + { + source.Reply(_("Sorry, the maximum of \002{0}\002 certificate entries has been reached."), max); + return; + } + + if (source.GetAccount() == nc) + { + User *u = source.GetUser(); + + if (!u || u->fingerprint.empty()) + { + source.Reply(_("You are not using a client certificate.")); + return; + } + + certfp = u->fingerprint; + } + + if (FindCert(cl, certfp)) + { + source.Reply(_("Fingerprint \002{0}\002 already present on the certificate list of \002{0}\002."), certfp, nc->GetDisplay()); + return; + } + + if (certmap.find(certfp) != certmap.end()) + { + source.Reply(_("Fingerprint \002{0}\002 is already in use."), certfp); + return; + } + + NSCertEntry *e = certentry.Create(); + e->SetAccount(nc); + e->SetCert(certfp); + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD certificate fingerprint " << certfp << " to " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 added to the certificate list of \002{1}\002."), certfp, nc->GetDisplay()); + } + + void DoDel(CommandSource &source, NickServ::Account *nc, Anope::string certfp) + { + std::vector<NSCertEntry *> cl = nc->GetRefs<NSCertEntry *>(certentry); + + if (certfp.empty()) + { + User *u = source.GetUser(); + if (u) + certfp = u->fingerprint; + } + + if (certfp.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + NSCertEntry *cert = FindCert(cl, certfp); + if (!cert) + { + source.Reply(_("\002{0}\002 not found on the certificate list of \002{1}\002."), certfp, nc->GetDisplay()); + return; + } + + cert->Delete(); + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE certificate fingerprint " << certfp << " from " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), certfp, nc->GetDisplay()); + } + + void DoList(CommandSource &source, NickServ::Account *nc) + { + std::vector<NSCertEntry *> cl = nc->GetRefs<NSCertEntry *>(certentry); + + if (cl.empty()) + { + source.Reply(_("The certificate list of \002{0}\002 is empty."), nc->GetDisplay()); + return; + } + + source.Reply(_("Certificate list for \002{0}\002:"), nc->GetDisplay()); + for (NSCertEntry *e : cl) + source.Reply(" {0}", e->GetCert()); + } + + public: + CommandNSCert(Module *creator) : Command(creator, "nickserv/cert", 1, 3) + { + this->SetDesc(_("Modify the nickname client certificate list")); + this->SetSyntax(_("ADD [\037nickname\037] [\037fingerprint\037]")); + this->SetSyntax(_("DEL [\037nickname\037] \037fingerprint\037")); + this->SetSyntax(_("LIST [\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &cmd = params[0]; + Anope::string nick, certfp; + + if (cmd.equals_ci("LIST")) + nick = params.size() > 1 ? params[1] : ""; + else + { + nick = params.size() == 3 ? params[1] : ""; + certfp = params.size() > 1 ? params[params.size() - 1] : ""; + } + + NickServ::Account *nc; + if (!nick.empty() && source.HasPriv("nickserv/access")) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("secureadmins", "yes") && source.GetAccount() != na->GetAccount() && na->GetAccount()->IsServicesOper() && !cmd.equals_ci("LIST")) + { + source.Reply(_("You may view, but not modify, the certificate list of other Services Operators.")); + return; + } + + nc = na->GetAccount(); + } + else + nc = source.nc; + + if (cmd.equals_ci("LIST")) + return this->DoList(source, nc); + else if (nc->HasFieldS("NS_SUSPENDED")) + source.Reply(_("\002{0}\002 is suspended."), nc->GetDisplay()); + else if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode.")); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, nc, certfp); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, nc, certfp); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Modifies or displays the certificate list for your account." + "If you connect to IRC and provide a client certificate with a matching fingerprint in the certificate list, you will be automatically identified to services." + " Services Operators may provide \037nickname\037 to modify other users' certificate lists.\n" + "\n" + "Examples:\n" + "\n" + " {0} ADD\n" + " Adds your current fingerprint to the certificate list and automatically identifies you when you connect to IRC using this certificate.\n" + "\n" + " {0} DEL <fingerprint>\n" + " Removes \"<fingerprint>\" from your certificate list.")); + return true; + } +}; + +class NSCert : public Module + , public EventHook<Event::Fingerprint> + , public EventHook<NickServ::Event::NickValidate> +{ + CommandNSCert commandnscert; + CertServiceImpl cs; + + EventHandlers<Event::NickCertEvents> onnickservevents; + + public: + NSCert(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnscert(this) + , cs(this) + , onnickservevents(this) + { + if (!IRCD || !IRCD->CanCertFP) + throw ModuleException("Your IRCd does not support ssl client certificates"); + + events = &onnickservevents; + } + + void OnFingerprint(User *u) override + { + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (!NickServ || u->IsIdentified()) + return; + + NickServ::Account *nc = cs.FindAccountFromCert(u->fingerprint); + if (!nc || nc->HasFieldS("NS_SUSPENDED")) + return; + + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na && na->GetAccount() == nc) + u->Identify(na); + else + u->Login(nc); + + u->SendMessage(NickServ, _("SSL certificate fingerprint accepted, you are now identified to \002%s\002."), nc->GetDisplay().c_str()); + Log(NickServ) << u->GetMask() << " automatically identified for account " << nc->GetDisplay() << " via SSL certificate fingerprint"; + } + + EventReturn OnNickValidate(User *u, NickServ::Nick *na) override + { + if (u->fingerprint.empty()) + return EVENT_CONTINUE; + + if (cs.Matches(u, na->GetAccount())) + { + ServiceBot *NickServ = Config->GetClient("NickServ"); + u->Identify(na); + u->SendMessage(NickServ, _("SSL certificate fingerprint accepted, you are now identified.")); + Log(NickServ) << u->GetMask() << " automatically identified for account " << na->GetAccount()->GetDisplay() << " via SSL certificate fingerprint"; + return EVENT_ALLOW; + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(NSCert) diff --git a/modules/nickserv/drop.cpp b/modules/nickserv/drop.cpp new file mode 100644 index 000000000..62ee41970 --- /dev/null +++ b/modules/nickserv/drop.cpp @@ -0,0 +1,91 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_drop.h" + +class CommandNSDrop : public Command +{ + EventHandlers<Event::NickDrop> &onnickdrop; + + public: + CommandNSDrop(Module *creator, EventHandlers<Event::NickDrop> &event) : Command(creator, "nickserv/drop", 1, 1), onnickdrop(event) + { + this->SetSyntax(_("\037nickname\037")); + this->SetDesc(_("Cancel the registration of a nickname")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &nick = params[0]; + + if (Anope::ReadOnly && !source.HasPriv("nickserv/drop")) + { + source.Reply(_("Sorry, nickname de-registration is temporarily disabled.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + bool is_mine = source.GetAccount() == na->GetAccount(); + + if (!is_mine && !source.HasPriv("nickserv/drop")) + { + source.Reply(_("Access denied. You do not have the correct operator privileges to drop other user's nicknames.")); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("secureadmins", "yes") && !is_mine && na->GetAccount()->IsServicesOper()) + { + source.Reply(_("You may not drop other Services Operators' nicknames.")); + return; + } + + this->onnickdrop(&Event::NickDrop::OnNickDrop, source, na); + + Log(!is_mine ? LOG_ADMIN : LOG_COMMAND, source, this) << "to drop nickname " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ") (email: " << (!na->GetAccount()->GetEmail().empty() ? na->GetAccount()->GetEmail() : "none") << ")"; + na->Delete(); + + source.Reply(_("\002{0}\002 has been dropped."), nick); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Unregisters \037nickname\037. Once your nickname is dropped you may lose all of your access and channels that you may own. Any other user will be free to register \037nickname\037.")); + if (!source.HasPriv("nickserv/drop")) + source.Reply(_("You may drop any nickname within your group.")); + else + source.Reply(_("As a Services Operator, you may drop any nick.")); + + return true; + } +}; + +class NSDrop : public Module +{ + CommandNSDrop commandnsdrop; + EventHandlers<Event::NickDrop> onnickdrop; + + public: + NSDrop(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsdrop(this, onnickdrop) + , onnickdrop(this) + { + + } +}; + +MODULE_INIT(NSDrop) diff --git a/modules/nickserv/getemail.cpp b/modules/nickserv/getemail.cpp new file mode 100644 index 000000000..7b94efbea --- /dev/null +++ b/modules/nickserv/getemail.cpp @@ -0,0 +1,66 @@ +/* NickServ core functions + * + * (C) 2003-2014 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. + * + * A simple call to check for all emails that a user may have registered + * with. It returns the nicks that match the email you provide. Wild + * Cards are not excepted. Must use user@email-host. + */ + +#include "module.h" + +class CommandNSGetEMail : public Command +{ + public: + CommandNSGetEMail(Module *creator) : Command(creator, "nickserv/getemail", 1, 1) + { + this->SetDesc(_("Matches and returns all users that registered using given email")); + 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 (NickServ::Account *nc : NickServ::service->GetAccountList()) + if (!nc->GetEmail().empty() && nc->GetEmail().equals_ci(email)) + { + ++j; + source.Reply(_("Email matched: \002{0}\002 to \002{1}\002."), nc->GetDisplay(), email); + } + + if (j <= 0) + { + source.Reply(_("There are no accounts with an email that matches \002{0}\002."), email); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Returns the matching accounts whose email address is \037email\037.")); + return true; + } +}; + +class NSGetEMail : public Module +{ + CommandNSGetEMail commandnsgetemail; + + public: + NSGetEMail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsgetemail(this) + { + + } +}; + +MODULE_INIT(NSGetEMail) diff --git a/modules/nickserv/getpass.cpp b/modules/nickserv/getpass.cpp new file mode 100644 index 000000000..a526986ec --- /dev/null +++ b/modules/nickserv/getpass.cpp @@ -0,0 +1,74 @@ +/* NickServ core functions + * + * (C) 2003-2014 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 CommandNSGetPass : public Command +{ + public: + CommandNSGetPass(Module *creator) : Command(creator, "nickserv/getpass", 1, 1) + { + this->SetDesc(_("Retrieve the password for a nickname")); + this->SetSyntax(_("\037account\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &nick = params[0]; + Anope::string tmp_pass; + NickServ::Nick *na = NickServ::FindNick(nick); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("secureadmins", "yes") && na->GetAccount()->IsServicesOper()) + { + source.Reply(_("You may not get the password of other Services Operators.")); + return; + } + + if (!Anope::Decrypt(na->GetAccount()->GetPassword(), tmp_pass)) + { + source.Reply(_("The \002{0}\002 command is unavailable because encryption is in use."), source.command); + return; + } + + Log(LOG_ADMIN, source, this) << "for " << na->GetNick(); + source.Reply(_("Password of \002{0}\02 is \002%s\002."), na->GetNick(), tmp_pass); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Returns the password for the given account. This command may not be available if password encryption is in use.")); + return true; + } +}; + +class NSGetPass : public Module +{ + CommandNSGetPass commandnsgetpass; + + public: + NSGetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsgetpass(this) + { + + Anope::string tmp_pass = "plain:tmp"; + if (!Anope::Decrypt(tmp_pass, tmp_pass)) + throw ModuleException("Incompatible with the encryption module being used"); + + } +}; + +MODULE_INIT(NSGetPass) diff --git a/modules/nickserv/group.cpp b/modules/nickserv/group.cpp new file mode 100644 index 000000000..9a9b933f4 --- /dev/null +++ b/modules/nickserv/group.cpp @@ -0,0 +1,385 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_cert.h" +#include "modules/ns_group.h" + +class NSGroupRequestListener : public NickServ::IdentifyRequestListener +{ + EventHandlers<Event::NickGroup> &onnickgroup; + CommandSource source; + Command *cmd; + Anope::string nick; + Reference<NickServ::Nick> target; + + public: + NSGroupRequestListener(EventHandlers<Event::NickGroup> &event, CommandSource &src, Command *c, const Anope::string &n, NickServ::Nick *targ) : onnickgroup(event), source(src), cmd(c), nick(n), target(targ) { } + + void OnSuccess(NickServ::IdentifyRequest *) override + { + if (!source.GetUser() || source.GetUser()->nick != nick || !target) + return; + + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(nick); + /* If the nick is already registered, drop it. */ + if (na) + { + Event::OnChangeCoreDisplay(&Event::ChangeCoreDisplay::OnChangeCoreDisplay, na->GetAccount(), u->nick); + delete na; + } + + na = NickServ::nick.Create(); + na->SetNick(nick); + na->SetAccount(target->GetAccount()); + na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); + na->SetLastRealname(u->realname); + na->SetLastSeen(Anope::CurTime); + na->SetTimeRegistered(Anope::CurTime); + + u->Login(target->GetAccount()); + this->onnickgroup(&Event::NickGroup::OnNickGroup, u, target); + + Log(LOG_COMMAND, source, cmd) << "to make " << nick << " join group of " << target->GetNick() << " (" << target->GetAccount()->GetDisplay() << ") (email: " << (!target->GetAccount()->GetEmail().empty() ? target->GetAccount()->GetEmail() : "none") << ")"; + source.Reply(_("You are now in the group of \002{0}\002."), target->GetNick()); + + u->lastnickreg = Anope::CurTime; + + } + + void OnFail(NickServ::IdentifyRequest *) override + { + if (!source.GetUser()) + return; + + Log(LOG_COMMAND, source, cmd) << "and failed to group to " << target->GetNick(); + source.Reply(_("Password incorrect.")); + source.GetUser()->BadPassword(); + } +}; + +class CommandNSGroup : public Command +{ + EventHandlers<Event::NickGroup> &onnickgroup; + + public: + CommandNSGroup(Module *creator, EventHandlers<Event::NickGroup> &event) : Command(creator, "nickserv/group", 0, 2), onnickgroup(event) + { + this->SetDesc(_("Join a group")); + this->SetSyntax(_("\037[target]\037 \037[password]\037")); + this->AllowUnregistered(true); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + User *u = source.GetUser(); + + Anope::string nick; + if (params.empty()) + { + NickServ::Account* core = u->Account(); + if (core) + nick = core->GetDisplay(); + } + else + nick = params[0]; + + if (nick.empty()) + { + this->SendSyntax(source); + return; + } + + const Anope::string &pass = params.size() > 1 ? params[1] : ""; + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, nickname grouping is temporarily disabled.")); + return; + } + + if (!IRCD->IsNickValid(u->nick)) + { + source.Reply(_("\002{0}\002 may not be registered."), u->nick); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("restrictopernicks")) + for (Oper *o : Serialize::GetObjects<Oper *>(operblock)) + { + if (!u->HasMode("OPER") && u->nick.find_ci(o->GetName()) != Anope::string::npos) + { + source.Reply(_("\002{0}\002 may not be registered because it is too similar to an operator nick."), u->nick); + return; + } + } + + NickServ::Nick *target, *na = NickServ::FindNick(u->nick); + const Anope::string &guestnick = Config->GetModule("nickserv")->Get<Anope::string>("guestnickprefix", "Guest"); + time_t reg_delay = Config->GetModule("nickserv")->Get<time_t>("regdelay"); + unsigned maxaliases = Config->GetModule(this->owner)->Get<unsigned>("maxaliases"); + if (!(target = NickServ::FindNick(nick))) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Anope::CurTime < u->lastnickreg + reg_delay) + { + source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), (reg_delay + u->lastnickreg) - Anope::CurTime, source.command); + return; + } + + if (target->GetAccount()->HasFieldS("NS_SUSPENDED")) + { + Log(LOG_COMMAND, source, this) << "and tried to group to suspended nick " << target->GetNick(); + source.Reply(_("\002{0}\002 is suspended."), target->GetNick()); + return; + } + + if (na && Config->GetModule(this->owner)->Get<bool>("nogroupchange")) + { + source.Reply(_("Your nick is already registered.")); + return; + } + + if (na && target->GetAccount() == na->GetAccount()) + { + source.Reply(_("You are already a member of the group of \002{0}\002."), target->GetNick()); + return; + } + + if (na && na->GetAccount() != u->Account()) + { + source.Reply(_("\002{0}\002 is already registered."), na->GetNick()); + return; + } + + if (na && Config->GetModule(this->owner)->Get<bool>("nogroupchange")) + { + source.Reply(_("You are already registered.")); + return; + } + + if (maxaliases && target->GetAccount()->GetRefs<NickServ::Nick *>(NickServ::nick).size() >= maxaliases && !target->GetAccount()->IsServicesOper()) + { + source.Reply(_("There are too many nicknames in your group.")); + return; + } + + if (u->nick.length() <= guestnick.length() + 7 && + u->nick.length() >= guestnick.length() + 1 && + !u->nick.find_ci(guestnick) && !u->nick.substr(guestnick.length()).find_first_not_of("1234567890")) + { + source.Reply(_("\002{0}\002 may not be registered."), u->nick); + return; + } + + bool ok = false; + if (!na && u->Account() == target->GetAccount()) + ok = true; + + if (certservice && certservice->Matches(u, target->GetAccount())) + ok = true; + + if (ok == false && !pass.empty()) + { + NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSGroupRequestListener(onnickgroup, source, this, u->nick, target), owner, target->GetAccount()->GetDisplay(), pass); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, source.GetUser(), req); + req->Dispatch(); + } + else + { + NSGroupRequestListener req(onnickgroup, source, this, u->nick, target); + + if (ok) + req.OnSuccess(nullptr); + else + req.OnFail(nullptr); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command makes your nickname join the \037target\037 nickname's group." + " \037password\037 is the password of the target nickname.\n" + "\n" + "Nicknames in the same group share channel privileges, memos, and most settings, including password." + "\n" + "You may be able to use this command even if you have not registered your nick yet." + " If your nick is already registered, you'll need to identify yourself before using this command.")); + return true; + } +}; + +class CommandNSUngroup : public Command +{ + public: + CommandNSUngroup(Module *creator) : Command(creator, "nickserv/ungroup", 0, 1) + { + this->SetDesc(_("Remove a nick from a group")); + this->SetSyntax(_("[\037nickname\037]")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + User *u = source.GetUser(); + Anope::string nick = !params.empty() ? params[0] : ""; + NickServ::Nick *na = NickServ::FindNick(!nick.empty() ? nick : u->nick); + + if (u->Account()->GetRefs<NickServ::Nick *>(NickServ::nick).size() == 1) + { + source.Reply(_("Your nickname is not grouped to anything, so you can't ungroup it.")); + return; + } + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), !nick.empty() ? nick : u->nick); + return; + } + + if (na->GetAccount() != u->Account()) + { + source.Reply(_("\002{0}\002 is not in your group."), na->GetNick()); + return; + } + + + NickServ::Account *oldcore = na->GetAccount(); + + if (na->GetNick().equals_ci(oldcore->GetDisplay())) + oldcore->SetDisplay(oldcore->GetRef<NickServ::Nick *>(NickServ::nick)); + + NickServ::Account *nc = NickServ::account.Create(); + nc->SetDisplay(na->GetNick()); + na->SetAccount(nc); + + nc->SetPassword(oldcore->GetPassword()); + if (!oldcore->GetEmail().empty()) + nc->SetEmail(oldcore->GetEmail()); + nc->SetLanguage(oldcore->GetLanguage()); + + source.Reply(_("\002{0}\002 has been ungrouped from \002{1}\002."), na->GetNick(), oldcore->GetDisplay()); + + User *user = User::Find(na->GetNick()); + if (user) + /* The user on the nick who was ungrouped may be identified to the old group, set -r */ + user->RemoveMode(source.service, "REGISTERED"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command ungroups your nickname, or if given, the specificed \037nickname\037, from the group it is in." + " The ungrouped nick keeps its registration time, password, email, and language. Everything else is reset." + " You may not ungroup yourself if there is only one nickname in your group.")); + return true; + } +}; + +class CommandNSGList : public Command +{ + public: + CommandNSGList(Module *creator) : Command(creator, "nickserv/glist", 0, 1) + { + this->SetDesc(_("Lists all nicknames in your group")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &nick = !params.empty() ? params[0] : ""; + NickServ::Account *nc; + + if (!nick.empty() && source.IsServicesOper()) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + nc = na->GetAccount(); + } + else + nc = source.GetAccount(); + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Nick")).AddColumn(_("Expires")); + time_t nickserv_expire = Config->GetModule("nickserv")->Get<time_t>("expire", "21d"), + unconfirmed_expire = Config->GetModule("nickserv")->Get<time_t>("unconfirmedexpire", "1d"); + for (NickServ::Nick *na2 : nc->GetRefs<NickServ::Nick *>(NickServ::nick)) + { + Anope::string expires; + if (na2->HasFieldS("NS_NO_EXPIRE")) + expires = _("Does not expire"); + else if (!nickserv_expire || Anope::NoExpire) + ; + else if (na2->GetAccount()->HasFieldS("UNCONFIRMED") && unconfirmed_expire) + expires = Anope::strftime(na2->GetTimeRegistered() + unconfirmed_expire, source.GetAccount()); + else + expires = Anope::strftime(na2->GetLastSeen() + nickserv_expire, source.GetAccount()); + + ListFormatter::ListEntry entry; + entry["Nick"] = na2->GetNick(); + entry["Expires"] = expires; + list.AddEntry(entry); + } + + source.Reply(nc != source.GetAccount() ? _("List of nicknames in the group of \002%s\002:") : _("List of nicknames in your group:"), nc->GetDisplay().c_str()); + std::vector<Anope::string> replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("%d nickname(s) in the group."), replies.size()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (source.IsServicesOper()) + source.Reply(_("Without a parameter, lists all nicknames that are in your group.\n" + "\n" + "With a parameter, lists all nicknames that are in the group of the given nick.\n" + "Specifying a nick is limited to \002Services Operators\002."), + source.command); + else + source.Reply(_("Lists all nicknames in your group.")); + + return true; + } +}; + +class NSGroup : public Module +{ + CommandNSGroup commandnsgroup; + CommandNSUngroup commandnsungroup; + CommandNSGList commandnsglist; + + EventHandlers<Event::NickGroup> onnickgroup; + + public: + NSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsgroup(this, onnickgroup) + , commandnsungroup(this) + , commandnsglist(this) + , onnickgroup(this) + { + if (Config->GetModule("nickserv")->Get<bool>("nonicknameownership")) + throw ModuleException(modname + " can not be used with options:nonicknameownership enabled"); + } +}; + +MODULE_INIT(NSGroup) diff --git a/modules/nickserv/identify.cpp b/modules/nickserv/identify.cpp new file mode 100644 index 000000000..122378d8a --- /dev/null +++ b/modules/nickserv/identify.cpp @@ -0,0 +1,125 @@ +/* NickServ core functions + * + * (C) 2003-2014 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 NSIdentifyRequestListener : public NickServ::IdentifyRequestListener +{ + CommandSource source; + Command *cmd; + + public: + NSIdentifyRequestListener(CommandSource &s, Command *c) : source(s), cmd(c) { } + + void OnSuccess(NickServ::IdentifyRequest *req) override + { + if (!source.GetUser()) + return; + + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), req->GetAccount()); + return; + } + + if (u->IsIdentified()) + Log(LOG_COMMAND, source, cmd) << "to log out of account " << u->Account()->GetDisplay(); + + Log(LOG_COMMAND, source, cmd) << "and identified for account " << na->GetAccount()->GetDisplay(); + source.Reply(_("Password accepted - you are now recognized as \002{0}\002."), na->GetAccount()->GetDisplay()); + u->Identify(na); + } + + void OnFail(NickServ::IdentifyRequest *req) override + { + if (!source.GetUser()) + return; + + bool accountexists = NickServ::FindNick(req->GetAccount()) != NULL; + Log(LOG_COMMAND, source, cmd) << "and failed to identify to" << (accountexists ? " " : " nonexistent ") << "account " << req->GetAccount(); + if (accountexists) + { + source.Reply(_("Password incorrect.")); + source.GetUser()->BadPassword(); + } + else + source.Reply("\002{0}\002 isn't registered.", req->GetAccount()); + } +}; + +class CommandNSIdentify : public Command +{ + public: + CommandNSIdentify(Module *creator) : Command(creator, "nickserv/identify", 1, 2) + { + this->SetDesc(_("Identify yourself with your password")); + this->SetSyntax(_("[\037account\037] \037password\037")); + this->AllowUnregistered(true); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + User *u = source.GetUser(); + + const Anope::string &nick = params.size() == 2 ? params[0] : u->nick; + Anope::string pass = params[params.size() - 1]; + + NickServ::Nick *na = NickServ::FindNick(nick); + if (na && na->GetAccount()->HasFieldS("NS_SUSPENDED")) + { + source.Reply(_("\002{0}\002 is suspended."), na->GetNick()); + return; + } + + if (u->Account() && na && u->Account() == na->GetAccount()) + { + source.Reply(_("You are already identified.")); + return; + } + + unsigned int maxlogins = Config->GetModule(this->owner)->Get<unsigned int>("maxlogins"); + if (na && maxlogins && na->GetAccount()->users.size() >= maxlogins) + { + source.Reply(_("Account \002{0}\002 has already reached the maximum number of simultaneous logins ({1})."), na->GetAccount()->GetDisplay(), maxlogins); + return; + } + + NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSIdentifyRequestListener(source, this), owner, na ? na->GetAccount()->GetDisplay() : nick, pass); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, u, req); + + req->Dispatch(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Logs you in to account \037account\037. If no \037account\037 is given, your current nickname is used." + " Many commands require you to authenticate with this command before you use them. \037password\037 should be the same one you registered with.")); + return true; + } +}; + +class NSIdentify : public Module +{ + CommandNSIdentify commandnsidentify; + + public: + NSIdentify(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandnsidentify(this) + { + + } +}; + +MODULE_INIT(NSIdentify) diff --git a/modules/nickserv/info.cpp b/modules/nickserv/info.cpp new file mode 100644 index 000000000..7091e53fd --- /dev/null +++ b/modules/nickserv/info.cpp @@ -0,0 +1,281 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_info.h" +#include "modules/ns_set.h" + +class CommandNSInfo : public Command +{ + EventHandlers<Event::NickInfo> &onnickinfo; + + public: + CommandNSInfo(Module *creator, EventHandlers<Event::NickInfo> &event) : Command(creator, "nickserv/info", 0, 2), onnickinfo(event) + { + this->SetDesc(_("Displays information about a given nickname")); + this->SetSyntax(_("[\037nickname\037]")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + + const Anope::string &nick = params.size() ? params[0] : (source.nc ? source.nc->GetDisplay() : source.GetNick()); + NickServ::Nick *na = NickServ::FindNick(nick); + bool has_auspex = source.HasPriv("nickserv/auspex"); + + if (!na) + { + if (ServiceBot::Find(nick, true)) + source.Reply(_("\002{0}\002 is part of this Network's Services."), nick); + else + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + bool nick_online = false, show_hidden = false; + + /* Is the real owner of the nick we're looking up online? -TheShadow */ + User *u2 = User::Find(na->GetNick()); + if (u2 && u2->Account() == na->GetAccount()) + { + nick_online = true; + na->SetLastSeen(Anope::CurTime); + } + + if (has_auspex || na->GetAccount() == source.GetAccount()) + show_hidden = true; + + source.Reply(_("\002{0}\002 is \002{1}\002"), na->GetNick(), na->GetLastRealname()); + + if (na->GetAccount()->HasFieldS("UNCONFIRMED")) + source.Reply(_("\002{0}\002 has not confirmed their account."), na->GetNick()); + + if (na->GetAccount()->IsServicesOper() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_STATUS"))) + source.Reply(_("\002{0}\002 is a Services Operator of type \002{0}\002."), na->GetNick(), na->GetAccount()->o->GetType()->GetName()); + + InfoFormatter info(source.nc); + + if (nick_online) + { + bool shown = false; + if (show_hidden && !na->GetLastRealhost().empty()) + { + info[_("Online from")] = na->GetLastRealhost(); + shown = true; + } + if ((show_hidden || !na->GetAccount()->HasFieldS("HIDE_MASK")) && (!shown || na->GetLastUsermask() != na->GetLastRealhost())) + info[_("Online from")] = na->GetLastUsermask(); + else + source.Reply(_("\002{0}\002 is currently online."), na->GetNick()); + } + else + { + Anope::string shown; + if (show_hidden || !na->GetAccount()->HasFieldS("HIDE_MASK")) + { + info[_("Last seen address")] = na->GetLastUsermask(); + shown = na->GetLastUsermask(); + } + + if (show_hidden && !na->GetLastRealhost().empty() && na->GetLastRealhost() != shown) + info[_("Last seen address")] = na->GetLastRealhost(); + } + + info[_("Registered")] = Anope::strftime(na->GetTimeRegistered(), source.GetAccount()); + + if (!nick_online) + info[_("Last seen")] = Anope::strftime(na->GetLastSeen(), source.GetAccount()); + + if (!na->GetLastQuit().empty() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_QUIT"))) + info[_("Last quit message")] = na->GetLastQuit(); + + if (!na->GetAccount()->GetEmail().empty() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_EMAIL"))) + info[_("Email address")] = na->GetAccount()->GetEmail(); + + if (show_hidden) + { + if (na->HasVhost()) + { + if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) + info[_("VHost")] = na->GetVhostIdent() + "@" + na->GetVhostHost(); + else + info[_("VHost")] = na->GetVhostHost(); + } + } + + this->onnickinfo(&Event::NickInfo::OnNickInfo, source, na, info, show_hidden); + + std::vector<Anope::string> replies; + info.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Displays information about the given nickname, such as\n" + "the nick's owner, last seen address and time, and nick\n" + "options. If no nick is given, and you are identified,\n" + "your account name is used, else your current nickname is\n" + "used.")); + + return true; + } +}; + + +class CommandNSSetHide : public Command +{ + public: + CommandNSSetHide(Module *creator, const Anope::string &sname = "nickserv/set/hide", size_t min = 2) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Hide certain pieces of nickname information")); + this->SetSyntax("{EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m, const Anope::string &arg) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + const char *onmsg, *offmsg, *flag; + + if (param.equals_ci("EMAIL")) + { + flag = "HIDE_EMAIL"; + onmsg = _("The \002e-mail address\002 of \002{0}\002 will now be \002hidden\002."); + offmsg = _("The \002e-mail address\002 of \002{0}\002 will now be \002shown\002."); + } + else if (param.equals_ci("USERMASK")) + { + flag = "HIDE_MASK"; + onmsg = _("The \002last seen host mask\002 of \002{0}\002 will now be \002hidden\002."); + offmsg = _("The \002last seen host mask\002 of \002{0}\002 will now be \002shown\002."); + } + else if (param.equals_ci("STATUS")) + { + flag = "HIDE_STATUS"; + onmsg = _("The \002services operator status\002 of \002{0}\002 will now be \002hidden\002."); + offmsg = _("The \002services operator status\002 of \002{0}\002 will now be \002shown\002."); + } + else if (param.equals_ci("QUIT")) + { + flag = "HIDE_QUIT"; + onmsg = _("The \002last quit message\002 of \002{0}\002 will now be \002hidden\002."); + offmsg = _("The \002last quit message\002 of \002{0}\002 will now be \002shown\002."); + } + else + { + this->OnSyntaxError(source, "HIDE"); + return; + } + + if (arg.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change hide " << param.upper() << " to " << arg.upper() << " for " << nc->GetDisplay(); + nc->SetS<bool>(flag, true); + source.Reply(onmsg, nc->GetDisplay(), source.service->nick); + } + else if (arg.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change hide " << param.upper() << " to " << arg.upper() << " for " << nc->GetDisplay(); + nc->UnsetS<bool>(flag); + source.Reply(offmsg, nc->GetDisplay(), source.service->nick); + } + else + this->OnSyntaxError(source, "HIDE"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Allows you to prevent certain pieces of information from being displayed when someone does a %s \002INFO\002 on you." //XXX + " You can hide the e-mail address (\002EMAIL\002), last seen hostmask (\002USERMASK\002), the services access status (\002STATUS\002) and last quit message (\002QUIT\002)." + "The second parameter specifies whether the information should\n" + "be displayed (\002OFF\002) or hidden (\002ON\002)."), source.service->nick); + return true; + } +}; + +class CommandNSSASetHide : public CommandNSSetHide +{ + public: + CommandNSSASetHide(Module *creator) : CommandNSSetHide(creator, "nickserv/saset/hide", 3) + { + this->SetSyntax(_("\037nickname\037 {EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->ClearSyntax(); + this->Run(source, params[0], params[1], params[2]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Allows you to prevent certain pieces of information from being displayed when someone does a %s \002INFO\002 on the \037nickname\037. " + " You can hide the e-mail address (\002EMAIL\002), last seen hostmask (\002USERMASK\002), the services access status (\002STATUS\002) and last quit message (\002QUIT\002)." + " The second parameter specifies whether the information should be displayed (\002OFF\002) or hidden (\002ON\002)."), + source.service->nick); + return true; + } +}; + +class NSInfo : public Module +{ + CommandNSInfo commandnsinfo; + + CommandNSSetHide commandnssethide; + CommandNSSASetHide commandnssasethide; + + EventHandlers<Event::NickInfo> onnickinfo; + + Serialize::Field<NickServ::Account, bool> hide_email, hide_usermask, hide_status, hide_quit; + + public: + NSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsinfo(this, onnickinfo) + , commandnssethide(this) + , commandnssasethide(this) + , onnickinfo(this) + , hide_email(this, NickServ::account, "HIDE_EMAIL") + , hide_usermask(this, NickServ::account, "HIDE_MASK") + , hide_status(this, NickServ::account, "HIDE_STATUS") + , hide_quit(this, NickServ::account, "HIDE_QUIT") + { + + } +}; + +MODULE_INIT(NSInfo) diff --git a/modules/nickserv/list.cpp b/modules/nickserv/list.cpp new file mode 100644 index 000000000..2820326d2 --- /dev/null +++ b/modules/nickserv/list.cpp @@ -0,0 +1,290 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_info.h" +#include "modules/ns_set.h" + +class CommandNSList : public Command +{ + public: + CommandNSList(Module *creator) : Command(creator, "nickserv/list", 1, 2) + { + this->SetDesc(_("List all registered nicknames that match a given pattern")); + this->SetSyntax(_("\037pattern\037 [SUSPENDED] [NOEXPIRE] [UNCONFIRMED]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + + Anope::string pattern = params[0]; + const NickServ::Account *mync; + unsigned nnicks; + bool is_servadmin = source.HasCommand("nickserv/list"); + int count = 0, from = 0, to = 0; + bool suspended, nsnoexpire, unconfirmed; + unsigned listmax = Config->GetModule(this->owner)->Get<unsigned>("listmax", "50"); + + suspended = nsnoexpire = unconfirmed = false; + + if (pattern[0] == '#') + { + Anope::string n1, n2; + sepstream(pattern.substr(1), '-').GetToken(n1, 0); + sepstream(pattern, '-').GetToken(n2, 1); + try + { + from = convertTo<int>(n1); + to = convertTo<int>(n2); + } + catch (const ConvertException &) + { + source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); + return; + } + + pattern = "*"; + } + + nnicks = 0; + + if (is_servadmin && params.size() > 1) + { + Anope::string keyword; + spacesepstream keywords(params[1]); + while (keywords.GetToken(keyword)) + { + if (keyword.equals_ci("NOEXPIRE")) + nsnoexpire = true; + if (keyword.equals_ci("SUSPENDED")) + suspended = true; + if (keyword.equals_ci("UNCONFIRMED")) + unconfirmed = true; + } + } + + mync = source.nc; + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Nick")).AddColumn(_("Last usermask")); + + // XXX wtf + Anope::map<NickServ::Nick *> ordered_map; + for (NickServ::Nick *na : NickServ::service->GetNickList()) + ordered_map[na->GetNick()] = na; + + for (Anope::map<NickServ::Nick *>::const_iterator it = ordered_map.begin(), it_end = ordered_map.end(); it != it_end; ++it) + { + NickServ::Nick *na = it->second; + + /* Don't show private nicks to non-services admins. */ + if (na->GetAccount()->HasFieldS("NS_PRIVATE") && !is_servadmin && na->GetAccount() != mync) + continue; + else if (nsnoexpire && !na->HasFieldS("NS_NO_EXPIRE")) + continue; + else if (suspended && !na->GetAccount()->HasFieldS("NS_SUSPENDED")) + continue; + else if (unconfirmed && !na->GetAccount()->HasFieldS("UNCONFIRMED")) + continue; + + /* We no longer compare the pattern against the output buffer. + * Instead we build a nice nick!user@host buffer to compare. + * The output is then generated separately. -TheShadow */ + Anope::string buf = Anope::printf("%s!%s", na->GetNick().c_str(), !na->GetLastUsermask().empty() ? na->GetLastUsermask().c_str() : "*@*"); + if (na->GetNick().equals_ci(pattern) || Anope::Match(buf, pattern, false, true)) + { + if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nnicks <= listmax) + { + bool isnoexpire = false; + if (is_servadmin && na->HasFieldS("NS_NO_EXPIRE")) + isnoexpire = true; + + ListFormatter::ListEntry entry; + entry["Nick"] = (isnoexpire ? "!" : "") + na->GetNick(); + if (na->GetAccount()->HasFieldS("HIDE_MASK") && !is_servadmin && na->GetAccount() != mync) + entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Hostname hidden]")); + else if (na->GetAccount()->HasFieldS("NS_SUSPENDED")) + entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Suspended]")); + else if (na->GetAccount()->HasFieldS("UNCONFIRMED")) + entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Unconfirmed]")); + else + entry["Last usermask"] = na->GetLastUsermask(); + list.AddEntry(entry); + } + ++count; + } + } + + source.Reply(_("List of entries matching \002{0}\002:"), pattern); + + std::vector<Anope::string> replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of list - \002{0}\002/\002{1}\002 matches shown."), nnicks > listmax ? listmax : nnicks, nnicks); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists all registered nicknames which match the given pattern, in \037nick!user@host\037 format." + " Nicks with the \002PRIVATE\002 option set will only be displayed to Services Operators with the proper access." + " Nicks with the \002NOEXPIRE\002 option set will have a \002!\002 prefixed to the nickname for Services Operators to see.\n" + "\n" + "Note that a preceding '#' specifies a range.\n" + "\n" + "If the SUSPENDED, UNCONFIRMED or NOEXPIRE options are given, only\n" + "nicks which, respectively, are SUSPENDED, UNCONFIRMED or have the\n" + "NOEXPIRE flag set will be displayed. If multiple options are\n" + "given, all nicks matching at least one option will be displayed.\n" + "Note that these options are limited to \037Services Operators\037.\n" + "\n" + "Examples:\n" + "\n" + " {0} *!joeuser@foo.com\n" + " Lists all registered nicks owned by joeuser@foo.com.\n" + "\n" + " {0} *Bot*!*@*\n" + " Lists all registered nicks with \002Bot\002 in their names (case insensitive).\n" + "\n" + " {0} * NOEXPIRE\n" + " Lists all registered nicks which have been set to not expire.\n" + "\n" + " {0} #51-100\n" + " Lists all registered nicks within the given range (51-100).")); + + const Anope::string ®exengine = Config->GetBlock("options")->Get<Anope::string>("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your pattern in // if this is desired."), regexengine); + } + + return true; + } +}; + + +class CommandNSSetPrivate : public Command +{ + public: + CommandNSSetPrivate(Module *creator, const Anope::string &sname = "nickserv/set/private", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Prevent your account from appearing in the LIST command")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable private for " << nc->GetDisplay(); + nc->SetS<bool>("NS_PRIVATE", true); + source.Reply(_("Private option is now \002on\002 for \002{0}\002."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable private for " << nc->GetDisplay(); + nc->UnsetS<bool>("NS_PRIVATE"); + source.Reply(_("Private option is now \002off\002 for \002{0}\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "PRIVATE"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the privacy option on or off for your account." + " When \002PRIVATE\002 is set, your account will not appear in the account list." + " However, anyone who knows your account can still request information about it.")); + return true; + } +}; + +class CommandNSSASetPrivate : public CommandNSSetPrivate +{ + public: + CommandNSSASetPrivate(Module *creator) : CommandNSSetPrivate(creator, "nickserv/saset/private", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the privacy option on or off for \037account\037." + " When \002PRIVATE\002 is set, the account will not appear in the account list." + " However, anyone who knows your account can still request information about it.")); + return true; + } +}; + + +class NSList : public Module + , public EventHook<Event::NickInfo> +{ + CommandNSList commandnslist; + + CommandNSSetPrivate commandnssetprivate; + CommandNSSASetPrivate commandnssasetprivate; + + Serialize::Field<NickServ::Account, bool> priv; + + public: + NSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnslist(this) + , commandnssetprivate(this) + , commandnssasetprivate(this) + , priv(this, NickServ::account, "NS_PRIVATE") + { + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_all) override + { + if (!show_all) + return; + + if (priv.HasExt(na->GetAccount())) + info.AddOption(_("Private")); + } +}; + +MODULE_INIT(NSList) diff --git a/modules/nickserv/logout.cpp b/modules/nickserv/logout.cpp new file mode 100644 index 000000000..9bcb543fc --- /dev/null +++ b/modules/nickserv/logout.cpp @@ -0,0 +1,100 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/nickserv.h" + +class CommandNSLogout : public Command +{ + public: + CommandNSLogout(Module *creator) : Command(creator, "nickserv/logout", 0, 2) + { + this->SetDesc(_("Reverses the effect of the IDENTIFY command")); + this->SetSyntax(_("[\037nickname\037 [REVALIDATE]]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + + const Anope::string &nick = !params.empty() ? params[0] : ""; + const Anope::string ¶m = params.size() > 1 ? params[1] : ""; + + if (!source.IsServicesOper() && !nick.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + User *u2 = !nick.empty() ? User::Find(nick, true) : source.GetUser(); + if (!u2) + { + source.Reply(_("\002{0}\002 isn't currently online."), !nick.empty() ? nick : source.GetNick()); + return; + } + + if (!nick.empty() && u2->IsServicesOper()) + { + source.Reply(_("You can't logout \002{0}\002, they are a Services Operator."), nick); + return; + } + +#if 0 + if (!nick.empty() && !param.empty() && param.equals_ci("REVALIDATE") && NickServ::service) + NickServ::service->Validate(u2); +#endif + + u2->super_admin = false; /* Dont let people logout and remain a SuperAdmin */ + Log(LOG_COMMAND, source, this) << "to logout " << u2->nick; + + if (!nick.empty()) + source.Reply(_("\002{0}\002 has been logged out."), nick); + else + source.Reply(_("You have been logged out.")); + + IRCD->SendLogout(u2); + u2->RemoveMode(source.service, "REGISTERED"); + u2->Logout(); + + /* Send out an event */ + Event::OnNickLogout(&Event::NickLogout::OnNickLogout, u2); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Logs you out of your account")); + #if 0 + source.Reply(_("Without a parameter, reverses the effect of the \002IDENTIFY\002\n" + "command, i.e. make you not recognized as the real owner of the nick\n" + "anymore. Note, however, that you won't be asked to reidentify\n" + "yourself.\n" + " \n" + "With a parameter, does the same for the given nick. If you\n" + "specify \002REVALIDATE\002 as well, Services will ask the given nick\n" + "to re-identify. This is limited to \002Services Operators\002.")); + #endif + + return true; + } +}; + +class NSLogout : public Module +{ + CommandNSLogout commandnslogout; + + public: + NSLogout(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnslogout(this) + { + + } +}; + +MODULE_INIT(NSLogout) diff --git a/modules/nickserv/main/CMakeLists.txt b/modules/nickserv/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/nickserv/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/nickserv/main/account.cpp b/modules/nickserv/main/account.cpp new file mode 100644 index 000000000..85adf1a33 --- /dev/null +++ b/modules/nickserv/main/account.cpp @@ -0,0 +1,129 @@ +/* + * + * (C) 2003-2014 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" +#include "accounttype.h" +#include "modules/ns_access.h" + +AccountImpl::~AccountImpl() +{ + NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); + map.erase(this->GetDisplay()); +} + +void AccountImpl::Delete() +{ + Event::OnDelCore(&Event::DelCore::OnDelCore, this); + + for (unsigned i = users.size(); i > 0; --i) + users[i - 1]->Logout(); + + return Serialize::Object::Delete(); +} + +Anope::string AccountImpl::GetDisplay() +{ + return Get<Anope::string>(&AccountType::display); +} + +void AccountImpl::SetDisplay(const Anope::string &disp) +{ + Set(&AccountType::display, disp); +} + +Anope::string AccountImpl::GetPassword() +{ + return Get(&AccountType::pass); +} + +void AccountImpl::SetPassword(const Anope::string &pass) +{ + Set(&AccountType::pass, pass); +} + +Anope::string AccountImpl::GetEmail() +{ + return Get(&AccountType::email); +} + +void AccountImpl::SetEmail(const Anope::string &email) +{ + Set(&AccountType::email, email); +} + +Anope::string AccountImpl::GetLanguage() +{ + return Get(&AccountType::language); +} + +void AccountImpl::SetLanguage(const Anope::string &lang) +{ + Set(&AccountType::language, lang); +} + +MemoServ::MemoInfo *AccountImpl::GetMemos() +{ + return GetRef<MemoServ::MemoInfo *>(MemoServ::memoinfo); +} + +void AccountImpl::SetDisplay(NickServ::Nick *na) +{ + if (na->GetAccount() != this || na->GetNick() == this->GetDisplay()) + return; + + Event::OnChangeCoreDisplay(&Event::ChangeCoreDisplay::OnChangeCoreDisplay, this, na->GetNick()); + + NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); + + /* Remove the core from the list */ + map.erase(this->GetDisplay()); + + this->SetDisplay(na->GetNick()); + + NickServ::Account* &nc = map[this->GetDisplay()]; + if (nc) + Log(LOG_DEBUG) << "Duplicate account " << this->GetDisplay() << " in nickcore table?"; + + nc = this; +} + +bool AccountImpl::IsServicesOper() const +{ + return this->o != NULL; +} + +bool AccountImpl::IsOnAccess(User *u) +{ + Anope::string buf = u->GetIdent() + "@" + u->host, buf2, buf3; + if (!u->vhost.empty()) + buf2 = u->GetIdent() + "@" + u->vhost; + if (!u->GetCloakedHost().empty()) + buf3 = u->GetIdent() + "@" + u->GetCloakedHost(); + + for (NickAccess *access : GetRefs<NickAccess *>(nsaccess)) + { + Anope::string a = access->GetMask(); + if (Anope::Match(buf, a) || (!buf2.empty() && Anope::Match(buf2, a)) || (!buf3.empty() && Anope::Match(buf3, a))) + return true; + } + return false; +} + +unsigned int AccountImpl::GetChannelCount() +{ + unsigned int i = 0; + for (ChanServ::Channel *c : GetRefs<ChanServ::Channel *>(ChanServ::channel)) + if (c->GetFounder() == this) + ++i; + return i; +} + diff --git a/modules/nickserv/main/account.h b/modules/nickserv/main/account.h new file mode 100644 index 000000000..584548563 --- /dev/null +++ b/modules/nickserv/main/account.h @@ -0,0 +1,35 @@ +#include "modules/nickserv.h" + +class AccountImpl : public NickServ::Account +{ + public: + AccountImpl(Serialize::TypeBase *type) : NickServ::Account(type) { } + AccountImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Account(type, id) { } + ~AccountImpl(); + void Delete() override; + + Anope::string GetDisplay() override; + void SetDisplay(const Anope::string &) override; + + Anope::string GetPassword() override; + void SetPassword(const Anope::string &) override; + + Anope::string GetEmail() override; + void SetEmail(const Anope::string &) override; + + Anope::string GetLanguage() override; + void SetLanguage(const Anope::string &) override; + + MemoServ::MemoInfo *GetMemos() override; + + void SetDisplay(NickServ::Nick *na) override; + bool IsServicesOper() const override; + /*void AddAccess(const Anope::string &entry) override; + Anope::string GetAccess(unsigned entry) const override; + unsigned GetAccessCount() const override; + bool FindAccess(const Anope::string &entry) override; + void EraseAccess(const Anope::string &entry) override; + void ClearAccess() override;*/ + bool IsOnAccess(User *u) override; + unsigned int GetChannelCount() override; +}; diff --git a/modules/nickserv/main/accounttype.cpp b/modules/nickserv/main/accounttype.cpp new file mode 100644 index 000000000..3dad436b4 --- /dev/null +++ b/modules/nickserv/main/accounttype.cpp @@ -0,0 +1,42 @@ +#include "module.h" +#include "accounttype.h" +//#include "account.h" + +AccountType::AccountType(Module *me) : Serialize::Type<AccountImpl>(me, "NickCore") + , display(this, "display") + , pass(this, "pass") + , email(this, "email") + , language(this, "language") +{ + +} + +void AccountType::Display::SetField(AccountImpl *acc, const Anope::string &disp) +{ + NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); + + if (!acc->GetDisplay().empty()) + map.erase(acc->GetDisplay()); + + Serialize::Field<AccountImpl, Anope::string>::SetField(acc, disp); + + if (!disp.empty()) + map[disp] = acc; + + acc->o = Oper::Find(disp); +} + +NickServ::Account *AccountType::FindAccount(const Anope::string &acc) +{ + Serialize::ID id; + EventReturn result = Event::OnSerialize(&Event::SerializeEvents::OnSerializeFind, this, &this->display, acc, id); + if (result == EVENT_ALLOW) + return RequireID(id); + + NickServ::nickcore_map &map = NickServ::service->GetAccountMap(); + auto it = map.find(acc); + if (it != map.end()) + return it->second; + return nullptr; +} + diff --git a/modules/nickserv/main/accounttype.h b/modules/nickserv/main/accounttype.h new file mode 100644 index 000000000..792629877 --- /dev/null +++ b/modules/nickserv/main/accounttype.h @@ -0,0 +1,23 @@ +#include "account.h" + +class AccountType : public Serialize::Type<AccountImpl> +{ + public: + /* Name of the account */ + struct Display : Serialize::Field<AccountImpl, Anope::string> + { + using Serialize::Field<AccountImpl, Anope::string>::Field; + + void SetField(AccountImpl *s, const Anope::string &) override; + } display; + /* User password in form of hashm:data */ + Serialize::Field<AccountImpl, Anope::string> pass; + Serialize::Field<AccountImpl, Anope::string> email; + /* Locale name of the language of the user. Empty means default language */ + Serialize::Field<AccountImpl, Anope::string> language; + + + AccountType(Module *); + + NickServ::Account *FindAccount(const Anope::string &nick); +}; diff --git a/modules/nickserv/main/identifyrequest.cpp b/modules/nickserv/main/identifyrequest.cpp new file mode 100644 index 000000000..4fc2f9989 --- /dev/null +++ b/modules/nickserv/main/identifyrequest.cpp @@ -0,0 +1,51 @@ +#include "identifyrequest.h" + +IdentifyRequestImpl::IdentifyRequestImpl(NickServ::IdentifyRequestListener *li, Module *o, const Anope::string &acc, const Anope::string &pass) : NickServ::IdentifyRequest(li, o, acc, pass) +{ + std::set<NickServ::IdentifyRequest *> &requests = NickServ::service->GetIdentifyRequests(); + requests.insert(this); +} + +IdentifyRequestImpl::~IdentifyRequestImpl() +{ + std::set<NickServ::IdentifyRequest *> &requests = NickServ::service->GetIdentifyRequests(); + requests.erase(this); + delete l; +} + +void IdentifyRequestImpl::Hold(Module *m) +{ + holds.insert(m); +} + +void IdentifyRequestImpl::Release(Module *m) +{ + holds.erase(m); + if (holds.empty() && dispatched) + { + if (!success) + l->OnFail(this); + delete this; + } +} + +void IdentifyRequestImpl::Success(Module *m) +{ + if (!success) + { + l->OnSuccess(this); + success = true; + } +} + +void IdentifyRequestImpl::Dispatch() +{ + if (holds.empty()) + { + if (!success) + l->OnFail(this); + delete this; + } + else + dispatched = true; +} diff --git a/modules/nickserv/main/identifyrequest.h b/modules/nickserv/main/identifyrequest.h new file mode 100644 index 000000000..e74d3129e --- /dev/null +++ b/modules/nickserv/main/identifyrequest.h @@ -0,0 +1,13 @@ +#include "modules/nickserv.h" + +class IdentifyRequestImpl : public NickServ::IdentifyRequest +{ + public: + IdentifyRequestImpl(NickServ::IdentifyRequestListener *, Module *o, const Anope::string &acc, const Anope::string &pass); + virtual ~IdentifyRequestImpl(); + + void Hold(Module *m) override; + void Release(Module *m) override; + void Success(Module *m) override; + void Dispatch() override; +}; diff --git a/modules/nickserv/main/mode.cpp b/modules/nickserv/main/mode.cpp new file mode 100644 index 000000000..61fe551c1 --- /dev/null +++ b/modules/nickserv/main/mode.cpp @@ -0,0 +1,23 @@ +#include "modules/nickserv.h" +#include "modetype.h" + +NickServ::Account *ModeImpl::GetAccount() +{ + return Get(&NSModeType::account); +} + +void ModeImpl::SetAccount(NickServ::Account *a) +{ + Set(&NSModeType::account, a); +} + +Anope::string ModeImpl::GetMode() +{ + return Get(&NSModeType::mode); +} + +void ModeImpl::SetMode(const Anope::string &m) +{ + Set(&NSModeType::mode, m); +} + diff --git a/modules/nickserv/main/mode.h b/modules/nickserv/main/mode.h new file mode 100644 index 000000000..9fc4c331e --- /dev/null +++ b/modules/nickserv/main/mode.h @@ -0,0 +1,14 @@ + +class ModeImpl : public NickServ::Mode +{ + public: + ModeImpl(Serialize::TypeBase *type) : NickServ::Mode(type) { } + ModeImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Mode(type, id) { } + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *) override; + + Anope::string GetMode() override; + void SetMode(const Anope::string &) override; +}; + diff --git a/modules/nickserv/main/modetype.h b/modules/nickserv/main/modetype.h new file mode 100644 index 000000000..0c4aefecc --- /dev/null +++ b/modules/nickserv/main/modetype.h @@ -0,0 +1,14 @@ +#include "mode.h" + +class NSModeType : public Serialize::Type<ModeImpl> +{ + public: + Serialize::ObjectField<ModeImpl, NickServ::Account *> account; + Serialize::Field<ModeImpl, Anope::string> mode; + + NSModeType(Module *creator) : Serialize::Type<ModeImpl>(creator, "NSKeepMode") + , account(this, "account", true) + , mode(this, "mode") + { + } +}; diff --git a/modules/nickserv/main/nick.cpp b/modules/nickserv/main/nick.cpp new file mode 100644 index 000000000..0ac55ff93 --- /dev/null +++ b/modules/nickserv/main/nick.cpp @@ -0,0 +1,192 @@ +/* + * + * (C) 2003-2014 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" +#include "nicktype.h" + +NickImpl::~NickImpl() +{ + /* Remove us from the aliases list */ + NickServ::nickalias_map &map = NickServ::service->GetNickMap(); + map.erase(GetNick()); +} + +void NickImpl::Delete() +{ + Event::OnDelNick(&Event::DelNick::OnDelNick, this); + + if (this->GetAccount()) + { + /* Next: see if our core is still useful. */ + std::vector<NickServ::Nick *> aliases = this->GetAccount()->GetRefs<NickServ::Nick *>(NickServ::nick); + + auto it = std::find(aliases.begin(), aliases.end(), this); + if (it != aliases.end()) + aliases.erase(it); + + if (aliases.empty()) + { + /* just me */ + this->GetAccount()->Delete(); + } + else + { + /* Display updating stuff */ + if (GetNick().equals_ci(this->GetAccount()->GetDisplay())) + this->GetAccount()->SetDisplay(aliases[0]); + } + } + + return Serialize::Object::Delete(); +} + +Anope::string NickImpl::GetNick() +{ + return Get<Anope::string>(&NickType::nick); +} + +void NickImpl::SetNick(const Anope::string &nick) +{ + Set(&NickType::nick, nick); +} + +Anope::string NickImpl::GetLastQuit() +{ + return Get(&NickType::last_quit); +} + +void NickImpl::SetLastQuit(const Anope::string &lq) +{ + Set(&NickType::last_quit, lq); +} + +Anope::string NickImpl::GetLastRealname() +{ + return Get(&NickType::last_realname); +} + +void NickImpl::SetLastRealname(const Anope::string &lr) +{ + Set(&NickType::last_realname, lr); +} + +Anope::string NickImpl::GetLastUsermask() +{ + return Get(&NickType::last_usermask); +} + +void NickImpl::SetLastUsermask(const Anope::string &lu) +{ + Set(&NickType::last_usermask, lu); +} + +Anope::string NickImpl::GetLastRealhost() +{ + return Get(&NickType::last_realhost); +} + +void NickImpl::SetLastRealhost(const Anope::string &lr) +{ + Set(&NickType::last_realhost, lr); +} + +time_t NickImpl::GetTimeRegistered() +{ + return Get(&NickType::time_registered); +} + +void NickImpl::SetTimeRegistered(const time_t &tr) +{ + Set(&NickType::time_registered, tr); +} + +time_t NickImpl::GetLastSeen() +{ + return Get(&NickType::last_seen); +} + +void NickImpl::SetLastSeen(const time_t &ls) +{ + Set(&NickType::last_seen, ls); +} + +NickServ::Account *NickImpl::GetAccount() +{ + return Get(&NickType::nc); +} + +void NickImpl::SetAccount(NickServ::Account *acc) +{ + Set(&NickType::nc, acc); +} + +void NickImpl::SetVhost(const Anope::string &ident, const Anope::string &host, const Anope::string &creator, time_t created) +{ + Set(&NickType::vhost_ident, ident); + Set(&NickType::vhost_host, host); + Set(&NickType::vhost_creator, creator); + Set(&NickType::vhost_created, created); +} + +void NickImpl::RemoveVhost() +{ + Anope::string e; + Set(&NickType::vhost_ident, e); + Set(&NickType::vhost_host, e); + Set(&NickType::vhost_creator, e); + Set(&NickType::vhost_created, 0); +} + +bool NickImpl::HasVhost() +{ + return !GetVhostHost().empty(); +} + +Anope::string NickImpl::GetVhostIdent() +{ + return Get(&NickType::vhost_ident); +} + +void NickImpl::SetVhostIdent(const Anope::string &i) +{ + Set(&NickType::vhost_ident, i); +} + +Anope::string NickImpl::GetVhostHost() +{ + return Get(&NickType::vhost_host); +} + +void NickImpl::SetVhostHost(const Anope::string &h) +{ + Set(&NickType::vhost_host, h); +} + +Anope::string NickImpl::GetVhostCreator() +{ + return Get(&NickType::vhost_creator); +} + +void NickImpl::SetVhostCreator(const Anope::string &c) +{ + Set(&NickType::vhost_creator, c); +} + +time_t NickImpl::GetVhostCreated() +{ + return Get(&NickType::vhost_created); +} + +void NickImpl::SetVhostCreated(const time_t &cr) +{ + Set(&NickType::vhost_created, cr); +} diff --git a/modules/nickserv/main/nick.h b/modules/nickserv/main/nick.h new file mode 100644 index 000000000..043d7c052 --- /dev/null +++ b/modules/nickserv/main/nick.h @@ -0,0 +1,46 @@ + +class NickImpl : public NickServ::Nick +{ + public: + NickImpl(Serialize::TypeBase *type) : NickServ::Nick(type) { } + NickImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Nick(type, id) { } + ~NickImpl(); + void Delete() override; + + Anope::string GetNick() override; + void SetNick(const Anope::string &) override; + + Anope::string GetLastQuit() override; + void SetLastQuit(const Anope::string &) override; + + Anope::string GetLastRealname() override; + void SetLastRealname(const Anope::string &) override; + + Anope::string GetLastUsermask() override; + void SetLastUsermask(const Anope::string &) override; + + Anope::string GetLastRealhost() override; + void SetLastRealhost(const Anope::string &) override; + + time_t GetTimeRegistered() override; + void SetTimeRegistered(const time_t &) override; + + time_t GetLastSeen() override; + void SetLastSeen(const time_t &) override; + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *acc) override; + + void SetVhost(const Anope::string &ident, const Anope::string &host, const Anope::string &creator, time_t created = Anope::CurTime) override; + void RemoveVhost() override; + bool HasVhost() override; + + Anope::string GetVhostIdent() override; + void SetVhostIdent(const Anope::string &) override; + Anope::string GetVhostHost() override; + void SetVhostHost(const Anope::string &) override; + Anope::string GetVhostCreator() override; + void SetVhostCreator(const Anope::string &) override; + time_t GetVhostCreated() override; + void SetVhostCreated(const time_t &) override; +}; diff --git a/modules/nickserv/main/nickserv.cpp b/modules/nickserv/main/nickserv.cpp new file mode 100644 index 000000000..26f1457cc --- /dev/null +++ b/modules/nickserv/main/nickserv.cpp @@ -0,0 +1,686 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_info.h" +#include "modules/ns_group.h" +#include "modules/ns_update.h" +#include "modules/help.h" +#include "modules/nickserv.h" +#include "identifyrequest.h" +#include "nicktype.h" +#include "accounttype.h" +#include "modetype.h" + +class NickServCollide; +static std::set<NickServCollide *> collides; + +/** Timer for colliding nicks to force people off of nicknames + */ +class NickServCollide : public Timer +{ + NickServ::NickServService *service; + Reference<User> u; + time_t ts; + Reference<NickServ::Nick> na; + + public: + NickServCollide(Module *me, NickServ::NickServService *nss, User *user, NickServ::Nick *nick, time_t delay) : Timer(me, delay), service(nss), u(user), ts(user->timestamp), na(nick) + { + collides.insert(this); + } + + ~NickServCollide() + { + collides.erase(this); + } + + NickServ::Nick *GetNick() + { + return na; + } + + User *GetUser() + { + return u; + } + + void Tick(time_t t) override + { + if (!u || !na) + return; + /* If they identified or don't exist anymore, don't kill them. */ + if (u->Account() == na->GetAccount() || u->timestamp > ts) + return; + + service->Collide(u, na); + } +}; + +/** Timer for removing HELD status from nicks. + */ +class NickServHeld : public Timer +{ + Reference<NickServ::Nick> na; + Anope::string nick; + public: + NickServHeld(Module *me, NickServ::Nick *n, long l) : Timer(me, l), na(n), nick(na->GetNick()) + { + n->SetS<bool>("HELD", true); + } + + void Tick(time_t) + { + if (na) + na->UnsetS<bool>("HELD"); + } +}; + +class NickServRelease; +static Anope::map<NickServRelease *> NickServReleases; + +/** Timer for releasing nicks to be available for use + */ +class NickServRelease : public User, public Timer +{ + Anope::string nick; + + public: + NickServRelease(Module *me, NickServ::Nick *na, time_t delay) : User(na->GetNick(), Config->GetModule("nickserv")->Get<Anope::string>("enforceruser", "user"), + Config->GetModule("nickserv")->Get<Anope::string>("enforcerhost", "services.localhost.net"), "", "", Me, "Services Enforcer", Anope::CurTime, "", IRCD->UID_Retrieve(), NULL), Timer(me, delay), nick(na->GetNick()) + { + /* Erase the current release timer and use the new one */ + Anope::map<NickServRelease *>::iterator nit = NickServReleases.find(this->nick); + if (nit != NickServReleases.end()) + { + IRCD->SendQuit(nit->second, ""); + delete nit->second; + } + + NickServReleases.insert(std::make_pair(this->nick, this)); + + IRCD->SendClientIntroduction(this); + } + + ~NickServRelease() + { + IRCD->SendQuit(this, ""); + NickServReleases.erase(this->nick); + } + + void Tick(time_t t) override { } +}; + +class NickServCore : public Module, public NickServ::NickServService + , public EventHook<Event::Shutdown> + , public EventHook<Event::Restart> + , public EventHook<Event::UserLogin> + , public EventHook<Event::DelNick> + , public EventHook<Event::DelCore> + , public EventHook<Event::ChangeCoreDisplay> + , public EventHook<Event::NickIdentify> + , public EventHook<Event::NickGroup> + , public EventHook<Event::NickUpdate> + , public EventHook<Event::UserConnect> + , public EventHook<Event::PostUserLogoff> + , public EventHook<Event::ServerSync> + , public EventHook<Event::UserNickChange> + , public EventHook<Event::UserModeSet> + , public EventHook<Event::Help> + , public EventHook<Event::ExpireTick> + , public EventHook<Event::NickInfo> + , public EventHook<Event::ModuleUnload> + , public EventHook<Event::NickCoreCreate> + , public EventHook<Event::UserQuit> +{ + Reference<ServiceBot> NickServ; + std::vector<Anope::string> defaults; + ExtensibleItem<bool> held, collided; + EventHandlers<NickServ::Event::PreNickExpire> onprenickexpire; + EventHandlers<NickServ::Event::NickExpire> onnickexpire; + EventHandlers<NickServ::Event::NickRegister> onnickregister; + EventHandlers<NickServ::Event::NickValidate> onnickvalidate; + std::set<NickServ::IdentifyRequest *> identifyrequests; + NickServ::nickalias_map NickList; + NickServ::nickcore_map AccountList; + NickType nick_type; + AccountType account_type; + NSModeType mode_type; + + void OnCancel(User *u, NickServ::Nick *na) + { + if (collided.HasExt(na)) + { + collided.Unset(na); + + new NickServHeld(this, na, Config->GetModule("nickserv")->Get<time_t>("releasetimeout", "1m")); + + if (IRCD->CanSVSHold) + IRCD->SendSVSHold(na->GetNick(), Config->GetModule("nickserv")->Get<time_t>("releasetimeout", "1m")); + else + new NickServRelease(this, na, Config->GetModule("nickserv")->Get<time_t>("releasetimeout", "1m")); + } + } + + public: + NickServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , NickServ::NickServService(this) + , held(this, "HELD") + , collided(this, "COLLIDED") + , onprenickexpire(this) + , onnickexpire(this) + , onnickregister(this) + , onnickvalidate(this) + , nick_type(this) + , account_type(this) + , mode_type(this) + { + } + + ~NickServCore() + { + OnShutdown(); + } + + void OnShutdown() override + { + /* On shutdown, restart, or mod unload, remove all of our holds for nicks (svshold or qlines) + * because some IRCds do not allow us to have these automatically expire + */ + for (NickServ::Nick *na : nick_type.List<NickServ::Nick *>()) + this->Release(na); + } + + void OnRestart() override + { + OnShutdown(); + } + + void Validate(User *u) override + { + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (!na) + return; + + EventReturn MOD_RESULT = this->onnickvalidate(&NickServ::Event::NickValidate::OnNickValidate, u, na); + if (MOD_RESULT == EVENT_STOP) + { + this->Collide(u, na); + return; + } + else if (MOD_RESULT == EVENT_ALLOW) + return; + + if (!na->GetAccount()->HasFieldS("NS_SECURE") && u->IsRecognized()) + { + na->SetLastSeen(Anope::CurTime); + na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); + na->SetLastRealname(u->realname); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("nonicknameownership")) + return; + + bool on_access = u->IsRecognized(false); + + if (on_access || !na->GetAccount()->HasFieldS("KILL_IMMED")) + { + if (na->GetAccount()->HasFieldS("NS_SECURE")) + u->SendMessage(*NickServ, _("This nickname is registered and protected. If this is your nickname, type \002{0}{1} IDENTIFY \037password\037\002. Otherwise, please choose a different nickname."), Config->StrictPrivmsg, NickServ->nick); // XXX + else + u->SendMessage(*NickServ, _("This nickname is owned by someone else. If this is your nickname, type \002{0}{1} IDENTIFY \037password\037\002. Otherwise, please choose a different nickname."), Config->StrictPrivmsg, NickServ->nick); // XXX + } + if (na->GetAccount()->HasFieldS("KILLPROTECT") && !on_access) + { + if (na->GetAccount()->HasFieldS("KILL_IMMED")) + { + u->SendMessage(*NickServ, _("This nickname has been registered; you may not use it.")); + this->Collide(u, na); + } + else if (na->GetAccount()->HasFieldS("KILL_QUICK")) + { + time_t killquick = Config->GetModule("nickserv")->Get<time_t>("killquick", "20s"); + u->SendMessage(*NickServ, _("If you do not change within %s, I will change your nick."), Anope::Duration(killquick, u->Account()).c_str()); + new NickServCollide(this, this, u, na, killquick); + } + else + { + time_t kill = Config->GetModule("nickserv")->Get<time_t>("kill", "60s"); + u->SendMessage(*NickServ, _("If you do not change within %s, I will change your nick."), Anope::Duration(kill, u->Account()).c_str()); + new NickServCollide(this, this, u, na, kill); + } + } + + } + + void OnUserLogin(User *u) override + { + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na && na->GetAccount() == u->Account() && !Config->GetModule("nickserv")->Get<bool>("nonicknameownership") && !na->GetAccount()->HasFieldS("UNCONFIRMED")) + u->SetMode(NickServ, "REGISTERED"); + + const Anope::string &modesonid = Config->GetModule(this)->Get<Anope::string>("modesonid"); + if (!modesonid.empty()) + u->SetModes(NickServ, "%s", modesonid.c_str()); + } + + void Collide(User *u, NickServ::Nick *na) override + { + if (na) + collided.Set(na, true); + + if (IRCD->CanSVSNick) + { + unsigned nicklen = Config->GetBlock("networkinfo")->Get<unsigned>("nicklen"); + const Anope::string &guestprefix = Config->GetModule("nickserv")->Get<Anope::string>("guestnickprefix", "Guest"); + + Anope::string guestnick; + + int i = 0; + do + { + guestnick = guestprefix + stringify(static_cast<uint16_t>(rand())); + if (guestnick.length() > nicklen) + guestnick = guestnick.substr(0, nicklen); + } + while (User::Find(guestnick) && i++ < 10); + + if (i == 11) + u->Kill(*NickServ, "Services nickname-enforcer kill"); + else + { + u->SendMessage(*NickServ, _("Your nickname is now being changed to \002%s\002"), guestnick.c_str()); + IRCD->SendForceNickChange(u, guestnick, Anope::CurTime); + } + } + else + u->Kill(*NickServ, "Services nickname-enforcer kill"); + } + + void Release(NickServ::Nick *na) override + { + if (held.HasExt(na)) + { + if (IRCD->CanSVSHold) + IRCD->SendSVSHoldDel(na->GetNick()); + else + { + User *u = User::Find(na->GetNick()); + if (u && u->server == Me) + { + u->Quit(); + } + } + + held.Unset(na); + } + collided.Unset(na); /* clear pending collide */ + } + + NickServ::IdentifyRequest *CreateIdentifyRequest(NickServ::IdentifyRequestListener *l, Module *o, const Anope::string &acc, const Anope::string &pass) override + { + return new IdentifyRequestImpl(l, o, acc, pass); + } + + std::set<NickServ::IdentifyRequest *>& GetIdentifyRequests() override + { + return identifyrequests; + } + + std::vector<NickServ::Nick *> GetNickList() override + { + return nick_type.List<NickServ::Nick *>(); + } + + NickServ::nickalias_map& GetNickMap() override + { + return NickList; + } + + std::vector<NickServ::Account *> GetAccountList() override + { + return account_type.List<NickServ::Account *>(); + } + + NickServ::nickcore_map& GetAccountMap() override + { + return AccountList; + } + + NickServ::Nick *FindNick(const Anope::string &nick) override + { + return nick_type.FindNick(nick); + } + + NickServ::Account *FindAccount(const Anope::string &acc) override + { + return account_type.FindAccount(acc); + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &nsnick = conf->GetModule(this)->Get<Anope::string>("client"); + + if (nsnick.empty()) + throw ConfigException(Module::name + ": <client> must be defined"); + + ServiceBot *bi = ServiceBot::Find(nsnick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + nsnick); + + NickServ = bi; + + spacesepstream(conf->GetModule(this)->Get<Anope::string>("defaults", "ns_secure memo_signon memo_receive")).GetTokens(defaults); + if (defaults.empty()) + { + defaults.push_back("NS_SECURE"); + defaults.push_back("MEMO_SIGNON"); + defaults.push_back("MEMO_RECEIVE"); + } + else if (defaults[0].equals_ci("none")) + defaults.clear(); + } + + void OnDelNick(NickServ::Nick *na) override + { + User *u = User::Find(na->GetNick()); + if (u && u->Account() == na->GetAccount()) + { + IRCD->SendLogout(u); + u->RemoveMode(NickServ, "REGISTERED"); + u->Logout(); + } + } + + void OnDelCore(NickServ::Account *nc) override + { + Log(NickServ, "nick") << "Deleting nickname group " << nc->GetDisplay(); + + /* Clean up this nick core from any users online */ + for (unsigned int i = nc->users.size(); i > 0; --i) + { + User *user = nc->users[i - 1]; + IRCD->SendLogout(user); + user->RemoveMode(NickServ, "REGISTERED"); + user->Logout(); + Event::OnNickLogout(&Event::NickLogout::OnNickLogout, user); + } + } + + void OnChangeCoreDisplay(NickServ::Account *nc, const Anope::string &newdisplay) override + { + Log(LOG_NORMAL, "nick", NickServ) << "Changing " << nc->GetDisplay() << " nickname group display to " << newdisplay; + } + + void OnNickIdentify(User *u) override + { + Configuration::Block *block = Config->GetModule(this); + + if (block->Get<bool>("modeonid", "yes")) + + for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) + { + ChanUserContainer *cc = it->second; + Channel *c = cc->chan; + if (c) + c->SetCorrectModes(u, true); + } + + const Anope::string &modesonid = block->Get<Anope::string>("modesonid"); + if (!modesonid.empty()) + u->SetModes(NickServ, "%s", modesonid.c_str()); + + if (block->Get<bool>("forceemail", "yes") && u->Account()->GetEmail().empty()) + { + u->SendMessage(*NickServ, _("You must now supply an e-mail for your nick.\n" + "This e-mail will allow you to retrieve your password in\n" + "case you forget it.")); + u->SendMessage(*NickServ, _("Type \002%s%s SET EMAIL \037e-mail\037\002 in order to set your e-mail.\n" + "Your privacy is respected; this e-mail won't be given to\n" + "any third-party person."), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str()); + } + + for (std::set<NickServCollide *>::iterator it = collides.begin(); it != collides.end(); ++it) + { + NickServCollide *c = *it; + if (c->GetUser() == u && c->GetNick() && c->GetNick()->GetAccount() == u->Account()) + { + delete c; + break; + } + } + } + + void OnNickGroup(User *u, NickServ::Nick *target) override + { + if (!target->GetAccount()->HasFieldS("UNCONFIRMED")) + u->SetMode(NickServ, "REGISTERED"); + } + + void OnNickUpdate(User *u) override + { + for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) + { + ChanUserContainer *cc = it->second; + Channel *c = cc->chan; + if (c) + c->SetCorrectModes(u, true); + } + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (u->Quitting() || !u->server->IsSynced() || u->server->IsULined()) + return; + + const NickServ::Nick *na = NickServ::FindNick(u->nick); + + const Anope::string &unregistered_notice = Config->GetModule(this)->Get<Anope::string>("unregistered_notice"); + if (!Config->GetModule("nickserv")->Get<bool>("nonicknameownership") && !unregistered_notice.empty() && !na && !u->Account()) + u->SendMessage(*NickServ, unregistered_notice); + else if (na && !u->IsIdentified(true)) + this->Validate(u); + } + + void OnPostUserLogoff(User *u) override + { + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na) + OnCancel(u, na); + } + + void OnServerSync(Server *s) override + { + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + { + User *u = it->second; + + if (u->server == s) + { + if (u->HasMode("REGISTERED") && !u->IsIdentified(true)) + u->RemoveMode(NickServ, "REGISTERED"); + if (!u->IsIdentified()) + this->Validate(u); + } + } + } + + void OnUserNickChange(User *u, const Anope::string &oldnick) override + { + NickServ::Nick *old_na = NickServ::FindNick(oldnick), *na = NickServ::FindNick(u->nick); + /* If the new nick isn't registered or it's registered and not yours */ + if (!na || na->GetAccount() != u->Account()) + { + /* Remove +r, but keep an account associated with the user */ + u->RemoveMode(NickServ, "REGISTERED"); + + this->Validate(u); + } + else + { + /* Reset +r and re-send account (even though it really should be set at this point) */ + IRCD->SendLogin(u, na); + if (!Config->GetModule("nickserv")->Get<bool>("nonicknameownership") && na->GetAccount() == u->Account() && !na->GetAccount()->HasFieldS("UNCONFIRMED")) + u->SetMode(NickServ, "REGISTERED"); + Log(u, "", NickServ) << u->GetMask() << " automatically identified for group " << u->Account()->GetDisplay(); + } + + if (!u->nick.equals_ci(oldnick) && old_na) + OnCancel(u, old_na); + } + + void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (u->server->IsSynced() && mname == "REGISTERED" && !u->IsIdentified(true)) + u->RemoveMode(NickServ, mname); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!params.empty() || source.c || source.service != *NickServ) + return EVENT_CONTINUE; + if (!Config->GetModule("nickserv")->Get<bool>("nonicknameownership")) + source.Reply(_("\002%s\002 allows you to register a nickname and\n" + "prevent others from using it. The following\n" + "commands allow for registration and maintenance of\n" + "nicknames; to use them, type \002%s%s \037command\037\002.\n" + "For more information on a specific command, type\n" + "\002%s%s %s \037command\037\002.\n"), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), source.command.c_str()); + else + source.Reply(_("\002%s\002 allows you to register an account.\n" + "The following commands allow for registration and maintenance of\n" + "accounts; to use them, type \002%s%s \037command\037\002.\n" + "For more information on a specific command, type\n" + "\002%s%s %s \037command\037\002.\n"), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), source.command.c_str()); + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!params.empty() || source.c || source.service != *NickServ) + return; + if (source.IsServicesOper()) + source.Reply(_(" \n" + "Services Operators can also drop any nickname without needing\n" + "to identify for the nick, and may view the access list for\n" + "any nickname.")); + time_t nickserv_expire = Config->GetModule(this)->Get<time_t>("expire", "21d"); + if (nickserv_expire >= 86400) + source.Reply(_(" \n" + "Accounts that are not used anymore are subject to\n" + "the automatic expiration, i.e. they will be deleted\n" + "after %d days if not used."), nickserv_expire / 86400); + source.Reply(_(" \n" + "\002NOTICE:\002 This service is intended to provide a way for\n" + "IRC users to ensure their identity is not compromised.\n" + "It is \002NOT\002 intended to facilitate \"stealing\" of\n" + "nicknames or other malicious actions. Abuse of %s\n" + "will result in, at minimum, loss of the abused\n" + "nickname(s)."), NickServ->nick.c_str()); + } + + void OnNickCoreCreate(NickServ::Account *nc) override + { + /* Set default flags */ + for (unsigned i = 0; i < defaults.size(); ++i) + nc->SetS<bool>(defaults[i].upper(), true); + } + + void OnUserQuit(User *u, const Anope::string &msg) override + { + if (u->server && !u->server->GetQuitReason().empty() && Config->GetModule(this)->Get<bool>("hidenetsplitquit")) + return; + + /* Update last quit and last seen for the user */ + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na && !na->GetAccount()->HasFieldS("NS_SUSPENDED") && (u->IsRecognized() || u->IsIdentified(true))) + { + na->SetLastSeen(Anope::CurTime); + na->SetLastQuit(msg); + } + } + + void OnExpireTick() override + { + if (Anope::NoExpire || Anope::ReadOnly) + return; + + time_t nickserv_expire = Config->GetModule(this)->Get<time_t>("expire", "21d"); + + for (NickServ::Nick *na : nick_type.List<NickServ::Nick *>()) + { + User *u = User::Find(na->GetNick()); + if (u && (u->IsIdentified(true) || u->IsRecognized())) + na->SetLastSeen(Anope::CurTime); + + bool expire = false; + + if (nickserv_expire && Anope::CurTime - na->GetLastSeen() >= nickserv_expire) + expire = true; + + this->onprenickexpire(&NickServ::Event::PreNickExpire::OnPreNickExpire, na, expire); + + if (expire) + { + Log(LOG_NORMAL, "nickserv/expire", NickServ) << "Expiring nickname " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ") (e-mail: " << (na->GetAccount()->GetEmail().empty() ? "none" : na->GetAccount()->GetEmail()) << ")"; + this->onnickexpire(&NickServ::Event::NickExpire::OnNickExpire, na); + delete na; + } + } + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + if (!na->GetAccount()->HasFieldS("UNCONFIRMED")) + { + time_t nickserv_expire = Config->GetModule(this)->Get<time_t>("expire", "21d"); + if (!na->HasFieldS("NS_NO_EXPIRE") && nickserv_expire && !Anope::NoExpire && (source.HasPriv("nickserv/auspex") || na->GetLastSeen() != Anope::CurTime)) + info[_("Expires")] = Anope::strftime(na->GetLastSeen() + nickserv_expire, source.GetAccount()); + } + else + { + time_t unconfirmed_expire = Config->GetModule(this)->Get<time_t>("unconfirmedexpire", "1d"); + info[_("Expires")] = Anope::strftime(na->GetTimeRegistered() + unconfirmed_expire, source.GetAccount()); + } + } + + void OnModuleUnload(User *u, Module *m) override + { + for (std::set<NickServ::IdentifyRequest *>::iterator it = identifyrequests.begin(), it_end = identifyrequests.end(); it != it_end;) + { + NickServ::IdentifyRequest *ir = *it; + ++it; + + ir->Release(m); +#if 0 + ir->holds.erase(m); + if (ir->holds.empty() && ir->dispatched) + { + if (!ir->success) + ir->OnFail(); + delete ir; + continue; + } + + if (ir->GetOwner() == m) + { + if (!ir->success) + ir->OnFail(); + delete ir; + } +#endif + } + } +}; + +MODULE_INIT(NickServCore) + diff --git a/modules/nickserv/main/nicktype.cpp b/modules/nickserv/main/nicktype.cpp new file mode 100644 index 000000000..9607aaa2f --- /dev/null +++ b/modules/nickserv/main/nicktype.cpp @@ -0,0 +1,45 @@ +#include "module.h" +#include "nicktype.h" + +NickType::NickType(Module *me) : Serialize::Type<NickImpl>(me, "NickAlias") + , nick(this, "nick") + , last_quit(this, "last_quit") + , last_realname(this, "last_realname") + , last_usermask(this, "last_usermask") + , last_realhost(this, "last_realhost") + , time_registered(this, "time_registered") + , last_seen(this, "last_seen") + , vhost_ident(this, "vhost_ident") + , vhost_host(this, "vhost_host") + , vhost_creator(this, "vhost_creator") + , vhost_created(this, "vhost_created") + , nc(this, "nc") +{ + +} + +void NickType::Nick::SetField(NickImpl *na, const Anope::string &value) +{ + /* Remove us from the aliases list */ + NickServ::nickalias_map &map = NickServ::service->GetNickMap(); + map.erase(GetField(na)); + + Serialize::Field<NickImpl, Anope::string>::SetField(na, value); + + map[value] = na; +} + +NickServ::Nick *NickType::FindNick(const Anope::string &n) +{ + Serialize::ID id; + EventReturn result = Event::OnSerialize(&Event::SerializeEvents::OnSerializeFind, this, &this->nick, n, id); + if (result == EVENT_ALLOW) + return RequireID(id); + + NickServ::nickalias_map &map = NickServ::service->GetNickMap(); + auto it = map.find(n); + if (it != map.end()) + return it->second; + return nullptr; +} + diff --git a/modules/nickserv/main/nicktype.h b/modules/nickserv/main/nicktype.h new file mode 100644 index 000000000..d205845e9 --- /dev/null +++ b/modules/nickserv/main/nicktype.h @@ -0,0 +1,32 @@ +#include "nick.h" + +class NickType : public Serialize::Type<NickImpl> +{ + public: + struct Nick : Serialize::Field<NickImpl, Anope::string> + { + using Serialize::Field<NickImpl, Anope::string>::Field; + + void SetField(NickImpl *s, const Anope::string &value) override; + } nick; + Serialize::Field<NickImpl, Anope::string> last_quit; + Serialize::Field<NickImpl, Anope::string> last_realname; + /* Last usermask this nick was seen on, eg user@host */ + Serialize::Field<NickImpl, Anope::string> last_usermask; + /* Last uncloaked usermask, requires nickserv/auspex to see */ + Serialize::Field<NickImpl, Anope::string> last_realhost; + Serialize::Field<NickImpl, time_t> time_registered; + Serialize::Field<NickImpl, time_t> last_seen; + + Serialize::Field<NickImpl, Anope::string> vhost_ident; + Serialize::Field<NickImpl, Anope::string> vhost_host; + Serialize::Field<NickImpl, Anope::string> vhost_creator; + Serialize::Field<NickImpl, time_t> vhost_created; + + /* Account this nick is tied to. Multiple nicks can be tied to a single account. */ + Serialize::ObjectField<NickImpl, NickServ::Account *> nc; + + NickType(Module *); + + NickServ::Nick *FindNick(const Anope::string &nick); +}; diff --git a/modules/nickserv/maxemail.cpp b/modules/nickserv/maxemail.cpp new file mode 100644 index 000000000..38054f819 --- /dev/null +++ b/modules/nickserv/maxemail.cpp @@ -0,0 +1,80 @@ +/* ns_maxemail.cpp - Limit the amount of times an email address + * can be used for a NickServ account. + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Included in the Anope module pack since Anope 1.7.9 + * Anope Coder: GeniusDex <geniusdex@anope.org> + * + * Please read COPYING and README for further details. + */ + +#include "module.h" + +class NSMaxEmail : public Module + , public EventHook<Event::PreCommand> +{ + bool CheckLimitReached(CommandSource &source, const Anope::string &email) + { + int NSEmailMax = Config->GetModule(this)->Get<int>("maxemails"); + + if (NSEmailMax < 1 || email.empty()) + return false; + + if (this->CountEmail(email, source.nc) < NSEmailMax) + return false; + + if (NSEmailMax == 1) + source.Reply(_("The email address \002{0}\002 has reached its usage limit of \0021\002 user."), email); + else + source.Reply(_("The email address \002{0}\002 has reached its usage limit of \002{1}\002 users."), email, NSEmailMax); + + return true; + } + + int CountEmail(const Anope::string &email, NickServ::Account *unc) + { + int count = 0; + + if (email.empty()) + return 0; + + for (NickServ::Account *nc : NickServ::service->GetAccountList()) + if (unc != nc && !nc->GetEmail().empty() && nc->GetEmail().equals_ci(email)) + ++count; + + return count; + } + + public: + NSMaxEmail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + { + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> ¶ms) override + { + if (source.IsOper()) + return EVENT_CONTINUE; + + if (command->name == "nickserv/register") + { + if (this->CheckLimitReached(source, params.size() > 1 ? params[1] : "")) + return EVENT_STOP; + } + else if (command->name == "nickserv/set/email") + { + if (this->CheckLimitReached(source, params.size() > 0 ? params[0] : "")) + return EVENT_STOP; + } + else if (command->name == "nickserv/ungroup" && source.GetAccount()) + { + if (this->CheckLimitReached(source, source.GetAccount()->GetEmail())) + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(NSMaxEmail) diff --git a/modules/nickserv/recover.cpp b/modules/nickserv/recover.cpp new file mode 100644 index 000000000..65033bab9 --- /dev/null +++ b/modules/nickserv/recover.cpp @@ -0,0 +1,262 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_cert.h" +#include "modules/nickserv.h" + +typedef std::map<Anope::string, ChannelStatus> NSRecoverInfo; + +class NSRecoverRequestListener : public NickServ::IdentifyRequestListener +{ + CommandSource source; + Command *cmd; + Anope::string user; + Anope::string pass; + + public: + NSRecoverRequestListener(CommandSource &src, Command *c, const Anope::string &nick, const Anope::string &p) : source(src), cmd(c), user(nick), pass(p) { } + + void OnSuccess(NickServ::IdentifyRequest *) override + { + User *u = User::Find(user, true); + if (!source.GetUser() || !source.service) + return; + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + return; + + Log(LOG_COMMAND, source, cmd) << "for " << na->GetNick(); + + /* Nick is being held by us, release it */ + if (na->HasFieldS("HELD")) + { + NickServ::service->Release(na); + source.Reply(_("Service's hold on \002{0}\002 has been released."), na->GetNick()); + } + else if (!u) + { + source.Reply(_("No one is using your nick, and services are not holding it.")); + } + // If the user being recovered is identified for the account of the nick then the user is the + // same person that is executing the command, so kill them off (old GHOST command). + else if (u->Account() == na->GetAccount()) + { + if (!source.GetAccount() && na->GetAccount()->HasFieldS("NS_SECURE")) + { + source.GetUser()->Login(u->Account()); + Log(LOG_COMMAND, source, cmd) << "and was automatically identified to " << u->Account()->GetDisplay(); + } + + if (Config->GetModule("ns_recover")->Get<bool>("restoreonrecover")) + { + if (!u->chans.empty()) + { + NSRecoverInfo i; + for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) + i[it->first->name] = it->second->status; + source.GetUser()->Extend<NSRecoverInfo>("recover", i); + } + } + + u->SendMessage(*source.service, _("This nickname has been recovered by \002{0}\002. If you did not do this, then \002{0}\002 may have your password, and you should change it."), + source.GetNick()); + + Anope::string buf = source.command.upper() + " command used by " + source.GetNick(); + u->Kill(*source.service, buf); + + source.Reply(_("Ghost with your nick has been killed.")); + + if (IRCD->CanSVSNick) + IRCD->SendForceNickChange(source.GetUser(), user, Anope::CurTime); + } + /* User is not identified or not identified to the same account as the person using this command */ + else + { + if (!source.GetAccount() && na->GetAccount()->HasFieldS("NS_SECURE")) + { + source.GetUser()->Login(na->GetAccount()); // Identify the user using the command if they arent identified + Log(LOG_COMMAND, source, cmd) << "and was automatically identified to " << na->GetNick() << " (" << na->GetAccount()->GetDisplay() << ")"; + } + + u->SendMessage(*source.service, _("This nickname has been recovered by \002{0}\002."), source.GetNick()); + if (NickServ::service) + NickServ::service->Collide(u, na); + + if (IRCD->CanSVSNick) + { + /* If we can svsnick then release our hold and svsnick the user using the command */ + if (NickServ::service) + NickServ::service->Release(na); + IRCD->SendForceNickChange(source.GetUser(), user, Anope::CurTime); + source.Reply(_("You have regained control of \002%s\002 and are now identified as \002%s\002."), user, na->GetAccount()->GetDisplay().c_str()); + } + else + source.Reply(_("The user with your nick has been removed. Use this command again to release services's hold on your nick.")); + } + } + + void OnFail(NickServ::IdentifyRequest *) override + { + if (NickServ::FindNick(user) != NULL) + { + source.Reply(_("Access denied.")); + if (!pass.empty()) + { + Log(LOG_COMMAND, source, cmd) << "with an invalid password for " << user; + if (source.GetUser()) + source.GetUser()->BadPassword(); + } + } + else + source.Reply(_("\002{0}\002 isn't registered."), user); + } +}; + +class CommandNSRecover : public Command +{ + public: + CommandNSRecover(Module *creator) : Command(creator, "nickserv/recover", 1, 2) + { + this->SetDesc(_("Regains control of your nick")); + this->SetSyntax(_("\037nickname\037 [\037password\037]")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &nick = params[0]; + const Anope::string &pass = params.size() > 1 ? params[1] : ""; + + User *user = User::Find(nick, true); + + if (user && source.GetUser() == user) + { + source.Reply(_("You can't %s yourself!"), source.command.lower().c_str()); + return; + } + + NickServ::Nick *na = NickServ::FindNick(nick); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (na->GetAccount()->HasFieldS("NS_SUSPENDED")) + { + source.Reply(_("\002{0}\002 is suspended."), na->GetNick()); + return; + } + + bool ok = false; + if (source.GetAccount() == na->GetAccount()) + ok = true; + else if (!na->GetAccount()->HasFieldS("NS_SECURE") && source.GetUser() && na->GetAccount()->IsOnAccess(source.GetUser())) + ok = true; + + if (certservice && source.GetUser() && certservice->Matches(source.GetUser(), na->GetAccount())) + ok = true; + + if (ok == false && !pass.empty()) + { + NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSRecoverRequestListener(source, this, na->GetNick(), pass), owner, na->GetNick(), pass); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, source.GetUser(), req); + req->Dispatch(); + } + else + { + NSRecoverRequestListener req(source, this, na->GetNick(), pass); + + if (ok) + req.OnSuccess(nullptr); + else + req.OnFail(nullptr); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Recovers your nickname from another user or from services." + "If services are currently holding your nickname, the hold will be released." + " If another user is holding your nickname and is identified they will be killed." + " If they are not identified they will be forced off of the nickname.")); + return true; + } +}; + +class NSRecover : public Module + , public EventHook<Event::UserNickChange> + , public EventHook<Event::JoinChannel> +{ + CommandNSRecover commandnsrecover; + ExtensibleItem<NSRecoverInfo> recover; + + public: + NSRecover(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsrecover(this) + , recover(this, "recover") + { + + if (Config->GetModule("nickserv")->Get<bool>("nonicknameownership")) + throw ModuleException(modname + " can not be used with options:nonicknameownership enabled"); + + } + + void OnUserNickChange(User *u, const Anope::string &oldnick) override + { + if (Config->GetModule(this)->Get<bool>("restoreonrecover")) + { + NSRecoverInfo *ei = recover.Get(u); + ServiceBot *NickServ = Config->GetClient("NickServ"); + + if (ei != NULL && NickServ != NULL) + for (NSRecoverInfo::iterator it = ei->begin(), it_end = ei->end(); it != it_end;) + { + Channel *c = Channel::Find(it->first); + const Anope::string &cname = it->first; + ++it; + + /* User might already be on the channel */ + if (u->FindChannel(c)) + this->OnJoinChannel(u, c); + else if (IRCD->CanSVSJoin) + IRCD->SendSVSJoin(NickServ, u, cname, ""); + } + } + } + + void OnJoinChannel(User *u, Channel *c) override + { + if (Config->GetModule(this)->Get<bool>("restoreonrecover")) + { + NSRecoverInfo *ei = recover.Get(u); + + if (ei != NULL) + { + NSRecoverInfo::iterator it = ei->find(c->name); + if (it != ei->end()) + { + for (size_t i = 0; i < it->second.Modes().length(); ++i) + c->SetMode(c->ci->WhoSends(), ModeManager::FindChannelModeByChar(it->second.Modes()[i]), u->GetUID()); + + ei->erase(it); + if (ei->empty()) + recover.Unset(u); + } + } + } + } +}; + +MODULE_INIT(NSRecover) diff --git a/modules/nickserv/register.cpp b/modules/nickserv/register.cpp new file mode 100644 index 000000000..c7e6e5af2 --- /dev/null +++ b/modules/nickserv/register.cpp @@ -0,0 +1,426 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/nickserv.h" + +static bool SendRegmail(User *u, NickServ::Nick *na, ServiceBot *bi); + +class CommandNSConfirm : 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<Anope::string> ¶ms) override + { + const Anope::string &passcode = params[0]; + + if (source.nc && !source.nc->HasFieldS("UNCONFIRMED") && source.HasPriv("nickserv/confirm")) + { + NickServ::Nick *na = NickServ::FindNick(passcode); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), passcode); + return; + } + + if (na->GetAccount()->HasFieldS("UNCONFIRMED") == false) + { + source.Reply(_("\002{0}\002 is already confirmed."), na->GetNick()); + return; + } + + na->GetAccount()->UnsetS<bool>("UNCONFIRMED"); + if (NickServ::Event::OnNickConfirm) + NickServ::Event::OnNickConfirm(&NickServ::Event::NickConfirm::OnNickConfirm, source.GetUser(), na->GetAccount()); + Log(LOG_ADMIN, source, this) << "to confirm nick " << na->GetNick() << " (" << na->GetAccount()->GetDisplay() << ")"; + source.Reply(_("\002{0}\002 has been confirmed."), na->GetNick()); + } + else if (source.nc) + { + Anope::string *code = source.nc->GetExt<Anope::string>("passcode"); + if (code == nullptr || *code != passcode) + { + source.Reply(_("Invalid passcode.")); + return; + } + + NickServ::Account *nc = source.nc; + nc->ShrinkOK<Anope::string>("passcode"); + Log(LOG_COMMAND, source, this) << "to confirm their email"; + source.Reply(_("Your email address of \002{0}\002 has been confirmed."), source.nc->GetEmail()); + nc->UnsetS<bool>("UNCONFIRMED"); + if (NickServ::Event::OnNickConfirm) + NickServ::Event::OnNickConfirm(&NickServ::Event::NickConfirm::OnNickConfirm, source.GetUser(), nc); + + if (source.GetUser()) + { + NickServ::Nick *na = NickServ::FindNick(source.GetNick()); + if (na) + { + IRCD->SendLogin(source.GetUser(), na); + if (!Config->GetModule("nickserv")->Get<bool>("nonicknameownership") && na->GetAccount() == source.GetAccount() && !na->GetAccount()->HasFieldS("UNCONFIRMED")) + source.GetUser()->SetMode(source.service, "REGISTERED"); + } + } + } + else + source.Reply(_("Invalid passcode.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + 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 when resetting your password to force identify you to your account so you may change your password.")); + if (source.HasPriv("nickserv/confirm")) + source.Reply(_("Additionally, Services Operators with the \037nickserv/confirm\037 permission can\n" + "replace \037passcode\037 with a users nick to force validate them.")); + return true; + } +}; + +class CommandNSRegister : 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> ¶ms) override + { + User *u = source.GetUser(); + Anope::string u_nick = source.GetNick(); + size_t nicklen = u_nick.length(); + Anope::string pass = params[0]; + Anope::string email = params.size() > 1 ? params[1] : ""; + const Anope::string &nsregister = Config->GetModule(this->owner)->Get<Anope::string>("registration"); + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, nickname registration is temporarily disabled.")); + 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) + { + source.Reply(_("You must have been using this nickname for at least {0} seconds to register."), nickregdelay); + return; + } + + /* Prevent "Guest" nicks from being registered. -TheShadow */ + + /* Guest nick can now have a series of between 1 and 7 digits. + * --lara + */ + const Anope::string &guestnick = Config->GetModule("nickserv")->Get<Anope::string>("guestnickprefix", "Guest"); + if (nicklen <= guestnick.length() + 7 && nicklen >= guestnick.length() + 1 && !u_nick.find_ci(guestnick) && u_nick.substr(guestnick.length()).find_first_not_of("1234567890") == Anope::string::npos) + { + source.Reply(_("\002{0}\002 may not be registered."), u_nick); + return; + } + + if (!IRCD->IsNickValid(u_nick)) + { + source.Reply(_("\002{0}\002 may not be registered."), u_nick); + return; + } + + if (ServiceBot::Find(u_nick, true)) + { + source.Reply(_("\002{0}\002 may not be registered."), u_nick); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("restrictopernicks")) + for (Oper *o : Serialize::GetObjects<Oper *>(operblock)) + { + if (!source.IsOper() && u_nick.find_ci(o->GetName()) != Anope::string::npos) + { + source.Reply(_("\002{0}\002 may not be registered."), u_nick); + return; + } + } + + unsigned int passlen = Config->GetModule("nickserv")->Get<unsigned>("passlen", "32"); + + if (Config->GetModule("nickserv")->Get<bool>("forceemail", "yes") && email.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (u && Anope::CurTime < u->lastnickreg + reg_delay) + { + source.Reply(_("Please wait \002{0}\002 seconds before using the {1} command again."), (u->lastnickreg + reg_delay) - Anope::CurTime, source.command); + return; + } + + if (NickServ::FindNick(u_nick) != NULL) + { + source.Reply(_("\002{0}\002 is already registered."), u_nick); + return; + } + + if (pass.equals_ci(u_nick) || (Config->GetBlock("options")->Get<bool>("strictpasswords") && pass.length() < 5)) + { + source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed" + " (e.g. your real name or your nickname), and cannot contain the space or tab characters.")); + return; + } + + if (pass.length() > Config->GetModule("nickserv")->Get<unsigned>("passlen", "32")) + { + source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get<unsigned>("passlen", "32")); + return; + } + + if (!email.empty() && !Mail::Validate(email)) + { + source.Reply(_("\002{0}\002 is not a valid e-mail address."), email); + return; + } + + NickServ::Account *nc = NickServ::account.Create(); + nc->SetDisplay(u_nick); + + NickServ::Nick *na = NickServ::nick.Create(); + na->SetNick(u_nick); + na->SetAccount(nc); + Anope::string epass; + Anope::Encrypt(pass, epass); + nc->SetPassword(epass); + if (!email.empty()) + nc->SetEmail(email); + + if (u) + { + na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); + na->SetLastRealname(u->realname); + } + else + na->SetLastRealname(source.GetNick()); + + Log(LOG_COMMAND, source, this) << "to register " << na->GetNick() << " (email: " << (!na->GetAccount()->GetEmail().empty() ? na->GetAccount()->GetEmail() : "none") << ")"; + + source.Reply(_("\002{0}\002 has been registered."), u_nick); + + Anope::string tmp_pass; + if (Anope::Decrypt(na->GetAccount()->GetPassword(), tmp_pass)) + source.Reply(_("Your password is \002{0}\002 - remember this for later use."), tmp_pass); + + if (nsregister.equals_ci("admin")) + { + nc->SetS<bool>("UNCONFIRMED", true); + // User::Identify() called below will notify the user that their registration is pending + } + else if (nsregister.equals_ci("mail")) + { + if (!email.empty()) + { + nc->SetS<bool>("UNCONFIRMED", true); + SendRegmail(NULL, na, source.service); + } + } + + if (NickServ::Event::OnNickRegister) + NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, source.GetUser(), na, pass); + + if (u) + { + u->Identify(na); + u->lastnickreg = Anope::CurTime; + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Registers your nickname. Once your nickname is registered, you will be able to use most features of services, including owning and managing channels." + "Make sure you remember the password - you'll need it to identify yourself later. Your email address will only be used if you forget your password.")); + + if (!Config->GetModule("nickserv")->Get<bool>("forceemail", "yes")) + { + source.Reply(" "); + source.Reply(_("The \037email\037 parameter is optional and will set the email\n" + "for your nick immediately.\n" + "Your privacy is respected; this e-mail won't be given to\n" + "any third-party person. You may also wish to \002SET HIDE\002 it\n" + "after registering if it isn't the default setting already.")); + } + + if (!Config->GetModule("nickserv")->Get<bool>("nonicknameownership")) + { + source.Reply(" "); + source.Reply(_("This command also creates a new group for your nickname, which will allow you to group other nicknames later, which share the same configuration, the same set of memos and the same channel privileges.")); + } + return true; + } +}; + +class CommandNSResend : public Command +{ + public: + CommandNSResend(Module *creator) : Command(creator, "nickserv/resend", 0, 0) + { + this->SetDesc(_("Resend registration confirmation email")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!Config->GetModule(this->owner)->Get<Anope::string>("registration").equals_ci("mail")) + { + source.Reply(_("Access denied.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(source.GetNick()); + + if (na == NULL) + { + source.Reply(_("Your nickname isn't registered.")); + return; + } + + if (na->GetAccount() != source.GetAccount() || !source.nc->HasFieldS("UNCONFIRMED")) + { + source.Reply(_("Your account is already confirmed.")); + return; + } + + if (Anope::CurTime < source.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 verification code for " << source.GetNick(); + return; + } + + na->GetAccount()->lastmail = Anope::CurTime; + source.Reply(_("Your passcode has been re-sent to \002{0}\002."), na->GetAccount()->GetEmail()); + Log(LOG_COMMAND, source, this) << "to resend registration verification code"; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (!Config->GetModule(this->owner)->Get<Anope::string>("registration").equals_ci("mail")) + return false; + + source.Reply(_("This command will resend you the registration confirmation email.")); + return true; + } + + void OnServHelp(CommandSource &source) override + { + if (Config->GetModule(this->owner)->Get<Anope::string>("registration").equals_ci("mail")) + Command::OnServHelp(source); + } +}; + +class NSRegister : public Module + , public EventHook<Event::NickIdentify> + , public EventHook<NickServ::Event::PreNickExpire> +{ + CommandNSRegister commandnsregister; + CommandNSConfirm commandnsconfirm; + CommandNSResend commandnsrsend; + + Serialize::Field<NickServ::Account, bool> unconfirmed; + Serialize::Field<NickServ::Account, Anope::string> passcode; + + public: + NSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsregister(this) + , commandnsconfirm(this) + , commandnsrsend(this) + , unconfirmed(this, NickServ::account, "UNCONFIRMED") + , passcode(this, NickServ::account, "passcode") + { + if (Config->GetModule(this)->Get<Anope::string>("registration").equals_ci("disable")) + throw ModuleException("Module " + this->name + " will not load with registration disabled."); + } + + void OnNickIdentify(User *u) override + { + ServiceBot *NickServ; + if (unconfirmed.HasExt(u->Account()) && (NickServ = Config->GetClient("NickServ"))) + { + const Anope::string &nsregister = Config->GetModule(this)->Get<Anope::string>("registration"); + if (nsregister.equals_ci("admin")) + u->SendMessage(NickServ, _("All new accounts must be validated by an administrator. Please wait for your registration to be confirmed.")); + else + u->SendMessage(NickServ, _("Your email address is not confirmed. To confirm it, follow the instructions that were emailed to you.")); + NickServ::Nick *this_na = NickServ::FindNick(u->Account()->GetDisplay()); + time_t time_registered = Anope::CurTime - this_na->GetTimeRegistered(); + time_t unconfirmed_expire = Config->GetModule(this)->Get<time_t>("unconfirmedexpire", "1d"); + if (unconfirmed_expire > time_registered) + u->SendMessage(NickServ, _("Your account will expire, if not confirmed, in %s."), Anope::Duration(unconfirmed_expire - time_registered, u->Account()).c_str()); + } + } + + void OnPreNickExpire(NickServ::Nick *na, bool &expire) override + { + if (unconfirmed.HasExt(na->GetAccount())) + { + time_t unconfirmed_expire = Config->GetModule(this)->Get<time_t>("unconfirmedexpire", "1d"); + if (unconfirmed_expire && Anope::CurTime - na->GetTimeRegistered() >= unconfirmed_expire) + expire = true; + } + } +}; + +static bool SendRegmail(User *u, NickServ::Nick *na, ServiceBot *bi) +{ + NickServ::Account *nc = na->GetAccount(); + + Anope::string *code = na->GetAccount()->GetExt<Anope::string>("passcode"); + if (code == NULL) + code = na->GetAccount()->Extend<Anope::string>("passcode", Anope::Random(9)); + + Anope::string subject = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get<Anope::string>("registration_subject").c_str()), + message = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get<Anope::string>("registration_message").c_str()); + + subject = subject.replace_all_cs("%n", na->GetNick()); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get<Anope::string>("networkname")); + subject = subject.replace_all_cs("%c", *code); + + message = message.replace_all_cs("%n", na->GetNick()); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get<Anope::string>("networkname")); + message = message.replace_all_cs("%c", *code); + + return Mail::Send(u, nc, bi, subject, message); +} + +MODULE_INIT(NSRegister) diff --git a/modules/nickserv/resetpass.cpp b/modules/nickserv/resetpass.cpp new file mode 100644 index 000000000..3be5a4c06 --- /dev/null +++ b/modules/nickserv/resetpass.cpp @@ -0,0 +1,141 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" + +static bool SendResetEmail(User *u, NickServ::Nick *na, ServiceBot *bi); + +class CommandNSResetPass : public Command +{ + public: + CommandNSResetPass(Module *creator) : Command(creator, "nickserv/resetpass", 2, 2) + { + this->SetDesc(_("Helps you reset lost passwords")); + this->SetSyntax(_("\037account\037 \037email\037")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + NickServ::Nick *na = NickServ::FindNick(params[0]); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), params[0]); + return; + } + + if (!na->GetAccount()->GetEmail().equals_ci(params[1])) + { + source.Reply(_("Incorrect email address.")); + return; + } + + if (SendResetEmail(source.GetUser(), na, source.service)) + { + Log(LOG_COMMAND, source, this) << "for " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ")"; + source.Reply(_("Password reset email for \002{0}\002 has been sent."), na->GetNick()); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends a passcode to the email address of \037account\037 with instructions on how to reset their password. \037email\037 must be the email address associated to \037account\037.")); + return true; + } +}; + +struct ResetInfo +{ + Anope::string code; + time_t time; +}; + +class NSResetPass : public Module + , public EventHook<Event::PreCommand> +{ + CommandNSResetPass commandnsresetpass; + ExtensibleItem<ResetInfo> reset; + + public: + NSResetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsresetpass(this), reset(this, "reset") + { + if (!Config->GetBlock("mail")->Get<bool>("usemail")) + throw ModuleException("Not using mail."); + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> ¶ms) override + { + if (command->name == "nickserv/confirm" && params.size() > 1) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return EVENT_STOP; + } + + NickServ::Nick *na = NickServ::FindNick(params[0]); + + ResetInfo *ri = na ? reset.Get(na->GetAccount()) : NULL; + if (na && ri) + { + NickServ::Account *nc = na->GetAccount(); + 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->UnsetS<bool>("UNCONFIRMED"); + + Log(LOG_COMMAND, source, &commandnsresetpass) << "confirmed RESETPASS to forcefully identify as " << na->GetNick(); + + if (source.GetUser()) + { + source.GetUser()->Identify(na); + source.Reply(_("You are now identified for \002{0}\002. Change your password now."), na->GetAccount()->GetDisplay()); + } + } + else + return EVENT_CONTINUE; + + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } +}; + +static bool SendResetEmail(User *u, NickServ::Nick *na, ServiceBot *bi) +{ + Anope::string subject = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get<Anope::string>("reset_subject").c_str()), + message = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get<Anope::string>("reset_message").c_str()), + passcode = Anope::Random(20); + + subject = subject.replace_all_cs("%n", na->GetNick()); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get<Anope::string>("networkname")); + subject = subject.replace_all_cs("%c", passcode); + + message = message.replace_all_cs("%n", na->GetNick()); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get<Anope::string>("networkname")); + message = message.replace_all_cs("%c", passcode); + + na->GetAccount()->Extend<ResetInfo>("reset", ResetInfo{passcode, Anope::CurTime}); + + return Mail::Send(u, na->GetAccount(), bi, subject, message); +} + +MODULE_INIT(NSResetPass) diff --git a/modules/nickserv/set.cpp b/modules/nickserv/set.cpp new file mode 100644 index 000000000..a75386743 --- /dev/null +++ b/modules/nickserv/set.cpp @@ -0,0 +1,1244 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_info.h" +#include "modules/ns_set.h" +#include "modules/nickserv.h" + +class CommandNSSet : public Command +{ + public: + CommandNSSet(Module *creator) : Command(creator, "nickserv/set", 1, 3) + { + this->SetDesc(_("Set options, including kill protection")); + this->SetSyntax(_("\037option\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets various options on your account\n" + "\n" + "Available options:")); + + Anope::string this_name = source.command; + bool hide_privileged_commands = Config->GetBlock("options")->Get<bool>("hideprivilegedcommands"), + hide_registered_commands = Config->GetBlock("options")->Get<bool>("hideregisteredcommands"); + for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + + if (c_name.find_ci(this_name + " ") == 0) + { + ServiceReference<Command> c("Command", info.name); + // XXX dup + 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); + } + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), + Config->StrictPrivmsg, source.service->nick, help->cname, this_name); + + return true; + } +}; + +class CommandNSSASet : public Command +{ + public: + CommandNSSASet(Module *creator) : Command(creator, "nickserv/saset", 2, 4) + { + this->SetDesc(_("Set SET-options on another nickname")); + this->SetSyntax(_("\037option\037 \037nickname\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->OnSyntaxError(source, ""); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets various options on other users accounts\n" + "\n" + "Available options:")); + + Anope::string this_name = source.command; + for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + + if (c_name.find_ci(this_name + " ") == 0) + { + ServiceReference<Command> command("Command", info.name); + if (command) + { + source.command = c_name; + command->OnServHelp(source); + } + } + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), + Config->StrictPrivmsg, source.service->nick, help->cname, this_name); + + return true; + } +}; + +class CommandNSSetPassword : public Command +{ + public: + CommandNSSetPassword(Module *creator) : Command(creator, "nickserv/set/password", 1) + { + this->SetDesc(_("Changes your password")); + this->SetSyntax(_("\037new-password\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string ¶m = params[0]; + unsigned len = param.length(); + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if (source.GetNick().equals_ci(param) || (Config->GetBlock("options")->Get<bool>("strictpasswords") && len < 5)) + { + source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed (e.g. your real name or your nick), and cannot contain the space or tab characters.")); + return; + } + + if (len > Config->GetModule("nickserv")->Get<unsigned>("passlen", "32")) + { + source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get<unsigned>("passlen", "32")); + return; + } + + Log(LOG_COMMAND, source, this) << "to change their password"; + + Anope::string tmp_pass; + Anope::Encrypt(param, tmp_pass); + source.nc->SetPassword(tmp_pass); + + if (Anope::Decrypt(source.nc->GetPassword(), tmp_pass)) + source.Reply(_("Password for \002{0}\002 changed to \002{1]\002."), source.nc->GetDisplay(), tmp_pass); + else + source.Reply(_("Password for \002{0}\002 changed."), source.nc->GetDisplay()); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes your password to \037new-password\037.")); + return true; + } +}; + +class CommandNSSASetPassword : public Command +{ + public: + CommandNSSASetPassword(Module *creator) : Command(creator, "nickserv/saset/password", 2, 2) + { + this->SetDesc(_("Changes the password of another user")); + this->SetSyntax(_("\037account\037 \037new-password\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *setter_na = NickServ::FindNick(params[0]); + if (setter_na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), params[0]); + return; + } + NickServ::Account *nc = setter_na->GetAccount(); + + size_t len = params[1].length(); + + if (Config->GetModule("nickserv")->Get<bool>("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper()) + { + source.Reply(_("You may not change the password of other Services Operators.")); + return; + } + + if (nc->GetDisplay().equals_ci(params[1]) || (Config->GetBlock("options")->Get<bool>("strictpasswords") && len < 5)) + { + source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed (e.g. your real name or your nick), and cannot contain the space or tab characters.")); + return; + } + + if (len > Config->GetModule("nickserv")->Get<unsigned>("passlen", "32")) + { + source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get<unsigned>("passlen", "32")); + return; + } + + Log(LOG_ADMIN, source, this) << "to change the password of " << nc->GetDisplay(); + + Anope::string tmp_pass; + Anope::Encrypt(params[1], tmp_pass); + nc->SetPassword(tmp_pass); + if (Anope::Decrypt(nc->GetPassword(), tmp_pass) == 1) + source.Reply(_("Password for \002{0}\002 changed to \002{1}\002."), nc->GetDisplay(), tmp_pass); + else + source.Reply(_("Password for \002{0}\002 changed."), nc->GetDisplay()); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the password of \037account\037 to \037new-password\037.")); + return true; + } +}; + +class CommandNSSetAutoOp : public Command +{ + public: + CommandNSSetAutoOp(Module *creator, const Anope::string &sname = "nickserv/set/autoop", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Sets whether services should set channel status modes on you automatically.")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable autoop for " << na->GetAccount()->GetDisplay(); + nc->SetS<bool>("AUTOOP", true); + source.Reply(_("Services will from now on set status modes on \002{0}\002 in channels."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable autoop for " << na->GetAccount()->GetDisplay(); + nc->UnsetS<bool>("AUTOOP"); + source.Reply(_("Services will no longer set status modes on \002{0}\002 in channels."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "AUTOOP"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets whether you will be given your channel status modes automatically when you join a channel." + " Note that depending on channel settings some modes may not get set automatically.")); + return true; + } +}; + +class CommandNSSASetAutoOp : public CommandNSSetAutoOp +{ + public: + CommandNSSASetAutoOp(Module *creator) : CommandNSSetAutoOp(creator, "nickserv/saset/autoop", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets whether the given nickname will be given their status modes automatically when they join a channel." + " Note that depending on channel settings some modes may not get set automatically.")); + return true; + } +}; + +class CommandNSSetDisplay : public Command +{ + public: + CommandNSSetDisplay(Module *creator, const Anope::string &sname = "nickserv/set/display", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Set the display of your group in Services")); + this->SetSyntax(_("\037new-display\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *user_na = NickServ::FindNick(user), *na = NickServ::FindNick(param); + + if (Config->GetModule("nickserv")->Get<bool>("nonicknameownership")) + { + source.Reply(_("This command may not be used on this network because nickname ownership is disabled.")); + return; + } + + if (user_na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + + if (!na || na->GetAccount() != user_na->GetAccount()) + { + source.Reply(_("The new display must be a nickname of the nickname group \002{0}\02."), user_na->GetAccount()->GetDisplay()); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, user_na->GetAccount(), param); + if (MOD_RESULT == EVENT_STOP) + return; + + Log(user_na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the display of " << user_na->GetAccount()->GetDisplay() << " to " << na->GetNick(); + + user_na->GetAccount()->SetDisplay(na); + if (source.GetUser()) + IRCD->SendLogin(source.GetUser(), na); + source.Reply(_("The new display is now \002{0}\002."), user_na->GetAccount()->GetDisplay()); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the display used to refer to your nickname group in services. The new display nickname must be a nickname of your group.")); + return true; + } +}; + +class CommandNSSASetDisplay : public CommandNSSetDisplay +{ + public: + CommandNSSASetDisplay(Module *creator) : CommandNSSetDisplay(creator, "nickserv/saset/display", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 \037new-display\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the display used to refer to the nickname group \037account\037 in services. The new display nickname must be a nickname in the group of \037account\037.")); + return true; + } +}; + +class CommandNSSetEmail : public Command +{ + static bool SendConfirmMail(User *u, ServiceBot *bi, const Anope::string &new_email) + { + Anope::string code = Anope::Random(9); + + u->Account()->Extend<std::pair<Anope::string, Anope::string> >("ns_set_email", std::make_pair(new_email, code)); + + Anope::string subject = Config->GetBlock("mail")->Get<Anope::string>("emailchange_subject"), + message = Config->GetBlock("mail")->Get<Anope::string>("emailchange_message"); + + subject = subject.replace_all_cs("%e", u->Account()->GetEmail()); + subject = subject.replace_all_cs("%E", new_email); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get<Anope::string>("networkname")); + subject = subject.replace_all_cs("%c", code); + + message = message.replace_all_cs("%e", u->Account()->GetEmail()); + message = message.replace_all_cs("%E", new_email); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get<Anope::string>("networkname")); + message = message.replace_all_cs("%c", code); + + Anope::string old = u->Account()->GetEmail(); + u->Account()->SetEmail(new_email); + bool b = Mail::Send(u, u->Account(), bi, subject, message); + u->Account()->SetEmail(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 E-mail 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(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + if (nc->HasFieldS("UNCONFIRMED")) + { + source.Reply(_("You may not change the email of an unconfirmed account.")); + return; + } + + if (param.empty() && Config->GetModule("nickserv")->Get<bool>("forceemail", "yes")) + { + source.Reply(_("You cannot unset the e-mail on this network.")); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper()) + { + source.Reply(_("You may not change the e-mail of other Services Operators.")); + return; + } + + if (!param.empty() && !Mail::Validate(param)) + { + source.Reply(_("\002{0}\002 is not a valid e-mail address."), param); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.empty()) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the email of " << nc->GetDisplay(); + nc->SetEmail(""); + source.Reply(_("E-mail address for \002{0}\002 unset."), nc->GetDisplay()); + } + else if (Config->GetModule("nickserv")->Get<bool>("confirmemailchanges") && !source.IsServicesOper()) + { + if (SendConfirmMail(source.GetUser(), source.service, param)) + source.Reply(_("A confirmation e-mail has been sent to \002{0}\002. Follow the instructions in it to change your e-mail address."), param); + } + else + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the email of " << nc->GetDisplay() << " to " << param; + nc->SetEmail(param); + source.Reply(_("E-mail address for \002{0}\002 changed to \002{1}\002."), nc->GetDisplay(), param); + } + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params.size() ? params[0] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes your email address to \037address\037.")); + return true; + } +}; + +class CommandNSSASetEmail : public CommandNSSetEmail +{ + public: + CommandNSSASetEmail(Module *creator) : CommandNSSetEmail(creator, "nickserv/saset/email", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\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 + { + source.Reply(_("Changes the email address of \037account\037 to \037address\037.")); + return true; + } +}; + +class CommandNSSetKeepModes : public Command +{ + public: + CommandNSSetKeepModes(Module *creator, const Anope::string &sname = "nickserv/set/keepmodes", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Enable or disable keep modes")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable keepmodes for " << nc->GetDisplay(); + nc->SetS<bool>("NS_KEEP_MODES", true); + source.Reply(_("Keep modes for \002{0}\002 is now \002on\002."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable keepmodes for " << nc->GetDisplay(); + nc->UnsetS<bool>("NS_KEEP_MODES"); + source.Reply(_("Keep modes for \002{0}\002 is now \002off\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, ""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables keepmodes for your account. If keepmodes is enabled, services will remember your usermodes and attempt to re-set them the next time you log on.")); + return true; + } +}; + +class CommandNSSASetKeepModes : public CommandNSSetKeepModes +{ + public: + CommandNSSASetKeepModes(Module *creator) : CommandNSSetKeepModes(creator, "nickserv/saset/keepmodes", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables keepmodes for \037account\037. If keep modes is enabled, services will remember users' usermodes and attempt to re-set them the next time they log pn.")); + return true; + } +}; + +class CommandNSSetKill : public Command +{ + public: + CommandNSSetKill(Module *creator, const Anope::string &sname = "nickserv/set/kill", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Turn protection on or off")); + this->SetSyntax("{ON | QUICK | IMMED | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("nonicknameownership")) + { + source.Reply(_("This command may not be used on this network because nickname ownership is disabled.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + nc->SetS<bool>("KILLPROTECT", true); + nc->UnsetS<bool>("KILL_QUICK"); + nc->UnsetS<bool>("KILL_IMMED"); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill on for " << nc->GetDisplay(); + source.Reply(_("Protection is now \002on\002 for \002{0}\002."), nc->GetDisplay()); + } + else if (param.equals_ci("QUICK")) + { + nc->SetS<bool>("KILLPROTECT", true); + nc->SetS<bool>("KILL_QUICK", true); + nc->UnsetS<bool>("KILL_IMMED"); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill quick for " << nc->GetDisplay(); + source.Reply(_("Protection is now \002on\002 for \002{0}\002, with a reduced delay."), nc->GetDisplay()); + } + else if (param.equals_ci("IMMED")) + { + if (Config->GetModule(this->owner)->Get<bool>("allowkillimmed")) + { + nc->SetS<bool>("KILLPROTECT",true); + nc->UnsetS<bool>("KILL_QUICK"); + nc->SetS<bool>("KILL_IMMED", true); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill immed for " << nc->GetDisplay(); + source.Reply(_("Protection is now \002on\002 for \002{0}\002, with no delay."), nc->GetDisplay()); + } + else + source.Reply(_("The \002IMMED\002 option is not available on this network.")); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetS<bool>("KILLPROTECT"); + nc->UnsetS<bool>("KILL_QUICK"); + nc->UnsetS<bool>("KILL_IMMED"); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable kill for " << nc->GetDisplay(); + source.Reply(_("Protection is now \002off\002 for \002{0}\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "KILL"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the automatic protection option for your account on or off." + " With protection on, if another user tries to use one of your nicknames, they will be given {0} to change to another their nickname, after which {1} will forcibly change their nickname.\n" + "\n" + "If you select \002QUICK\002, the user will be given only {1} to change their nick instead {0}." + " If you select \002IMMED\002, the user's nickname will be changed immediately \037without\037 being warned first or given a chance to change their nick." + " With this set, the only way to use the nickname is to match an entry on the account's access list.")); + return true; + } +}; + +class CommandNSSASetKill : public CommandNSSetKill +{ + public: + CommandNSSASetKill(Module *creator) : CommandNSSetKill(creator, "nickserv/saset/kill", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | QUICK | IMMED | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the automatic protection option for \037account\037 on or off." + " With protection on, if another user tries to use one of the nicknames in the group, they will be given {0} to change to another their nickname, after which {1} will forcibly change their nickname.\n" + "\n" + "If you select \002QUICK\002, the user will be given only {1} to change their nick instead {0}." + " If you select \002IMMED\002, the user's nickname will be changed immediately \037without\037 being warned first or given a chance to change their nick." + " With this set, the only way to use the nickname is to match an entry on the account's access list.")); + return true; + } +}; + +class CommandNSSetLanguage : public Command +{ + public: + CommandNSSetLanguage(Module *creator, const Anope::string &sname = "nickserv/set/language", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Set the language Services will use when messaging you")); + this->SetSyntax(_("\037language\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param != "en_US") + for (unsigned j = 0; j < Language::Languages.size(); ++j) + { + if (Language::Languages[j] == param) + break; + else if (j + 1 == Language::Languages.size()) + { + this->OnSyntaxError(source, ""); + return; + } + } + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the language of " << nc->GetDisplay() << " to " << param; + + nc->SetLanguage(param); + source.Reply(_("Language changed to \002English\002.")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶m) override + { + this->Run(source, source.nc->GetDisplay(), param[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the language services will use when sending messages to you (for example, when responding to a command you send). \037language\037 should be chosen from the following list of supported languages:")); + + source.Reply(" en_US (English)"); + for (unsigned j = 0; j < Language::Languages.size(); ++j) + { + const Anope::string &langname = Language::Translate(Language::Languages[j].c_str(), _("English")); + if (langname == "English") + continue; + source.Reply(" %s (%s)", Language::Languages[j].c_str(), langname.c_str()); + } + + return true; + } +}; + +class CommandNSSASetLanguage : public CommandNSSetLanguage +{ + public: + CommandNSSASetLanguage(Module *creator) : CommandNSSetLanguage(creator, "nickserv/saset/language", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 \037language\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the language services will use when sending messages to the given user (for example, when responding to a command they send). \037language\037 should be chosen from the following list of supported languages:")); + source.Reply(" en (English)"); + for (unsigned j = 0; j < Language::Languages.size(); ++j) + { + const Anope::string &langname = Language::Translate(Language::Languages[j].c_str(), _("English")); + if (langname == "English") + continue; + source.Reply(" %s (%s)", Language::Languages[j].c_str(), langname.c_str()); + } + return true; + } +}; + +class CommandNSSetMessage : public Command +{ + public: + CommandNSSetMessage(Module *creator, const Anope::string &sname = "nickserv/set/message", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Change the communication method of Services")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + if (!Config->GetBlock("options")->Get<bool>("useprivmsg")) + { + source.Reply(_("You cannot %s on this network."), source.command); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable " << source.command << " for " << nc->GetDisplay(); + nc->SetS<bool>("MSG", true); + source.Reply(_("Services will now reply to \002{0}\002 with \002messages\002."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable " << source.command << " for " << nc->GetDisplay(); + nc->UnsetS<bool>("MSG"); + source.Reply(_("Services will now reply to \002{0}\002 with \002notices\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "MSG"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + Anope::string cmd = source.command; + size_t i = cmd.find_last_of(' '); + if (i != Anope::string::npos) + cmd = cmd.substr(i + 1); + + source.Reply(_("Allows you to choose the way services communicate with you. With \002{0}\002 set, services will use messages instead of notices."), cmd); + return true; + } + + void OnServHelp(CommandSource &source) override + { + if (!Config->GetBlock("options")->Get<bool>("useprivmsg")) + Command::OnServHelp(source); + } +}; + +class CommandNSSASetMessage : public CommandNSSetMessage +{ + public: + CommandNSSASetMessage(Module *creator) : CommandNSSetMessage(creator, "nickserv/saset/message", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | OFF}")); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + Anope::string cmd = source.command; + size_t i = cmd.find_last_of(' '); + if (i != Anope::string::npos) + cmd = cmd.substr(i + 1); + + source.Reply(_("Allows you to choose the way services communicate with the given user. With \002{0}\002 set, services will use messages instead of notices."), cmd); + return true; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params[1]); + } +}; + +class CommandNSSetSecure : public Command +{ + public: + CommandNSSetSecure(Module *creator, const Anope::string &sname = "nickserv/set/secure", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Turn nickname security on or off")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable secure for " << nc->GetDisplay(); + nc->SetS<bool>("NS_SECURE", true); + source.Reply(_("Secure option is now \002on\002 for \002{0}\002."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable secure for " << nc->GetDisplay(); + nc->UnsetS<bool>("NS_SECURE"); + source.Reply(_("Secure option is now \002off\002 for \002{0}\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "SECURE"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the security feature on or off for your account." + " With \002SECURE\002 set, you must enter your password before you will be recognized as the owner of the account," + " regardless of whether your address is on the access list or not." + " However, if you are on the access list, services will not force you to change your nickname, regardless of the setting of the \002kill\002 option.")); + return true; + } +}; + +class CommandNSSASetSecure : public CommandNSSetSecure +{ + public: + CommandNSSASetSecure(Module *creator) : CommandNSSetSecure(creator, "nickserv/saset/secure", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the security feature on or off for \037account\037." + " With \002SECURE\002 set, you the user must enter their password before they will be recognized as the owner of the account," + " regardless of whether their address address is on the access list or not." + " However, if they are on the access list, services will not force them to change your nickname, regardless of the setting of the \002kill\002 option.")); + return true; + } +}; + +class CommandNSSASetNoexpire : public Command +{ + public: + CommandNSSASetNoexpire(Module *creator) : Command(creator, "nickserv/saset/noexpire", 1, 2) + { + this->SetDesc(_("Prevent the nickname from expiring")); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &user = params[0]; + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + + Anope::string param = params.size() > 1 ? params[1] : ""; + + if (param.equals_ci("ON")) + { + Log(LOG_ADMIN, source, this) << "to enable noexpire for " << na->GetAccount()->GetDisplay(); + na->SetS<bool>("NS_NO_EXPIRE", true); + source.Reply(_("\002{0}\002 \002will not\002 expire."), na->GetNick()); + } + else if (param.equals_ci("OFF")) + { + Log(LOG_ADMIN, source, this) << "to disable noexpire for " << na->GetAccount()->GetDisplay(); + na->UnsetS<bool>("NS_NO_EXPIRE"); + source.Reply(_("\002{0}\002 \002will\002 expire."), na->GetNick()); + } + else + this->OnSyntaxError(source, "NOEXPIRE"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets whether the given nickname will expire. Setting this to \002ON\002 prevents the nickname from expiring.")); + return true; + } +}; + +class NSSet : public Module + , public EventHook<Event::PreCommand> + , public EventHook<Event::SetCorrectModes> + , public EventHook<NickServ::Event::PreNickExpire> + , public EventHook<Event::NickInfo> + , public EventHook<Event::UserModeSet> + , public EventHook<Event::UserModeUnset> + , public EventHook<Event::UserLogin> +{ + CommandNSSet commandnsset; + CommandNSSASet commandnssaset; + + CommandNSSetAutoOp commandnssetautoop; + CommandNSSASetAutoOp commandnssasetautoop; + + CommandNSSetDisplay commandnssetdisplay; + CommandNSSASetDisplay commandnssasetdisplay; + + CommandNSSetEmail commandnssetemail; + CommandNSSASetEmail commandnssasetemail; + + CommandNSSetKeepModes commandnssetkeepmodes; + CommandNSSASetKeepModes commandnssasetkeepmodes; + + CommandNSSetKill commandnssetkill; + CommandNSSASetKill commandnssasetkill; + + CommandNSSetLanguage commandnssetlanguage; + CommandNSSASetLanguage commandnssasetlanguage; + + CommandNSSetMessage commandnssetmessage; + CommandNSSASetMessage commandnssasetmessage; + + CommandNSSetPassword commandnssetpassword; + CommandNSSASetPassword commandnssasetpassword; + + CommandNSSetSecure commandnssetsecure; + CommandNSSASetSecure commandnssasetsecure; + + CommandNSSASetNoexpire commandnssasetnoexpire; + + Serialize::Field<NickServ::Account, bool> autoop, keep_modes, killprotect, kill_quick, kill_immed, message, secure; + Serialize::Field<NickServ::Nick, bool> noexpire; + + /* email, passcode */ + ExtensibleItem<std::pair<Anope::string, Anope::string > > ns_set_email; + + EventHandlers<Event::SetNickOption> onsetnickoption; + + public: + NSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsset(this) + , commandnssaset(this) + , commandnssetautoop(this) + , commandnssasetautoop(this) + , commandnssetdisplay(this) + , commandnssasetdisplay(this) + , commandnssetemail(this) + , commandnssasetemail(this) + , commandnssetkeepmodes(this) + , commandnssasetkeepmodes(this) + , commandnssetkill(this) + , commandnssasetkill(this) + , commandnssetlanguage(this) + , commandnssasetlanguage(this) + , commandnssetmessage(this) + , commandnssasetmessage(this) + , commandnssetpassword(this) + , commandnssasetpassword(this) + , commandnssetsecure(this) + , commandnssasetsecure(this) + , commandnssasetnoexpire(this) + + , autoop(this, NickServ::account, "AUTOOP") + , keep_modes(this, NickServ::account, "NS_KEEP_MODES") + , killprotect(this, NickServ::account, "KILLPROTECT") + , kill_quick(this, NickServ::account, "KILL_QUICK") + , kill_immed(this, NickServ::account, "KILL_IMMED") + , message(this, NickServ::account, "MSG") + , secure(this, NickServ::account, "NS_SECURE") + , noexpire(this, NickServ::nick, "NS_NO_EXPIRE") + + , ns_set_email(this, "ns_set_email") + + , onsetnickoption(this) + { + + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> ¶ms) override + { + NickServ::Account *uac = source.nc; + + if (command->name == "nickserv/confirm" && !params.empty() && uac) + { + std::pair<Anope::string, Anope::string> *n = ns_set_email.Get(uac); + if (n) + { + if (params[0] == n->second) + { + uac->SetEmail(n->first); + Log(LOG_COMMAND, source, command) << "to confirm their email address change to " << uac->GetEmail(); + source.Reply(_("Your email address has been changed to \002%s\002."), uac->GetEmail().c_str()); + ns_set_email.Unset(uac); + return EVENT_STOP; + } + } + } + + return EVENT_CONTINUE; + } + + void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override + { + if (chan->ci) + { + /* Only give modes if autoop is set */ + give_modes &= !user->Account() || autoop.HasExt(user->Account()); + } + } + + void OnPreNickExpire(NickServ::Nick *na, bool &expire) override + { + if (noexpire.HasExt(na)) + expire = false; + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + if (!show_hidden) + return; + + if (kill_immed.HasExt(na->GetAccount())) + info.AddOption(_("Immediate protection")); + else if (kill_quick.HasExt(na->GetAccount())) + info.AddOption(_("Quick protection")); + else if (killprotect.HasExt(na->GetAccount())) + info.AddOption(_("Protection")); + if (secure.HasExt(na->GetAccount())) + info.AddOption(_("Security")); + if (message.HasExt(na->GetAccount())) + info.AddOption(_("Message mode")); + if (autoop.HasExt(na->GetAccount())) + info.AddOption(_("Auto-op")); + if (noexpire.HasExt(na)) + info.AddOption(_("No expire")); + if (keep_modes.HasExt(na->GetAccount())) + info.AddOption(_("Keep modes")); + } + + void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (u->Account() && setter.GetUser() == u && NickServ::mode) + { + NickServ::Mode *m = NickServ::mode.Create(); + m->SetAccount(u->Account()); + m->SetMode(mname); + } + } + + void OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (u->Account() && setter.GetUser() == u) + { + for (NickServ::Mode *m : u->Account()->GetRefs<NickServ::Mode *>(NickServ::mode)) + if (m->GetMode() == mname) + m->Delete(); + } + } + + void OnUserLogin(User *u) override + { + if (keep_modes.HasExt(u->Account())) + for (NickServ::Mode *mode : u->Account()->GetRefs<NickServ::Mode *>(NickServ::mode)) + { + UserMode *um = ModeManager::FindUserModeByName(mode->GetMode()); + /* if the null user can set the mode, then it's probably safe */ + if (um && um->CanSet(NULL)) + u->SetMode(NULL, mode->GetMode()); + } + } +}; + +MODULE_INIT(NSSet) diff --git a/modules/nickserv/set_misc.cpp b/modules/nickserv/set_misc.cpp new file mode 100644 index 000000000..5d03797b1 --- /dev/null +++ b/modules/nickserv/set_misc.cpp @@ -0,0 +1,225 @@ +/* + * + * (C) 2003-2014 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" +#include "modules/ns_set_misc.h" +#include "modules/ns_info.h" +#include "modules/ns_set.h" + +static Anope::map<Anope::string> descriptions; + +class NSMiscDataImpl : public NSMiscData +{ + public: + NSMiscDataImpl(Serialize::TypeBase *type) : NSMiscData(type) { } + NSMiscDataImpl(Serialize::TypeBase *type, Serialize::ID id) : NSMiscData(type, id) { } + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *s) override; + + Anope::string GetName() override; + void SetName(const Anope::string &n) override; + + Anope::string GetData() override; + void SetData(const Anope::string &d) override; +}; + +class NSMiscDataType : public Serialize::Type<NSMiscDataImpl> +{ + public: + Serialize::ObjectField<NSMiscDataImpl, NickServ::Account *> owner; + Serialize::Field<NSMiscDataImpl, Anope::string> name, data; + + NSMiscDataType(Module *me) : Serialize::Type<NSMiscDataImpl>(me, "NSMiscData") + , owner(this, "nc", true) + , name(this, "name") + , data(this, "data") + { + } +}; + +NickServ::Account *NSMiscDataImpl::GetAccount() +{ + return Get(&NSMiscDataType::owner); +} + +void NSMiscDataImpl::SetAccount(NickServ::Account *s) +{ + Set(&NSMiscDataType::owner, s); +} + +Anope::string NSMiscDataImpl::GetName() +{ + return Get(&NSMiscDataType::name); +} + +void NSMiscDataImpl::SetName(const Anope::string &n) +{ + Set(&NSMiscDataType::name, n); +} + +Anope::string NSMiscDataImpl::GetData() +{ + return Get(&NSMiscDataType::data); +} + +void NSMiscDataImpl::SetData(const Anope::string &d) +{ + Set(&NSMiscDataType::data, d); +} + +class CommandNSSetMisc : public Command +{ + Anope::string GetAttribute(const Anope::string &command) + { + size_t sp = command.rfind(' '); + if (sp != Anope::string::npos) + return command.substr(sp + 1); + return command; + } + + public: + CommandNSSetMisc(Module *creator, const Anope::string &cname = "nickserv/set/misc", size_t min = 0) : Command(creator, cname, min, min + 1) + { + this->SetSyntax(_("[\037parameter\037]")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + Anope::string scommand = GetAttribute(source.command); + + /* remove existing */ + for (NSMiscData *data : nc->GetRefs<NSMiscData *>(nsmiscdata)) + if (data->GetName() == scommand) + { + data->Delete(); + break; + } + + if (!param.empty()) + { + NSMiscData *data = nsmiscdata.Create(); + data->SetAccount(nc); + data->SetName(scommand); + data->SetData(param); + + source.Reply(_("\002{0}\002 for \002{1}\002 set to \002{2}\002."), scommand, nc->GetDisplay(), param); + } + else + { + source.Reply(_("\002{0}\002 for \002{1}\002 unset."), scommand, nc->GetDisplay()); + } + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), !params.empty() ? params[0] : ""); + } + + void OnServHelp(CommandSource &source) override + { + if (descriptions.count(source.command)) + { + this->SetDesc(descriptions[source.command]); + Command::OnServHelp(source); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (descriptions.count(source.command)) + { + source.Reply(Language::Translate(source.nc, descriptions[source.command].c_str())); + return true; + } + return false; + } +}; + +class CommandNSSASetMisc : public CommandNSSetMisc +{ + public: + CommandNSSASetMisc(Module *creator) : CommandNSSetMisc(creator, "nickserv/saset/misc", 1) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 [\037parameter\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + this->Run(source, params[0], params.size() > 1 ? params[1] : ""); + } +}; + +class NSSetMisc : public Module + , public EventHook<Event::NickInfo> +{ + CommandNSSetMisc commandnssetmisc; + CommandNSSASetMisc commandnssasetmisc; + NSMiscDataType type; + + public: + NSSetMisc(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnssetmisc(this) + , commandnssasetmisc(this) + , type(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + descriptions.clear(); + + for (int i = 0; i < conf->CountBlock("command"); ++i) + { + Configuration::Block *block = conf->GetBlock("command", i); + + const Anope::string &cmd = block->Get<Anope::string>("command"); + + if (cmd != "nickserv/set/misc" && cmd != "nickserv/saset/misc") + continue; + + Anope::string cname = block->Get<Anope::string>("name"); + Anope::string desc = block->Get<Anope::string>("misc_description"); + + if (cname.empty() || desc.empty()) + continue; + + descriptions[cname] = desc; + } + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool) override + { + for (NSMiscData *data : na->GetAccount()->GetRefs<NSMiscData *>(nsmiscdata)) + info[data->GetName()] = data->GetData(); + } +}; + +MODULE_INIT(NSSetMisc) diff --git a/modules/nickserv/status.cpp b/modules/nickserv/status.cpp new file mode 100644 index 000000000..7dc751d8b --- /dev/null +++ b/modules/nickserv/status.cpp @@ -0,0 +1,88 @@ +/* NickServ core functions + * + * (C) 2003-2014 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 CommandNSStatus : public Command +{ + public: + CommandNSStatus(Module *creator) : Command(creator, "nickserv/status", 0, 16) + { + this->SetDesc(_("Returns the owner status of the given nickname")); + this->SetSyntax(_("[\037nickname\037]")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &nick = !params.empty() ? params[0] : source.GetNick(); + const NickServ::Nick *na = NickServ::FindNick(nick); + spacesepstream sep(nick); + Anope::string nickbuf; + + while (sep.GetToken(nickbuf)) + { + #if 0 + User *u2 = User::Find(nickbuf, true); + if (!u2) /* Nick is not online */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 0, ""); + else if (u2->IsIdentified() && na && na->GetAccount() == u2->Account()) /* Nick is identified */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 3, u2->Account()->GetDisplay().c_str()); + else if (u2->IsRecognized()) /* Nick is recognised, but NOT identified */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 2, u2->Account() ? u2->Account()->GetDisplay().c_str() : ""); + else if (!na) /* Nick is online, but NOT a registered */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 0, ""); + else + /* Nick is not identified for the nick, but they could be logged into an account, + * so we tell the user about it + */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 1, u2->Account() ? u2->Account()->GetDisplay().c_str() : ""); + #endif + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Returns whether the user using the given nickname is\n" + "recognized as the owner of the nickname. The response has\n" + "this format:\n" + " \n" + " \037nickname\037 \037status-code\037 \037account\037\n" + " \n" + "where \037nickname\037 is the nickname sent with the command,\n" + "\037status-code\037 is one of the following, and \037account\037\n" + "is the account they are logged in as.\n" + " \n" + " 0 - no such user online \002or\002 nickname not registered\n" + " 1 - user not recognized as nickname's owner\n" + " 2 - user recognized as owner via access list only\n" + " 3 - user recognized as owner via password identification\n" + " \n" + "If no nickname is given, your status will be returned.")); + return true; + } +}; + +class NSStatus : public Module +{ + CommandNSStatus commandnsstatus; + + public: + NSStatus(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsstatus(this) + { + throw ModuleException("Remind Adam to fix this"); + } +}; + +MODULE_INIT(NSStatus) diff --git a/modules/nickserv/suspend.cpp b/modules/nickserv/suspend.cpp new file mode 100644 index 000000000..2859eacc1 --- /dev/null +++ b/modules/nickserv/suspend.cpp @@ -0,0 +1,339 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_suspend.h" +#include "modules/ns_info.h" +#include "modules/nickserv.h" + +class NSSuspendInfoImpl : public NSSuspendInfo +{ + public: + NSSuspendInfoImpl(Serialize::TypeBase *type) : NSSuspendInfo(type) { } + NSSuspendInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : NSSuspendInfo(type, id) { } + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *) override; + + Anope::string GetBy() override; + void SetBy(const Anope::string &by) override; + + Anope::string GetReason() override; + void SetReason(const Anope::string &reason) override; + + time_t GetWhen() override; + void SetWhen(const time_t &w) override; + + time_t GetExpires() override; + void SetExpires(const time_t &e) override; +}; + +class NSSuspendType : public Serialize::Type<NSSuspendInfoImpl> +{ + public: + Serialize::ObjectField<NSSuspendInfoImpl, NickServ::Account *> account; + Serialize::Field<NSSuspendInfoImpl, Anope::string> by, reason; + Serialize::Field<NSSuspendInfoImpl, time_t> when, expires; + + NSSuspendType(Module *me) : Serialize::Type<NSSuspendInfoImpl>(me, "NSSuspendInfo") + , account(this, "nick", true) + , by(this, "by") + , reason(this, "reason") + , when(this, "time") + , expires(this, "expires") + { + } +}; + +NickServ::Account *NSSuspendInfoImpl::GetAccount() +{ + return Get(&NSSuspendType::account); +} + +void NSSuspendInfoImpl::SetAccount(NickServ::Account *s) +{ + Set(&NSSuspendType::account, s); +} + +Anope::string NSSuspendInfoImpl::GetBy() +{ + return Get(&NSSuspendType::by); +} + +void NSSuspendInfoImpl::SetBy(const Anope::string &by) +{ + Set(&NSSuspendType::by, by); +} + +Anope::string NSSuspendInfoImpl::GetReason() +{ + return Get(&NSSuspendType::reason); +} + +void NSSuspendInfoImpl::SetReason(const Anope::string &reason) +{ + Set(&NSSuspendType::reason, reason); +} + +time_t NSSuspendInfoImpl::GetWhen() +{ + return Get(&NSSuspendType::when); +} + +void NSSuspendInfoImpl::SetWhen(const time_t &w) +{ + Set(&NSSuspendType::when, w); +} + +time_t NSSuspendInfoImpl::GetExpires() +{ + return Get(&NSSuspendType::expires); +} + +void NSSuspendInfoImpl::SetExpires(const time_t &e) +{ + Set(&NSSuspendType::expires, e); +} + +class CommandNSSuspend : public Command +{ + EventHandlers<Event::NickSuspend> &onnicksuspend; + + public: + CommandNSSuspend(Module *creator, EventHandlers<Event::NickSuspend> &event) : Command(creator, "nickserv/suspend", 2, 3), onnicksuspend(event) + { + this->SetDesc(_("Suspend a given nick")); + this->SetSyntax(_("\037account\037 [+\037expiry\037] [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + + const Anope::string &nick = params[0]; + Anope::string expiry = params[1]; + Anope::string reason = params.size() > 2 ? params[2] : ""; + time_t expiry_secs = Config->GetModule(this->owner)->Get<time_t>("suspendexpire"); + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (expiry[0] != '+') + { + reason = expiry + " " + reason; + reason.trim(); + expiry.clear(); + } + else + { + expiry_secs = Anope::DoTime(expiry); + if (expiry_secs == -1) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + } + + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Config->GetModule("nickserv")->Get<bool>("secureadmins", "yes") && na->GetAccount()->IsServicesOper()) + { + source.Reply(_("You may not suspend other Services Operators' nicknames.")); + return; + } + + NSSuspendInfo *si = na->GetAccount()->GetRef<NSSuspendInfo *>(nssuspendinfo); + if (!si) + { + source.Reply(_("\002%s\002 is already suspended."), na->GetAccount()->GetDisplay().c_str()); + return; + } + + NickServ::Account *nc = na->GetAccount(); + + si = nssuspendinfo.Create(); + si->SetAccount(nc); + si->SetBy(source.GetNick()); + si->SetReason(reason); + si->SetWhen(Anope::CurTime); + si->SetExpires(expiry_secs ? expiry_secs + Anope::CurTime : 0); + + for (NickServ::Nick *na2 : nc->GetRefs<NickServ::Nick *>(NickServ::nick)) + { + na2->SetLastQuit(reason); + + User *u2 = User::Find(na2->GetNick(), true); + if (u2) + { + u2->Logout(); + if (NickServ::service) + NickServ::service->Collide(u2, na2); + } + } + + Log(LOG_ADMIN, source, this) << "for " << nick << " (" << (!reason.empty() ? reason : "No reason") << "), expires on " << (expiry_secs ? Anope::strftime(Anope::CurTime + expiry_secs) : "never"); + source.Reply(_("\002{0}\002 is now suspended."), na->GetNick()); + + this->onnicksuspend(&Event::NickSuspend::OnNickSuspend, na); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Suspends \037account\037, which prevents it from being used while keeping all the data for it." + " If an expiry is given the account will be unsuspended after that period of time, otherwise the default expiry from the configuration is used.")); // XXX + return true; + } +}; + +class CommandNSUnSuspend : public Command +{ + EventHandlers<Event::NickUnsuspend> &onnickunsuspend; + + public: + CommandNSUnSuspend(Module *creator, EventHandlers<Event::NickUnsuspend> &event) : Command(creator, "nickserv/unsuspend", 1, 1), onnickunsuspend(event) + { + this->SetDesc(_("Unsuspend a given nick")); + this->SetSyntax(_("\037account\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &nick = params[0]; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + NSSuspendInfo *si = na->GetAccount()->GetRef<NSSuspendInfo *>(nssuspendinfo); + if (!si) + { + source.Reply(_("\002{0}\002 is not suspended."), na->GetNick()); + return; + } + + Log(LOG_ADMIN, source, this) << "for " << na->GetNick() << " which was suspended by " << (!si->GetBy().empty() ? si->GetBy() : "(none)") << " for: " << (!si->GetReason().empty() ? si->GetReason() : "No reason"); + + si->Delete(); + + source.Reply(_("\002{0}\002 is now released."), na->GetNick()); + + this->onnickunsuspend(&Event::NickUnsuspend::OnNickUnsuspend, na); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Unsuspends \037account\037, which allows it to be used again.")); + return true; + } +}; + +class NSSuspend : public Module + , public EventHook<Event::NickInfo> + , public EventHook<NickServ::Event::PreNickExpire> + , public EventHook<NickServ::Event::NickValidate> +{ + CommandNSSuspend commandnssuspend; + CommandNSUnSuspend commandnsunsuspend; + EventHandlers<Event::NickSuspend> onnicksuspend; + EventHandlers<Event::NickUnsuspend> onnickunsuspend; + std::vector<Anope::string> show; + NSSuspendType nst; + + struct trim + { + Anope::string operator()(Anope::string s) const + { + return s.trim(); + } + }; + + bool Show(CommandSource &source, const Anope::string &what) const + { + return source.IsOper() || std::find(show.begin(), show.end(), what) != show.end(); + } + + public: + NSSuspend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnssuspend(this, onnicksuspend) + , commandnsunsuspend(this, onnickunsuspend) + , onnicksuspend(this) + , onnickunsuspend(this) + , nst(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + Anope::string s = conf->GetModule(this)->Get<Anope::string>("show"); + commasepstream(s).GetTokens(show); + std::transform(show.begin(), show.end(), show.begin(), trim()); + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + NSSuspendInfo *s = na->GetAccount()->GetRef<NSSuspendInfo *>(nssuspendinfo); + if (!s) + return; + + if (show_hidden || Show(source, "suspended")) + info[_("Suspended")] = _("This nickname is \002suspended\002."); + if (!s->GetBy().empty() && (show_hidden || Show(source, "by"))) + info[_("Suspended by")] = s->GetBy(); + if (!s->GetReason().empty() && (show_hidden || Show(source, "reason"))) + info[_("Suspend reason")] = s->GetReason(); + if (s->GetWhen() && (show_hidden || Show(source, "on"))) + info[_("Suspended on")] = Anope::strftime(s->GetWhen(), source.GetAccount()); + if (s->GetExpires() && (show_hidden || Show(source, "expires"))) + info[_("Suspension expires")] = Anope::strftime(s->GetExpires(), source.GetAccount()); + } + + void OnPreNickExpire(NickServ::Nick *na, bool &expire) override + { + NSSuspendInfo *s = na->GetAccount()->GetRef<NSSuspendInfo *>(nssuspendinfo); + if (!s) + return; + + expire = false; + + if (!s->GetExpires()) + return; + + if (s->GetExpires() < Anope::CurTime) + { + na->SetLastSeen(Anope::CurTime); + s->Delete(); + + Log(LOG_NORMAL, "nickserv/expire", Config->GetClient("NickServ")) << "Expiring suspend for " << na->GetNick(); + } + } + + EventReturn OnNickValidate(User *u, NickServ::Nick *na) override + { + NSSuspendInfo *s = na->GetAccount()->GetRef<NSSuspendInfo *>(nssuspendinfo); + if (!s) + return EVENT_CONTINUE; + + u->SendMessage(Config->GetClient("NickServ"), _("\002{0}\002 is suspended."), u->nick); + return EVENT_STOP; + } +}; + +MODULE_INIT(NSSuspend) diff --git a/modules/nickserv/update.cpp b/modules/nickserv/update.cpp new file mode 100644 index 000000000..0022f389d --- /dev/null +++ b/modules/nickserv/update.cpp @@ -0,0 +1,67 @@ +/* NickServ core functions + * + * (C) 2003-2014 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" +#include "modules/ns_update.h" + +class CommandNSUpdate : public Command +{ + EventHandlers<Event::NickUpdate> &onnickupdate; + + public: + CommandNSUpdate(Module *creator, EventHandlers<Event::NickUpdate> &event) : Command(creator, "nickserv/update", 0, 0), onnickupdate(event) + { + this->SetDesc(_("Updates your current status, i.e. it checks for new memos")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(u->nick); + + if (na && na->GetAccount() == source.GetAccount()) + { + na->SetLastRealname(u->realname); + na->SetLastSeen(Anope::CurTime); + } + + this->onnickupdate(&Event::NickUpdate::OnNickUpdate, u); + + source.Reply(_("Status updated (memos, vhost, chmodes, flags).")); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Updates your current status, i.e. it checks for new memos,\n" + "sets needed channel modes and updates your vhost and\n" + "your userflags (lastseentime, etc).")); + return true; + } +}; + +class NSUpdate : public Module +{ + CommandNSUpdate commandnsupdate; + EventHandlers<Event::NickUpdate> onnickupdate; + + public: + NSUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsupdate(this, onnickupdate) + , onnickupdate(this) + { + + } +}; + +MODULE_INIT(NSUpdate) |
