From 6302f980fe6dad83aab7d2dc1046dadea5ffd939 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 14 Dec 2015 16:52:24 -0500 Subject: New source tree structure for modules. From commands/cs_access => chanserv/access etc. --- modules/nickserv/CMakeLists.txt | 1 + modules/nickserv/access.cpp | 257 ++++++ modules/nickserv/ajoin.cpp | 381 +++++++++ modules/nickserv/alist.cpp | 133 +++ modules/nickserv/cert.cpp | 364 +++++++++ modules/nickserv/drop.cpp | 91 +++ modules/nickserv/getemail.cpp | 66 ++ modules/nickserv/getpass.cpp | 74 ++ modules/nickserv/group.cpp | 385 +++++++++ modules/nickserv/identify.cpp | 125 +++ modules/nickserv/info.cpp | 281 +++++++ modules/nickserv/list.cpp | 290 +++++++ modules/nickserv/logout.cpp | 100 +++ modules/nickserv/main/CMakeLists.txt | 1 + modules/nickserv/main/account.cpp | 129 +++ modules/nickserv/main/account.h | 35 + modules/nickserv/main/accounttype.cpp | 42 + modules/nickserv/main/accounttype.h | 23 + modules/nickserv/main/identifyrequest.cpp | 51 ++ modules/nickserv/main/identifyrequest.h | 13 + modules/nickserv/main/mode.cpp | 23 + modules/nickserv/main/mode.h | 14 + modules/nickserv/main/modetype.h | 14 + modules/nickserv/main/nick.cpp | 192 +++++ modules/nickserv/main/nick.h | 46 ++ modules/nickserv/main/nickserv.cpp | 686 ++++++++++++++++ modules/nickserv/main/nicktype.cpp | 45 ++ modules/nickserv/main/nicktype.h | 32 + modules/nickserv/maxemail.cpp | 80 ++ modules/nickserv/recover.cpp | 262 ++++++ modules/nickserv/register.cpp | 426 ++++++++++ modules/nickserv/resetpass.cpp | 141 ++++ modules/nickserv/set.cpp | 1244 +++++++++++++++++++++++++++++ modules/nickserv/set_misc.cpp | 225 ++++++ modules/nickserv/status.cpp | 88 ++ modules/nickserv/suspend.cpp | 339 ++++++++ modules/nickserv/update.cpp | 67 ++ 37 files changed, 6766 insertions(+) create mode 100644 modules/nickserv/CMakeLists.txt create mode 100644 modules/nickserv/access.cpp create mode 100644 modules/nickserv/ajoin.cpp create mode 100644 modules/nickserv/alist.cpp create mode 100644 modules/nickserv/cert.cpp create mode 100644 modules/nickserv/drop.cpp create mode 100644 modules/nickserv/getemail.cpp create mode 100644 modules/nickserv/getpass.cpp create mode 100644 modules/nickserv/group.cpp create mode 100644 modules/nickserv/identify.cpp create mode 100644 modules/nickserv/info.cpp create mode 100644 modules/nickserv/list.cpp create mode 100644 modules/nickserv/logout.cpp create mode 100644 modules/nickserv/main/CMakeLists.txt create mode 100644 modules/nickserv/main/account.cpp create mode 100644 modules/nickserv/main/account.h create mode 100644 modules/nickserv/main/accounttype.cpp create mode 100644 modules/nickserv/main/accounttype.h create mode 100644 modules/nickserv/main/identifyrequest.cpp create mode 100644 modules/nickserv/main/identifyrequest.h create mode 100644 modules/nickserv/main/mode.cpp create mode 100644 modules/nickserv/main/mode.h create mode 100644 modules/nickserv/main/modetype.h create mode 100644 modules/nickserv/main/nick.cpp create mode 100644 modules/nickserv/main/nick.h create mode 100644 modules/nickserv/main/nickserv.cpp create mode 100644 modules/nickserv/main/nicktype.cpp create mode 100644 modules/nickserv/main/nicktype.h create mode 100644 modules/nickserv/maxemail.cpp create mode 100644 modules/nickserv/recover.cpp create mode 100644 modules/nickserv/register.cpp create mode 100644 modules/nickserv/resetpass.cpp create mode 100644 modules/nickserv/set.cpp create mode 100644 modules/nickserv/set_misc.cpp create mode 100644 modules/nickserv/status.cpp create mode 100644 modules/nickserv/suspend.cpp create mode 100644 modules/nickserv/update.cpp (limited to 'modules/nickserv') 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 +{ + public: + Serialize::ObjectField account; + Serialize::Field mask; + + NickAccessType(Module *creator) : Serialize::Type(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 access = nc->GetRefs(nsaccess); + + if (access.size() >= Config->GetModule(this->owner)->Get("accessmax", "32")) + { + source.Reply(_("Sorry, the maximum of \002{0}\002 access entries has been reached."), Config->GetModule(this->owner)->Get("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(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 access = nc->GetRefs(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 ¶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("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 +{ + 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("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 +{ + public: + Serialize::ObjectField owner; + Serialize::Field channel, key; + + AutoJoinType(Module *me) : Serialize::Type(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 channels = nc->GetRefs(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 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 channels = nc->GetRefs(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("ajoinmax")) + { + source.Reply(_("Sorry, the maximum of \002{0}\002 auto join entries has been reached."), Config->GetModule(this->owner)->Get("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 channels = nc->GetRefs(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 ¶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 +{ + 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 channels = u->Account()->GetRefs(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(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 ¶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 chans = nc->GetRefs(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 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 certmap; +static EventHandlers *events; + +class CertServiceImpl : public CertService +{ + public: + CertServiceImpl(Module *o) : CertService(o) { } + + NickServ::Account* FindAccountFromCert(const Anope::string &cert) override + { + Anope::hash_map::iterator it = certmap.find(cert); + if (it != certmap.end()) + return it->second; + return NULL; + } + + bool Matches(User *u, NickServ::Account *nc) override + { + std::vector cl = nc->GetRefs(certentry); + return !u->fingerprint.empty() && FindCert(cl, u->fingerprint); + } + + NSCertEntry *FindCert(const std::vector &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 +{ + public: + struct Account : Serialize::ObjectField + { + using Serialize::ObjectField::ObjectField; + + void SetField(NSCertEntryImpl *s, NickServ::Account *acc) override + { + const Anope::string &cert = s->GetCert(); + if (!cert.empty()) + certmap.erase(cert); + + Serialize::ObjectField::SetField(s, acc); + + if (!cert.empty() && s->GetAccount()) + certmap[cert] = acc; + } + } nc; + + struct Mask : Serialize::Field + { + using Serialize::Field::Field; + + void SetField(NSCertEntryImpl *s, const Anope::string &m) override + { + const Anope::string &old = GetField(s); + if (!old.empty()) + certmap.erase(old); + + Serialize::Field::SetField(s, m); + + if (!m.empty() && s->GetAccount()) + certmap[m] = s->GetAccount(); + } + } mask; + + NSCertEntryType(Module *me) : Serialize::Type(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(&NSCertEntryType::nc); +} + +void NSCertEntryImpl::SetAccount(NickServ::Account *nc) +{ + Set(&NSCertEntryType::nc, nc); +} + +Anope::string NSCertEntryImpl::GetCert() +{ + return Get(&NSCertEntryType::mask); +} + +void NSCertEntryImpl::SetCert(const Anope::string &mask) +{ + Set(&NSCertEntryType::mask, mask); +} + +class CommandNSCert : public Command +{ + NSCertEntry *FindCert(const std::vector &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 cl = nc->GetRefs(certentry); + unsigned max = Config->GetModule(this->owner)->Get("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 cl = nc->GetRefs(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 cl = nc->GetRefs(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 ¶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("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 \n" + " Removes \"\" from your certificate list.")); + return true; + } +}; + +class NSCert : public Module + , public EventHook + , public EventHook +{ + CommandNSCert commandnscert; + CertServiceImpl cs; + + EventHandlers 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 &onnickdrop; + + public: + CommandNSDrop(Module *creator, EventHandlers &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 ¶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("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 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 ¶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 ¶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("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 &onnickgroup; + CommandSource source; + Command *cmd; + Anope::string nick; + Reference target; + + public: + NSGroupRequestListener(EventHandlers &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 &onnickgroup; + + public: + CommandNSGroup(Module *creator, EventHandlers &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 ¶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("restrictopernicks")) + for (Oper *o : Serialize::GetObjects(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("guestnickprefix", "Guest"); + time_t reg_delay = Config->GetModule("nickserv")->Get("regdelay"); + unsigned maxaliases = Config->GetModule(this->owner)->Get("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("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("nogroupchange")) + { + source.Reply(_("You are already registered.")); + return; + } + + if (maxaliases && target->GetAccount()->GetRefs(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 ¶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).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::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 ¶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("expire", "21d"), + unconfirmed_expire = Config->GetModule("nickserv")->Get("unconfirmedexpire", "1d"); + for (NickServ::Nick *na2 : nc->GetRefs(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 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 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("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 ¶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("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 &onnickinfo; + + public: + CommandNSInfo(Module *creator, EventHandlers &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 ¶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