summaryrefslogtreecommitdiff
path: root/modules/nickserv
diff options
context:
space:
mode:
Diffstat (limited to 'modules/nickserv')
-rw-r--r--modules/nickserv/CMakeLists.txt1
-rw-r--r--modules/nickserv/access.cpp257
-rw-r--r--modules/nickserv/ajoin.cpp381
-rw-r--r--modules/nickserv/alist.cpp133
-rw-r--r--modules/nickserv/cert.cpp364
-rw-r--r--modules/nickserv/drop.cpp91
-rw-r--r--modules/nickserv/getemail.cpp66
-rw-r--r--modules/nickserv/getpass.cpp74
-rw-r--r--modules/nickserv/group.cpp385
-rw-r--r--modules/nickserv/identify.cpp125
-rw-r--r--modules/nickserv/info.cpp281
-rw-r--r--modules/nickserv/list.cpp290
-rw-r--r--modules/nickserv/logout.cpp100
-rw-r--r--modules/nickserv/main/CMakeLists.txt1
-rw-r--r--modules/nickserv/main/account.cpp129
-rw-r--r--modules/nickserv/main/account.h35
-rw-r--r--modules/nickserv/main/accounttype.cpp42
-rw-r--r--modules/nickserv/main/accounttype.h23
-rw-r--r--modules/nickserv/main/identifyrequest.cpp51
-rw-r--r--modules/nickserv/main/identifyrequest.h13
-rw-r--r--modules/nickserv/main/mode.cpp23
-rw-r--r--modules/nickserv/main/mode.h14
-rw-r--r--modules/nickserv/main/modetype.h14
-rw-r--r--modules/nickserv/main/nick.cpp192
-rw-r--r--modules/nickserv/main/nick.h46
-rw-r--r--modules/nickserv/main/nickserv.cpp686
-rw-r--r--modules/nickserv/main/nicktype.cpp45
-rw-r--r--modules/nickserv/main/nicktype.h32
-rw-r--r--modules/nickserv/maxemail.cpp80
-rw-r--r--modules/nickserv/recover.cpp262
-rw-r--r--modules/nickserv/register.cpp426
-rw-r--r--modules/nickserv/resetpass.cpp141
-rw-r--r--modules/nickserv/set.cpp1244
-rw-r--r--modules/nickserv/set_misc.cpp225
-rw-r--r--modules/nickserv/status.cpp88
-rw-r--r--modules/nickserv/suspend.cpp339
-rw-r--r--modules/nickserv/update.cpp67
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> &params) 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> &params) 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> &params) 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
+ {
+ fo