summaryrefslogtreecommitdiff
path: root/src/coremods
diff options
context:
space:
mode:
Diffstat (limited to 'src/coremods')
-rw-r--r--src/coremods/core_channel/cmd_invite.cpp179
-rw-r--r--src/coremods/core_channel/cmd_join.cpp60
-rw-r--r--src/coremods/core_channel/cmd_kick.cpp133
-rw-r--r--src/coremods/core_channel/cmd_names.cpp114
-rw-r--r--src/coremods/core_channel/cmd_topic.cpp102
-rw-r--r--src/coremods/core_channel/cmode_k.cpp89
-rw-r--r--src/coremods/core_channel/cmode_l.cpp50
-rw-r--r--src/coremods/core_channel/core_channel.cpp281
-rw-r--r--src/coremods/core_channel/core_channel.h219
-rw-r--r--src/coremods/core_channel/invite.cpp208
-rw-r--r--src/coremods/core_channel/invite.h127
-rw-r--r--src/coremods/core_dns.cpp850
-rw-r--r--src/coremods/core_hostname_lookup.cpp232
-rw-r--r--src/coremods/core_info/cmd_admin.cpp43
-rw-r--r--src/coremods/core_info/cmd_commands.cpp59
-rw-r--r--src/coremods/core_info/cmd_info.cpp91
-rw-r--r--src/coremods/core_info/cmd_modules.cpp89
-rw-r--r--src/coremods/core_info/cmd_motd.cpp63
-rw-r--r--src/coremods/core_info/cmd_time.cpp38
-rw-r--r--src/coremods/core_info/cmd_version.cpp40
-rw-r--r--src/coremods/core_info/core_info.cpp61
-rw-r--r--src/coremods/core_info/core_info.h169
-rw-r--r--src/coremods/core_ison.cpp81
-rw-r--r--src/coremods/core_list.cpp215
-rw-r--r--src/coremods/core_loadmodule.cpp125
-rw-r--r--src/coremods/core_lusers.cpp173
-rw-r--r--src/coremods/core_oper/cmd_die.cpp81
-rw-r--r--src/coremods/core_oper/cmd_kill.cpp145
-rw-r--r--src/coremods/core_oper/cmd_oper.cpp72
-rw-r--r--src/coremods/core_oper/cmd_rehash.cpp94
-rw-r--r--src/coremods/core_oper/cmd_restart.cpp64
-rw-r--r--src/coremods/core_oper/core_oper.cpp55
-rw-r--r--src/coremods/core_oper/core_oper.h129
-rw-r--r--src/coremods/core_privmsg.cpp297
-rw-r--r--src/coremods/core_reloadmodule.cpp635
-rw-r--r--src/coremods/core_stats.cpp400
-rw-r--r--src/coremods/core_stub.cpp156
-rw-r--r--src/coremods/core_user/cmd_away.cpp65
-rw-r--r--src/coremods/core_user/cmd_mode.cpp177
-rw-r--r--src/coremods/core_user/cmd_nick.cpp98
-rw-r--r--src/coremods/core_user/cmd_part.cpp65
-rw-r--r--src/coremods/core_user/cmd_quit.cpp51
-rw-r--r--src/coremods/core_user/cmd_user.cpp79
-rw-r--r--src/coremods/core_user/core_user.cpp183
-rw-r--r--src/coremods/core_user/core_user.h222
-rw-r--r--src/coremods/core_user/umode_o.cpp51
-rw-r--r--src/coremods/core_user/umode_s.cpp145
-rw-r--r--src/coremods/core_userhost.cpp85
-rw-r--r--src/coremods/core_wallops.cpp69
-rw-r--r--src/coremods/core_who.cpp591
-rw-r--r--src/coremods/core_whois.cpp370
-rw-r--r--src/coremods/core_whowas.cpp310
-rw-r--r--src/coremods/core_xline/cmd_eline.cpp96
-rw-r--r--src/coremods/core_xline/cmd_gline.cpp105
-rw-r--r--src/coremods/core_xline/cmd_kline.cpp104
-rw-r--r--src/coremods/core_xline/cmd_qline.cpp89
-rw-r--r--src/coremods/core_xline/cmd_zline.cpp107
-rw-r--r--src/coremods/core_xline/core_xline.cpp93
-rw-r--r--src/coremods/core_xline/core_xline.h163
59 files changed, 9337 insertions, 0 deletions
diff --git a/src/coremods/core_channel/cmd_invite.cpp b/src/coremods/core_channel/cmd_invite.cpp
new file mode 100644
index 000000000..e9ce03a0c
--- /dev/null
+++ b/src/coremods/core_channel/cmd_invite.cpp
@@ -0,0 +1,179 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_channel.h"
+#include "invite.h"
+
+CommandInvite::CommandInvite(Module* parent, Invite::APIImpl& invapiimpl)
+ : Command(parent, "INVITE", 0, 0)
+ , invapi(invapiimpl)
+{
+ Penalty = 4;
+ syntax = "[<nick> <channel>]";
+}
+
+/** Handle /INVITE
+ */
+CmdResult CommandInvite::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ ModResult MOD_RESULT;
+
+ if (parameters.size() >= 2)
+ {
+ User* u;
+ if (IS_LOCAL(user))
+ u = ServerInstance->FindNickOnly(parameters[0]);
+ else
+ u = ServerInstance->FindNick(parameters[0]);
+
+ Channel* c = ServerInstance->FindChan(parameters[1]);
+ time_t timeout = 0;
+ if (parameters.size() >= 3)
+ {
+ if (IS_LOCAL(user))
+ timeout = ServerInstance->Time() + InspIRCd::Duration(parameters[2]);
+ else if (parameters.size() > 3)
+ timeout = ConvToInt(parameters[3]);
+ }
+
+ if (!c)
+ {
+ user->WriteNumeric(Numerics::NoSuchChannel(parameters[1]));
+ return CMD_FAILURE;
+ }
+ if ((!u) || (u->registered != REG_ALL))
+ {
+ user->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
+ return CMD_FAILURE;
+ }
+
+ // Verify channel timestamp if the INVITE is coming from a remote server
+ if (!IS_LOCAL(user))
+ {
+ // Remote INVITE commands must carry a channel timestamp
+ if (parameters.size() < 3)
+ return CMD_INVALID;
+
+ // Drop the invite if our channel TS is lower
+ time_t RemoteTS = ConvToInt(parameters[2]);
+ if (c->age < RemoteTS)
+ return CMD_FAILURE;
+ }
+
+ if ((IS_LOCAL(user)) && (!c->HasUser(user)))
+ {
+ user->WriteNumeric(ERR_NOTONCHANNEL, c->name, "You're not on that channel!");
+ return CMD_FAILURE;
+ }
+
+ if (c->HasUser(u))
+ {
+ user->WriteNumeric(ERR_USERONCHANNEL, u->nick, c->name, "is already on channel");
+ return CMD_FAILURE;
+ }
+
+ FIRST_MOD_RESULT(OnUserPreInvite, MOD_RESULT, (user,u,c,timeout));
+
+ if (MOD_RESULT == MOD_RES_DENY)
+ {
+ return CMD_FAILURE;
+ }
+ else if (MOD_RESULT == MOD_RES_PASSTHRU)
+ {
+ if (IS_LOCAL(user))
+ {
+ unsigned int rank = c->GetPrefixValue(user);
+ if (rank < HALFOP_VALUE)
+ {
+ // Check whether halfop mode is available and phrase error message accordingly
+ ModeHandler* mh = ServerInstance->Modes->FindMode('h', MODETYPE_CHANNEL);
+ user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, InspIRCd::Format("You must be a channel %soperator",
+ (mh && mh->name == "halfop" ? "half-" : "")));
+ return CMD_FAILURE;
+ }
+ }
+ }
+
+ if (IS_LOCAL(u))
+ {
+ invapi.Create(IS_LOCAL(u), c, timeout);
+ u->WriteFrom(user,"INVITE %s :%s",u->nick.c_str(),c->name.c_str());
+ }
+
+ if (IS_LOCAL(user))
+ {
+ user->WriteNumeric(RPL_INVITING, u->nick, c->name);
+ if (u->IsAway())
+ user->WriteNumeric(RPL_AWAY, u->nick, u->awaymsg);
+ }
+
+ char prefix = 0;
+ unsigned int minrank = 0;
+ switch (announceinvites)
+ {
+ case Invite::ANNOUNCE_OPS:
+ {
+ prefix = '@';
+ minrank = OP_VALUE;
+ break;
+ }
+ case Invite::ANNOUNCE_DYNAMIC:
+ {
+ PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h');
+ if ((mh) && (mh->name == "halfop"))
+ {
+ prefix = mh->GetPrefix();
+ minrank = mh->GetPrefixRank();
+ }
+ break;
+ }
+ default:
+ {
+ }
+ }
+
+ CUList excepts;
+ FOREACH_MOD(OnUserInvite, (user, u, c, timeout, minrank, excepts));
+
+ if (announceinvites != Invite::ANNOUNCE_NONE)
+ c->WriteAllExcept(user, true, prefix, excepts, "NOTICE %s :*** %s invited %s into the channel", c->name.c_str(), user->nick.c_str(), u->nick.c_str());
+ }
+ else if (IS_LOCAL(user))
+ {
+ // pinched from ircu - invite with not enough parameters shows channels
+ // youve been invited to but haven't joined yet.
+ const Invite::List* list = invapi.GetList(IS_LOCAL(user));
+ if (list)
+ {
+ for (Invite::List::const_iterator i = list->begin(); i != list->end(); ++i)
+ user->WriteNumeric(RPL_INVITELIST, (*i)->chan->name);
+ }
+ user->WriteNumeric(RPL_ENDOFINVITELIST, "End of INVITE list");
+ }
+ return CMD_SUCCESS;
+}
+
+RouteDescriptor CommandInvite::GetRouting(User* user, const std::vector<std::string>& parameters)
+{
+ return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST);
+}
diff --git a/src/coremods/core_channel/cmd_join.cpp b/src/coremods/core_channel/cmd_join.cpp
new file mode 100644
index 000000000..a60f1b2c6
--- /dev/null
+++ b/src/coremods/core_channel/cmd_join.cpp
@@ -0,0 +1,60 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_channel.h"
+
+CommandJoin::CommandJoin(Module* parent)
+ : SplitCommand(parent, "JOIN", 1, 2)
+{
+ syntax = "<channel>{,<channel>} {<key>{,<key>}}";
+ Penalty = 2;
+}
+
+/** Handle /JOIN
+ */
+CmdResult CommandJoin::HandleLocal(const std::vector<std::string>& parameters, LocalUser *user)
+{
+ if (parameters.size() > 1)
+ {
+ if (CommandParser::LoopCall(user, this, parameters, 0, 1, false))
+ return CMD_SUCCESS;
+
+ if (ServerInstance->IsChannel(parameters[0]))
+ {
+ Channel::JoinUser(user, parameters[0], false, parameters[1]);
+ return CMD_SUCCESS;
+ }
+ }
+ else
+ {
+ if (CommandParser::LoopCall(user, this, parameters, 0, -1, false))
+ return CMD_SUCCESS;
+
+ if (ServerInstance->IsChannel(parameters[0]))
+ {
+ Channel::JoinUser(user, parameters[0]);
+ return CMD_SUCCESS;
+ }
+ }
+
+ user->WriteNumeric(ERR_BADCHANMASK, parameters[0], "Invalid channel name");
+ return CMD_FAILURE;
+}
diff --git a/src/coremods/core_channel/cmd_kick.cpp b/src/coremods/core_channel/cmd_kick.cpp
new file mode 100644
index 000000000..05e279751
--- /dev/null
+++ b/src/coremods/core_channel/cmd_kick.cpp
@@ -0,0 +1,133 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_channel.h"
+
+CommandKick::CommandKick(Module* parent)
+ : Command(parent, "KICK", 2, 3)
+{
+ syntax = "<channel> <nick>{,<nick>} [<reason>]";
+}
+
+/** Handle /KICK
+ */
+CmdResult CommandKick::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ Channel* c = ServerInstance->FindChan(parameters[0]);
+ User* u;
+
+ if (CommandParser::LoopCall(user, this, parameters, 1))
+ return CMD_SUCCESS;
+
+ if (IS_LOCAL(user))
+ u = ServerInstance->FindNickOnly(parameters[1]);
+ else
+ u = ServerInstance->FindNick(parameters[1]);
+
+ if (!c)
+ {
+ user->WriteNumeric(Numerics::NoSuchChannel(parameters[0]));
+ return CMD_FAILURE;
+ }
+ if ((!u) || (u->registered != REG_ALL))
+ {
+ user->WriteNumeric(Numerics::NoSuchNick(parameters[1]));
+ return CMD_FAILURE;
+ }
+
+ Membership* srcmemb = NULL;
+ if (IS_LOCAL(user))
+ {
+ srcmemb = c->GetUser(user);
+ if (!srcmemb)
+ {
+ user->WriteNumeric(ERR_NOTONCHANNEL, parameters[0], "You're not on that channel!");
+ return CMD_FAILURE;
+ }
+
+ if (u->server->IsULine())
+ {
+ user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, "You may not kick a u-lined client");
+ return CMD_FAILURE;
+ }
+ }
+
+ const Channel::MemberMap::iterator victimiter = c->userlist.find(u);
+ if (victimiter == c->userlist.end())
+ {
+ user->WriteNumeric(ERR_USERNOTINCHANNEL, u->nick, c->name, "They are not on that channel");
+ return CMD_FAILURE;
+ }
+ Membership* const memb = victimiter->second;
+
+ // KICKs coming from servers can carry a membership id
+ if ((!IS_LOCAL(user)) && (parameters.size() > 3))
+ {
+ // If the current membership id is not equal to the one in the message then the user rejoined
+ if (memb->id != Membership::IdFromString(parameters[2]))
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Dropped KICK due to membership id mismatch: " + ConvToStr(memb->id) + " != " + parameters[2]);
+ return CMD_FAILURE;
+ }
+ }
+
+ const bool has_reason = (parameters.size() > 2);
+ const std::string reason((has_reason ? parameters.back() : user->nick), 0, ServerInstance->Config->Limits.MaxKick);
+
+ // Do the following checks only if the KICK is done by a local user;
+ // each server enforces its own rules.
+ if (srcmemb)
+ {
+ // Modules are allowed to explicitly allow or deny kicks done by local users
+ ModResult res;
+ FIRST_MOD_RESULT(OnUserPreKick, res, (user, memb, reason));
+ if (res == MOD_RES_DENY)
+ return CMD_FAILURE;
+
+ if (res == MOD_RES_PASSTHRU)
+ {
+ unsigned int them = srcmemb->getRank();
+ unsigned int req = HALFOP_VALUE;
+ for (std::string::size_type i = 0; i < memb->modes.length(); i++)
+ {
+ ModeHandler* mh = ServerInstance->Modes->FindMode(memb->modes[i], MODETYPE_CHANNEL);
+ if (mh && mh->GetLevelRequired(true) > req)
+ req = mh->GetLevelRequired(true);
+ }
+
+ if (them < req)
+ {
+ user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, InspIRCd::Format("You must be a channel %soperator",
+ req > HALFOP_VALUE ? "" : "half-"));
+ return CMD_FAILURE;
+ }
+ }
+ }
+
+ c->KickUser(user, victimiter, reason);
+
+ return CMD_SUCCESS;
+}
+
+RouteDescriptor CommandKick::GetRouting(User* user, const std::vector<std::string>& parameters)
+{
+ return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST);
+}
diff --git a/src/coremods/core_channel/cmd_names.cpp b/src/coremods/core_channel/cmd_names.cpp
new file mode 100644
index 000000000..92f0810de
--- /dev/null
+++ b/src/coremods/core_channel/cmd_names.cpp
@@ -0,0 +1,114 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_channel.h"
+
+CommandNames::CommandNames(Module* parent)
+ : SplitCommand(parent, "NAMES", 0, 0)
+ , secretmode(parent, "secret")
+ , privatemode(parent, "private")
+ , invisiblemode(parent, "invisible")
+{
+ syntax = "{<channel>{,<channel>}}";
+}
+
+/** Handle /NAMES
+ */
+CmdResult CommandNames::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
+{
+ Channel* c;
+
+ if (parameters.empty())
+ {
+ user->WriteNumeric(RPL_ENDOFNAMES, '*', "End of /NAMES list.");
+ return CMD_SUCCESS;
+ }
+
+ if (CommandParser::LoopCall(user, this, parameters, 0))
+ return CMD_SUCCESS;
+
+ c = ServerInstance->FindChan(parameters[0]);
+ if (c)
+ {
+ // Show the NAMES list if one of the following is true:
+ // - the channel is not secret
+ // - the user doing the /NAMES is inside the channel
+ // - the user doing the /NAMES has the channels/auspex privilege
+
+ // If the user is inside the channel or has privs, instruct SendNames() to show invisible (+i) members
+ bool show_invisible = ((c->HasUser(user)) || (user->HasPrivPermission("channels/auspex")));
+ if ((show_invisible) || (!c->IsModeSet(secretmode)))
+ {
+ SendNames(user, c, show_invisible);
+ return CMD_SUCCESS;
+ }
+ }
+
+ user->WriteNumeric(Numerics::NoSuchChannel(parameters[0]));
+ return CMD_FAILURE;
+}
+
+void CommandNames::SendNames(LocalUser* user, Channel* chan, bool show_invisible)
+{
+ Numeric::Builder<' '> reply(user, RPL_NAMREPLY, false, chan->name.size() + 3);
+ Numeric::Numeric& numeric = reply.GetNumeric();
+ if (chan->IsModeSet(secretmode))
+ numeric.push(std::string(1, '@'));
+ else if (chan->IsModeSet(privatemode))
+ numeric.push(std::string(1, '*'));
+ else
+ numeric.push(std::string(1, '='));
+
+ numeric.push(chan->name);
+ numeric.push(std::string());
+
+ std::string prefixlist;
+ std::string nick;
+ const Channel::MemberMap& members = chan->GetUsers();
+ for (Channel::MemberMap::const_iterator i = members.begin(); i != members.end(); ++i)
+ {
+ if ((!show_invisible) && (i->first->IsModeSet(invisiblemode)))
+ {
+ // Member is invisible and we are not supposed to show them
+ continue;
+ }
+
+ Membership* const memb = i->second;
+
+ prefixlist.clear();
+ char prefix = memb->GetPrefixChar();
+ if (prefix)
+ prefixlist.push_back(prefix);
+ nick = i->first->nick;
+
+ ModResult res;
+ FIRST_MOD_RESULT(OnNamesListItem, res, (user, memb, prefixlist, nick));
+
+ // See if a module wants us to exclude this user from NAMES
+ if (res == MOD_RES_DENY)
+ continue;
+
+ reply.Add(prefixlist, nick);
+ }
+
+ reply.Flush();
+ user->WriteNumeric(RPL_ENDOFNAMES, chan->name, "End of /NAMES list.");
+}
diff --git a/src/coremods/core_channel/cmd_topic.cpp b/src/coremods/core_channel/cmd_topic.cpp
new file mode 100644
index 000000000..fe913225e
--- /dev/null
+++ b/src/coremods/core_channel/cmd_topic.cpp
@@ -0,0 +1,102 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_channel.h"
+
+CommandTopic::CommandTopic(Module* parent)
+ : SplitCommand(parent, "TOPIC", 1, 2)
+ , exemptionprov(parent)
+ , secretmode(parent, "secret")
+ , topiclockmode(parent, "topiclock")
+{
+ syntax = "<channel> [<topic>]";
+ Penalty = 2;
+}
+
+CmdResult CommandTopic::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
+{
+ Channel* c = ServerInstance->FindChan(parameters[0]);
+ if (!c)
+ {
+ user->WriteNumeric(Numerics::NoSuchChannel(parameters[0]));
+ return CMD_FAILURE;
+ }
+
+ if (parameters.size() == 1)
+ {
+ if ((c->IsModeSet(secretmode)) && (!c->HasUser(user)))
+ {
+ user->WriteNumeric(Numerics::NoSuchChannel(c->name));
+ return CMD_FAILURE;
+ }
+
+ if (c->topic.length())
+ {
+ Topic::ShowTopic(user, c);
+ }
+ else
+ {
+ user->WriteNumeric(RPL_NOTOPICSET, c->name, "No topic is set.");
+ }
+ return CMD_SUCCESS;
+ }
+
+ std::string t = parameters[1]; // needed, in case a module wants to change it
+ ModResult res;
+ FIRST_MOD_RESULT(OnPreTopicChange, res, (user,c,t));
+
+ if (res == MOD_RES_DENY)
+ return CMD_FAILURE;
+ if (res != MOD_RES_ALLOW)
+ {
+ if (!c->HasUser(user))
+ {
+ user->WriteNumeric(ERR_NOTONCHANNEL, c->name, "You're not on that channel!");
+ return CMD_FAILURE;
+ }
+ if (c->IsModeSet(topiclockmode))
+ {
+ ModResult MOD_RESULT = CheckExemption::Call(exemptionprov, user, c, "topiclock");
+ if (!MOD_RESULT.check(c->GetPrefixValue(user) >= HALFOP_VALUE))
+ {
+ user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, "You do not have access to change the topic on this channel");
+ return CMD_FAILURE;
+ }
+ }
+ }
+
+ // Make sure the topic is not longer than the limit in the config
+ if (t.length() > ServerInstance->Config->Limits.MaxTopic)
+ t.erase(ServerInstance->Config->Limits.MaxTopic);
+
+ // Only change if the new topic is different than the current one
+ if (c->topic != t)
+ c->SetTopic(user, t, ServerInstance->Time());
+ return CMD_SUCCESS;
+}
+
+void Topic::ShowTopic(LocalUser* user, Channel* chan)
+{
+ user->WriteNumeric(RPL_TOPIC, chan->name, chan->topic);
+ user->WriteNumeric(RPL_TOPICTIME, chan->name, chan->setby, (unsigned long)chan->topicset);
+}
diff --git a/src/coremods/core_channel/cmode_k.cpp b/src/coremods/core_channel/cmode_k.cpp
new file mode 100644
index 000000000..4fc29e04c
--- /dev/null
+++ b/src/coremods/core_channel/cmode_k.cpp
@@ -0,0 +1,89 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_channel.h"
+
+const std::string::size_type ModeChannelKey::maxkeylen = 32;
+
+ModeChannelKey::ModeChannelKey(Module* Creator)
+ : ParamMode<ModeChannelKey, LocalStringExt>(Creator, "key", 'k', PARAM_ALWAYS)
+{
+}
+
+ModeAction ModeChannelKey::OnModeChange(User* source, User*, Channel* channel, std::string &parameter, bool adding)
+{
+ const std::string* key = ext.get(channel);
+ bool exists = (key != NULL);
+ if (IS_LOCAL(source))
+ {
+ if (exists == adding)
+ return MODEACTION_DENY;
+ if (exists && (parameter != *key))
+ {
+ /* Key is currently set and the correct key wasnt given */
+ return MODEACTION_DENY;
+ }
+ } else {
+ if (exists && adding && parameter == *key)
+ {
+ /* no-op, don't show */
+ return MODEACTION_DENY;
+ }
+ }
+
+ if (adding)
+ {
+ // When joining a channel multiple keys are delimited with a comma so we strip
+ // them out here to avoid creating channels that are unjoinable.
+ size_t commapos;
+ while ((commapos = parameter.find(',')) != std::string::npos)
+ parameter.erase(commapos, 1);
+
+ // Truncate the parameter to the maximum key length.
+ if (parameter.length() > maxkeylen)
+ parameter.erase(maxkeylen);
+
+ // If the password is empty here then it only consisted of commas. This is not
+ // acceptable so we reject the mode change.
+ if (parameter.empty())
+ return MODEACTION_DENY;
+
+ ext.set(channel, parameter);
+ }
+ else
+ ext.unset(channel);
+
+ channel->SetMode(this, adding);
+ return MODEACTION_ALLOW;
+}
+
+void ModeChannelKey::SerializeParam(Channel* chan, const std::string* key, std::string& out)
+{
+ out += *key;
+}
+
+ModeAction ModeChannelKey::OnSet(User* source, Channel* chan, std::string& param)
+{
+ // Dummy function, never called
+ return MODEACTION_DENY;
+}
diff --git a/src/coremods/core_channel/cmode_l.cpp b/src/coremods/core_channel/cmode_l.cpp
new file mode 100644
index 000000000..e71eb500e
--- /dev/null
+++ b/src/coremods/core_channel/cmode_l.cpp
@@ -0,0 +1,50 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_channel.h"
+
+ModeChannelLimit::ModeChannelLimit(Module* Creator)
+ : ParamMode<ModeChannelLimit, LocalIntExt>(Creator, "limit", 'l')
+ , minlimit(0)
+{
+}
+
+bool ModeChannelLimit::ResolveModeConflict(std::string &their_param, const std::string &our_param, Channel*)
+{
+ /* When TS is equal, the higher channel limit wins */
+ return (atoi(their_param.c_str()) < atoi(our_param.c_str()));
+}
+
+ModeAction ModeChannelLimit::OnSet(User* user, Channel* chan, std::string& parameter)
+{
+ size_t limit = ConvToNum<size_t>(parameter);
+ if (limit < minlimit)
+ return MODEACTION_DENY;
+
+ ext.set(chan, limit);
+ return MODEACTION_ALLOW;
+}
+
+void ModeChannelLimit::SerializeParam(Channel* chan, intptr_t n, std::string& out)
+{
+ out += ConvToStr(n);
+}
diff --git a/src/coremods/core_channel/core_channel.cpp b/src/coremods/core_channel/core_channel.cpp
new file mode 100644
index 000000000..ccf4d1a6e
--- /dev/null
+++ b/src/coremods/core_channel/core_channel.cpp
@@ -0,0 +1,281 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014-2015 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_channel.h"
+#include "invite.h"
+#include "listmode.h"
+
+class CoreModChannel : public Module, public CheckExemption::EventListener
+{
+ Invite::APIImpl invapi;
+ CommandInvite cmdinvite;
+ CommandJoin cmdjoin;
+ CommandKick cmdkick;
+ CommandNames cmdnames;
+ CommandTopic cmdtopic;
+
+ ModeChannelBan banmode;
+ SimpleChannelModeHandler inviteonlymode;
+ ModeChannelKey keymode;
+ ModeChannelLimit limitmode;
+ SimpleChannelModeHandler moderatedmode;
+ SimpleChannelModeHandler noextmsgmode;
+ ModeChannelOp opmode;
+ SimpleChannelModeHandler privatemode;
+ SimpleChannelModeHandler secretmode;
+ SimpleChannelModeHandler topiclockmode;
+ ModeChannelVoice voicemode;
+
+ insp::flat_map<std::string, char> exemptions;
+
+ ModResult IsInvited(User* user, Channel* chan)
+ {
+ LocalUser* localuser = IS_LOCAL(user);
+ if ((localuser) && (invapi.IsInvited(localuser, chan)))
+ return MOD_RES_ALLOW;
+ return MOD_RES_PASSTHRU;
+ }
+
+ public:
+ CoreModChannel()
+ : CheckExemption::EventListener(this)
+ , invapi(this)
+ , cmdinvite(this, invapi)
+ , cmdjoin(this)
+ , cmdkick(this)
+ , cmdnames(this)
+ , cmdtopic(this)
+ , banmode(this)
+ , inviteonlymode(this, "inviteonly", 'i')
+ , keymode(this)
+ , limitmode(this)
+ , moderatedmode(this, "moderated", 'm')
+ , noextmsgmode(this, "noextmsg", 'n')
+ , opmode(this)
+ , privatemode(this, "private", 'p')
+ , secretmode(this, "secret", 's')
+ , topiclockmode(this, "topiclock", 't')
+ , voicemode(this)
+ {
+ }
+
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ ConfigTag* optionstag = ServerInstance->Config->ConfValue("options");
+ Implementation events[] = { I_OnCheckKey, I_OnCheckLimit, I_OnCheckChannelBan };
+ if (optionstag->getBool("invitebypassmodes", true))
+ ServerInstance->Modules.Attach(events, this, sizeof(events)/sizeof(Implementation));
+ else
+ {
+ for (unsigned int i = 0; i < sizeof(events)/sizeof(Implementation); i++)
+ ServerInstance->Modules.Detach(events[i], this);
+ }
+
+ std::string current;
+ irc::spacesepstream defaultstream(optionstag->getString("exemptchanops"));
+ insp::flat_map<std::string, char> exempts;
+ while (defaultstream.GetToken(current))
+ {
+ std::string::size_type pos = current.find(':');
+ if (pos == std::string::npos || (pos + 2) > current.size())
+ throw ModuleException("Invalid exemptchanops value '" + current + "' at " + optionstag->getTagLocation());
+
+ const std::string restriction = current.substr(0, pos);
+ const char prefix = current[pos + 1];
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Exempting prefix %c from %s", prefix, restriction.c_str());
+ exempts[restriction] = prefix;
+ }
+ exemptions.swap(exempts);
+
+ ConfigTag* securitytag = ServerInstance->Config->ConfValue("security");
+ const std::string announceinvites = securitytag->getString("announceinvites", "dynamic");
+ if (stdalgo::string::equalsci(announceinvites, "none"))
+ cmdinvite.announceinvites = Invite::ANNOUNCE_NONE;
+ else if (stdalgo::string::equalsci(announceinvites, "all"))
+ cmdinvite.announceinvites = Invite::ANNOUNCE_ALL;
+ else if (stdalgo::string::equalsci(announceinvites, "ops"))
+ cmdinvite.announceinvites = Invite::ANNOUNCE_OPS;
+ else if (stdalgo::string::equalsci(announceinvites, "dynamic"))
+ cmdinvite.announceinvites = Invite::ANNOUNCE_DYNAMIC;
+ else
+ throw ModuleException(announceinvites + " is an invalid <security:announceinvites> value, at " + securitytag->getTagLocation());
+
+ // In 2.0 we allowed limits of 0 to be set. This is non-standard behaviour
+ // and will be removed in the next major release.
+ limitmode.minlimit = optionstag->getBool("allowzerolimit", true) ? 0 : 1;
+
+ banmode.DoRehash();
+ }
+
+ void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
+ {
+ tokens["KEYLEN"] = ConvToStr(ModeChannelKey::maxkeylen);
+
+ // Build a map of limits to their mode character.
+ insp::flat_map<int, std::string> limits;
+ const ModeParser::ListModeList& listmodes = ServerInstance->Modes->GetListModes();
+ for (ModeParser::ListModeList::const_iterator iter = listmodes.begin(); iter != listmodes.end(); ++iter)
+ {
+ const unsigned int limit = (*iter)->GetLowerLimit();
+ limits[limit].push_back((*iter)->GetModeChar());
+ }
+
+ // Generate the MAXLIST token from the limits map.
+ std::string& buffer = tokens["MAXLIST"];
+ for (insp::flat_map<int, std::string>::const_iterator iter = limits.begin(); iter != limits.end(); ++iter)
+ {
+ if (!buffer.empty())
+ buffer.push_back(',');
+
+ buffer.append(iter->second);
+ buffer.push_back(':');
+ buffer.append(ConvToStr(iter->first));
+ }
+ }
+
+ ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string&, std::string&, const std::string& keygiven) CXX11_OVERRIDE
+ {
+ if (!chan)
+ return MOD_RES_PASSTHRU;
+
+ // Check whether the channel key is correct.
+ const std::string ckey = chan->GetModeParameter(&keymode);
+ if (!ckey.empty())
+ {
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnCheckKey, MOD_RESULT, (user, chan, keygiven));
+ if (!MOD_RESULT.check(InspIRCd::TimingSafeCompare(ckey, keygiven)))
+ {
+ // If no key provided, or key is not the right one, and can't bypass +k (not invited or option not enabled)
+ user->WriteNumeric(ERR_BADCHANNELKEY, chan->name, "Cannot join channel (Incorrect channel key)");
+ return MOD_RES_DENY;
+ }
+ }
+
+ // Check whether the invite only mode is set.
+ if (chan->IsModeSet(inviteonlymode))
+ {
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnCheckInvite, MOD_RESULT, (user, chan));
+ if (MOD_RESULT != MOD_RES_ALLOW)
+ {
+ user->WriteNumeric(ERR_INVITEONLYCHAN, chan->name, "Cannot join channel (Invite only)");
+ return MOD_RES_DENY;
+ }
+ }
+
+ // Check whether the limit would be exceeded by this user joining.
+ if (chan->IsModeSet(limitmode))
+ {
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnCheckLimit, MOD_RESULT, (user, chan));
+ if (!MOD_RESULT.check(chan->GetUserCounter() < static_cast<size_t>(limitmode.ext.get(chan))))
+ {
+ user->WriteNumeric(ERR_CHANNELISFULL, chan->name, "Cannot join channel (Channel is full)");
+ return MOD_RES_DENY;
+ }
+ }
+
+ // Everything looks okay.
+ return MOD_RES_PASSTHRU;
+ }
+
+ void OnPostJoin(Membership* memb) CXX11_OVERRIDE
+ {
+ Channel* const chan = memb->chan;
+ LocalUser* const localuser = IS_LOCAL(memb->user);
+ if (localuser)
+ {
+ // Remove existing invite, if any
+ invapi.Remove(localuser, chan);
+
+ if (chan->topic.length())
+ Topic::ShowTopic(localuser, chan);
+
+ // Show all members of the channel, including invisible (+i) users
+ cmdnames.SendNames(localuser, chan, true);
+ }
+ }
+
+ ModResult OnCheckKey(User* user, Channel* chan, const std::string& keygiven) CXX11_OVERRIDE
+ {
+ // Hook only runs when being invited bypasses +bkl
+ return IsInvited(user, chan);
+ }
+
+ ModResult OnCheckChannelBan(User* user, Channel* chan) CXX11_OVERRIDE
+ {
+ // Hook only runs when being invited bypasses +bkl
+ return IsInvited(user, chan);
+ }
+
+ ModResult OnCheckLimit(User* user, Channel* chan) CXX11_OVERRIDE
+ {
+ // Hook only runs when being invited bypasses +bkl
+ return IsInvited(user, chan);
+ }
+
+ ModResult OnCheckInvite(User* user, Channel* chan) CXX11_OVERRIDE
+ {
+ // Hook always runs
+ return IsInvited(user, chan);
+ }
+
+ void OnUserDisconnect(LocalUser* user) CXX11_OVERRIDE
+ {
+ invapi.RemoveAll(user);
+ }
+
+ void OnChannelDelete(Channel* chan) CXX11_OVERRIDE
+ {
+ // Make sure the channel won't appear in invite lists from now on, don't wait for cull to unset the ext
+ invapi.RemoveAll(chan);
+ }
+
+ ModResult OnCheckExemption(User* user, Channel* chan, const std::string& restriction) CXX11_OVERRIDE
+ {
+ if (!exemptions.count(restriction))
+ return MOD_RES_PASSTHRU;
+
+ unsigned int mypfx = chan->GetPrefixValue(user);
+ char minmode = exemptions[restriction];
+
+ PrefixMode* mh = ServerInstance->Modes->FindPrefixMode(minmode);
+ if (mh && mypfx >= mh->GetPrefixRank())
+ return MOD_RES_ALLOW;
+ if (mh || minmode == '*')
+ return MOD_RES_DENY;
+ return MOD_RES_PASSTHRU;
+ }
+
+ void Prioritize() CXX11_OVERRIDE
+ {
+ ServerInstance->Modules.SetPriority(this, I_OnPostJoin, PRIORITY_FIRST);
+ ServerInstance->Modules.SetPriority(this, I_OnUserPreJoin, PRIORITY_LAST);
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the INVITE, JOIN, KICK, NAMES, and TOPIC commands", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModChannel)
diff --git a/src/coremods/core_channel/core_channel.h b/src/coremods/core_channel/core_channel.h
new file mode 100644
index 000000000..e59b2cdbd
--- /dev/null
+++ b/src/coremods/core_channel/core_channel.h
@@ -0,0 +1,219 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "inspircd.h"
+#include "listmode.h"
+#include "modules/exemption.h"
+
+namespace Topic
+{
+ void ShowTopic(LocalUser* user, Channel* chan);
+}
+
+namespace Invite
+{
+ class APIImpl;
+
+ /** Used to indicate who we announce invites to on a channel. */
+ enum AnnounceState
+ {
+ /** Don't send invite announcements. */
+ ANNOUNCE_NONE,
+
+ /** Send invite announcements to all users. */
+ ANNOUNCE_ALL,
+
+ /** Send invite announcements to channel operators and higher. */
+ ANNOUNCE_OPS,
+
+ /** Send invite announcements to channel half-operators (if available) and higher. */
+ ANNOUNCE_DYNAMIC
+ };
+}
+
+/** Handle /INVITE.
+ */
+class CommandInvite : public Command
+{
+ Invite::APIImpl& invapi;
+
+ public:
+ Invite::AnnounceState announceinvites;
+
+ /** Constructor for invite.
+ */
+ CommandInvite(Module* parent, Invite::APIImpl& invapiimpl);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE;
+};
+
+/** Handle /JOIN.
+ */
+class CommandJoin : public SplitCommand
+{
+ public:
+ /** Constructor for join.
+ */
+ CommandJoin(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+};
+
+/** Handle /TOPIC.
+ */
+class CommandTopic : public SplitCommand
+{
+ CheckExemption::EventProvider exemptionprov;
+ ChanModeReference secretmode;
+ ChanModeReference topiclockmode;
+
+ public:
+ /** Constructor for topic.
+ */
+ CommandTopic(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+};
+
+/** Handle /NAMES.
+ */
+class CommandNames : public SplitCommand
+{
+ ChanModeReference secretmode;
+ ChanModeReference privatemode;
+ UserModeReference invisiblemode;
+
+ public:
+ /** Constructor for names.
+ */
+ CommandNames(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+
+ /** Spool the NAMES list for a given channel to the given user
+ * @param user User to spool the NAMES list to
+ * @param chan Channel whose nicklist to send
+ * @param show_invisible True to show invisible (+i) members to the user, false to omit them from the list
+ */
+ void SendNames(LocalUser* user, Channel* chan, bool show_invisible);
+};
+
+/** Handle /KICK.
+ */
+class CommandKick : public Command
+{
+ public:
+ /** Constructor for kick.
+ */
+ CommandKick(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE;
+};
+
+/** Channel mode +b
+ */
+class ModeChannelBan : public ListModeBase
+{
+ public:
+ ModeChannelBan(Module* Creator)
+ : ListModeBase(Creator, "ban", 'b', "End of channel ban list", 367, 368, true, "maxbans")
+ {
+ }
+};
+
+/** Channel mode +k
+ */
+class ModeChannelKey : public ParamMode<ModeChannelKey, LocalStringExt>
+{
+ public:
+ static const std::string::size_type maxkeylen;
+ ModeChannelKey(Module* Creator);
+ ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE;
+ void SerializeParam(Channel* chan, const std::string* key, std::string& out) ;
+ ModeAction OnSet(User* source, Channel* chan, std::string& param) CXX11_OVERRIDE;
+};
+
+/** Channel mode +l
+ */
+class ModeChannelLimit : public ParamMode<ModeChannelLimit, LocalIntExt>
+{
+ public:
+ size_t minlimit;
+ ModeChannelLimit(Module* Creator);
+ bool ResolveModeConflict(std::string& their_param, const std::string& our_param, Channel* channel) CXX11_OVERRIDE;
+ void SerializeParam(Channel* chan, intptr_t n, std::string& out);
+ ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE;
+};
+
+/** Channel mode +o
+ */
+class ModeChannelOp : public PrefixMode
+{
+ public:
+ ModeChannelOp(Module* Creator)
+ : PrefixMode(Creator, "op", 'o', OP_VALUE, '@')
+ {
+ ranktoset = ranktounset = OP_VALUE;
+ }
+};
+
+/** Channel mode +v
+ */
+class ModeChannelVoice : public PrefixMode
+{
+ public:
+ ModeChannelVoice(Module* Creator)
+ : PrefixMode(Creator, "voice", 'v', VOICE_VALUE, '+')
+ {
+ selfremove = false;
+ ranktoset = ranktounset = HALFOP_VALUE;
+ }
+};
diff --git a/src/coremods/core_channel/invite.cpp b/src/coremods/core_channel/invite.cpp
new file mode 100644
index 000000000..7ac662edc
--- /dev/null
+++ b/src/coremods/core_channel/invite.cpp
@@ -0,0 +1,208 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2012, 2015 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+#include "invite.h"
+
+class InviteExpireTimer : public Timer
+{
+ Invite::Invite* const inv;
+
+ bool Tick(time_t currtime) CXX11_OVERRIDE;
+
+ public:
+ InviteExpireTimer(Invite::Invite* invite, time_t timeout);
+};
+
+static Invite::APIImpl* apiimpl;
+
+void RemoveInvite(Invite::Invite* inv, bool remove_user, bool remove_chan)
+{
+ apiimpl->Destruct(inv, remove_user, remove_chan);
+}
+
+void UnserializeInvite(LocalUser* user, const std::string& str)
+{
+ apiimpl->Unserialize(user, str);
+}
+
+Invite::APIBase::APIBase(Module* parent)
+ : DataProvider(parent, "core_channel_invite")
+{
+}
+
+Invite::APIImpl::APIImpl(Module* parent)
+ : APIBase(parent)
+ , userext(parent, "invite_user")
+ , chanext(parent, "invite_chan")
+{
+ apiimpl = this;
+}
+
+void Invite::APIImpl::Destruct(Invite* inv, bool remove_user, bool remove_chan)
+{
+ Store<LocalUser>* ustore = userext.get(inv->user);
+ if (ustore)
+ {
+ ustore->invites.erase(inv);
+ if ((remove_user) && (ustore->invites.empty()))
+ userext.unset(inv->user);
+ }
+
+ Store<Channel>* cstore = chanext.get(inv->chan);
+ if (cstore)
+ {
+ cstore->invites.erase(inv);
+ if ((remove_chan) && (cstore->invites.empty()))
+ chanext.unset(inv->chan);
+ }
+
+ delete inv;
+}
+
+bool Invite::APIImpl::Remove(LocalUser* user, Channel* chan)
+{
+ Invite* inv = Find(user, chan);
+ if (inv)
+ {
+ Destruct(inv);
+ return true;
+ }
+ return false;
+}
+
+void Invite::APIImpl::Create(LocalUser* user, Channel* chan, time_t timeout)
+{
+ if ((timeout != 0) && (ServerInstance->Time() >= timeout))
+ // Expired, don't bother
+ return;
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Invite::APIImpl::Create(): user=%s chan=%s timeout=%lu", user->uuid.c_str(), chan->name.c_str(), (unsigned long)timeout);
+
+ Invite* inv = Find(user, chan);
+ if (inv)
+ {
+ // We only ever extend invites, so nothing to do if the existing one is not timed
+ if (!inv->IsTimed())
+ return;
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Invite::APIImpl::Create(): changing expiration in %p", (void*) inv);
+ if (timeout == 0)
+ {
+ // Convert timed invite to non-expiring
+ delete inv->expiretimer;
+ inv->expiretimer = NULL;
+ }
+ else if (inv->expiretimer->GetTrigger() >= ServerInstance->Time() + timeout)
+ {
+ // New expiration time is further than the current, extend the expiration
+ inv->expiretimer->SetInterval(timeout - ServerInstance->Time());
+ }
+ }
+ else
+ {
+ inv = new Invite(user, chan);
+ if (timeout)
+ {
+ inv->expiretimer = new InviteExpireTimer(inv, timeout - ServerInstance->Time());
+ ServerInstance->Timers.AddTimer(inv->expiretimer);
+ }
+
+ userext.get(user, true)->invites.push_front(inv);
+ chanext.get(chan, true)->invites.push_front(inv);
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Invite::APIImpl::Create(): created new Invite %p", (void*) inv);
+ }
+}
+
+Invite::Invite* Invite::APIImpl::Find(LocalUser* user, Channel* chan)
+{
+ const List* list = APIImpl::GetList(user);
+ if (!list)
+ return NULL;
+
+ for (List::iterator i = list->begin(); i != list->end(); ++i)
+ {
+ Invite* inv = *i;
+ if (inv->chan == chan)
+ return inv;
+ }
+
+ return NULL;
+}
+
+const Invite::List* Invite::APIImpl::GetList(LocalUser* user)
+{
+ Store<LocalUser>* list = userext.get(user);
+ if (list)
+ return &list->invites;
+ return NULL;
+}
+
+void Invite::APIImpl::Unserialize(LocalUser* user, const std::string& value)
+{
+ irc::spacesepstream ss(value);
+ for (std::string channame, exptime; (ss.GetToken(channame) && ss.GetToken(exptime)); )
+ {
+ Channel* chan = ServerInstance->FindChan(channame);
+ if (chan)
+ Create(user, chan, ConvToInt(exptime));
+ }
+}
+
+Invite::Invite::Invite(LocalUser* u, Channel* c)
+ : user(u)
+ , chan(c)
+ , expiretimer(NULL)
+{
+}
+
+Invite::Invite::~Invite()
+{
+ delete expiretimer;
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Invite::~ %p", (void*) this);
+}
+
+void Invite::Invite::Serialize(SerializeFormat format, bool show_chans, std::string& out)
+{
+ if (show_chans)
+ out.append(this->chan->name);
+ else
+ out.append((format == FORMAT_USER) ? user->nick : user->uuid);
+ out.push_back(' ');
+
+ if (expiretimer)
+ out.append(ConvToStr(expiretimer->GetTrigger()));
+ else
+ out.push_back('0');
+ out.push_back(' ');
+}
+
+InviteExpireTimer::InviteExpireTimer(Invite::Invite* invite, time_t timeout)
+ : Timer(timeout)
+ , inv(invite)
+{
+}
+
+bool InviteExpireTimer::Tick(time_t currtime)
+{
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "InviteExpireTimer::Tick(): expired %p", (void*) inv);
+ apiimpl->Destruct(inv);
+ return false;
+}
diff --git a/src/coremods/core_channel/invite.h b/src/coremods/core_channel/invite.h
new file mode 100644
index 000000000..2a99ec2df
--- /dev/null
+++ b/src/coremods/core_channel/invite.h
@@ -0,0 +1,127 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "modules/invite.h"
+
+namespace Invite
+{
+ template<typename T>
+ struct Store
+ {
+ typedef insp::intrusive_list<Invite, T> List;
+
+ /** List of pending Invites
+ */
+ List invites;
+ };
+
+ template<typename T, ExtensionItem::ExtensibleType ExtType>
+ class ExtItem;
+
+ class APIImpl;
+}
+
+extern void RemoveInvite(Invite::Invite* inv, bool remove_user, bool remove_chan);
+extern void UnserializeInvite(LocalUser* user, const std::string& value);
+
+template<typename T, ExtensionItem::ExtensibleType ExtType>
+class Invite::ExtItem : public ExtensionItem
+{
+ public:
+ ExtItem(Module* owner, const char* extname)
+ : ExtensionItem(extname, ExtType, owner)
+ {
+ }
+
+ Store<T>* get(Extensible* ext, bool create = false)
+ {
+ Store<T>* store = static_cast<Store<T>*>(get_raw(ext));
+ if ((create) && (!store))
+ {
+ store = new Store<T>;
+ set_raw(ext, store);
+ }
+ return store;
+ }
+
+ void unset(Extensible* ext)
+ {
+ void* store = unset_raw(ext);
+ if (store)
+ free(store);
+ }
+
+ void free(void* item) CXX11_OVERRIDE
+ {
+ Store<T>* store = static_cast<Store<T>*>(item);
+ for (typename Store<T>::List::iterator i = store->invites.begin(); i != store->invites.end(); )
+ {
+ Invite* inv = *i;
+ // Destructing the Invite invalidates the iterator, so move it now
+ ++i;
+ RemoveInvite(inv, (ExtType != ExtensionItem::EXT_USER), (ExtType == ExtensionItem::EXT_USER));
+ }
+
+ delete store;
+ }
+
+ std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE
+ {
+ if (format == FORMAT_NETWORK)
+ return std::string();
+
+ std::string ret;
+ Store<T>* store = static_cast<Store<T>*>(item);
+ for (typename insp::intrusive_list<Invite, T>::iterator i = store->invites.begin(); i != store->invites.end(); ++i)
+ {
+ Invite* inv = *i;
+ inv->Serialize(format, (ExtType == ExtensionItem::EXT_USER), ret);
+ }
+ if (!ret.empty())
+ ret.erase(ret.length()-1);
+ return ret;
+ }
+
+ void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE
+ {
+ if ((ExtType != ExtensionItem::EXT_CHANNEL) && (format != FORMAT_NETWORK))
+ UnserializeInvite(static_cast<LocalUser*>(container), value);
+ }
+};
+
+class Invite::APIImpl : public APIBase
+{
+ ExtItem<LocalUser, ExtensionItem::EXT_USER> userext;
+ ExtItem<Channel, ExtensionItem::EXT_CHANNEL> chanext;
+
+ public:
+ APIImpl(Module* owner);
+
+ void Create(LocalUser* user, Channel* chan, time_t timeout) CXX11_OVERRIDE;
+ Invite* Find(LocalUser* user, Channel* chan) CXX11_OVERRIDE;
+ bool Remove(LocalUser* user, Channel* chan) CXX11_OVERRIDE;
+ const List* GetList(LocalUser* user) CXX11_OVERRIDE;
+
+ void RemoveAll(LocalUser* user) { userext.unset(user); }
+ void RemoveAll(Channel* chan) { chanext.unset(chan); }
+ void Destruct(Invite* inv, bool remove_chan = true, bool remove_user = true);
+ void Unserialize(LocalUser* user, const std::string& value);
+};
diff --git a/src/coremods/core_dns.cpp b/src/coremods/core_dns.cpp
new file mode 100644
index 000000000..4caea9c48
--- /dev/null
+++ b/src/coremods/core_dns.cpp
@@ -0,0 +1,850 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2013 Adam <Adam@anope.org>
+ * Copyright (C) 2003-2013 Anope Team <team@anope.org>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "inspircd.h"
+#include "modules/dns.h"
+#include <iostream>
+#include <fstream>
+
+#ifdef _WIN32
+#include <Iphlpapi.h>
+#pragma comment(lib, "Iphlpapi.lib")
+#endif
+
+namespace DNS
+{
+ /** Maximum value of a dns request id, 16 bits wide, 0xFFFF.
+ */
+ const unsigned int MAX_REQUEST_ID = 0xFFFF;
+}
+
+using namespace DNS;
+
+/** A full packet sent or recieved to/from the nameserver
+ */
+class Packet : public Query
+{
+ void PackName(unsigned char* output, unsigned short output_size, unsigned short& pos, const std::string& name)
+ {
+ if (pos + name.length() + 2 > output_size)
+ throw Exception("Unable to pack name");
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Packing name " + name);
+
+ irc::sepstream sep(name, '.');
+ std::string token;
+
+ while (sep.GetToken(token))
+ {
+ output[pos++] = token.length();
+ memcpy(&output[pos], token.data(), token.length());
+ pos += token.length();
+ }
+
+ output[pos++] = 0;
+ }
+
+ std::string UnpackName(const unsigned char* input, unsigned short input_size, unsigned short& pos)
+ {
+ std::string name;
+ unsigned short pos_ptr = pos, lowest_ptr = input_size;
+ bool compressed = false;
+
+ if (pos_ptr >= input_size)
+ throw Exception("Unable to unpack name - no input");
+
+ while (input[pos_ptr] > 0)
+ {
+ unsigned short offset = input[pos_ptr];
+
+ if (offset & POINTER)
+ {
+ if ((offset & POINTER) != POINTER)
+ throw Exception("Unable to unpack name - bogus compression header");
+ if (pos_ptr + 1 >= input_size)
+ throw Exception("Unable to unpack name - bogus compression header");
+
+ /* Place pos at the second byte of the first (farthest) compression pointer */
+ if (compressed == false)
+ {
+ ++pos;
+ compressed = true;
+ }
+
+ pos_ptr = (offset & LABEL) << 8 | input[pos_ptr + 1];
+
+ /* Pointers can only go back */
+ if (pos_ptr >= lowest_ptr)
+ throw Exception("Unable to unpack name - bogus compression pointer");
+ lowest_ptr = pos_ptr;
+ }
+ else
+ {
+ if (pos_ptr + offset + 1 >= input_size)
+ throw Exception("Unable to unpack name - offset too large");
+ if (!name.empty())
+ name += ".";
+ for (unsigned i = 1; i <= offset; ++i)
+ name += input[pos_ptr + i];
+
+ pos_ptr += offset + 1;
+ if (compressed == false)
+ /* Move up pos */
+ pos = pos_ptr;
+ }
+ }
+
+ /* +1 pos either to one byte after the compression pointer or one byte after the ending \0 */
+ ++pos;
+
+ if (name.empty())
+ throw Exception("Unable to unpack name - no name");
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unpack name " + name);
+
+ return name;
+ }
+
+ Question UnpackQuestion(const unsigned char* input, unsigned short input_size, unsigned short& pos)
+ {
+ Question q;
+
+ q.name = this->UnpackName(input, input_size, pos);
+
+ if (pos + 4 > input_size)
+ throw Exception("Unable to unpack question");
+
+ q.type = static_cast<QueryType>(input[pos] << 8 | input[pos + 1]);
+ pos += 2;
+
+ // Skip over query class code
+ pos += 2;
+
+ return q;
+ }
+
+ ResourceRecord UnpackResourceRecord(const unsigned char* input, unsigned short input_size, unsigned short& pos)
+ {
+ ResourceRecord record = static_cast<ResourceRecord>(this->UnpackQuestion(input, input_size, pos));
+
+ if (pos + 6 > input_size)
+ throw Exception("Unable to unpack resource record");
+
+ record.ttl = (input[pos] << 24) | (input[pos + 1] << 16) | (input[pos + 2] << 8) | input[pos + 3];
+ pos += 4;
+
+ uint16_t rdlength = input[pos] << 8 | input[pos + 1];
+ pos += 2;
+
+ switch (record.type)
+ {
+ case QUERY_A:
+ {
+ if (pos + 4 > input_size)
+ throw Exception("Unable to unpack resource record");
+
+ irc::sockets::sockaddrs addrs;
+ memset(&addrs, 0, sizeof(addrs));
+
+ addrs.in4.sin_family = AF_INET;
+ addrs.in4.sin_addr.s_addr = input[pos] | (input[pos + 1] << 8) | (input[pos + 2] << 16) | (input[pos + 3] << 24);
+ pos += 4;
+
+ record.rdata = addrs.addr();
+ break;
+ }
+ case QUERY_AAAA:
+ {
+ if (pos + 16 > input_size)
+ throw Exception("Unable to unpack resource record");
+
+ irc::sockets::sockaddrs addrs;
+ memset(&addrs, 0, sizeof(addrs));
+
+ addrs.in6.sin6_family = AF_INET6;
+ for (int j = 0; j < 16; ++j)
+ addrs.in6.sin6_addr.s6_addr[j] = input[pos + j];
+ pos += 16;
+
+ record.rdata = addrs.addr();
+
+ break;
+ }
+ case QUERY_CNAME:
+ case QUERY_PTR:
+ {
+ record.rdata = this->UnpackName(input, input_size, pos);
+ if (!InspIRCd::IsHost(record.rdata))
+ throw Exception("Invalid name"); // XXX: Causes the request to time out
+
+ break;
+ }
+ case QUERY_TXT:
+ {
+ if (pos + rdlength > input_size)
+ throw Exception("Unable to unpack txt resource record");
+
+ record.rdata = std::string(reinterpret_cast<const char *>(input + pos), rdlength);
+ pos += rdlength;
+
+ if (record.rdata.find_first_of("\r\n\0", 0, 3) != std::string::npos)
+ throw Exception("Invalid character in txt record");
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (!record.name.empty() && !record.rdata.empty())
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, record.name + " -> " + record.rdata);
+
+ return record;
+ }
+
+ public:
+ static const int POINTER = 0xC0;
+ static const int LABEL = 0x3F;
+ static const int HEADER_LENGTH = 12;
+
+ /* ID for this packet */
+ RequestId id;
+ /* Flags on the packet */
+ unsigned short flags;
+
+ Packet() : id(0), flags(0)
+ {
+ }
+
+ void Fill(const unsigned char* input, const unsigned short len)
+ {
+ if (len < HEADER_LENGTH)
+ throw Exception("Unable to fill packet");
+
+ unsigned short packet_pos = 0;
+
+ this->id = (input[packet_pos] << 8) | input[packet_pos + 1];
+ packet_pos += 2;
+
+ this->flags = (input[packet_pos] << 8) | input[packet_pos + 1];
+ packet_pos += 2;
+
+ unsigned short qdcount = (input[packet_pos] << 8) | input[packet_pos + 1];
+ packet_pos += 2;
+
+ unsigned short ancount = (input[packet_pos] << 8) | input[packet_pos + 1];
+ packet_pos += 2;
+
+ unsigned short nscount = (input[packet_pos] << 8) | input[packet_pos + 1];
+ packet_pos += 2;
+
+ unsigned short arcount = (input[packet_pos] << 8) | input[packet_pos + 1];
+ packet_pos += 2;
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "qdcount: " + ConvToStr(qdcount) + " ancount: " + ConvToStr(ancount) + " nscount: " + ConvToStr(nscount) + " arcount: " + ConvToStr(arcount));
+
+ if (qdcount != 1)
+ throw Exception("Question count != 1 in incoming packet");
+
+ this->question = this->UnpackQuestion(input, len, packet_pos);
+
+ for (unsigned i = 0; i < ancount; ++i)
+ this->answers.push_back(this->UnpackResourceRecord(input, len, packet_pos));
+ }
+
+ unsigned short Pack(unsigned char* output, unsigned short output_size)
+ {
+ if (output_size < HEADER_LENGTH)
+ throw Exception("Unable to pack packet");
+
+ unsigned short pos = 0;
+
+ output[pos++] = this->id >> 8;
+ output[pos++] = this->id & 0xFF;
+ output[pos++] = this->flags >> 8;
+ output[pos++] = this->flags & 0xFF;
+ output[pos++] = 0; // Question count, high byte
+ output[pos++] = 1; // Question count, low byte
+ output[pos++] = 0; // Answer count, high byte
+ output[pos++] = 0; // Answer count, low byte
+ output[pos++] = 0;
+ output[pos++] = 0;
+ output[pos++] = 0;
+ output[pos++] = 0;
+
+ {
+ Question& q = this->question;
+
+ if (q.type == QUERY_PTR)
+ {
+ irc::sockets::sockaddrs ip;
+ irc::sockets::aptosa(q.name, 0, ip);
+
+ if (q.name.find(':') != std::string::npos)
+ {
+ static const char* const hex = "0123456789abcdef";
+ char reverse_ip[128];
+ unsigned reverse_ip_count = 0;
+ for (int j = 15; j >= 0; --j)
+ {
+ reverse_ip[reverse_ip_count++] = hex[ip.in6.sin6_addr.s6_addr[j] & 0xF];
+ reverse_ip[reverse_ip_count++] = '.';
+ reverse_ip[reverse_ip_count++] = hex[ip.in6.sin6_addr.s6_addr[j] >> 4];
+ reverse_ip[reverse_ip_count++] = '.';
+ }
+ reverse_ip[reverse_ip_count++] = 0;
+
+ q.name = reverse_ip;
+ q.name += "ip6.arpa";
+ }
+ else
+ {
+ unsigned long forward = ip.in4.sin_addr.s_addr;
+ ip.in4.sin_addr.s_addr = forward << 24 | (forward & 0xFF00) << 8 | (forward & 0xFF0000) >> 8 | forward >> 24;
+
+ q.name = ip.addr() + ".in-addr.arpa";
+ }
+ }
+
+ this->PackName(output, output_size, pos, q.name);
+
+ if (pos + 4 >= output_size)
+ throw Exception("Unable to pack packet");
+
+ short s = htons(q.type);
+ memcpy(&output[pos], &s, 2);
+ pos += 2;
+
+ // Query class, always IN
+ output[pos++] = 0;
+ output[pos++] = 1;
+ }
+
+ return pos;
+ }
+};
+
+class MyManager : public Manager, public Timer, public EventHandler
+{
+ typedef TR1NS::unordered_map<Question, Query, Question::hash> cache_map;
+ cache_map cache;
+
+ irc::sockets::sockaddrs myserver;
+ bool unloading;
+
+ /** Maximum number of entries in cache
+ */
+ static const unsigned int MAX_CACHE_SIZE = 1000;
+
+ static bool IsExpired(const Query& record, time_t now = ServerInstance->Time())
+ {
+ const ResourceRecord& req = record.answers[0];
+ return (req.created + static_cast<time_t>(req.ttl) < now);
+ }
+
+ /** Check the DNS cache to see if request can be handled by a cached result
+ * @return true if a cached result was found.
+ */
+ bool CheckCache(DNS::Request* req, const DNS::Question& question)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "cache: Checking cache for " + question.name);
+
+ cache_map::iterator it = this->cache.find(question);
+ if (it == this->cache.end())
+ return false;
+
+ Query& record = it->second;
+ if (IsExpired(record))
+ {
+ this->cache.erase(it);
+ return false;
+ }
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "cache: Using cached result for " + question.name);
+ record.cached = true;
+ req->OnLookupComplete(&record);
+ return true;
+ }
+
+ /** Add a record to the dns cache
+ * @param r The record
+ */
+ void AddCache(Query& r)
+ {
+ if (cache.size() >= MAX_CACHE_SIZE)
+ cache.clear();
+
+ // Determine the lowest TTL value and use that as the TTL of the cache entry
+ unsigned int cachettl = UINT_MAX;
+ for (std::vector<ResourceRecord>::const_iterator i = r.answers.begin(); i != r.answers.end(); ++i)
+ {
+ const ResourceRecord& rr = *i;
+ if (rr.ttl < cachettl)
+ cachettl = rr.ttl;
+ }
+
+ cachettl = std::min(cachettl, (unsigned int)5*60);
+ ResourceRecord& rr = r.answers.front();
+ // Set TTL to what we've determined to be the lowest
+ rr.ttl = cachettl;
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "cache: added cache for " + rr.name + " -> " + rr.rdata + " ttl: " + ConvToStr(rr.ttl));
+ this->cache[r.question] = r;
+ }
+
+ public:
+ DNS::Request* requests[MAX_REQUEST_ID+1];
+
+ MyManager(Module* c) : Manager(c), Timer(5*60, true)
+ , unloading(false)
+ {
+ for (unsigned int i = 0; i <= MAX_REQUEST_ID; ++i)
+ requests[i] = NULL;
+ ServerInstance->Timers.AddTimer(this);
+ }
+
+ ~MyManager()
+ {
+ // Ensure Process() will fail for new requests
+ unloading = true;
+
+ for (unsigned int i = 0; i <= MAX_REQUEST_ID; ++i)
+ {
+ DNS::Request* request = requests[i];
+ if (!request)
+ continue;
+
+ Query rr(request->question);
+ rr.error = ERROR_UNKNOWN;
+ request->OnError(&rr);
+
+ delete request;
+ }
+ }
+
+ void Process(DNS::Request* req) CXX11_OVERRIDE
+ {
+ if ((unloading) || (req->creator->dying))
+ throw Exception("Module is being unloaded");
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Processing request to lookup " + req->question.name + " of type " + ConvToStr(req->question.type) + " to " + this->myserver.addr());
+
+ /* Create an id */
+ unsigned int tries = 0;
+ int id;
+ do
+ {
+ id = ServerInstance->GenRandomInt(DNS::MAX_REQUEST_ID+1);
+
+ if (++tries == DNS::MAX_REQUEST_ID*5)
+ {
+ // If we couldn't find an empty slot this many times, do a sequential scan as a last
+ // resort. If an empty slot is found that way, go on, otherwise throw an exception
+ id = -1;
+ for (unsigned int i = 0; i <= DNS::MAX_REQUEST_ID; i++)
+ {
+ if (!this->requests[i])
+ {
+ id = i;
+ break;
+ }
+ }
+
+ if (id == -1)
+ throw Exception("DNS: All ids are in use");
+
+ break;
+ }
+ }
+ while (this->requests[id]);
+
+ req->id = id;
+ this->requests[req->id] = req;
+
+ Packet p;
+ p.flags = QUERYFLAGS_RD;
+ p.id = req->id;
+ p.question = req->question;
+
+ unsigned char buffer[524];
+ unsigned short len = p.Pack(buffer, sizeof(buffer));
+
+ /* Note that calling Pack() above can actually change the contents of p.question.name, if the query is a PTR,
+ * to contain the value that would be in the DNS cache, which is why this is here.
+ */
+ if (req->use_cache && this->CheckCache(req, p.question))
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Using cached result");
+ delete req;
+ return;
+ }
+
+ // Update name in the original request so question checking works for PTR queries
+ req->question.name = p.question.name;
+
+ if (SocketEngine::SendTo(this, buffer, len, 0, this->myserver) != len)
+ throw Exception("DNS: Unable to send query");
+
+ // Add timer for timeout
+ ServerInstance->Timers.AddTimer(req);
+ }
+
+ void RemoveRequest(DNS::Request* req) CXX11_OVERRIDE
+ {
+ if (requests[req->id] == req)
+ requests[req->id] = NULL;
+ }
+
+ std::string GetErrorStr(Error e) CXX11_OVERRIDE
+ {
+ switch (e)
+ {
+ case ERROR_UNLOADED:
+ return "Module is unloading";
+ case ERROR_TIMEDOUT:
+ return "Request timed out";
+ case ERROR_NOT_AN_ANSWER:
+ case ERROR_NONSTANDARD_QUERY:
+ case ERROR_FORMAT_ERROR:
+ case ERROR_MALFORMED:
+ return "Malformed answer";
+ case ERROR_SERVER_FAILURE:
+ case ERROR_NOT_IMPLEMENTED:
+ case ERROR_REFUSED:
+ case ERROR_INVALIDTYPE:
+ return "Nameserver failure";
+ case ERROR_DOMAIN_NOT_FOUND:
+ case ERROR_NO_RECORDS:
+ return "Domain not found";
+ case ERROR_NONE:
+ case ERROR_UNKNOWN:
+ default:
+ return "Unknown error";
+ }
+ }
+
+ void OnEventHandlerError(int errcode) CXX11_OVERRIDE
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "UDP socket got an error event");
+ }
+
+ void OnEventHandlerRead() CXX11_OVERRIDE
+ {
+ unsigned char buffer[524];
+ irc::sockets::sockaddrs from;
+ socklen_t x = sizeof(from);
+
+ int length = SocketEngine::RecvFrom(this, buffer, sizeof(buffer), 0, &from.sa, &x);
+
+ if (length < Packet::HEADER_LENGTH)
+ return;
+
+ if (myserver != from)
+ {
+ std::string server1 = from.str();
+ std::string server2 = myserver.str();
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Got a result from the wrong server! Bad NAT or DNS forging attempt? '%s' != '%s'",
+ server1.c_str(), server2.c_str());
+ return;
+ }
+
+ Packet recv_packet;
+ bool valid = false;
+
+ try
+ {
+ recv_packet.Fill(buffer, length);
+ valid = true;
+ }
+ catch (Exception& ex)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, ex.GetReason());
+ }
+
+ // recv_packet.id must be filled in here
+ DNS::Request* request = this->requests[recv_packet.id];
+ if (request == NULL)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received an answer for something we didn't request");
+ return;
+ }
+
+ if (request->question != recv_packet.question)
+ {
+ // This can happen under high latency, drop it silently, do not fail the request
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received an answer that isn't for a question we asked");
+ return;
+ }
+
+ if (!valid)
+ {
+ ServerInstance->stats.DnsBad++;
+ recv_packet.error = ERROR_MALFORMED;
+ request->OnError(&recv_packet);
+ }
+ else if (recv_packet.flags & QUERYFLAGS_OPCODE)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received a nonstandard query");
+ ServerInstance->stats.DnsBad++;
+ recv_packet.error = ERROR_NONSTANDARD_QUERY;
+ request->OnError(&recv_packet);
+ }
+ else if (!(recv_packet.flags & QUERYFLAGS_QR) || (recv_packet.flags & QUERYFLAGS_RCODE))
+ {
+ Error error = ERROR_UNKNOWN;
+
+ switch (recv_packet.flags & QUERYFLAGS_RCODE)
+ {
+ case 1:
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "format error");
+ error = ERROR_FORMAT_ERROR;
+ break;
+ case 2:
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "server error");
+ error = ERROR_SERVER_FAILURE;
+ break;
+ case 3:
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "domain not found");
+ error = ERROR_DOMAIN_NOT_FOUND;
+ break;
+ case 4:
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "not implemented");
+ error = ERROR_NOT_IMPLEMENTED;
+ break;
+ case 5:
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "refused");
+ error = ERROR_REFUSED;
+ break;
+ default:
+ break;
+ }
+
+ ServerInstance->stats.DnsBad++;
+ recv_packet.error = error;
+ request->OnError(&recv_packet);
+ }
+ else if (recv_packet.answers.empty())
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "No resource records returned");
+ ServerInstance->stats.DnsBad++;
+ recv_packet.error = ERROR_NO_RECORDS;
+ request->OnError(&recv_packet);
+ }
+ else
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Lookup complete for " + request->question.name);
+ ServerInstance->stats.DnsGood++;
+ request->OnLookupComplete(&recv_packet);
+ this->AddCache(recv_packet);
+ }
+
+ ServerInstance->stats.Dns++;
+
+ /* Request's destructor removes it from the request map */
+ delete request;
+ }
+
+ bool Tick(time_t now) CXX11_OVERRIDE
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "cache: purging DNS cache");
+
+ for (cache_map::iterator it = this->cache.begin(); it != this->cache.end(); )
+ {
+ const Query& query = it->second;
+ if (IsExpired(query, now))
+ this->cache.erase(it++);
+ else
+ ++it;
+ }
+ return true;
+ }
+
+ void Rehash(const std::string& dnsserver, std::string sourceaddr, unsigned int sourceport)
+ {
+ if (this->GetFd() > -1)
+ {
+ SocketEngine::Shutdown(this, 2);
+ SocketEngine::Close(this);
+
+ /* Remove expired entries from the cache */
+ this->Tick(ServerInstance->Time());
+ }
+
+ irc::sockets::aptosa(dnsserver, DNS::PORT, myserver);
+
+ /* Initialize mastersocket */
+ int s = socket(myserver.sa.sa_family, SOCK_DGRAM, 0);
+ this->SetFd(s);
+
+ /* Have we got a socket? */
+ if (this->GetFd() != -1)
+ {
+ SocketEngine::SetReuse(s);
+ SocketEngine::NonBlocking(s);
+
+ irc::sockets::sockaddrs bindto;
+ if (sourceaddr.empty())
+ {
+ // set a sourceaddr for irc::sockets::aptosa() based on the servers af type
+ if (myserver.sa.sa_family == AF_INET)
+ sourceaddr = "0.0.0.0";
+ else if (myserver.sa.sa_family == AF_INET6)
+ sourceaddr = "::";
+ }
+ irc::sockets::aptosa(sourceaddr, sourceport, bindto);
+
+ if (SocketEngine::Bind(this->GetFd(), bindto) < 0)
+ {
+ /* Failed to bind */
+ ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Error binding dns socket - hostnames will NOT resolve");
+ SocketEngine::Close(this->GetFd());
+ this->SetFd(-1);
+ }
+ else if (!SocketEngine::AddFd(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE))
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Internal error starting DNS - hostnames will NOT resolve.");
+ SocketEngine::Close(this->GetFd());
+ this->SetFd(-1);
+ }
+
+ if (bindto.sa.sa_family != myserver.sa.sa_family)
+ ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Nameserver address family differs from source address family - hostnames might not resolve");
+ }
+ else
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Error creating DNS socket - hostnames will NOT resolve");
+ }
+ }
+};
+
+class ModuleDNS : public Module
+{
+ MyManager manager;
+ std::string DNSServer;
+ std::string SourceIP;
+ unsigned int SourcePort;
+
+ void FindDNSServer()
+ {
+#ifdef _WIN32
+ // attempt to look up their nameserver from the system
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: <dns:server> not defined, attempting to find a working server in the system settings...");
+
+ PFIXED_INFO pFixedInfo;
+ DWORD dwBufferSize = sizeof(FIXED_INFO);
+ pFixedInfo = (PFIXED_INFO) HeapAlloc(GetProcessHeap(), 0, sizeof(FIXED_INFO));
+
+ if (pFixedInfo)
+ {
+ if (GetNetworkParams(pFixedInfo, &dwBufferSize) == ERROR_BUFFER_OVERFLOW)
+ {
+ HeapFree(GetProcessHeap(), 0, pFixedInfo);
+ pFixedInfo = (PFIXED_INFO) HeapAlloc(GetProcessHeap(), 0, dwBufferSize);
+ }
+
+ if (pFixedInfo)
+ {
+ if (GetNetworkParams(pFixedInfo, &dwBufferSize) == NO_ERROR)
+ DNSServer = pFixedInfo->DnsServerList.IpAddress.String;
+
+ HeapFree(GetProcessHeap(), 0, pFixedInfo);
+ }
+
+ if (!DNSServer.empty())
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "<dns:server> set to '%s' as first active resolver in the system settings.", DNSServer.c_str());
+ return;
+ }
+ }
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No viable nameserver found! Defaulting to nameserver '127.0.0.1'!");
+#else
+ // attempt to look up their nameserver from /etc/resolv.conf
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: <dns:server> not defined, attempting to find working server in /etc/resolv.conf...");
+
+ std::ifstream resolv("/etc/resolv.conf");
+
+ while (resolv >> DNSServer)
+ {
+ if (DNSServer == "nameserver")
+ {
+ resolv >> DNSServer;
+ if (DNSServer.find_first_not_of("0123456789.") == std::string::npos || DNSServer.find_first_not_of("0123456789ABCDEFabcdef:") == std::string::npos)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "<dns:server> set to '%s' as first resolver in /etc/resolv.conf.",DNSServer.c_str());
+ return;
+ }
+ }
+ }
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "/etc/resolv.conf contains no viable nameserver entries! Defaulting to nameserver '127.0.0.1'!");
+#endif
+ DNSServer = "127.0.0.1";
+ }
+
+ public:
+ ModuleDNS() : manager(this)
+ , SourcePort(0)
+ {
+ }
+
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ std::string oldserver = DNSServer;
+ const std::string oldip = SourceIP;
+ const unsigned int oldport = SourcePort;
+
+ ConfigTag* tag = ServerInstance->Config->ConfValue("dns");
+ DNSServer = tag->getString("server");
+ SourceIP = tag->getString("sourceip");
+ SourcePort = tag->getUInt("sourceport", 0, 0, UINT16_MAX);
+
+ if (DNSServer.empty())
+ FindDNSServer();
+
+ if (oldserver != DNSServer || oldip != SourceIP || oldport != SourcePort)
+ this->manager.Rehash(DNSServer, SourceIP, SourcePort);
+ }
+
+ void OnUnloadModule(Module* mod) CXX11_OVERRIDE
+ {
+ for (unsigned int i = 0; i <= MAX_REQUEST_ID; ++i)
+ {
+ DNS::Request* req = this->manager.requests[i];
+ if (!req)
+ continue;
+
+ if (req->creator == mod)
+ {
+ Query rr(req->question);
+ rr.error = ERROR_UNLOADED;
+ req->OnError(&rr);
+
+ delete req;
+ }
+ }
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("DNS support", VF_CORE|VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleDNS)
+
diff --git a/src/coremods/core_hostname_lookup.cpp b/src/coremods/core_hostname_lookup.cpp
new file mode 100644
index 000000000..4320b1e57
--- /dev/null
+++ b/src/coremods/core_hostname_lookup.cpp
@@ -0,0 +1,232 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2013-2016 Adam <Adam@anope.org>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "modules/dns.h"
+
+namespace
+{
+ LocalIntExt* dl;
+ LocalStringExt* ph;
+}
+
+/** Derived from Resolver, and performs user forward/reverse lookups.
+ */
+class UserResolver : public DNS::Request
+{
+ /** UUID we are looking up */
+ const std::string uuid;
+
+ /** True if the lookup is forward, false if is a reverse lookup
+ */
+ const bool fwd;
+
+ public:
+ /** Create a resolver.
+ * @param mgr DNS Manager
+ * @param me this module
+ * @param user The user to begin lookup on
+ * @param to_resolve The IP or host to resolve
+ * @param qt The query type
+ */
+ UserResolver(DNS::Manager* mgr, Module* me, LocalUser* user, const std::string& to_resolve, DNS::QueryType qt)
+ : DNS::Request(mgr, me, to_resolve, qt)
+ , uuid(user->uuid)
+ , fwd(qt == DNS::QUERY_A || qt == DNS::QUERY_AAAA)
+ {
+ }
+
+ /** Called on successful lookup
+ * if a previous result has already come back.
+ * @param r The finished query
+ */
+ void OnLookupComplete(const DNS::Query* r) CXX11_OVERRIDE
+ {
+ LocalUser* bound_user = (LocalUser*)ServerInstance->FindUUID(uuid);
+ if (!bound_user)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Resolution finished for user '%s' who is gone", uuid.c_str());
+ return;
+ }
+
+ const DNS::ResourceRecord* ans_record = r->FindAnswerOfType(this->question.type);
+ if (ans_record == NULL)
+ {
+ OnError(r);
+ return;
+ }
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "DNS result for %s: '%s' -> '%s'", uuid.c_str(), ans_record->name.c_str(), ans_record->rdata.c_str());
+
+ if (!fwd)
+ {
+ // first half of resolution is done. We now need to verify that the host matches.
+ ph->set(bound_user, ans_record->rdata);
+
+ UserResolver* res_forward;
+ if (bound_user->client_sa.sa.sa_family == AF_INET6)
+ {
+ /* IPV6 forward lookup */
+ res_forward = new UserResolver(this->manager, this->creator, bound_user, ans_record->rdata, DNS::QUERY_AAAA);
+ }
+ else
+ {
+ /* IPV4 lookup */
+ res_forward = new UserResolver(this->manager, this->creator, bound_user, ans_record->rdata, DNS::QUERY_A);
+ }
+ try
+ {
+ this->manager->Process(res_forward);
+ }
+ catch (DNS::Exception& e)
+ {
+ delete res_forward;
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error in resolver: " + e.GetReason());
+
+ bound_user->WriteNotice("*** There was an internal error resolving your host, using your IP address (" + bound_user->GetIPString() + ") instead.");
+ dl->set(bound_user, 0);
+ }
+ }
+ else
+ {
+ /* Both lookups completed */
+
+ irc::sockets::sockaddrs* user_ip = &bound_user->client_sa;
+ bool rev_match = false;
+ if (user_ip->sa.sa_family == AF_INET6)
+ {
+ struct in6_addr res_bin;
+ if (inet_pton(AF_INET6, ans_record->rdata.c_str(), &res_bin))
+ {
+ rev_match = !memcmp(&user_ip->in6.sin6_addr, &res_bin, sizeof(res_bin));
+ }
+ }
+ else
+ {
+ struct in_addr res_bin;
+ if (inet_pton(AF_INET, ans_record->rdata.c_str(), &res_bin))
+ {
+ rev_match = !memcmp(&user_ip->in4.sin_addr, &res_bin, sizeof(res_bin));
+ }
+ }
+
+ dl->set(bound_user, 0);
+
+ if (rev_match)
+ {
+ std::string* hostname = ph->get(bound_user);
+
+ if (hostname == NULL)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: User has no hostname attached when doing a forward lookup");
+ bound_user->WriteNotice("*** There was an internal error resolving your host, using your IP address (" + bound_user->GetIPString() + ") instead.");
+ return;
+ }
+ else if (hostname->length() <= ServerInstance->Config->Limits.MaxHost)
+ {
+ /* Hostnames starting with : are not a good thing (tm) */
+ if ((*hostname)[0] == ':')
+ hostname->insert(0, "0");
+
+ bound_user->WriteNotice("*** Found your hostname (" + *hostname + (r->cached ? ") -- cached" : ")"));
+ bound_user->ChangeRealHost(hostname->substr(0, ServerInstance->Config->Limits.MaxHost), true);
+ }
+ else
+ {
+ bound_user->WriteNotice("*** Your hostname is longer than the maximum of " + ConvToStr(ServerInstance->Config->Limits.MaxHost) + " characters, using your IP address (" + bound_user->GetIPString() + ") instead.");
+ }
+
+ ph->unset(bound_user);
+ }
+ else
+ {
+ bound_user->WriteNotice("*** Your hostname does not match up with your IP address. Sorry, using your IP address (" + bound_user->GetIPString() + ") instead.");
+ }
+ }
+ }
+
+ /** Called on failed lookup
+ * @param query The errored query
+ */
+ void OnError(const DNS::Query* query) CXX11_OVERRIDE
+ {
+ LocalUser* bound_user = (LocalUser*)ServerInstance->FindUUID(uuid);
+ if (bound_user)
+ {
+ bound_user->WriteNotice("*** Could not resolve your hostname: " + this->manager->GetErrorStr(query->error) + "; using your IP address (" + bound_user->GetIPString() + ") instead.");
+ dl->set(bound_user, 0);
+ }
+ }
+};
+
+class ModuleHostnameLookup : public Module
+{
+ LocalIntExt dnsLookup;
+ LocalStringExt ptrHosts;
+ dynamic_reference<DNS::Manager> DNS;
+
+ public:
+ ModuleHostnameLookup()
+ : dnsLookup("dnsLookup", ExtensionItem::EXT_USER, this)
+ , ptrHosts("ptrHosts", ExtensionItem::EXT_USER, this)
+ , DNS(this, "DNS")
+ {
+ dl = &dnsLookup;
+ ph = &ptrHosts;
+ }
+
+ void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE
+ {
+ if (!DNS || !user->MyClass->resolvehostnames)
+ {
+ user->WriteNotice("*** Skipping host resolution (disabled by server administrator)");
+ return;
+ }
+
+ user->WriteNotice("*** Looking up your hostname...");
+
+ UserResolver* res_reverse = new UserResolver(*this->DNS, this, user, user->GetIPString(), DNS::QUERY_PTR);
+ try
+ {
+ /* If both the reverse and forward queries are cached, the user will be able to pass DNS completely
+ * before Process() completes, which is why dnsLookup.set() is here, before Process()
+ */
+ this->dnsLookup.set(user, 1);
+ this->DNS->Process(res_reverse);
+ }
+ catch (DNS::Exception& e)
+ {
+ this->dnsLookup.set(user, 0);
+ delete res_reverse;
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error in resolver: " + e.GetReason());
+ }
+ }
+
+ ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
+ {
+ return this->dnsLookup.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU;
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides support for DNS lookups on connecting clients", VF_CORE|VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleHostnameLookup)
diff --git a/src/coremods/core_info/cmd_admin.cpp b/src/coremods/core_info/cmd_admin.cpp
new file mode 100644
index 000000000..f79ebc036
--- /dev/null
+++ b/src/coremods/core_info/cmd_admin.cpp
@@ -0,0 +1,43 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_info.h"
+
+CommandAdmin::CommandAdmin(Module* parent)
+ : ServerTargetCommand(parent, "ADMIN")
+{
+ Penalty = 2;
+ syntax = "[<servername>]";
+}
+
+/** Handle /ADMIN
+ */
+CmdResult CommandAdmin::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName)
+ return CMD_SUCCESS;
+ user->WriteRemoteNumeric(RPL_ADMINME, InspIRCd::Format("Administrative info for %s", ServerInstance->Config->ServerName.c_str()));
+ if (!AdminName.empty())
+ user->WriteRemoteNumeric(RPL_ADMINLOC1, InspIRCd::Format("Name - %s", AdminName.c_str()));
+ user->WriteRemoteNumeric(RPL_ADMINLOC2, InspIRCd::Format("Nickname - %s", AdminNick.c_str()));
+ user->WriteRemoteNumeric(RPL_ADMINEMAIL, InspIRCd::Format("E-Mail - %s", AdminEmail.c_str()));
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_info/cmd_commands.cpp b/src/coremods/core_info/cmd_commands.cpp
new file mode 100644
index 000000000..a7a622f5f
--- /dev/null
+++ b/src/coremods/core_info/cmd_commands.cpp
@@ -0,0 +1,59 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_info.h"
+
+enum
+{
+ // InspIRCd-specific.
+ RPL_COMMANDS = 700,
+ RPL_COMMANDSEND = 701
+};
+
+CommandCommands::CommandCommands(Module* parent)
+ : Command(parent, "COMMANDS", 0, 0)
+{
+ Penalty = 3;
+}
+
+/** Handle /COMMANDS
+ */
+CmdResult CommandCommands::Handle (const std::vector<std::string>&, User *user)
+{
+ const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands();
+ std::vector<std::string> list;
+ list.reserve(commands.size());
+ for (CommandParser::CommandMap::const_iterator i = commands.begin(); i != commands.end(); ++i)
+ {
+ // Don't show S2S commands to users
+ if (i->second->flags_needed == FLAG_SERVERONLY)
+ continue;
+
+ Module* src = i->second->creator;
+ list.push_back(InspIRCd::Format("%s %s %d %d", i->second->name.c_str(), src->ModuleSourceFile.c_str(),
+ i->second->min_params, i->second->Penalty));
+ }
+ std::sort(list.begin(), list.end());
+ for(unsigned int i=0; i < list.size(); i++)
+ user->WriteNumeric(RPL_COMMANDS, list[i]);
+ user->WriteNumeric(RPL_COMMANDSEND, "End of COMMANDS list");
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_info/cmd_info.cpp b/src/coremods/core_info/cmd_info.cpp
new file mode 100644
index 000000000..e84daeccb
--- /dev/null
+++ b/src/coremods/core_info/cmd_info.cpp
@@ -0,0 +1,91 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2011 Jackmcbarn <jackmcbarn@jackmcbarn.no-ip.org>
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2015 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_info.h"
+
+CommandInfo::CommandInfo(Module* parent)
+ : ServerTargetCommand(parent, "INFO")
+{
+ Penalty = 4;
+ syntax = "[<servername>]";
+}
+
+static const char* const lines[] = {
+ " -/\\- \2InspIRCd\2 -\\/-",
+ " November 2002 - Present",
+ " ",
+ "\2Core Developers\2:",
+ " Attila Molnar, Attila, <attilamolnar@hush.com>",
+ " Peter Powell, SaberUK, <petpow@saberuk.com>",
+ " ",
+ "\2Former Developers\2:",
+ " Oliver Lupton, Om, <om@inspircd.org>",
+ " John Brooks, Special, <special@inspircd.org>",
+ " Dennis Friis, peavey, <peavey@inspircd.org>",
+ " Thomas Stagner, aquanight, <aquanight@inspircd.org>",
+ " Uli Schlachter, psychon, <psychon@inspircd.org>",
+ " Matt Smith, dz, <dz@inspircd.org>",
+ " Daniel De Graaf, danieldg, <danieldg@inspircd.org>",
+ " ",
+ "\2Founding Developers\2:",
+ " Craig Edwards, Brain, <brain@inspircd.org>",
+ " Craig McLure, Craig, <craig@inspircd.org>",
+ " Robin Burchell, w00t, <w00t@inspircd.org>",
+ " ",
+ "\2Active Contributors\2:",
+ " Adam",
+ " ",
+ "\2Former Contributors\2:",
+ " dmb Zaba skenmy GreenReaper",
+ " Dan Jason satmd owine",
+ " Adremelech John2 jilles HiroP",
+ " eggy Bricker AnMaster djGrrr",
+ " nenolod Quension praetorian pippijn",
+ " CC jamie typobox43 Burlex (win32)",
+ " Stskeeps ThaPrince BuildSmart Thunderhacker",
+ " Skip LeaChim Majic MacGyver",
+ " Namegduf Ankit Phoenix Taros",
+ " jackmcbarn ChrisTX Shawn Shutter",
+ " ",
+ "\2Thanks To\2:",
+ " Asmo Brik fraggeln genius3000",
+ " Sheogorath",
+ " ",
+ " Best experienced with: \2An IRC client\2",
+ NULL
+};
+
+/** Handle /INFO
+ */
+CmdResult CommandInfo::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName)
+ return CMD_SUCCESS;
+
+ int i=0;
+ while (lines[i])
+ user->WriteRemoteNumeric(RPL_INFO, lines[i++]);
+ FOREACH_MOD(OnInfo, (user));
+ user->WriteRemoteNumeric(RPL_ENDOFINFO, "End of /INFO list");
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_info/cmd_modules.cpp b/src/coremods/core_info/cmd_modules.cpp
new file mode 100644
index 000000000..fa8c2aebb
--- /dev/null
+++ b/src/coremods/core_info/cmd_modules.cpp
@@ -0,0 +1,89 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_info.h"
+
+enum
+{
+ // From ircd-ratbox with an InspIRCd-specific format.
+ RPL_MODLIST = 702,
+ RPL_ENDOFMODLIST = 703
+};
+
+CommandModules::CommandModules(Module* parent)
+ : ServerTargetCommand(parent, "MODULES")
+{
+ Penalty = 4;
+ syntax = "[<servername>]";
+}
+
+/** Handle /MODULES
+ */
+CmdResult CommandModules::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ // Don't ask remote servers about their modules unless the local user asking is an oper
+ // 2.0 asks anyway, so let's handle that the same way
+ bool for_us = (parameters.empty() || parameters[0] == ServerInstance->Config->ServerName);
+ if ((!for_us) || (!IS_LOCAL(user)))
+ {
+ if (!user->IsOper())
+ {
+ user->WriteNotice("*** You cannot check what modules other servers have loaded.");
+ return CMD_FAILURE;
+ }
+
+ // From an oper and not for us, forward
+ if (!for_us)
+ return CMD_SUCCESS;
+ }
+
+ const ModuleManager::ModuleMap& mods = ServerInstance->Modules->GetModules();
+
+ for (ModuleManager::ModuleMap::const_iterator i = mods.begin(); i != mods.end(); ++i)
+ {
+ Module* m = i->second;
+ Version V = m->GetVersion();
+
+ if (IS_LOCAL(user) && user->HasPrivPermission("servers/auspex"))
+ {
+ std::string flags("VCO");
+ size_t pos = 0;
+ for (int mult = 2; mult <= VF_OPTCOMMON; mult *= 2, ++pos)
+ if (!(V.Flags & mult))
+ flags[pos] = '-';
+
+#ifdef INSPIRCD_STATIC
+ user->WriteRemoteNumeric(RPL_MODLIST, m->ModuleSourceFile, INSPIRCD_VERSION, flags, V.description);
+#else
+ std::string srcrev = m->ModuleDLLManager->GetVersion();
+ user->WriteRemoteNumeric(RPL_MODLIST, m->ModuleSourceFile, srcrev.empty() ? "*" : srcrev, flags, V.description);
+#endif
+ }
+ else
+ {
+ user->WriteRemoteNumeric(RPL_MODLIST, m->ModuleSourceFile, '*', '*', V.description);
+ }
+ }
+ user->WriteRemoteNumeric(RPL_ENDOFMODLIST, "End of MODULES list");
+
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_info/cmd_motd.cpp b/src/coremods/core_info/cmd_motd.cpp
new file mode 100644
index 000000000..cfb083eed
--- /dev/null
+++ b/src/coremods/core_info/cmd_motd.cpp
@@ -0,0 +1,63 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_info.h"
+
+CommandMotd::CommandMotd(Module* parent)
+ : ServerTargetCommand(parent, "MOTD")
+{
+ syntax = "[<servername>]";
+}
+
+/** Handle /MOTD
+ */
+CmdResult CommandMotd::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName)
+ {
+ // Give extra penalty if a non-oper queries the /MOTD of a remote server
+ LocalUser* localuser = IS_LOCAL(user);
+ if ((localuser) && (!user->IsOper()))
+ localuser->CommandFloodPenalty += 2000;
+ return CMD_SUCCESS;
+ }
+
+ ConfigTag* tag = ServerInstance->Config->EmptyTag;
+ LocalUser* localuser = IS_LOCAL(user);
+ if (localuser)
+ tag = localuser->GetClass()->config;
+ std::string motd_name = tag->getString("motd", "motd");
+ ConfigFileCache::iterator motd = ServerInstance->Config->Files.find(motd_name);
+ if (motd == ServerInstance->Config->Files.end())
+ {
+ user->WriteRemoteNumeric(ERR_NOMOTD, "Message of the day file is missing.");
+ return CMD_SUCCESS;
+ }
+
+ user->WriteRemoteNumeric(RPL_MOTDSTART, InspIRCd::Format("%s message of the day", ServerInstance->Config->ServerName.c_str()));
+
+ for (file_cache::iterator i = motd->second.begin(); i != motd->second.end(); i++)
+ user->WriteRemoteNumeric(RPL_MOTD, InspIRCd::Format("- %s", i->c_str()));
+
+ user->WriteRemoteNumeric(RPL_ENDOFMOTD, "End of message of the day.");
+
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_info/cmd_time.cpp b/src/coremods/core_info/cmd_time.cpp
new file mode 100644
index 000000000..6755e5837
--- /dev/null
+++ b/src/coremods/core_info/cmd_time.cpp
@@ -0,0 +1,38 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_info.h"
+
+CommandTime::CommandTime(Module* parent)
+ : ServerTargetCommand(parent, "TIME")
+{
+ syntax = "[<servername>]";
+}
+
+CmdResult CommandTime::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName)
+ return CMD_SUCCESS;
+
+ user->WriteRemoteNumeric(RPL_TIME, ServerInstance->Config->ServerName, InspIRCd::TimeString(ServerInstance->Time()));
+
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_info/cmd_version.cpp b/src/coremods/core_info/cmd_version.cpp
new file mode 100644
index 000000000..9ec0108b1
--- /dev/null
+++ b/src/coremods/core_info/cmd_version.cpp
@@ -0,0 +1,40 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_info.h"
+
+CommandVersion::CommandVersion(Module* parent)
+ : Command(parent, "VERSION", 0, 0)
+{
+ syntax = "[<servername>]";
+}
+
+CmdResult CommandVersion::Handle (const std::vector<std::string>&, User *user)
+{
+ std::string version = ServerInstance->GetVersionString((user->IsOper()));
+ user->WriteNumeric(RPL_VERSION, version);
+ LocalUser *lu = IS_LOCAL(user);
+ if (lu != NULL)
+ {
+ ServerInstance->ISupport.SendTo(lu);
+ }
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_info/core_info.cpp b/src/coremods/core_info/core_info.cpp
new file mode 100644
index 000000000..bd519076d
--- /dev/null
+++ b/src/coremods/core_info/core_info.cpp
@@ -0,0 +1,61 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_info.h"
+
+RouteDescriptor ServerTargetCommand::GetRouting(User* user, const std::vector<std::string>& parameters)
+{
+ // Parameter must be a server name, not a nickname or uuid
+ if ((!parameters.empty()) && (parameters[0].find('.') != std::string::npos))
+ return ROUTE_UNICAST(parameters[0]);
+ return ROUTE_LOCALONLY;
+}
+
+class CoreModInfo : public Module
+{
+ CommandAdmin cmdadmin;
+ CommandCommands cmdcommands;
+ CommandInfo cmdinfo;
+ CommandModules cmdmodules;
+ CommandMotd cmdmotd;
+ CommandTime cmdtime;
+ CommandVersion cmdversion;
+
+ public:
+ CoreModInfo()
+ : cmdadmin(this), cmdcommands(this), cmdinfo(this), cmdmodules(this), cmdmotd(this), cmdtime(this), cmdversion(this)
+ {
+ }
+
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ ConfigTag* tag = ServerInstance->Config->ConfValue("admin");
+ cmdadmin.AdminName = tag->getString("name");
+ cmdadmin.AdminEmail = tag->getString("email", "null@example.com");
+ cmdadmin.AdminNick = tag->getString("nick", "admin");
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the ADMIN, COMMANDS, INFO, MODULES, MOTD, TIME and VERSION commands", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModInfo)
diff --git a/src/coremods/core_info/core_info.h b/src/coremods/core_info/core_info.h
new file mode 100644
index 000000000..53b949ac5
--- /dev/null
+++ b/src/coremods/core_info/core_info.h
@@ -0,0 +1,169 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "inspircd.h"
+
+/** These commands require no parameters, but if there is a parameter it is a server name where the command will be routed to.
+ */
+class ServerTargetCommand : public Command
+{
+ public:
+ ServerTargetCommand(Module* mod, const std::string& Name)
+ : Command(mod, Name)
+ {
+ }
+
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE;
+};
+
+/** Handle /ADMIN.
+ */
+class CommandAdmin : public ServerTargetCommand
+{
+ public:
+ /** Holds the admin's name, for output in
+ * the /ADMIN command.
+ */
+ std::string AdminName;
+
+ /** Holds the email address of the admin,
+ * for output in the /ADMIN command.
+ */
+ std::string AdminEmail;
+
+ /** Holds the admin's nickname, for output
+ * in the /ADMIN command
+ */
+ std::string AdminNick;
+
+ /** Constructor for admin.
+ */
+ CommandAdmin(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /COMMANDS.
+ */
+class CommandCommands : public Command
+{
+ public:
+ /** Constructor for commands.
+ */
+ CommandCommands(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /INFO.
+ */
+class CommandInfo : public ServerTargetCommand
+{
+ public:
+ /** Constructor for info.
+ */
+ CommandInfo(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /MODULES.
+ */
+class CommandModules : public ServerTargetCommand
+{
+ public:
+ /** Constructor for modules.
+ */
+ CommandModules(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /MOTD.
+ */
+class CommandMotd : public ServerTargetCommand
+{
+ public:
+ /** Constructor for motd.
+ */
+ CommandMotd(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /TIME.
+ */
+class CommandTime : public ServerTargetCommand
+{
+ public:
+ /** Constructor for time.
+ */
+ CommandTime(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /VERSION.
+ */
+class CommandVersion : public Command
+{
+ public:
+ /** Constructor for version.
+ */
+ CommandVersion(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
diff --git a/src/coremods/core_ison.cpp b/src/coremods/core_ison.cpp
new file mode 100644
index 000000000..642e36b43
--- /dev/null
+++ b/src/coremods/core_ison.cpp
@@ -0,0 +1,81 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+/** Handle /ISON.
+ */
+class CommandIson : public SplitCommand
+{
+ public:
+ /** Constructor for ison.
+ */
+ CommandIson(Module* parent)
+ : SplitCommand(parent, "ISON", 1)
+ {
+ syntax = "<nick> {nick}";
+ }
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+};
+
+class IsonReplyBuilder : public Numeric::Builder<' ', true>
+{
+ public:
+ IsonReplyBuilder(LocalUser* user)
+ : Numeric::Builder<' ', true>(user, RPL_ISON)
+ {
+ }
+
+ void AddNick(const std::string& nickname)
+ {
+ User* const user = ServerInstance->FindNickOnly(nickname);
+ if ((user) && (user->registered == REG_ALL))
+ Add(user->nick);
+ }
+};
+
+/** Handle /ISON
+ */
+CmdResult CommandIson::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
+{
+ IsonReplyBuilder reply(user);
+
+ for (std::vector<std::string>::const_iterator i = parameters.begin(); i != parameters.end()-1; ++i)
+ {
+ const std::string& targetstr = *i;
+ reply.AddNick(targetstr);
+ }
+
+ // Last parameter can be a space separated list
+ irc::spacesepstream ss(parameters.back());
+ for (std::string token; ss.GetToken(token); )
+ reply.AddNick(token);
+
+ reply.Flush();
+ return CMD_SUCCESS;
+}
+
+
+COMMAND_INIT(CommandIson)
diff --git a/src/coremods/core_list.cpp b/src/coremods/core_list.cpp
new file mode 100644
index 000000000..600ec47c2
--- /dev/null
+++ b/src/coremods/core_list.cpp
@@ -0,0 +1,215 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+/** Handle /LIST.
+ */
+class CommandList : public Command
+{
+ private:
+ ChanModeReference secretmode;
+ ChanModeReference privatemode;
+
+ /** Parses the creation time or topic set time out of a LIST parameter.
+ * @param value The parameter containing a minute count.
+ * @return The UNIX time at \p value minutes ago.
+ */
+ time_t ParseMinutes(const std::string& value)
+ {
+ time_t minutes = ConvToNum<time_t>(value.c_str() + 2);
+ if (!minutes)
+ return 0;
+ return ServerInstance->Time() - (minutes * 60);
+ }
+
+ public:
+ /** Constructor for list.
+ */
+ CommandList(Module* parent)
+ : Command(parent,"LIST", 0, 0)
+ , secretmode(creator, "secret")
+ , privatemode(creator, "private")
+ {
+ Penalty = 5;
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+
+/** Handle /LIST
+ */
+CmdResult CommandList::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ // C: Searching based on creation time, via the "C<val" and "C>val" modifiers
+ // to search for a channel creation time that is lower or higher than val
+ // respectively.
+ time_t mincreationtime = 0;
+ time_t maxcreationtime = 0;
+
+ // M: Searching based on mask.
+ // N: Searching based on !mask.
+ bool match_name_topic = false;
+ bool match_inverted = false;
+ const char* match = NULL;
+
+ // T: Searching based on topic time, via the "T<val" and "T>val" modifiers to
+ // search for a topic time that is lower or higher than val respectively.
+ time_t mintopictime = 0;
+ time_t maxtopictime = 0;
+
+ // U: Searching based on user count within the channel, via the "<val" and
+ // ">val" modifiers to search for a channel that has less than or more than
+ // val users respectively.
+ size_t minusers = 0;
+ size_t maxusers = 0;
+
+ if ((parameters.size() == 1) && (!parameters[0].empty()))
+ {
+ if (parameters[0][0] == '<')
+ {
+ maxusers = ConvToNum<size_t>(parameters[0].c_str() + 1);
+ }
+ else if (parameters[0][0] == '>')
+ {
+ minusers = ConvToNum<size_t>(parameters[0].c_str() + 1);
+ }
+ else if (!parameters[0].compare(0, 2, "C<", 2))
+ {
+ mincreationtime = ParseMinutes(parameters[0]);
+ }
+ else if (!parameters[0].compare(0, 2, "C>", 2))
+ {
+ maxcreationtime = ParseMinutes(parameters[0]);
+ }
+ else if (!parameters[0].compare(0, 2, "T<", 2))
+ {
+ mintopictime = ParseMinutes(parameters[0]);
+ }
+ else if (!parameters[0].compare(0, 2, "T>", 2))
+ {
+ maxtopictime = ParseMinutes(parameters[0]);
+ }
+ else
+ {
+ // If the glob is prefixed with ! it is inverted.
+ match = parameters[0].c_str();
+ if (match[0] == '!')
+ {
+ match_inverted = true;
+ match += 1;
+ }
+
+ // Ensure that the user didn't just run "LIST !".
+ if (match[0])
+ match_name_topic = true;
+ }
+ }
+
+ const bool has_privs = user->HasPrivPermission("channels/auspex");
+
+ user->WriteNumeric(RPL_LISTSTART, "Channel", "Users Name");
+ const chan_hash& chans = ServerInstance->GetChans();
+ for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i)
+ {
+ Channel* const chan = i->second;
+
+ // Check the user count if a search has been specified.
+ const size_t users = chan->GetUserCounter();
+ if ((minusers && users <= minusers) || (maxusers && users >= maxusers))
+ continue;
+
+ // Check the creation ts if a search has been specified.
+ const time_t creationtime = chan->age;
+ if ((mincreationtime && creationtime <= mincreationtime) || (maxcreationtime && creationtime >= maxcreationtime))
+ continue;
+
+ // Check the topic ts if a search has been specified.
+ const time_t topictime = chan->topicset;
+ if ((mintopictime && (!topictime || topictime <= mintopictime)) || (maxtopictime && (!topictime || topictime >= maxtopictime)))
+ continue;
+
+ // Attempt to match a glob pattern.
+ if (match_name_topic)
+ {
+ bool matches = InspIRCd::Match(chan->name, match) || InspIRCd::Match(chan->topic, match);
+
+ // The user specified an match that we did not match.
+ if (!matches && !match_inverted)
+ continue;
+
+ // The user specified an inverted match that we did match.
+ if (matches && match_inverted)
+ continue;
+ }
+
+ // if the channel is not private/secret, OR the user is on the channel anyway
+ bool n = (has_privs || chan->HasUser(user));
+
+ // If we're not in the channel and +s is set on it, we want to ignore it
+ if ((n) || (!chan->IsModeSet(secretmode)))
+ {
+ if ((!n) && (chan->IsModeSet(privatemode)))
+ {
+ // Channel is private (+p) and user is outside/not privileged
+ user->WriteNumeric(RPL_LIST, '*', users, "");
+ }
+ else
+ {
+ /* User is in the channel/privileged, channel is not +s */
+ user->WriteNumeric(RPL_LIST, chan->name, users, InspIRCd::Format("[+%s] %s", chan->ChanModes(n), chan->topic.c_str()));
+ }
+ }
+ }
+ user->WriteNumeric(RPL_LISTEND, "End of channel list.");
+
+ return CMD_SUCCESS;
+}
+
+class CoreModList : public Module
+{
+ private:
+ CommandList cmd;
+
+ public:
+ CoreModList()
+ : cmd(this)
+ {
+ }
+
+ void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
+ {
+ tokens["ELIST"] = "CMNTU";
+ tokens["SAFELIST"];
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the LIST command", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModList)
diff --git a/src/coremods/core_loadmodule.cpp b/src/coremods/core_loadmodule.cpp
new file mode 100644
index 000000000..1064da6c8
--- /dev/null
+++ b/src/coremods/core_loadmodule.cpp
@@ -0,0 +1,125 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+/** Handle /LOADMODULE.
+ */
+class CommandLoadmodule : public Command
+{
+ public:
+ /** Constructor for loadmodule.
+ */
+ CommandLoadmodule ( Module* parent) : Command(parent,"LOADMODULE",1,1) { flags_needed='o'; syntax = "<modulename>"; }
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /LOADMODULE
+ */
+CmdResult CommandLoadmodule::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ if (ServerInstance->Modules->Load(parameters[0]))
+ {
+ ServerInstance->SNO->WriteGlobalSno('a', "NEW MODULE: %s loaded %s",user->nick.c_str(), parameters[0].c_str());
+ user->WriteNumeric(RPL_LOADEDMODULE, parameters[0], "Module successfully loaded.");
+ return CMD_SUCCESS;
+ }
+ else
+ {
+ user->WriteNumeric(ERR_CANTLOADMODULE, parameters[0], ServerInstance->Modules->LastError());
+ return CMD_FAILURE;
+ }
+}
+
+/** Handle /UNLOADMODULE.
+ */
+class CommandUnloadmodule : public Command
+{
+ public:
+ /** Constructor for unloadmodule.
+ */
+ CommandUnloadmodule(Module* parent)
+ : Command(parent,"UNLOADMODULE", 1)
+ {
+ flags_needed = 'o';
+ syntax = "<modulename>";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+CmdResult CommandUnloadmodule::Handle(const std::vector<std::string>& parameters, User* user)
+{
+ if (!ServerInstance->Config->ConfValue("security")->getBool("allowcoreunload") &&
+ InspIRCd::Match(parameters[0], "core_*.so", ascii_case_insensitive_map))
+ {
+ user->WriteNumeric(ERR_CANTUNLOADMODULE, parameters[0], "You cannot unload core commands!");
+ return CMD_FAILURE;
+ }
+
+ Module* m = ServerInstance->Modules->Find(parameters[0]);
+ if (m == creator)
+ {
+ user->WriteNumeric(ERR_CANTUNLOADMODULE, parameters[0], "You cannot unload module loading commands!");
+ return CMD_FAILURE;
+ }
+
+ if (m && ServerInstance->Modules->Unload(m))
+ {
+ ServerInstance->SNO->WriteGlobalSno('a', "MODULE UNLOADED: %s unloaded %s", user->nick.c_str(), parameters[0].c_str());
+ user->WriteNumeric(RPL_UNLOADEDMODULE, parameters[0], "Module successfully unloaded.");
+ }
+ else
+ {
+ user->WriteNumeric(ERR_CANTUNLOADMODULE, parameters[0], (m ? ServerInstance->Modules->LastError() : "No such module"));
+ return CMD_FAILURE;
+ }
+
+ return CMD_SUCCESS;
+}
+
+class CoreModLoadModule : public Module
+{
+ CommandLoadmodule cmdloadmod;
+ CommandUnloadmodule cmdunloadmod;
+
+ public:
+ CoreModLoadModule()
+ : cmdloadmod(this), cmdunloadmod(this)
+ {
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the LOADMODULE and UNLOADMODULE commands", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModLoadModule)
diff --git a/src/coremods/core_lusers.cpp b/src/coremods/core_lusers.cpp
new file mode 100644
index 000000000..a995e59e7
--- /dev/null
+++ b/src/coremods/core_lusers.cpp
@@ -0,0 +1,173 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+struct LusersCounters
+{
+ unsigned int max_local;
+ unsigned int max_global;
+ unsigned int invisible;
+
+ LusersCounters(unsigned int inv)
+ : max_local(ServerInstance->Users->LocalUserCount())
+ , max_global(ServerInstance->Users->RegisteredUserCount())
+ , invisible(inv)
+ {
+ }
+
+ inline void UpdateMaxUsers()
+ {
+ unsigned int current = ServerInstance->Users->LocalUserCount();
+ if (current > max_local)
+ max_local = current;
+
+ current = ServerInstance->Users->RegisteredUserCount();
+ if (current > max_global)
+ max_global = current;
+ }
+};
+
+/** Handle /LUSERS.
+ */
+class CommandLusers : public Command
+{
+ LusersCounters& counters;
+ public:
+ /** Constructor for lusers.
+ */
+ CommandLusers(Module* parent, LusersCounters& Counters)
+ : Command(parent,"LUSERS",0,0), counters(Counters)
+ { }
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /LUSERS
+ */
+CmdResult CommandLusers::Handle (const std::vector<std::string>&, User *user)
+{
+ unsigned int n_users = ServerInstance->Users->RegisteredUserCount();
+ ProtocolInterface::ServerList serverlist;
+ ServerInstance->PI->GetServerList(serverlist);
+ unsigned int n_serv = serverlist.size();
+ unsigned int n_local_servs = 0;
+ for (ProtocolInterface::ServerList::const_iterator i = serverlist.begin(); i != serverlist.end(); ++i)
+ {
+ if (i->parentname == ServerInstance->Config->ServerName)
+ n_local_servs++;
+ }
+ // fix for default GetServerList not returning us
+ if (!n_serv)
+ n_serv = 1;
+
+ counters.UpdateMaxUsers();
+
+ user->WriteNumeric(RPL_LUSERCLIENT, InspIRCd::Format("There are %d users and %d invisible on %d servers",
+ n_users - counters.invisible, counters.invisible, n_serv));
+
+ if (ServerInstance->Users->OperCount())
+ user->WriteNumeric(RPL_LUSEROP, ServerInstance->Users.OperCount(), "operator(s) online");
+
+ if (ServerInstance->Users->UnregisteredUserCount())
+ user->WriteNumeric(RPL_LUSERUNKNOWN, ServerInstance->Users.UnregisteredUserCount(), "unknown connections");
+
+ user->WriteNumeric(RPL_LUSERCHANNELS, ServerInstance->GetChans().size(), "channels formed");
+ user->WriteNumeric(RPL_LUSERME, InspIRCd::Format("I have %d clients and %d servers", ServerInstance->Users.LocalUserCount(), n_local_servs));
+ user->WriteNumeric(RPL_LOCALUSERS, InspIRCd::Format("Current local users: %d Max: %d", ServerInstance->Users.LocalUserCount(), counters.max_local));
+ user->WriteNumeric(RPL_GLOBALUSERS, InspIRCd::Format("Current global users: %d Max: %d", n_users, counters.max_global));
+
+ return CMD_SUCCESS;
+}
+
+class InvisibleWatcher : public ModeWatcher
+{
+ unsigned int& invisible;
+public:
+ InvisibleWatcher(Module* mod, unsigned int& Invisible)
+ : ModeWatcher(mod, "invisible", MODETYPE_USER), invisible(Invisible)
+ {
+ }
+
+ void AfterMode(User* source, User* dest, Channel* channel, const std::string& parameter, bool adding) CXX11_OVERRIDE
+ {
+ if (dest->registered != REG_ALL)
+ return;
+
+ if (adding)
+ invisible++;
+ else
+ invisible--;
+ }
+};
+
+class ModuleLusers : public Module
+{
+ UserModeReference invisiblemode;
+ LusersCounters counters;
+ CommandLusers cmd;
+ InvisibleWatcher mw;
+
+ unsigned int CountInvisible()
+ {
+ unsigned int c = 0;
+ const user_hash& users = ServerInstance->Users->GetUsers();
+ for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i)
+ {
+ User* u = i->second;
+ if (u->IsModeSet(invisiblemode))
+ c++;
+ }
+ return c;
+ }
+
+ public:
+ ModuleLusers()
+ : invisiblemode(this, "invisible")
+ , counters(CountInvisible())
+ , cmd(this, counters)
+ , mw(this, counters.invisible)
+ {
+ }
+
+ void OnPostConnect(User* user) CXX11_OVERRIDE
+ {
+ counters.UpdateMaxUsers();
+ if (user->IsModeSet(invisiblemode))
+ counters.invisible++;
+ }
+
+ void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE
+ {
+ if (user->IsModeSet(invisiblemode))
+ counters.invisible--;
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("LUSERS", VF_VENDOR | VF_CORE);
+ }
+};
+
+MODULE_INIT(ModuleLusers)
diff --git a/src/coremods/core_oper/cmd_die.cpp b/src/coremods/core_oper/cmd_die.cpp
new file mode 100644
index 000000000..5fe643520
--- /dev/null
+++ b/src/coremods/core_oper/cmd_die.cpp
@@ -0,0 +1,81 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "exitcodes.h"
+#include "core_oper.h"
+
+CommandDie::CommandDie(Module* parent)
+ : Command(parent, "DIE", 1)
+{
+ flags_needed = 'o';
+ syntax = "<server>";
+}
+
+static void QuitAll()
+{
+ const std::string quitmsg = "Server shutdown";
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ while (!list.empty())
+ ServerInstance->Users.QuitUser(list.front(), quitmsg);
+}
+
+void DieRestart::SendError(const std::string& message)
+{
+ const std::string unregline = "ERROR :" + message;
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ LocalUser* user = *i;
+ if (user->registered == REG_ALL)
+ {
+ user->WriteNotice(message);
+ }
+ else
+ {
+ // Unregistered connections receive ERROR, not a NOTICE
+ user->Write(unregline);
+ }
+ }
+}
+
+/** Handle /DIE
+ */
+CmdResult CommandDie::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ if (DieRestart::CheckPass(user, parameters[0], "diepass"))
+ {
+ {
+ std::string diebuf = "*** DIE command from " + user->GetFullHost() + ". Terminating.";
+ ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, diebuf);
+ DieRestart::SendError(diebuf);
+ }
+
+ QuitAll();
+ ServerInstance->Exit(EXIT_STATUS_DIE);
+ }
+ else
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Failed /DIE command from %s", user->GetFullRealHost().c_str());
+ ServerInstance->SNO->WriteGlobalSno('a', "Failed DIE Command from %s.", user->GetFullRealHost().c_str());
+ return CMD_FAILURE;
+ }
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_oper/cmd_kill.cpp b/src/coremods/core_oper/cmd_kill.cpp
new file mode 100644
index 000000000..20bbe5a26
--- /dev/null
+++ b/src/coremods/core_oper/cmd_kill.cpp
@@ -0,0 +1,145 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_oper.h"
+
+CommandKill::CommandKill(Module* parent)
+ : Command(parent, "KILL", 2, 2)
+{
+ flags_needed = 'o';
+ syntax = "<nickname> <reason>";
+ TRANSLATE2(TR_CUSTOM, TR_CUSTOM);
+}
+
+
+/** Handle /KILL
+ */
+CmdResult CommandKill::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ /* Allow comma seperated lists of users for /KILL (thanks w00t) */
+ if (CommandParser::LoopCall(user, this, parameters, 0))
+ {
+ // If we got a colon delimited list of nicks then the handler ran for each nick,
+ // and KILL commands were broadcast for remote targets.
+ return CMD_FAILURE;
+ }
+
+ User* target = ServerInstance->FindNick(parameters[0]);
+ if (!target)
+ {
+ user->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
+ return CMD_FAILURE;
+ }
+
+ /*
+ * Here, we need to decide how to munge kill messages. Whether to hide killer, what to show opers, etc.
+ * We only do this when the command is being issued LOCALLY, for remote KILL, we just copy the message we got.
+ *
+ * This conditional is so that we only append the "Killed (" prefix ONCE. If killer is remote, then the kill
+ * just gets processed and passed on, otherwise, if they are local, it gets prefixed. Makes sense :-) -- w00t
+ */
+
+ if (IS_LOCAL(user))
+ {
+ /*
+ * Moved this event inside the IS_LOCAL check also, we don't want half the network killing a user
+ * and the other half not. This would be a bad thing. ;p -- w00t
+ */
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnKill, MOD_RESULT, (user, target, parameters[1]));
+
+ if (MOD_RESULT == MOD_RES_DENY)
+ return CMD_FAILURE;
+
+ killreason = "Killed (";
+ if (!ServerInstance->Config->HideKillsServer.empty())
+ {
+ // hidekills is on, use it
+ killreason += ServerInstance->Config->HideKillsServer;
+ }
+ else
+ {
+ // hidekills is off, do nothing
+ killreason += user->nick;
+ }
+
+ killreason += " (" + parameters[1] + "))";
+ }
+ else
+ {
+ /* Leave it alone, remote server has already formatted it */
+ killreason.assign(parameters[1], 0, ServerInstance->Config->Limits.MaxQuit);
+ }
+
+ if ((!ServerInstance->Config->HideULineKills) || (!user->server->IsULine()))
+ {
+ if (IS_LOCAL(user) && IS_LOCAL(target))
+ ServerInstance->SNO->WriteGlobalSno('k', "Local kill by %s: %s (%s)", user->nick.c_str(), target->GetFullRealHost().c_str(), parameters[1].c_str());
+ else
+ ServerInstance->SNO->WriteToSnoMask('K', "Remote kill by %s: %s (%s)", user->nick.c_str(), target->GetFullRealHost().c_str(), parameters[1].c_str());
+ }
+
+ if (IS_LOCAL(user) || IS_LOCAL(target))
+ ServerInstance->Logs->Log("KILL", LOG_DEFAULT, "%s KILL: %s :%s!%s!%s (%s)",
+ IS_LOCAL(user) && IS_LOCAL(target) ? "LOCAL" : "REMOTE",
+ target->nick.c_str(),
+ ServerInstance->Config->ServerName.c_str(), user->GetDisplayedHost().c_str(), user->nick.c_str(),
+ parameters[1].c_str());
+
+ if (IS_LOCAL(target))
+ {
+ target->Write(":%s KILL %s :%s",
+ ServerInstance->Config->HideKillsServer.empty() ? user->GetFullHost().c_str() : ServerInstance->Config->HideKillsServer.c_str(),
+ target->nick.c_str(),
+ parameters[1].c_str());
+
+ this->lastuuid.clear();
+ }
+ else
+ {
+ this->lastuuid = target->uuid;
+ }
+
+ // send the quit out
+ ServerInstance->Users->QuitUser(target, killreason);
+
+ return CMD_SUCCESS;
+}
+
+RouteDescriptor CommandKill::GetRouting(User* user, const std::vector<std::string>& parameters)
+{
+ // FindNick() doesn't work here because we quit the target user in Handle() which
+ // removes it from the nicklist, so we check lastuuid: if it's empty then this KILL
+ // was for a local user, otherwise it contains the uuid of the user who was killed.
+ if (lastuuid.empty())
+ return ROUTE_LOCALONLY;
+ return ROUTE_BROADCAST;
+}
+
+
+void CommandKill::EncodeParameter(std::string& param, unsigned int index)
+{
+ // Manually translate the nick -> uuid (see above), and also the reason (params[1])
+ // because we decorate it if the oper is local and want remote servers to see the
+ // decorated reason not the original.
+ param = ((index == 0) ? lastuuid : killreason);
+}
diff --git a/src/coremods/core_oper/cmd_oper.cpp b/src/coremods/core_oper/cmd_oper.cpp
new file mode 100644
index 000000000..0322a059a
--- /dev/null
+++ b/src/coremods/core_oper/cmd_oper.cpp
@@ -0,0 +1,72 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_oper.h"
+
+CommandOper::CommandOper(Module* parent)
+ : SplitCommand(parent, "OPER", 2, 2)
+{
+ syntax = "<username> <password>";
+}
+
+CmdResult CommandOper::HandleLocal(const std::vector<std::string>& parameters, LocalUser *user)
+{
+ bool match_login = false;
+ bool match_pass = false;
+ bool match_hosts = false;
+
+ const std::string userHost = user->ident + "@" + user->GetRealHost();
+ const std::string userIP = user->ident + "@" + user->GetIPString();
+
+ ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]);
+ if (i != ServerInstance->Config->oper_blocks.end())
+ {
+ OperInfo* ifo = i->second;
+ ConfigTag* tag = ifo->oper_block;
+ match_login = true;
+ match_pass = ServerInstance->PassCompare(user, tag->getString("password"), parameters[1], tag->getString("hash"));
+ match_hosts = InspIRCd::MatchMask(tag->getString("host"), userHost, userIP);
+
+ if (match_pass && match_hosts)
+ {
+ /* found this oper's opertype */
+ user->Oper(ifo);
+ return CMD_SUCCESS;
+ }
+ }
+
+ std::string fields;
+ if (!match_login)
+ fields.append("login ");
+ if (!match_pass)
+ fields.append("password ");
+ if (!match_hosts)
+ fields.append("hosts");
+
+ // tell them they suck, and lag them up to help prevent brute-force attacks
+ user->WriteNumeric(ERR_NOOPERHOST, "Invalid oper credentials");
+ user->CommandFloodPenalty += 10000;
+
+ ServerInstance->SNO->WriteGlobalSno('o', "WARNING! Failed oper attempt by %s using login '%s': The following fields do not match: %s", user->GetFullRealHost().c_str(), parameters[0].c_str(), fields.c_str());
+ ServerInstance->Logs->Log("OPER", LOG_DEFAULT, "OPER: Failed oper attempt by %s using login '%s': The following fields did not match: %s", user->GetFullRealHost().c_str(), parameters[0].c_str(), fields.c_str());
+ return CMD_FAILURE;
+}
diff --git a/src/coremods/core_oper/cmd_rehash.cpp b/src/coremods/core_oper/cmd_rehash.cpp
new file mode 100644
index 000000000..5ce38eb2c
--- /dev/null
+++ b/src/coremods/core_oper/cmd_rehash.cpp
@@ -0,0 +1,94 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_oper.h"
+
+CommandRehash::CommandRehash(Module* parent)
+ : Command(parent, "REHASH", 0)
+{
+ flags_needed = 'o';
+ Penalty = 2;
+ syntax = "[<servermask>]";
+}
+
+CmdResult CommandRehash::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ std::string param = parameters.size() ? parameters[0] : "";
+
+ FOREACH_MOD(OnPreRehash, (user, param));
+
+ if (param.empty())
+ {
+ // standard rehash of local server
+ }
+ else if (param.find_first_of("*.") != std::string::npos)
+ {
+ // rehash of servers by server name (with wildcard)
+ if (!InspIRCd::Match(ServerInstance->Config->ServerName, parameters[0]))
+ {
+ // Doesn't match us. PreRehash is already done, nothing left to do
+ return CMD_SUCCESS;
+ }
+ }
+ else
+ {
+ // parameterized rehash
+
+ // the leading "-" is optional; remove it if present.
+ if (param[0] == '-')
+ param.erase(param.begin());
+
+ FOREACH_MOD(OnModuleRehash, (user, param));
+ return CMD_SUCCESS;
+ }
+
+ // Rehash for me. Try to start the rehash thread
+ if (!ServerInstance->ConfigThread)
+ {
+ std::string m = user->nick + " is rehashing config file " + FileSystem::GetFileName(ServerInstance->ConfigFileName) + " on " + ServerInstance->Config->ServerName;
+ ServerInstance->SNO->WriteGlobalSno('a', m);
+
+ if (IS_LOCAL(user))
+ user->WriteNumeric(RPL_REHASHING, FileSystem::GetFileName(ServerInstance->ConfigFileName), "Rehashing");
+ else
+ ServerInstance->PI->SendUserNotice(user, "*** Rehashing server " + FileSystem::GetFileName(ServerInstance->ConfigFileName));
+
+ /* Don't do anything with the logs here -- logs are restarted
+ * after the config thread has completed.
+ */
+ ServerInstance->Rehash(user->uuid);
+ }
+ else
+ {
+ /*
+ * A rehash is already in progress! ahh shit.
+ * XXX, todo: we should find some way to kill runaway rehashes that are blocking, this is a major problem for unrealircd users
+ */
+ if (IS_LOCAL(user))
+ user->WriteNotice("*** Could not rehash: A rehash is already in progress.");
+ else
+ ServerInstance->PI->SendUserNotice(user, "*** Could not rehash: A rehash is already in progress.");
+ }
+
+ // Always return success so spanningtree forwards an incoming REHASH even if we failed
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_oper/cmd_restart.cpp b/src/coremods/core_oper/cmd_restart.cpp
new file mode 100644
index 000000000..6c19329c3
--- /dev/null
+++ b/src/coremods/core_oper/cmd_restart.cpp
@@ -0,0 +1,64 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_oper.h"
+
+CommandRestart::CommandRestart(Module* parent)
+ : Command(parent, "RESTART", 1, 1)
+{
+ flags_needed = 'o';
+ syntax = "<server>";
+}
+
+CmdResult CommandRestart::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Restart: %s", user->nick.c_str());
+ if (DieRestart::CheckPass(user, parameters[0], "restartpass"))
+ {
+ ServerInstance->SNO->WriteGlobalSno('a', "RESTART command from %s, restarting server.", user->GetFullRealHost().c_str());
+
+ DieRestart::SendError("Server restarting.");
+
+#ifndef _WIN32
+ /* XXX: This hack sets FD_CLOEXEC on all possible file descriptors, so they're closed if the execvp() below succeeds.
+ * Certainly, this is not a nice way to do things and it's slow when the fd limit is high.
+ *
+ * A better solution would be to set the close-on-exec flag for each fd we create (or create them with O_CLOEXEC),
+ * however there is no guarantee that third party libs will do the same.
+ */
+ for (int i = getdtablesize(); --i > 2;)
+ {
+ int flags = fcntl(i, F_GETFD);
+ if (flags != -1)
+ fcntl(i, F_SETFD, flags | FD_CLOEXEC);
+ }
+#endif
+
+ execvp(ServerInstance->Config->cmdline.argv[0], ServerInstance->Config->cmdline.argv);
+ ServerInstance->SNO->WriteGlobalSno('a', "Failed RESTART - could not execute '%s' (%s)",
+ ServerInstance->Config->cmdline.argv[0], strerror(errno));
+ }
+ else
+ {
+ ServerInstance->SNO->WriteGlobalSno('a', "Failed RESTART Command from %s.", user->GetFullRealHost().c_str());
+ }
+ return CMD_FAILURE;
+}
diff --git a/src/coremods/core_oper/core_oper.cpp b/src/coremods/core_oper/core_oper.cpp
new file mode 100644
index 000000000..a6b2abd81
--- /dev/null
+++ b/src/coremods/core_oper/core_oper.cpp
@@ -0,0 +1,55 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_oper.h"
+
+namespace DieRestart
+{
+ bool CheckPass(User* user, const std::string& inputpass, const char* confentry)
+ {
+ ConfigTag* tag = ServerInstance->Config->ConfValue("power");
+ // The hash method for *BOTH* the die and restart passwords
+ const std::string hash = tag->getString("hash");
+ const std::string correctpass = tag->getString(confentry, ServerInstance->Config->ServerName);
+ return ServerInstance->PassCompare(user, correctpass, inputpass, hash);
+ }
+}
+
+class CoreModOper : public Module
+{
+ CommandDie cmddie;
+ CommandKill cmdkill;
+ CommandOper cmdoper;
+ CommandRehash cmdrehash;
+ CommandRestart cmdrestart;
+
+ public:
+ CoreModOper()
+ : cmddie(this), cmdkill(this), cmdoper(this), cmdrehash(this), cmdrestart(this)
+ {
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the DIE, KILL, OPER, REHASH, and RESTART commands", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModOper)
diff --git a/src/coremods/core_oper/core_oper.h b/src/coremods/core_oper/core_oper.h
new file mode 100644
index 000000000..be615239e
--- /dev/null
+++ b/src/coremods/core_oper/core_oper.h
@@ -0,0 +1,129 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "inspircd.h"
+
+namespace DieRestart
+{
+ /** Checks a die or restart password
+ * @param user The user executing /DIE or /RESTART
+ * @param inputpass The password given by the user
+ * @param confkey The name of the key in the power tag containing the correct password
+ * @return True if the given password was correct, false if it was not
+ */
+ bool CheckPass(User* user, const std::string& inputpass, const char* confkey);
+
+ /** Send an ERROR to unregistered users and a NOTICE to all registered local users
+ * @param message Message to send
+ */
+ void SendError(const std::string& message);
+}
+
+/** Handle /DIE.
+ */
+class CommandDie : public Command
+{
+ public:
+ /** Constructor for die.
+ */
+ CommandDie(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /KILL.
+ */
+class CommandKill : public Command
+{
+ std::string lastuuid;
+ std::string killreason;
+
+ public:
+ /** Constructor for kill.
+ */
+ CommandKill(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE;
+
+ void EncodeParameter(std::string& param, unsigned int index) CXX11_OVERRIDE;
+};
+
+/** Handle /OPER.
+ */
+class CommandOper : public SplitCommand
+{
+ public:
+ /** Constructor for oper.
+ */
+ CommandOper(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+};
+
+/** Handle /REHASH.
+ */
+class CommandRehash : public Command
+{
+ public:
+ /** Constructor for rehash.
+ */
+ CommandRehash(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /RESTART
+ */
+class CommandRestart : public Command
+{
+ public:
+ /** Constructor for restart.
+ */
+ CommandRestart(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
diff --git a/src/coremods/core_privmsg.cpp b/src/coremods/core_privmsg.cpp
new file mode 100644
index 000000000..29756a4c2
--- /dev/null
+++ b/src/coremods/core_privmsg.cpp
@@ -0,0 +1,297 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+namespace
+{
+ const char* MessageTypeString[] = { "PRIVMSG", "NOTICE" };
+}
+
+class MessageCommandBase : public Command
+{
+ ChanModeReference moderatedmode;
+ ChanModeReference noextmsgmode;
+
+ /** Send a PRIVMSG or NOTICE message to all local users from the given user
+ * @param user User sending the message
+ * @param msg The message to send
+ * @param mt Type of the message (MSG_PRIVMSG or MSG_NOTICE)
+ */
+ static void SendAll(User* user, const std::string& msg, MessageType mt);
+
+ public:
+ MessageCommandBase(Module* parent, MessageType mt)
+ : Command(parent, MessageTypeString[mt], 2, 2)
+ , moderatedmode(parent, "moderated")
+ , noextmsgmode(parent, "noextmsg")
+ {
+ syntax = "<target>{,<target>} <message>";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleMessage(const std::vector<std::string>& parameters, User* user, MessageType mt);
+
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE
+ {
+ if (IS_LOCAL(user))
+ // This is handled by the OnUserPostMessage hook to split the LoopCall pieces
+ return ROUTE_LOCALONLY;
+ else
+ return ROUTE_MESSAGE(parameters[0]);
+ }
+};
+
+void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt)
+{
+ const std::string message = ":" + user->GetFullHost() + " " + MessageTypeString[mt] + " $* :" + msg;
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ if ((*i)->registered == REG_ALL)
+ (*i)->Write(message);
+ }
+}
+
+CmdResult MessageCommandBase::HandleMessage(const std::vector<std::string>& parameters, User* user, MessageType mt)
+{
+ User *dest;
+ Channel *chan;
+
+ LocalUser* localuser = IS_LOCAL(user);
+ if (localuser)
+ localuser->idle_lastmsg = ServerInstance->Time();
+
+ if (CommandParser::LoopCall(user, this, parameters, 0))
+ return CMD_SUCCESS;
+
+ if (parameters[0][0] == '$')
+ {
+ if (!user->HasPrivPermission("users/mass-message"))
+ return CMD_SUCCESS;
+
+ std::string servername(parameters[0], 1);
+ MessageTarget msgtarget(&servername);
+ MessageDetails msgdetails(mt, parameters[1]);
+
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails));
+ if (MOD_RESULT == MOD_RES_DENY)
+ {
+ FOREACH_MOD(OnUserMessageBlocked, (user, msgtarget, msgdetails));
+ return CMD_FAILURE;
+ }
+
+ FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
+ if (InspIRCd::Match(ServerInstance->Config->ServerName, servername, NULL))
+ {
+ SendAll(user, msgdetails.text, mt);
+ }
+ FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
+ return CMD_SUCCESS;
+ }
+
+ char status = 0;
+ const char* target = parameters[0].c_str();
+
+ if (ServerInstance->Modes->FindPrefix(*target))
+ {
+ status = *target;
+ target++;
+ }
+ if (*target == '#')
+ {
+ chan = ServerInstance->FindChan(target);
+
+ if (chan)
+ {
+ if (localuser && chan->GetPrefixValue(user) < VOICE_VALUE)
+ {
+ if (chan->IsModeSet(noextmsgmode) && !chan->HasUser(user))
+ {
+ user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (no external messages)");
+ return CMD_FAILURE;
+ }
+
+ if (chan->IsModeSet(moderatedmode))
+ {
+ user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (+m)");
+ return CMD_FAILURE;
+ }
+
+ if (ServerInstance->Config->RestrictBannedUsers != ServerConfig::BUT_NORMAL)
+ {
+ if (chan->IsBanned(user))
+ {
+ if (ServerInstance->Config->RestrictBannedUsers == ServerConfig::BUT_RESTRICT_NOTIFY)
+ user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (you're banned)");
+ return CMD_FAILURE;
+ }
+ }
+ }
+
+ MessageTarget msgtarget(chan, status);
+ MessageDetails msgdetails(mt, parameters[1]);
+ msgdetails.exemptions.insert(user);
+
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails));
+ if (MOD_RESULT == MOD_RES_DENY)
+ {
+ FOREACH_MOD(OnUserMessageBlocked, (user, msgtarget, msgdetails));
+ return CMD_FAILURE;
+ }
+
+ /* Check again, a module may have zapped the input string */
+ if (msgdetails.text.empty())
+ {
+ user->WriteNumeric(ERR_NOTEXTTOSEND, "No text to send");
+ return CMD_FAILURE;
+ }
+
+ FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
+
+ if (status)
+ {
+ chan->WriteAllExcept(user, false, status, msgdetails.exemptions, "%s %c%s :%s", MessageTypeString[mt], status, chan->name.c_str(), msgdetails.text.c_str());
+ }
+ else
+ {
+ chan->WriteAllExcept(user, false, status, msgdetails.exemptions, "%s %s :%s", MessageTypeString[mt], chan->name.c_str(), msgdetails.text.c_str());
+ }
+
+ FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
+ }
+ else
+ {
+ /* channel does not exist */
+ user->WriteNumeric(Numerics::NoSuchChannel(parameters[0]));
+ return CMD_FAILURE;
+ }
+ return CMD_SUCCESS;
+ }
+
+ const char* destnick = parameters[0].c_str();
+
+ if (localuser)
+ {
+ const char* targetserver = strchr(destnick, '@');
+
+ if (targetserver)
+ {
+ std::string nickonly;
+
+ nickonly.assign(destnick, 0, targetserver - destnick);
+ dest = ServerInstance->FindNickOnly(nickonly);
+ if (dest && strcasecmp(dest->server->GetName().c_str(), targetserver + 1))
+ {
+ /* Incorrect server for user */
+ user->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
+ return CMD_FAILURE;
+ }
+ }
+ else
+ dest = ServerInstance->FindNickOnly(destnick);
+ }
+ else
+ dest = ServerInstance->FindNick(destnick);
+
+ if ((dest) && (dest->registered == REG_ALL))
+ {
+ if (parameters[1].empty())
+ {
+ user->WriteNumeric(ERR_NOTEXTTOSEND, "No text to send");
+ return CMD_FAILURE;
+ }
+
+ if ((dest->IsAway()) && (mt == MSG_PRIVMSG))
+ {
+ /* auto respond with aweh msg */
+ user->WriteNumeric(RPL_AWAY, dest->nick, dest->awaymsg);
+ }
+
+ MessageTarget msgtarget(dest);
+ MessageDetails msgdetails(mt, parameters[1]);
+
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails));
+ if (MOD_RESULT == MOD_RES_DENY)
+ {
+ FOREACH_MOD(OnUserMessageBlocked, (user, msgtarget, msgdetails));
+ return CMD_FAILURE;
+ }
+
+ FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
+
+ if (IS_LOCAL(dest))
+ {
+ // direct write, same server
+ dest->WriteFrom(user, "%s %s :%s", MessageTypeString[mt], dest->nick.c_str(), msgdetails.text.c_str());
+ }
+
+ FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
+ }
+ else
+ {
+ /* no such nick/channel */
+ user->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
+ return CMD_FAILURE;
+ }
+ return CMD_SUCCESS;
+}
+
+template<MessageType MT>
+class CommandMessage : public MessageCommandBase
+{
+ public:
+ CommandMessage(Module* parent)
+ : MessageCommandBase(parent, MT)
+ {
+ }
+
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE
+ {
+ return HandleMessage(parameters, user, MT);
+ }
+};
+
+class ModuleCoreMessage : public Module
+{
+ CommandMessage<MSG_PRIVMSG> CommandPrivmsg;
+ CommandMessage<MSG_NOTICE> CommandNotice;
+
+ public:
+ ModuleCoreMessage()
+ : CommandPrivmsg(this), CommandNotice(this)
+ {
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("PRIVMSG, NOTICE", VF_CORE|VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleCoreMessage)
diff --git a/src/coremods/core_reloadmodule.cpp b/src/coremods/core_reloadmodule.cpp
new file mode 100644
index 000000000..910144221
--- /dev/null
+++ b/src/coremods/core_reloadmodule.cpp
@@ -0,0 +1,635 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com>
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "listmode.h"
+#include "modules/reload.h"
+
+static Events::ModuleEventProvider* reloadevprov;
+
+class CommandReloadmodule : public Command
+{
+ Events::ModuleEventProvider evprov;
+ public:
+ /** Constructor for reloadmodule.
+ */
+ CommandReloadmodule(Module* parent)
+ : Command(parent, "RELOADMODULE", 1)
+ , evprov(parent, "event/reloadmodule")
+ {
+ reloadevprov = &evprov;
+ flags_needed = 'o';
+ syntax = "<modulename>";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+namespace ReloadModule
+{
+
+class DataKeeper
+{
+ /** Data we save for each mode and extension provided by the module
+ */
+ struct ProviderInfo
+ {
+ std::string itemname;
+ union
+ {
+ ModeHandler* mh;
+ ExtensionItem* extitem;
+ };
+
+ ProviderInfo(ModeHandler* mode)
+ : itemname(mode->name)
+ , mh(mode)
+ {
+ }
+
+ ProviderInfo(ExtensionItem* ei)
+ : itemname(ei->name)
+ , extitem(ei)
+ {
+ }
+ };
+
+ struct InstanceData
+ {
+ /** Position of the ModeHandler or ExtensionItem that the serialized data belongs to
+ */
+ size_t index;
+
+ /** Serialized data
+ */
+ std::string serialized;
+
+ InstanceData(size_t Index, const std::string& Serialized)
+ : index(Index)
+ , serialized(Serialized)
+ {
+ }
+ };
+
+ struct ModesExts
+ {
+ /** Mode data for the object, one entry per mode set by the module being reloaded
+ */
+ std::vector<InstanceData> modelist;
+
+ /** Extensions for the object, one entry per extension set by the module being reloaded
+ */
+ std::vector<InstanceData> extlist;
+
+ bool empty() const { return ((modelist.empty()) && (extlist.empty())); }
+
+ void swap(ModesExts& other)
+ {
+ modelist.swap(other.modelist);
+ extlist.swap(other.extlist);
+ }
+ };
+
+ struct OwnedModesExts : public ModesExts
+ {
+ /** User uuid or channel name
+ */
+ std::string owner;
+
+ OwnedModesExts(const std::string& Owner)
+ : owner(Owner)
+ {
+ }
+ };
+
+ // Data saved for each channel
+ struct ChanData : public OwnedModesExts
+ {
+ /** Type of data stored for each member who has any affected modes or extensions set
+ */
+ typedef OwnedModesExts MemberData;
+
+ /** List of data (modes and extensions) about each member
+ */
+ std::vector<MemberData> memberdatalist;
+
+ ChanData(Channel* chan)
+ : OwnedModesExts(chan->name)
+ {
+ }
+ };
+
+ // Data saved for each user
+ typedef OwnedModesExts UserData;
+
+ /** Module being reloaded
+ */
+ Module* mod;
+
+ /** Stores all user and channel modes provided by the module
+ */
+ std::vector<ProviderInfo> handledmodes[2];
+
+ /** Stores all extensions provided by the module
+ */
+ std::vector<ProviderInfo> handledexts;
+
+ /** Stores all of the module data related to users
+ */
+ std::vector<UserData> userdatalist;
+
+ /** Stores all of the module data related to channels and memberships
+ */
+ std::vector<ChanData> chandatalist;
+
+ /** Data attached by modules
+ */
+ ReloadModule::CustomData moddata;
+
+ void SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdatalist);
+ void SaveMemberData(Channel* chan, std::vector<ChanData::MemberData>& memberdatalist);
+ static void SaveListModes(Channel* chan, ListModeBase* lm, size_t index, ModesExts& currdata);
+
+ void CreateModeList(ModeType modetype);
+ void DoSaveUsers();
+ void DoSaveChans();
+
+ /** Link previously saved extension names to currently available ExtensionItems
+ */
+ void LinkExtensions();
+
+ /** Link previously saved mode names to currently available ModeHandlers
+ * @param modetype Type of the modes to look for
+ */
+ void LinkModes(ModeType modetype);
+
+ void DoRestoreUsers();
+ void DoRestoreChans();
+ void DoRestoreModules();
+
+ /** Restore previously saved modes and extensions on an Extensible.
+ * The extensions are set directly on the extensible, the modes are added into the provided mode change list.
+ * @param data Data to unserialize from
+ * @param extensible Object to restore
+ * @param modetype MODETYPE_USER if the object being restored is a User, MODETYPE_CHANNEL otherwise
+ * (for Channels and Memberships).
+ * @param modechange Mode change to populate with the modes
+ */
+ void RestoreObj(const OwnedModesExts& data, Extensible* extensible, ModeType modetype, Modes::ChangeList& modechange);
+
+ /** Restore all previously saved extensions on an Extensible
+ * @param list List of extensions and their serialized data to restore
+ * @param extensible Target Extensible
+ */
+ void RestoreExtensions(const std::vector<InstanceData>& list, Extensible* extensible);
+
+ /** Restore all previously saved modes on a User, Channel or Membership
+ * @param list List of modes to restore
+ * @param modetype MODETYPE_USER if the object being restored is a User, MODETYPE_CHANNEL otherwise
+ * @param modechange Mode change to populate with the modes
+ */
+ void RestoreModes(const std::vector<InstanceData>& list, ModeType modetype, Modes::ChangeList& modechange);
+
+ /** Restore all modes and extensions of all members on a channel
+ * @param chan Channel whose members are being restored
+ * @param memberdata Data to restore
+ * @param modechange Mode change to populate with prefix modes
+ */
+ void RestoreMemberData(Channel* chan, const std::vector<ChanData::MemberData>& memberdatalist, Modes::ChangeList& modechange);
+
+ /** Verify that a service which had its data saved is available and owned by the module that owned it previously
+ * @param service Service descriptor
+ * @param type Human-readable type of the service for log messages
+ */
+ void VerifyServiceProvider(const ProviderInfo& service, const char* type);
+
+ public:
+ /** Save module state
+ * @param currmod Module whose data to save
+ */
+ void Save(Module* currmod);
+
+ /** Restore module state
+ * @param newmod Newly loaded instance of the module which had its data saved
+ */
+ void Restore(Module* newmod);
+
+ /** Handle reload failure
+ */
+ void Fail();
+};
+
+void DataKeeper::DoSaveUsers()
+{
+ ModesExts currdata;
+
+ const user_hash& users = ServerInstance->Users->GetUsers();
+ for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i)
+ {
+ User* const user = i->second;
+
+ // Serialize user modes
+ for (size_t j = 0; j < handledmodes[MODETYPE_USER].size(); j++)
+ {
+ ModeHandler* mh = handledmodes[MODETYPE_USER][j].mh;
+ if (user->IsModeSet(mh))
+ currdata.modelist.push_back(InstanceData(j, mh->GetUserParameter(user)));
+ }
+
+ // Serialize all extensions attached to the User
+ SaveExtensions(user, currdata.extlist);
+
+ // Add to list if the user has any modes or extensions set that we are interested in, otherwise we don't
+ // have to do anything with this user when restoring
+ if (!currdata.empty())
+ {
+ userdatalist.push_back(UserData(user->uuid));
+ userdatalist.back().swap(currdata);
+ }
+ }
+}
+
+void DataKeeper::SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdata)
+{
+ const Extensible::ExtensibleStore& setexts = extensible->GetExtList();
+
+ // Position of the extension saved in the handledexts list
+ size_t index = 0;
+ for (std::vector<ProviderInfo>::const_iterator i = handledexts.begin(); i != handledexts.end(); ++i, index++)
+ {
+ ExtensionItem* const item = i->extitem;
+ Extensible::ExtensibleStore::const_iterator it = setexts.find(item);
+ if (it == setexts.end())
+ continue;
+
+ std::string value = item->serialize(FORMAT_INTERNAL, extensible, it->second);
+ // If the serialized value is empty the extension won't be saved and restored
+ if (!value.empty())
+ extdata.push_back(InstanceData(index, value));
+ }
+}
+
+void DataKeeper::SaveListModes(Channel* chan, ListModeBase* lm, size_t index, ModesExts& currdata)
+{
+ const ListModeBase::ModeList* list = lm->GetList(chan);
+ if (!list)
+ return;
+
+ for (ListModeBase::ModeList::const_iterator i = list->begin(); i != list->end(); ++i)
+ {
+ const ListModeBase::ListItem& listitem = *i;
+ currdata.modelist.push_back(InstanceData(index, listitem.mask));
+ }
+}
+
+void DataKeeper::DoSaveChans()
+{
+ ModesExts currdata;
+ std::vector<OwnedModesExts> currmemberdata;
+
+ const chan_hash& chans = ServerInstance->GetChans();
+ for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i)
+ {
+ Channel* const chan = i->second;
+
+ // Serialize channel modes
+ for (size_t j = 0; j < handledmodes[MODETYPE_CHANNEL].size(); j++)
+ {
+ ModeHandler* mh = handledmodes[MODETYPE_CHANNEL][j].mh;
+ ListModeBase* lm = mh->IsListModeBase();
+ if (lm)
+ SaveListModes(chan, lm, j, currdata);
+ else if (chan->IsModeSet(mh))
+ currdata.modelist.push_back(InstanceData(j, chan->GetModeParameter(mh)));
+ }
+
+ // Serialize all extensions attached to the Channel
+ SaveExtensions(chan, currdata.extlist);
+
+ // Serialize all extensions attached to and all modes set on all members of the channel
+ SaveMemberData(chan, currmemberdata);
+
+ // Same logic as in DoSaveUsers() plus we consider the modes and extensions of all members
+ if ((!currdata.empty()) || (!currmemberdata.empty()))
+ {
+ chandatalist.push_back(ChanData(chan));
+ chandatalist.back().swap(currdata);
+ chandatalist.back().memberdatalist.swap(currmemberdata);
+ }
+ }
+}
+
+void DataKeeper::SaveMemberData(Channel* chan, std::vector<OwnedModesExts>& memberdatalist)
+{
+ ModesExts currdata;
+ const Channel::MemberMap& users = chan->GetUsers();
+ for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i)
+ {
+ Membership* const memb = i->second;
+
+ for (size_t j = 0; j < handledmodes[MODETYPE_CHANNEL].size(); j++)
+ {
+ ModeHandler* mh = handledmodes[MODETYPE_CHANNEL][j].mh;
+ const PrefixMode* const pm = mh->IsPrefixMode();
+ if ((pm) && (memb->HasMode(pm)))
+ currdata.modelist.push_back(InstanceData(j, memb->user->uuid)); // Need to pass the user's uuid to the mode parser to set the mode later
+ }
+
+ SaveExtensions(memb, currdata.extlist);
+
+ // Same logic as in DoSaveUsers()
+ if (!currdata.empty())
+ {
+ memberdatalist.push_back(OwnedModesExts(memb->user->uuid));
+ memberdatalist.back().swap(currdata);
+ }
+ }
+}
+
+void DataKeeper::RestoreMemberData(Channel* chan, const std::vector<ChanData::MemberData>& memberdatalist, Modes::ChangeList& modechange)
+{
+ for (std::vector<ChanData::MemberData>::const_iterator i = memberdatalist.begin(); i != memberdatalist.end(); ++i)
+ {
+ const ChanData::MemberData& md = *i;
+ User* const user = ServerInstance->FindUUID(md.owner);
+ if (!user)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User %s is gone (while processing %s)", md.owner.c_str(), chan->name.c_str());
+ continue;
+ }
+
+ Membership* const memb = chan->GetUser(user);
+ if (!memb)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Member %s is no longer on channel %s", md.owner.c_str(), chan->name.c_str());
+ continue;
+ }
+
+ RestoreObj(md, memb, MODETYPE_CHANNEL, modechange);
+ }
+}
+
+void DataKeeper::CreateModeList(ModeType modetype)
+{
+ const ModeParser::ModeHandlerMap& modes = ServerInstance->Modes->GetModes(modetype);
+ for (ModeParser::ModeHandlerMap::const_iterator i = modes.begin(); i != modes.end(); ++i)
+ {
+ ModeHandler* mh = i->second;
+ if (mh->creator == mod)
+ handledmodes[modetype].push_back(ProviderInfo(mh));
+ }
+}
+
+void DataKeeper::Save(Module* currmod)
+{
+ this->mod = currmod;
+
+ const ExtensionManager::ExtMap& allexts = ServerInstance->Extensions.GetExts();
+ for (ExtensionManager::ExtMap::const_iterator i = allexts.begin(); i != allexts.end(); ++i)
+ {
+ ExtensionItem* ext = i->second;
+ if (ext->creator == mod)
+ handledexts.push_back(ProviderInfo(ext));
+ }
+
+ CreateModeList(MODETYPE_USER);
+ DoSaveUsers();
+
+ CreateModeList(MODETYPE_CHANNEL);
+ DoSaveChans();
+
+ FOREACH_MOD_CUSTOM(*reloadevprov, ReloadModule::EventListener, OnReloadModuleSave, (mod, this->moddata));
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Saved data about %lu users %lu chans %lu modules", (unsigned long)userdatalist.size(), (unsigned long)chandatalist.size(), (unsigned long)moddata.list.size());
+}
+
+void DataKeeper::VerifyServiceProvider(const ProviderInfo& service, const char* type)
+{
+ const ServiceProvider* sp = service.extitem;
+ if (!sp)
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "%s \"%s\" is no longer available", type, service.itemname.c_str());
+ else if (sp->creator != mod)
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "%s \"%s\" is now handled by %s", type, service.itemname.c_str(), (sp->creator ? sp->creator->ModuleSourceFile.c_str() : "<core>"));
+}
+
+void DataKeeper::LinkModes(ModeType modetype)
+{
+ std::vector<ProviderInfo>& list = handledmodes[modetype];
+ for (std::vector<ProviderInfo>::iterator i = list.begin(); i != list.end(); ++i)
+ {
+ ProviderInfo& item = *i;
+ item.mh = ServerInstance->Modes->FindMode(item.itemname, modetype);
+ VerifyServiceProvider(item, (modetype == MODETYPE_USER ? "User mode" : "Channel mode"));
+ }
+}
+
+void DataKeeper::LinkExtensions()
+{
+ for (std::vector<ProviderInfo>::iterator i = handledexts.begin(); i != handledexts.end(); ++i)
+ {
+ ProviderInfo& item = *i;
+ item.extitem = ServerInstance->Extensions.GetItem(item.itemname);
+ VerifyServiceProvider(item.extitem, "Extension");
+ }
+}
+
+void DataKeeper::Restore(Module* newmod)
+{
+ this->mod = newmod;
+
+ // Find the new extension items
+ LinkExtensions();
+ LinkModes(MODETYPE_USER);
+ LinkModes(MODETYPE_CHANNEL);
+
+ // Restore
+ DoRestoreUsers();
+ DoRestoreChans();
+ DoRestoreModules();
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restore finished");
+}
+
+void DataKeeper::Fail()
+{
+ this->mod = NULL;
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restore failed, notifying modules");
+ DoRestoreModules();
+}
+
+void DataKeeper::RestoreObj(const OwnedModesExts& data, Extensible* extensible, ModeType modetype, Modes::ChangeList& modechange)
+{
+ RestoreExtensions(data.extlist, extensible);
+ RestoreModes(data.modelist, modetype, modechange);
+}
+
+void DataKeeper::RestoreExtensions(const std::vector<InstanceData>& list, Extensible* extensible)
+{
+ for (std::vector<InstanceData>::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ const InstanceData& id = *i;
+ handledexts[id.index].extitem->unserialize(FORMAT_INTERNAL, extensible, id.serialized);
+ }
+}
+
+void DataKeeper::RestoreModes(const std::vector<InstanceData>& list, ModeType modetype, Modes::ChangeList& modechange)
+{
+ for (std::vector<InstanceData>::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ const InstanceData& id = *i;
+ modechange.push_add(handledmodes[modetype][id.index].mh, id.serialized);
+ }
+}
+
+void DataKeeper::DoRestoreUsers()
+{
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restoring user data");
+ Modes::ChangeList modechange;
+
+ for (std::vector<UserData>::const_iterator i = userdatalist.begin(); i != userdatalist.end(); ++i)
+ {
+ const UserData& userdata = *i;
+ User* const user = ServerInstance->FindUUID(userdata.owner);
+ if (!user)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User %s is gone", userdata.owner.c_str());
+ continue;
+ }
+
+ RestoreObj(userdata, user, MODETYPE_USER, modechange);
+ ServerInstance->Modes.Process(ServerInstance->FakeClient, NULL, user, modechange, ModeParser::MODE_LOCALONLY);
+ modechange.clear();
+ }
+}
+
+void DataKeeper::DoRestoreChans()
+{
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restoring channel data");
+ Modes::ChangeList modechange;
+
+ for (std::vector<ChanData>::const_iterator i = chandatalist.begin(); i != chandatalist.end(); ++i)
+ {
+ const ChanData& chandata = *i;
+ Channel* const chan = ServerInstance->FindChan(chandata.owner);
+ if (!chan)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Channel %s not found", chandata.owner.c_str());
+ continue;
+ }
+
+ RestoreObj(chandata, chan, MODETYPE_CHANNEL, modechange);
+ // Process the mode change before applying any prefix modes
+ ServerInstance->Modes.Process(ServerInstance->FakeClient, chan, NULL, modechange, ModeParser::MODE_LOCALONLY);
+ modechange.clear();
+
+ // Restore all member data
+ RestoreMemberData(chan, chandata.memberdatalist, modechange);
+ ServerInstance->Modes.Process(ServerInstance->FakeClient, chan, NULL, modechange, ModeParser::MODE_LOCALONLY);
+ modechange.clear();
+ }
+}
+
+void DataKeeper::DoRestoreModules()
+{
+ for (ReloadModule::CustomData::List::iterator i = moddata.list.begin(); i != moddata.list.end(); ++i)
+ {
+ ReloadModule::CustomData::Data& data = *i;
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Calling module data handler %p", (void*)data.handler);
+ data.handler->OnReloadModuleRestore(mod, data.data);
+ }
+}
+
+} // namespace ReloadModule
+
+class ReloadAction : public ActionBase
+{
+ Module* const mod;
+ const std::string uuid;
+ const std::string passedname;
+
+ public:
+ ReloadAction(Module* m, const std::string& uid, const std::string& passedmodname)
+ : mod(m)
+ , uuid(uid)
+ , passedname(passedmodname)
+ {
+ }
+
+ void Call() CXX11_OVERRIDE
+ {
+ ReloadModule::DataKeeper datakeeper;
+ datakeeper.Save(mod);
+
+ DLLManager* dll = mod->ModuleDLLManager;
+ std::string name = mod->ModuleSourceFile;
+ ServerInstance->Modules->DoSafeUnload(mod);
+ ServerInstance->GlobalCulls.Apply();
+ delete dll;
+ bool result = ServerInstance->Modules->Load(name);
+
+ if (result)
+ {
+ Module* newmod = ServerInstance->Modules->Find(name);
+ datakeeper.Restore(newmod);
+ }
+ else
+ datakeeper.Fail();
+
+ ServerInstance->SNO->WriteGlobalSno('a', "RELOAD MODULE: %s %ssuccessfully reloaded", passedname.c_str(), result ? "" : "un");
+ User* user = ServerInstance->FindUUID(uuid);
+ if (user)
+ user->WriteNumeric(RPL_LOADEDMODULE, passedname, InspIRCd::Format("Module %ssuccessfully reloaded.", (result ? "" : "un")));
+
+ ServerInstance->GlobalCulls.AddItem(this);
+ }
+};
+
+CmdResult CommandReloadmodule::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ Module* m = ServerInstance->Modules->Find(parameters[0]);
+ if (m == creator)
+ {
+ user->WriteNumeric(RPL_LOADEDMODULE, parameters[0], "You cannot reload core_reloadmodule (unload and load it)");
+ return CMD_FAILURE;
+ }
+
+ if (creator->dying)
+ return CMD_FAILURE;
+
+ if ((m) && (ServerInstance->Modules.CanUnload(m)))
+ {
+ ServerInstance->AtomicActions.AddAction(new ReloadAction(m, user->uuid, parameters[0]));
+ return CMD_SUCCESS;
+ }
+ else
+ {
+ user->WriteNumeric(RPL_LOADEDMODULE, parameters[0], "Could not find module by that name");
+ return CMD_FAILURE;
+ }
+}
+
+COMMAND_INIT(CommandReloadmodule)
diff --git a/src/coremods/core_stats.cpp b/src/coremods/core_stats.cpp
new file mode 100644
index 000000000..5642cd52e
--- /dev/null
+++ b/src/coremods/core_stats.cpp
@@ -0,0 +1,400 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "xline.h"
+#include "modules/stats.h"
+
+#ifdef _WIN32
+#include <psapi.h>
+#pragma comment(lib, "psapi.lib") // For GetProcessMemoryInfo()
+#endif
+
+/** Handle /STATS.
+ */
+class CommandStats : public Command
+{
+ Events::ModuleEventProvider statsevprov;
+ void DoStats(Stats::Context& stats);
+
+ public:
+ /** Constructor for stats.
+ */
+ CommandStats(Module* Creator)
+ : Command(Creator, "STATS", 1, 2)
+ , statsevprov(Creator, "event/stats")
+ {
+ allow_empty_last_param = false;
+ syntax = "<stats-symbol> [<servername>]";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE
+ {
+ if ((parameters.size() > 1) && (parameters[1].find('.') != std::string::npos))
+ return ROUTE_UNICAST(parameters[1]);
+ return ROUTE_LOCALONLY;
+ }
+};
+
+static void GenerateStatsLl(Stats::Context& stats)
+{
+ stats.AddRow(211, InspIRCd::Format("nick[ident@%s] sendq cmds_out bytes_out cmds_in bytes_in time_open", (stats.GetSymbol() == 'l' ? "host" : "ip")));
+
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ LocalUser* u = *i;
+ stats.AddRow(211, u->nick+"["+u->ident+"@"+(stats.GetSymbol() == 'l' ? u->GetDisplayedHost() : u->GetIPString())+"] "+ConvToStr(u->eh.getSendQSize())+" "+ConvToStr(u->cmds_out)+" "+ConvToStr(u->bytes_out)+" "+ConvToStr(u->cmds_in)+" "+ConvToStr(u->bytes_in)+" "+ConvToStr(ServerInstance->Time() - u->signon));
+ }
+}
+
+void CommandStats::DoStats(Stats::Context& stats)
+{
+ User* const user = stats.GetSource();
+ const char statschar = stats.GetSymbol();
+
+ bool isPublic = ServerInstance->Config->UserStats.find(statschar) != std::string::npos;
+ bool isRemoteOper = IS_REMOTE(user) && (user->IsOper());
+ bool isLocalOperWithPrivs = IS_LOCAL(user) && user->HasPrivPermission("servers/auspex");
+
+ if (!isPublic && !isRemoteOper && !isLocalOperWithPrivs)
+ {
+ ServerInstance->SNO->WriteToSnoMask('t',
+ "%s '%c' denied for %s (%s@%s)",
+ (IS_LOCAL(user) ? "Stats" : "Remote stats"),
+ statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str());
+ stats.AddRow(481, (std::string("Permission Denied - STATS ") + statschar + " requires the servers/auspex priv."));
+ return;
+ }
+
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT_CUSTOM(statsevprov, Stats::EventListener, OnStats, MOD_RESULT, (stats));
+ if (MOD_RESULT == MOD_RES_DENY)
+ {
+ stats.AddRow(219, statschar, "End of /STATS report");
+ ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)",
+ (IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str());
+ return;
+ }
+
+ switch (statschar)
+ {
+ /* stats p (show listening ports) */
+ case 'p':
+ {
+ for (std::vector<ListenSocket*>::const_iterator i = ServerInstance->ports.begin(); i != ServerInstance->ports.end(); ++i)
+ {
+ ListenSocket* ls = *i;
+ std::string type = ls->bind_tag->getString("type", "clients");
+ std::string hook = ls->bind_tag->getString("ssl", "plaintext");
+
+ stats.AddRow(249, ls->bind_sa.str() + " (" + type + ", " + hook + ")");
+ }
+ }
+ break;
+
+ /* These stats symbols must be handled by a linking module */
+ case 'n':
+ case 'c':
+ break;
+
+ case 'i':
+ {
+ for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); ++i)
+ {
+ ConnectClass* c = *i;
+ Stats::Row row(215);
+ row.push("I").push(c->name);
+
+ std::string param;
+ if (c->type == CC_ALLOW)
+ param.push_back('+');
+ if (c->type == CC_DENY)
+ param.push_back('-');
+
+ if (c->type == CC_NAMED)
+ param.push_back('*');
+ else
+ param.append(c->host);
+
+ row.push(param).push(c->config->getString("port", "*"));
+ row.push(ConvToStr(c->GetRecvqMax())).push(ConvToStr(c->GetSendqSoftMax())).push(ConvToStr(c->GetSendqHardMax())).push(ConvToStr(c->GetCommandRate()));
+
+ param = ConvToStr(c->GetPenaltyThreshold());
+ if (c->fakelag)
+ param.push_back('*');
+ row.push(param);
+
+ stats.AddRow(row);
+ }
+ }
+ break;
+
+ case 'Y':
+ {
+ int idx = 0;
+ for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++)
+ {
+ ConnectClass* c = *i;
+ stats.AddRow(215, 'i', "NOMATCH", '*', c->GetHost(), (c->limit ? c->limit : SocketEngine::GetMaxFds()), idx, ServerInstance->Config->ServerName, '*');
+ stats.AddRow(218, 'Y', idx, c->GetPingTime(), '0', c->GetSendqHardMax(), ConvToStr(c->GetRecvqMax())+" "+ConvToStr(c->GetRegTimeout()));
+ idx++;
+ }
+ }
+ break;
+
+ case 'P':
+ {
+ unsigned int idx = 0;
+ const UserManager::OperList& opers = ServerInstance->Users->all_opers;
+ for (UserManager::OperList::const_iterator i = opers.begin(); i != opers.end(); ++i)
+ {
+ User* oper = *i;
+ if (!oper->server->IsULine())
+ {
+ LocalUser* lu = IS_LOCAL(oper);
+ stats.AddRow(249, oper->nick + " (" + oper->ident + "@" + oper->GetDisplayedHost() + ") Idle: " +
+ (lu ? ConvToStr(ServerInstance->Time() - lu->idle_lastmsg) + " secs" : "unavailable"));
+ idx++;
+ }
+ }
+ stats.AddRow(249, ConvToStr(idx)+" OPER(s)");
+ }
+ break;
+
+ case 'k':
+ ServerInstance->XLines->InvokeStats("K",216,stats);
+ break;
+ case 'g':
+ ServerInstance->XLines->InvokeStats("G",223,stats);
+ break;
+ case 'q':
+ ServerInstance->XLines->InvokeStats("Q",217,stats);
+ break;
+ case 'Z':
+ ServerInstance->XLines->InvokeStats("Z",223,stats);
+ break;
+ case 'e':
+ ServerInstance->XLines->InvokeStats("E",223,stats);
+ break;
+ case 'E':
+ {
+ const SocketEngine::Statistics& sestats = SocketEngine::GetStats();
+ stats.AddRow(249, "Total events: "+ConvToStr(sestats.TotalEvents));
+ stats.AddRow(249, "Read events: "+ConvToStr(sestats.ReadEvents));
+ stats.AddRow(249, "Write events: "+ConvToStr(sestats.WriteEvents));
+ stats.AddRow(249, "Error events: "+ConvToStr(sestats.ErrorEvents));
+ break;
+ }
+
+ /* stats m (list number of times each command has been used, plus bytecount) */
+ case 'm':
+ {
+ const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands();
+ for (CommandParser::CommandMap::const_iterator i = commands.begin(); i != commands.end(); ++i)
+ {
+ if (i->second->use_count)
+ {
+ /* RPL_STATSCOMMANDS */
+ stats.AddRow(212, i->second->name, i->second->use_count);
+ }
+ }
+ }
+ break;
+
+ /* stats z (debug and memory info) */
+ case 'z':
+ {
+ stats.AddRow(249, "Users: "+ConvToStr(ServerInstance->Users->GetUsers().size()));
+ stats.AddRow(249, "Channels: "+ConvToStr(ServerInstance->GetChans().size()));
+ stats.AddRow(249, "Commands: "+ConvToStr(ServerInstance->Parser.GetCommands().size()));
+
+ float kbitpersec_in, kbitpersec_out, kbitpersec_total;
+ SocketEngine::GetStats().GetBandwidth(kbitpersec_in, kbitpersec_out, kbitpersec_total);
+
+ stats.AddRow(249, InspIRCd::Format("Bandwidth total: %03.5f kilobits/sec", kbitpersec_total));
+ stats.AddRow(249, InspIRCd::Format("Bandwidth out: %03.5f kilobits/sec", kbitpersec_out));
+ stats.AddRow(249, InspIRCd::Format("Bandwidth in: %03.5f kilobits/sec", kbitpersec_in));
+
+#ifndef _WIN32
+ /* Moved this down here so all the not-windows stuff (look w00tie, I didn't say win32!) is in one ifndef.
+ * Also cuts out some identical code in both branches of the ifndef. -- Om
+ */
+ rusage R;
+
+ /* Not sure why we were doing '0' with a RUSAGE_SELF comment rather than just using RUSAGE_SELF -- Om */
+ if (!getrusage(RUSAGE_SELF,&R)) /* RUSAGE_SELF */
+ {
+#ifndef __HAIKU__
+ stats.AddRow(249, "Total allocation: "+ConvToStr(R.ru_maxrss)+"K");
+ stats.AddRow(249, "Signals: "+ConvToStr(R.ru_nsignals));
+ stats.AddRow(249, "Page faults: "+ConvToStr(R.ru_majflt));
+ stats.AddRow(249, "Swaps: "+ConvToStr(R.ru_nswap));
+ stats.AddRow(249, "Context Switches: Voluntary; "+ConvToStr(R.ru_nvcsw)+" Involuntary; "+ConvToStr(R.ru_nivcsw));
+#endif
+ float n_elapsed = (ServerInstance->Time() - ServerInstance->stats.LastSampled.tv_sec) * 1000000
+ + (ServerInstance->Time_ns() - ServerInstance->stats.LastSampled.tv_nsec) / 1000;
+ float n_eaten = ((R.ru_utime.tv_sec - ServerInstance->stats.LastCPU.tv_sec) * 1000000 + R.ru_utime.tv_usec - ServerInstance->stats.LastCPU.tv_usec);
+ float per = (n_eaten / n_elapsed) * 100;
+
+ stats.AddRow(249, InspIRCd::Format("CPU Use (now): %03.5f%%", per));
+
+ n_elapsed = ServerInstance->Time() - ServerInstance->startup_time;
+ n_eaten = (float)R.ru_utime.tv_sec + R.ru_utime.tv_usec / 100000.0;
+ per = (n_eaten / n_elapsed) * 100;
+
+ stats.AddRow(249, InspIRCd::Format("CPU Use (total): %03.5f%%", per));
+ }
+#else
+ PROCESS_MEMORY_COUNTERS MemCounters;
+ if (GetProcessMemoryInfo(GetCurrentProcess(), &MemCounters, sizeof(MemCounters)))
+ {
+ stats.AddRow(249, "Total allocation: "+ConvToStr((MemCounters.WorkingSetSize + MemCounters.PagefileUsage) / 1024)+"K");
+ stats.AddRow(249, "Pagefile usage: "+ConvToStr(MemCounters.PagefileUsage / 1024)+"K");
+ stats.AddRow(249, "Page faults: "+ConvToStr(MemCounters.PageFaultCount));
+ }
+
+ FILETIME CreationTime;
+ FILETIME ExitTime;
+ FILETIME KernelTime;
+ FILETIME UserTime;
+ LARGE_INTEGER ThisSample;
+ if(GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime) &&
+ QueryPerformanceCounter(&ThisSample))
+ {
+ KernelTime.dwHighDateTime += UserTime.dwHighDateTime;
+ KernelTime.dwLowDateTime += UserTime.dwLowDateTime;
+ double n_eaten = (double)( ( (uint64_t)(KernelTime.dwHighDateTime - ServerInstance->stats.LastCPU.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime - ServerInstance->stats.LastCPU.dwLowDateTime) )/100000;
+ double n_elapsed = (double)(ThisSample.QuadPart - ServerInstance->stats.LastSampled.QuadPart) / ServerInstance->stats.QPFrequency.QuadPart;
+ double per = (n_eaten/n_elapsed);
+
+ stats.AddRow(249, InspIRCd::Format("CPU Use (now): %03.5f%%", per));
+
+ n_elapsed = ServerInstance->Time() - ServerInstance->startup_time;
+ n_eaten = (double)(( (uint64_t)(KernelTime.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime))/100000;
+ per = (n_eaten / n_elapsed);
+
+ stats.AddRow(249, InspIRCd::Format("CPU Use (total): %03.5f%%", per));
+ }
+#endif
+ }
+ break;
+
+ case 'T':
+ {
+ stats.AddRow(249, "accepts "+ConvToStr(ServerInstance->stats.Accept)+" refused "+ConvToStr(ServerInstance->stats.Refused));
+ stats.AddRow(249, "unknown commands "+ConvToStr(ServerInstance->stats.Unknown));
+ stats.AddRow(249, "nick collisions "+ConvToStr(ServerInstance->stats.Collisions));
+ stats.AddRow(249, "dns requests "+ConvToStr(ServerInstance->stats.DnsGood+ServerInstance->stats.DnsBad)+" succeeded "+ConvToStr(ServerInstance->stats.DnsGood)+" failed "+ConvToStr(ServerInstance->stats.DnsBad));
+ stats.AddRow(249, "connection count "+ConvToStr(ServerInstance->stats.Connects));
+ stats.AddRow(249, InspIRCd::Format("bytes sent %5.2fK recv %5.2fK",
+ ServerInstance->stats.Sent / 1024.0, ServerInstance->stats.Recv / 1024.0));
+ }
+ break;
+
+ /* stats o */
+ case 'o':
+ {
+ for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i)
+ {
+ OperInfo* ifo = i->second;
+ ConfigTag* tag = ifo->oper_block;
+ stats.AddRow(243, 'O', tag->getString("host"), '*', tag->getString("name"), tag->getString("type"), '0');
+ }
+ }
+ break;
+ case 'O':
+ {
+ for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->OperTypes.begin(); i != ServerInstance->Config->OperTypes.end(); ++i)
+ {
+ OperInfo* tag = i->second;
+ tag->init();
+ std::string umodes;
+ std::string cmodes;
+ for(char c='A'; c <= 'z'; c++)
+ {
+ ModeHandler* mh = ServerInstance->Modes->FindMode(c, MODETYPE_USER);
+ if (mh && mh->NeedsOper() && tag->AllowedUserModes[c - 'A'])
+ umodes.push_back(c);
+ mh = ServerInstance->Modes->FindMode(c, MODETYPE_CHANNEL);
+ if (mh && mh->NeedsOper() && tag->AllowedChanModes[c - 'A'])
+ cmodes.push_back(c);
+ }
+ stats.AddRow(243, 'O', tag->name, umodes, cmodes);
+ }
+ }
+ break;
+
+ /* stats l (show user I/O stats) */
+ case 'l':
+ /* stats L (show user I/O stats with IP addresses) */
+ case 'L':
+ GenerateStatsLl(stats);
+ break;
+
+ /* stats u (show server uptime) */
+ case 'u':
+ {
+ unsigned int up = static_cast<unsigned int>(ServerInstance->Time() - ServerInstance->startup_time);
+ stats.AddRow(242, InspIRCd::Format("Server up %u days, %.2u:%.2u:%.2u",
+ up / 86400, (up / 3600) % 24, (up / 60) % 60, up % 60));
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ stats.AddRow(219, statschar, "End of /STATS report");
+ ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)",
+ (IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str());
+ return;
+}
+
+CmdResult CommandStats::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ if (parameters.size() > 1 && parameters[1] != ServerInstance->Config->ServerName)
+ {
+ // Give extra penalty if a non-oper does /STATS <remoteserver>
+ LocalUser* localuser = IS_LOCAL(user);
+ if ((localuser) && (!user->IsOper()))
+ localuser->CommandFloodPenalty += 2000;
+ return CMD_SUCCESS;
+ }
+ Stats::Context stats(user, parameters[0][0]);
+ DoStats(stats);
+ const std::vector<Stats::Row>& rows = stats.GetRows();
+ for (std::vector<Stats::Row>::const_iterator i = rows.begin(); i != rows.end(); ++i)
+ {
+ const Stats::Row& row = *i;
+ user->WriteRemoteNumeric(row);
+ }
+
+ return CMD_SUCCESS;
+}
+
+COMMAND_INIT(CommandStats)
diff --git a/src/coremods/core_stub.cpp b/src/coremods/core_stub.cpp
new file mode 100644
index 000000000..6adc02f32
--- /dev/null
+++ b/src/coremods/core_stub.cpp
@@ -0,0 +1,156 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+
+/** Handle /CONNECT.
+ */
+class CommandConnect : public Command
+{
+ public:
+ /** Constructor for connect.
+ */
+ CommandConnect(Module* parent)
+ : Command(parent, "CONNECT", 1)
+ {
+ flags_needed = 'o';
+ syntax = "<servername>";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE
+ {
+ /*
+ * This is handled by the server linking module, if necessary. Do not remove this stub.
+ */
+ user->WriteNotice("Look into loading a linking module (like m_spanningtree) if you want this to do anything useful.");
+ return CMD_SUCCESS;
+ }
+};
+
+/** Handle /LINKS.
+ */
+class CommandLinks : public Command
+{
+ public:
+ /** Constructor for links.
+ */
+ CommandLinks(Module* parent)
+ : Command(parent, "LINKS", 0, 0)
+ {
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE
+ {
+ user->WriteNumeric(RPL_LINKS, ServerInstance->Config->ServerName, ServerInstance->Config->ServerName, InspIRCd::Format("0 %s", ServerInstance->Config->ServerDesc.c_str()));
+ user->WriteNumeric(RPL_ENDOFLINKS, '*', "End of /LINKS list.");
+ return CMD_SUCCESS;
+ }
+};
+
+/** Handle /SERVER.
+ */
+class CommandServer : public Command
+{
+ public:
+ /** Constructor for server.
+ */
+ CommandServer(Module* parent)
+ : Command(parent, "SERVER")
+ {
+ works_before_reg = true;
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE
+ {
+ if (user->registered == REG_ALL)
+ {
+ user->WriteNumeric(ERR_ALREADYREGISTERED, "You are already registered. (Perhaps your IRC client does not have a /SERVER command).");
+ }
+ else
+ {
+ user->WriteNumeric(ERR_NOTREGISTERED, "SERVER", "You may not register as a server (servers have separate ports from clients, change your config)");
+ }
+ return CMD_FAILURE;
+ }
+};
+
+/** Handle /SQUIT.
+ */
+class CommandSquit : public Command
+{
+ public:
+ /** Constructor for squit.
+ */
+ CommandSquit(Module* parent)
+ : Command(parent, "SQUIT", 1, 2)
+ {
+ flags_needed = 'o';
+ syntax = "<servername>";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE
+ {
+ user->WriteNotice("Look into loading a linking module (like m_spanningtree) if you want this to do anything useful.");
+ return CMD_FAILURE;
+ }
+};
+
+class CoreModStub : public Module
+{
+ CommandConnect cmdconnect;
+ CommandLinks cmdlinks;
+ CommandServer cmdserver;
+ CommandSquit cmdsquit;
+
+ public:
+ CoreModStub()
+ : cmdconnect(this), cmdlinks(this), cmdserver(this), cmdsquit(this)
+ {
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the stub commands CONNECT, LINKS, SERVER and SQUIT", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModStub)
diff --git a/src/coremods/core_user/cmd_away.cpp b/src/coremods/core_user/cmd_away.cpp
new file mode 100644
index 000000000..32d4a9d84
--- /dev/null
+++ b/src/coremods/core_user/cmd_away.cpp
@@ -0,0 +1,65 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_user.h"
+
+CommandAway::CommandAway(Module* parent)
+ : Command(parent, "AWAY", 0, 0)
+{
+ syntax = "[<message>]";
+}
+
+/** Handle /AWAY
+ */
+CmdResult CommandAway::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ ModResult MOD_RESULT;
+
+ if ((!parameters.empty()) && (!parameters[0].empty()))
+ {
+ FIRST_MOD_RESULT(OnSetAway, MOD_RESULT, (user, parameters[0]));
+
+ if (MOD_RESULT == MOD_RES_DENY && IS_LOCAL(user))
+ return CMD_FAILURE;
+
+ user->awaytime = ServerInstance->Time();
+ user->awaymsg.assign(parameters[0], 0, ServerInstance->Config->Limits.MaxAway);
+
+ user->WriteNumeric(RPL_NOWAWAY, "You have been marked as being away");
+ }
+ else
+ {
+ FIRST_MOD_RESULT(OnSetAway, MOD_RESULT, (user, ""));
+
+ if (MOD_RESULT == MOD_RES_DENY && IS_LOCAL(user))
+ return CMD_FAILURE;
+
+ user->awaymsg.clear();
+ user->WriteNumeric(RPL_UNAWAY, "You are no longer marked as being away");
+ }
+
+ return CMD_SUCCESS;
+}
+
+RouteDescriptor CommandAway::GetRouting(User* user, const std::vector<std::string>& parameters)
+{
+ return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST);
+}
diff --git a/src/coremods/core_user/cmd_mode.cpp b/src/coremods/core_user/cmd_mode.cpp
new file mode 100644
index 000000000..ec75d6191
--- /dev/null
+++ b/src/coremods/core_user/cmd_mode.cpp
@@ -0,0 +1,177 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2006-2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2004-2008 Craig Edwards <craigedwards@brainbox.cc>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "inspircd.h"
+#include "core_user.h"
+
+CommandMode::CommandMode(Module* parent)
+ : Command(parent, "MODE", 1)
+ , seq(0)
+{
+ syntax = "<target> <modes> {<mode-parameters>}";
+ memset(&sent, 0, sizeof(sent));
+}
+
+CmdResult CommandMode::Handle(const std::vector<std::string>& parameters, User* user)
+{
+ const std::string& target = parameters[0];
+ Channel* targetchannel = ServerInstance->FindChan(target);
+ User* targetuser = NULL;
+ if (!targetchannel)
+ {
+ if (IS_LOCAL(user))
+ targetuser = ServerInstance->FindNickOnly(target);
+ else
+ targetuser = ServerInstance->FindNick(target);
+ }
+
+ if ((!targetchannel) && (!targetuser))
+ {
+ if (target[0] == '#')
+ user->WriteNumeric(Numerics::NoSuchChannel(target));
+ else
+ user->WriteNumeric(Numerics::NoSuchNick(target));
+ return CMD_FAILURE;
+ }
+ if (parameters.size() == 1)
+ {
+ this->DisplayCurrentModes(user, targetuser, targetchannel);
+ return CMD_SUCCESS;
+ }
+
+ // Populate a temporary Modes::ChangeList with the parameters
+ Modes::ChangeList changelist;
+ ModeType type = targetchannel ? MODETYPE_CHANNEL : MODETYPE_USER;
+ ServerInstance->Modes.ModeParamsToChangeList(user, type, parameters, changelist);
+
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnPreMode, MOD_RESULT, (user, targetuser, targetchannel, changelist));
+
+ ModeParser::ModeProcessFlag flags = ModeParser::MODE_NONE;
+ if (IS_LOCAL(user))
+ {
+ if (MOD_RESULT == MOD_RES_PASSTHRU)
+ {
+ if ((targetuser) && (user != targetuser))
+ {
+ // Local users may only change the modes of other users if a module explicitly allows it
+ user->WriteNumeric(ERR_USERSDONTMATCH, "Can't change mode for other users");
+ return CMD_FAILURE;
+ }
+
+ // This is a mode change by a local user and modules didn't explicitly allow/deny.
+ // Ensure access checks will happen for each mode being changed.
+ flags |= ModeParser::MODE_CHECKACCESS;
+ }
+ else if (MOD_RESULT == MOD_RES_DENY)
+ return CMD_FAILURE; // Entire mode change denied by a module
+ }
+ else
+ flags |= ModeParser::MODE_LOCALONLY;
+
+ if (IS_LOCAL(user))
+ ServerInstance->Modes->ProcessSingle(user, targetchannel, targetuser, changelist, flags);
+ else
+ ServerInstance->Modes->Process(user, targetchannel, targetuser, changelist, flags);
+
+ if ((ServerInstance->Modes.GetLastParse().empty()) && (targetchannel) && (parameters.size() == 2))
+ {
+ /* Special case for displaying the list for listmodes,
+ * e.g. MODE #chan b, or MODE #chan +b without a parameter
+ */
+ this->DisplayListModes(user, targetchannel, parameters[1]);
+ }
+
+ return CMD_SUCCESS;
+}
+
+RouteDescriptor CommandMode::GetRouting(User* user, const std::vector<std::string>& parameters)
+{
+ return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST);
+}
+
+void CommandMode::DisplayListModes(User* user, Channel* chan, const std::string& mode_sequence)
+{
+ seq++;
+
+ for (std::string::const_iterator i = mode_sequence.begin(); i != mode_sequence.end(); ++i)
+ {
+ unsigned char mletter = *i;
+ if (mletter == '+')
+ continue;
+
+ ModeHandler* mh = ServerInstance->Modes->FindMode(mletter, MODETYPE_CHANNEL);
+ if (!mh || !mh->IsListMode())
+ return;
+
+ /* Ensure the user doesnt request the same mode twice,
+ * so they can't flood themselves off out of idiocy.
+ */
+ if (sent[mletter] == seq)
+ continue;
+
+ sent[mletter] = seq;
+ ServerInstance->Modes.ShowListModeList(user, chan, mh);
+ }
+}
+
+static std::string GetSnomasks(const User* user)
+{
+ ModeHandler* const snomask = ServerInstance->Modes.FindMode('s', MODETYPE_USER);
+ std::string snomaskstr = snomask->GetUserParameter(user);
+ // snomaskstr is empty if the snomask mode isn't set, otherwise it begins with a '+'.
+ // In the former case output a "+", not an empty string.
+ if (snomaskstr.empty())
+ snomaskstr.push_back('+');
+ return snomaskstr;
+}
+
+void CommandMode::DisplayCurrentModes(User* user, User* targetuser, Channel* targetchannel)
+{
+ if (targetchannel)
+ {
+ // Display channel's current mode string
+ user->WriteNumeric(RPL_CHANNELMODEIS, targetchannel->name, (std::string("+") + targetchannel->ChanModes(targetchannel->HasUser(user))));
+ user->WriteNumeric(RPL_CHANNELCREATED, targetchannel->name, (unsigned long)targetchannel->age);
+ }
+ else
+ {
+ if (targetuser == user)
+ {
+ // Display user's current mode string
+ user->WriteNumeric(RPL_UMODEIS, targetuser->GetModeLetters());
+ if (targetuser->IsOper())
+ user->WriteNumeric(RPL_SNOMASKIS, GetSnomasks(targetuser), "Server notice mask");
+ }
+ else if (user->HasPrivPermission("users/auspex"))
+ {
+ // Querying the modes of another user.
+ // We cannot use RPL_UMODEIS because that's only for showing the user's own modes.
+ user->WriteNumeric(RPL_OTHERUMODEIS, targetuser->nick, targetuser->GetModeLetters());
+ if (targetuser->IsOper())
+ user->WriteNumeric(RPL_OTHERSNOMASKIS, targetuser->nick, GetSnomasks(targetuser), "Server notice mask");
+ }
+ else
+ {
+ user->WriteNumeric(ERR_USERSDONTMATCH, "Can't view modes for other users");
+ }
+ }
+}
diff --git a/src/coremods/core_user/cmd_nick.cpp b/src/coremods/core_user/cmd_nick.cpp
new file mode 100644
index 000000000..80bfbe674
--- /dev/null
+++ b/src/coremods/core_user/cmd_nick.cpp
@@ -0,0 +1,98 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_user.h"
+
+CommandNick::CommandNick(Module* parent)
+ : SplitCommand(parent, "NICK", 1, 1)
+{
+ works_before_reg = true;
+ syntax = "<newnick>";
+ Penalty = 0;
+}
+
+/** Handle nick changes from users.
+ * NOTE: If you are used to ircds based on ircd2.8, and are looking
+ * for the client introduction code in here, youre in the wrong place.
+ * You need to look in the spanningtree module for this!
+ */
+CmdResult CommandNick::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
+{
+ std::string oldnick = user->nick;
+ std::string newnick = parameters[0];
+
+ // anything except the initial NICK gets a flood penalty
+ if (user->registered == REG_ALL)
+ user->CommandFloodPenalty += 4000;
+
+ if (newnick.empty())
+ {
+ user->WriteNumeric(ERR_NONICKNAMEGIVEN, "No nickname given");
+ return CMD_FAILURE;
+ }
+
+ if (newnick == "0")
+ {
+ newnick = user->uuid;
+ }
+ else if (!ServerInstance->IsNick(newnick))
+ {
+ user->WriteNumeric(ERR_ERRONEUSNICKNAME, newnick, "Erroneous Nickname");
+ return CMD_FAILURE;
+ }
+
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnUserPreNick, MOD_RESULT, (user, newnick));
+
+ // If a module denied the change, abort now
+ if (MOD_RESULT == MOD_RES_DENY)
+ return CMD_FAILURE;
+
+ // Disallow the nick change if <security:restrictbannedusers> is on and there is a ban matching this user in
+ // one of the channels they are on
+ if (ServerInstance->Config->RestrictBannedUsers != ServerConfig::BUT_NORMAL)
+ {
+ for (User::ChanList::iterator i = user->chans.begin(); i != user->chans.end(); ++i)
+ {
+ Channel* chan = (*i)->chan;
+ if (chan->GetPrefixValue(user) < VOICE_VALUE && chan->IsBanned(user))
+ {
+ if (ServerInstance->Config->RestrictBannedUsers == ServerConfig::BUT_RESTRICT_NOTIFY)
+ user->WriteNumeric(ERR_CANTCHANGENICK, InspIRCd::Format("Cannot change nickname while on %s (you're banned)",
+ chan->name.c_str()));
+ return CMD_FAILURE;
+ }
+ }
+ }
+
+ if (!user->ChangeNick(newnick))
+ return CMD_FAILURE;
+
+ if (user->registered < REG_NICKUSER)
+ {
+ user->registered = (user->registered | REG_NICK);
+ return CommandUser::CheckRegister(user);
+ }
+
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_user/cmd_part.cpp b/src/coremods/core_user/cmd_part.cpp
new file mode 100644
index 000000000..261d75a70
--- /dev/null
+++ b/src/coremods/core_user/cmd_part.cpp
@@ -0,0 +1,65 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_user.h"
+
+CommandPart::CommandPart(Module* parent)
+ : Command(parent, "PART", 1, 2)
+{
+ Penalty = 5;
+ syntax = "<channel>{,<channel>} [<reason>]";
+}
+
+CmdResult CommandPart::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ std::string reason;
+ if (parameters.size() > 1)
+ {
+ if (IS_LOCAL(user))
+ msgwrap.Wrap(parameters[1], reason);
+ else
+ reason = parameters[1];
+ }
+
+ if (CommandParser::LoopCall(user, this, parameters, 0))
+ return CMD_SUCCESS;
+
+ Channel* c = ServerInstance->FindChan(parameters[0]);
+
+ if (!c)
+ {
+ user->WriteNumeric(Numerics::NoSuchChannel(parameters[0]));
+ return CMD_FAILURE;
+ }
+
+ if (!c->PartUser(user, reason))
+ {
+ user->WriteNumeric(ERR_NOTONCHANNEL, c->name, "You're not on that channel");
+ return CMD_FAILURE;
+ }
+
+ return CMD_SUCCESS;
+}
+
+RouteDescriptor CommandPart::GetRouting(User* user, const std::vector<std::string>& parameters)
+{
+ return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST);
+}
diff --git a/src/coremods/core_user/cmd_quit.cpp b/src/coremods/core_user/cmd_quit.cpp
new file mode 100644
index 000000000..f9a4e1f70
--- /dev/null
+++ b/src/coremods/core_user/cmd_quit.cpp
@@ -0,0 +1,51 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_user.h"
+
+CommandQuit::CommandQuit(Module* parent)
+ : Command(parent, "QUIT", 0, 1)
+ , operquit("operquit", ExtensionItem::EXT_USER, parent)
+{
+ works_before_reg = true;
+ syntax = "[<message>]";
+}
+
+CmdResult CommandQuit::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ std::string quitmsg;
+ if (parameters.empty())
+ quitmsg = "Client exited";
+ else if (IS_LOCAL(user))
+ msgwrap.Wrap(parameters[0], quitmsg);
+ else
+ quitmsg = parameters[0];
+
+ std::string* operquitmsg = operquit.get(user);
+ ServerInstance->Users->QuitUser(user, quitmsg, operquitmsg);
+
+ return CMD_SUCCESS;
+}
+
+RouteDescriptor CommandQuit::GetRouting(User* user, const std::vector<std::string>& parameters)
+{
+ return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST);
+}
diff --git a/src/coremods/core_user/cmd_user.cpp b/src/coremods/core_user/cmd_user.cpp
new file mode 100644
index 000000000..452146adc
--- /dev/null
+++ b/src/coremods/core_user/cmd_user.cpp
@@ -0,0 +1,79 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_user.h"
+
+enum
+{
+ // From ircu.
+ ERR_INVALIDUSERNAME = 468
+};
+
+CommandUser::CommandUser(Module* parent)
+ : SplitCommand(parent, "USER", 4, 4)
+{
+ works_before_reg = true;
+ Penalty = 0;
+ syntax = "<username> <localhost> <remotehost> <realname>";
+}
+
+CmdResult CommandUser::HandleLocal(const std::vector<std::string>& parameters, LocalUser *user)
+{
+ /* A user may only send the USER command once */
+ if (!(user->registered & REG_USER))
+ {
+ if (!ServerInstance->IsIdent(parameters[0]))
+ {
+ user->WriteNumeric(ERR_INVALIDUSERNAME, name, "Your username is not valid");
+ return CMD_FAILURE;
+ }
+ else
+ {
+ user->ChangeIdent(parameters[0]);
+ user->fullname.assign(parameters[3].empty() ? "No info" : parameters[3], 0, ServerInstance->Config->Limits.MaxGecos);
+ user->registered = (user->registered | REG_USER);
+ }
+ }
+ else
+ {
+ user->WriteNumeric(ERR_ALREADYREGISTERED, "You may not reregister");
+ user->CommandFloodPenalty += 1000;
+ return CMD_FAILURE;
+ }
+
+ /* parameters 2 and 3 are local and remote hosts, and are ignored */
+ return CheckRegister(user);
+}
+
+CmdResult CommandUser::CheckRegister(LocalUser* user)
+{
+ // If the user is registered, return CMD_SUCCESS/CMD_FAILURE depending on what modules say, otherwise just
+ // return CMD_SUCCESS without doing anything, knowing the other handler will call us again
+ if (user->registered == REG_NICKUSER)
+ {
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT(OnUserRegister, MOD_RESULT, (user));
+ if (MOD_RESULT == MOD_RES_DENY)
+ return CMD_FAILURE;
+ }
+
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_user/core_user.cpp b/src/coremods/core_user/core_user.cpp
new file mode 100644
index 000000000..8504de8e0
--- /dev/null
+++ b/src/coremods/core_user/core_user.cpp
@@ -0,0 +1,183 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_user.h"
+
+/** Handle /PASS.
+ */
+class CommandPass : public SplitCommand
+{
+ public:
+ /** Constructor for pass.
+ */
+ CommandPass(Module* parent)
+ : SplitCommand(parent, "PASS", 1, 1)
+ {
+ works_before_reg = true;
+ Penalty = 0;
+ syntax = "<password>";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE
+ {
+ // Check to make sure they haven't registered -- Fix by FCS
+ if (user->registered == REG_ALL)
+ {
+ user->CommandFloodPenalty += 1000;
+ user->WriteNumeric(ERR_ALREADYREGISTERED, "You may not reregister");
+ return CMD_FAILURE;
+ }
+ user->password = parameters[0];
+
+ return CMD_SUCCESS;
+ }
+};
+
+/** Handle /PING.
+ */
+class CommandPing : public Command
+{
+ public:
+ /** Constructor for ping.
+ */
+ CommandPing(Module* parent)
+ : Command(parent, "PING", 1, 2)
+ {
+ syntax = "<servername> [:<servername>]";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE
+ {
+ user->WriteServ("PONG %s :%s", ServerInstance->Config->ServerName.c_str(), parameters[0].c_str());
+ return CMD_SUCCESS;
+ }
+};
+
+/** Handle /PONG.
+ */
+class CommandPong : public Command
+{
+ public:
+ /** Constructor for pong.
+ */
+ CommandPong(Module* parent)
+ : Command(parent, "PONG", 0, 1)
+ {
+ Penalty = 0;
+ syntax = "<ping-text>";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE
+ {
+ // set the user as alive so they survive to next ping
+ LocalUser* localuser = IS_LOCAL(user);
+ if (localuser)
+ {
+ // Increase penalty unless we've sent a PING and this is the reply
+ if (localuser->lastping)
+ localuser->CommandFloodPenalty += 1000;
+ else
+ localuser->lastping = 1;
+ }
+ return CMD_SUCCESS;
+ }
+};
+
+void MessageWrapper::Wrap(const std::string& message, std::string& out)
+{
+ // If there is a fixed message, it is stored in prefix. Otherwise prefix contains
+ // only the prefix, so append the message and the suffix
+ out.assign(prefix);
+ if (!fixed)
+ out.append(message).append(suffix);
+}
+
+void MessageWrapper::ReadConfig(const char* prefixname, const char* suffixname, const char* fixedname)
+{
+ ConfigTag* tag = ServerInstance->Config->ConfValue("options");
+ prefix = tag->getString(fixedname);
+ fixed = (!prefix.empty());
+ if (!fixed)
+ {
+ prefix = tag->getString(prefixname);
+ suffix = tag->getString(suffixname);
+ }
+}
+
+class CoreModUser : public Module
+{
+ CommandAway cmdaway;
+ CommandMode cmdmode;
+ CommandNick cmdnick;
+ CommandPart cmdpart;
+ CommandPass cmdpass;
+ CommandPing cmdping;
+ CommandPong cmdpong;
+ CommandQuit cmdquit;
+ CommandUser cmduser;
+ SimpleUserModeHandler invisiblemode;
+ ModeUserOperator operatormode;
+ ModeUserServerNoticeMask snomaskmode;
+
+ public:
+ CoreModUser()
+ : cmdaway(this)
+ , cmdmode(this)
+ , cmdnick(this)
+ , cmdpart(this)
+ , cmdpass(this)
+ , cmdping(this)
+ , cmdpong(this)
+ , cmdquit(this)
+ , cmduser(this)
+ , invisiblemode(this, "invisible", 'i')
+ , operatormode(this)
+ , snomaskmode(this)
+ {
+ }
+
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ cmdpart.msgwrap.ReadConfig("prefixpart", "suffixpart", "fixedpart");
+ cmdquit.msgwrap.ReadConfig("prefixquit", "suffixquit", "fixedquit");
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the AWAY, MODE, NICK, PART, PASS, PING, PONG, QUIT and USER commands", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModUser)
diff --git a/src/coremods/core_user/core_user.h b/src/coremods/core_user/core_user.h
new file mode 100644
index 000000000..befb07ef5
--- /dev/null
+++ b/src/coremods/core_user/core_user.h
@@ -0,0 +1,222 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "inspircd.h"
+#include "listmode.h"
+
+class MessageWrapper
+{
+ std::string prefix;
+ std::string suffix;
+ bool fixed;
+
+ public:
+ /**
+ * Wrap the given message according to the config rules
+ * @param message The message to wrap
+ * @param out String where the result is placed
+ */
+ void Wrap(const std::string& message, std::string& out);
+
+ /**
+ * Read the settings from the given config keys (options block)
+ * @param prefixname Name of the config key to read the prefix from
+ * @param suffixname Name of the config key to read the suffix from
+ * @param fixedname Name of the config key to read the fixed string string from.
+ * If this key has a non-empty value, all messages will be replaced with it.
+ */
+ void ReadConfig(const char* prefixname, const char* suffixname, const char* fixedname);
+};
+
+/** Handle /AWAY.
+ */
+class CommandAway : public Command
+{
+ public:
+ /** Constructor for away.
+ */
+ CommandAway(Module* parent);
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE;
+};
+
+class CommandMode : public Command
+{
+ unsigned int sent[256];
+ unsigned int seq;
+
+ /** Show the list of one or more list modes to a user.
+ * @param user User to send to.
+ * @param chan Channel whose lists to show.
+ * @param mode_sequence Mode letters to show the lists of.
+ */
+ void DisplayListModes(User* user, Channel* chan, const std::string& mode_sequence);
+
+ /** Show the current modes of a channel or a user to a user.
+ * @param user User to show the modes to.
+ * @param targetuser User whose modes to show. NULL if showing the modes of a channel.
+ * @param targetchannel Channel whose modes to show. NULL if showing the modes of a user.
+ */
+ void DisplayCurrentModes(User* user, User* targetuser, Channel* targetchannel);
+
+ public:
+ /** Constructor for mode.
+ */
+ CommandMode(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE;
+};
+
+/** Handle /NICK.
+ */
+class CommandNick : public SplitCommand
+{
+ public:
+ /** Constructor for nick.
+ */
+ CommandNick(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+};
+
+/** Handle /PART.
+ */
+class CommandPart : public Command
+{
+ public:
+ MessageWrapper msgwrap;
+
+ /** Constructor for part.
+ */
+ CommandPart(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE;
+};
+
+/** Handle /QUIT.
+ */
+class CommandQuit : public Command
+{
+ private:
+ StringExtItem operquit;
+
+ public:
+ MessageWrapper msgwrap;
+
+ /** Constructor for quit.
+ */
+ CommandQuit(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE;
+};
+
+/** Handle /USER.
+ */
+class CommandUser : public SplitCommand
+{
+ public:
+ /** Constructor for user.
+ */
+ CommandUser(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+
+ /** Run the OnUserRegister hook if the user has sent both NICK and USER. Called after an unregistered user
+ * successfully executes the USER or the NICK command.
+ * @param user User to inspect and possibly pass to the OnUserRegister hook
+ * @return CMD_FAILURE if OnUserRegister was called and it returned MOD_RES_DENY, CMD_SUCCESS in every other case
+ * (i.e. if the hook wasn't fired because the user still needs to send NICK/USER or if it was fired and finished with
+ * a non-MOD_RES_DENY result).
+ */
+ static CmdResult CheckRegister(LocalUser* user);
+};
+
+/** User mode +s
+ */
+class ModeUserServerNoticeMask : public ModeHandler
+{
+ /** Process a snomask modifier string, e.g. +abc-de
+ * @param user The target user
+ * @param input A sequence of notice mask characters
+ * @return The cleaned mode sequence which can be output,
+ * e.g. in the above example if masks c and e are not
+ * valid, this function will return +ab-d
+ */
+ std::string ProcessNoticeMasks(User* user, const std::string& input);
+
+ public:
+ ModeUserServerNoticeMask(Module* Creator);
+ ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding) CXX11_OVERRIDE;
+ void OnParameterMissing(User* user, User* dest, Channel* channel) CXX11_OVERRIDE;
+
+ /** Create a displayable mode string of the snomasks set on a given user
+ * @param user The user whose notice masks to format
+ * @return The notice mask character sequence
+ */
+ std::string GetUserParameter(const User* user) const CXX11_OVERRIDE;
+};
+
+/** User mode +o
+ */
+class ModeUserOperator : public ModeHandler
+{
+ public:
+ ModeUserOperator(Module* Creator);
+ ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding) CXX11_OVERRIDE;
+};
diff --git a/src/coremods/core_user/umode_o.cpp b/src/coremods/core_user/umode_o.cpp
new file mode 100644
index 000000000..20668fdaa
--- /dev/null
+++ b/src/coremods/core_user/umode_o.cpp
@@ -0,0 +1,51 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_user.h"
+
+ModeUserOperator::ModeUserOperator(Module* Creator)
+ : ModeHandler(Creator, "oper", 'o', PARAM_NONE, MODETYPE_USER)
+{
+ oper = true;
+}
+
+ModeAction ModeUserOperator::OnModeChange(User* source, User* dest, Channel*, std::string&, bool adding)
+{
+ /* Only opers can execute this class at all */
+ if (!source->server->IsULine() && !source->IsOper())
+ return MODEACTION_DENY;
+
+ /* Not even opers can GIVE the +o mode, only take it away */
+ if (adding)
+ return MODEACTION_DENY;
+
+ /* Set the bitfields.
+ * Note that oper status is only given in User::Oper()
+ * NOT here. It is impossible to directly set +o without
+ * verifying as an oper and getting an opertype assigned
+ * to your User!
+ */
+ char snomask = IS_LOCAL(dest) ? 'o' : 'O';
+ ServerInstance->SNO->WriteToSnoMask(snomask, "User %s de-opered (by %s)", dest->nick.c_str(), source->nick.c_str());
+ dest->UnOper();
+
+ return MODEACTION_ALLOW;
+}
diff --git a/src/coremods/core_user/umode_s.cpp b/src/coremods/core_user/umode_s.cpp
new file mode 100644
index 000000000..0122ebe3e
--- /dev/null
+++ b/src/coremods/core_user/umode_s.cpp
@@ -0,0 +1,145 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "core_user.h"
+
+ModeUserServerNoticeMask::ModeUserServerNoticeMask(Module* Creator)
+ : ModeHandler(Creator, "snomask", 's', PARAM_SETONLY, MODETYPE_USER)
+{
+ oper = true;
+}
+
+ModeAction ModeUserServerNoticeMask::OnModeChange(User* source, User* dest, Channel*, std::string &parameter, bool adding)
+{
+ if (adding)
+ {
+ dest->SetMode(this, true);
+ // Process the parameter (remove chars we don't understand, remove redundant chars, etc.)
+ parameter = ProcessNoticeMasks(dest, parameter);
+ return MODEACTION_ALLOW;
+ }
+ else
+ {
+ if (dest->IsModeSet(this))
+ {
+ dest->SetMode(this, false);
+ dest->snomasks.reset();
+ return MODEACTION_ALLOW;
+ }
+ }
+
+ // Mode not set and trying to unset, deny
+ return MODEACTION_DENY;
+}
+
+std::string ModeUserServerNoticeMask::GetUserParameter(const User* user) const
+{
+ std::string ret;
+ if (!user->IsModeSet(this))
+ return ret;
+
+ ret.push_back('+');
+ for (unsigned char n = 0; n < 64; n++)
+ {
+ if (user->snomasks[n])
+ ret.push_back(n + 'A');
+ }
+ return ret;
+}
+
+void ModeUserServerNoticeMask::OnParameterMissing(User* user, User* dest, Channel* channel)
+{
+ user->WriteNotice("*** The user mode +s requires a parameter (server notice mask). Please provide a parameter, e.g. '+s +*'.");
+}
+
+std::string ModeUserServerNoticeMask::ProcessNoticeMasks(User* user, const std::string& input)
+{
+ bool adding = true;
+ std::bitset<64> curr = user->snomasks;
+
+ for (std::string::const_iterator i = input.begin(); i != input.end(); ++i)
+ {
+ switch (*i)
+ {
+ case '+':
+ adding = true;
+ break;
+ case '-':
+ adding = false;
+ break;
+ case '*':
+ for (size_t j = 0; j < 64; j++)
+ {
+ if (ServerInstance->SNO->IsSnomaskUsable(j+'A'))
+ curr[j] = adding;
+ }
+ break;
+ default:
+ // For local users check whether the given snomask is valid and enabled - IsSnomaskUsable() tests both.
+ // For remote users accept what we were told, unless the snomask char is not a letter.
+ if (IS_LOCAL(user))
+ {
+ if (!ServerInstance->SNO->IsSnomaskUsable(*i))
+ {
+ user->WriteNumeric(ERR_UNKNOWNSNOMASK, *i, "is unknown snomask char to me");
+ continue;
+ }
+ }
+ else if (!(((*i >= 'a') && (*i <= 'z')) || ((*i >= 'A') && (*i <= 'Z'))))
+ continue;
+
+ size_t index = ((*i) - 'A');
+ curr[index] = adding;
+ break;
+ }
+ }
+
+ std::string plus = "+";
+ std::string minus = "-";
+
+ // Apply changes and construct two strings consisting of the newly added and the removed snomask chars
+ for (size_t i = 0; i < 64; i++)
+ {
+ bool isset = curr[i];
+ if (user->snomasks[i] != isset)
+ {
+ user->snomasks[i] = isset;
+ std::string& appendhere = (isset ? plus : minus);
+ appendhere.push_back(i+'A');
+ }
+ }
+
+ // Create the final string that will be shown to the user and sent to servers
+ // Form: "+ABc-de"
+ std::string output;
+ if (plus.length() > 1)
+ output = plus;
+
+ if (minus.length() > 1)
+ output += minus;
+
+ // Unset the snomask usermode itself if every snomask was unset
+ if (user->snomasks.none())
+ user->SetMode(this, false);
+
+ return output;
+}
diff --git a/src/coremods/core_userhost.cpp b/src/coremods/core_userhost.cpp
new file mode 100644
index 000000000..2ed19f4d7
--- /dev/null
+++ b/src/coremods/core_userhost.cpp
@@ -0,0 +1,85 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+/** Handle /USERHOST.
+ */
+class CommandUserhost : public Command
+{
+ UserModeReference hideopermode;
+
+ public:
+ /** Constructor for userhost.
+ */
+ CommandUserhost(Module* parent)
+ : Command(parent,"USERHOST", 1)
+ , hideopermode(parent, "hideoper")
+ {
+ syntax = "<nick> [<nick> ...]";
+ }
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+CmdResult CommandUserhost::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ const bool has_privs = user->HasPrivPermission("users/auspex");
+
+ std::string retbuf;
+
+ unsigned int max = parameters.size();
+ if (max > 5)
+ max = 5;
+
+ for (unsigned int i = 0; i < max; i++)
+ {
+ User *u = ServerInstance->FindNickOnly(parameters[i]);
+
+ if ((u) && (u->registered == REG_ALL))
+ {
+ retbuf += u->nick;
+
+ if (u->IsOper())
+ {
+ // XXX: +H hidden opers must not be shown as opers
+ if ((u == user) || (has_privs) || (!u->IsModeSet(hideopermode)))
+ retbuf += '*';
+ }
+
+ retbuf += '=';
+ retbuf += (u->IsAway() ? '-' : '+');
+ retbuf += u->ident;
+ retbuf += '@';
+ retbuf += u->GetHost(u == user || has_privs);
+ retbuf += ' ';
+ }
+ }
+
+ user->WriteNumeric(RPL_USERHOST, retbuf);
+
+ return CMD_SUCCESS;
+}
+
+COMMAND_INIT(CommandUserhost)
diff --git a/src/coremods/core_wallops.cpp b/src/coremods/core_wallops.cpp
new file mode 100644
index 000000000..d45ede846
--- /dev/null
+++ b/src/coremods/core_wallops.cpp
@@ -0,0 +1,69 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+/** Handle /WALLOPS.
+ */
+class CommandWallops : public Command
+{
+ SimpleUserModeHandler wallopsmode;
+
+ public:
+ /** Constructor for wallops.
+ */
+ CommandWallops(Module* parent)
+ : Command(parent, "WALLOPS", 1, 1)
+ , wallopsmode(parent, "wallops", 'w')
+ {
+ flags_needed = 'o';
+ syntax = "<any-text>";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+
+ RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE
+ {
+ return ROUTE_BROADCAST;
+ }
+};
+
+CmdResult CommandWallops::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ std::string wallop("WALLOPS :");
+ wallop.append(parameters[0]);
+
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ User* t = *i;
+ if (t->IsModeSet(wallopsmode))
+ t->WriteFrom(user, wallop);
+ }
+
+ return CMD_SUCCESS;
+}
+
+COMMAND_INIT(CommandWallops)
diff --git a/src/coremods/core_who.cpp b/src/coremods/core_who.cpp
new file mode 100644
index 000000000..305733e03
--- /dev/null
+++ b/src/coremods/core_who.cpp
@@ -0,0 +1,591 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2018 Peter Powell <petpow@saberuk.com>
+ * Copyright (C) 2014 Adam <Adam@anope.org>
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "modules/account.h"
+
+enum
+{
+ // From RFC 1459.
+ RPL_ENDOFWHO = 315,
+ RPL_WHOREPLY = 352,
+
+ // From ircu.
+ RPL_WHOSPCRPL = 354
+};
+
+struct WhoData
+{
+ // The flags for matching users to include.
+ std::bitset<UCHAR_MAX> flags;
+
+ // Whether we are matching using a wildcard or a flag.
+ bool fuzzy_match;
+
+ // The text to match against.
+ std::string matchtext;
+
+ // The WHO/WHOX responses we will send to the source.
+ std::vector<Numeric::Numeric> results;
+
+ // Whether the source requested a WHOX response.
+ bool whox;
+
+ // The fields to include in the WHOX response.
+ std::bitset<UCHAR_MAX> whox_fields;
+
+ // A user specified label for the WHOX response.
+ std::string whox_querytype;
+
+ WhoData(const std::vector<std::string>& parameters)
+ : whox(false)
+ {
+ // Find the matchtext and swap the 0 for a * so we can use InspIRCd::Match on it.
+ matchtext = parameters.size() > 2 ? parameters[2] : parameters[0];
+ if (matchtext == "0")
+ matchtext = "*";
+
+ // Fuzzy matches are when the source has not specified a specific user.
+ fuzzy_match = (parameters.size() > 1) || (matchtext.find_first_of("*?.") != std::string::npos);
+
+ // If flags have been specified by the source.
+ if (parameters.size() > 1)
+ {
+ std::bitset<UCHAR_MAX>* current_bitset = &flags;
+ for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter)
+ {
+ unsigned char chr = static_cast<unsigned char>(*iter);
+
+ // If the source specifies a percentage the rest of the flags are WHOX fields.
+ if (chr == '%')
+ {
+ whox = true;
+ current_bitset = &whox_fields;
+ continue;
+ }
+
+ // If we are in WHOX mode and the source specifies a comma
+ // the rest of the parameter is the query type.
+ if (whox && chr == ',')
+ {
+ whox_querytype.assign(++iter, parameters[1].end());
+ break;
+ }
+
+ // The source specified a matching flag.
+ current_bitset->set(chr);
+ }
+ }
+ }
+};
+
+class CommandWho : public SplitCommand
+{
+ private:
+ ChanModeReference secretmode;
+ ChanModeReference privatemode;
+ UserModeReference hidechansmode;
+ UserModeReference invisiblemode;
+
+ /** Determines whether a user can view the users of a channel. */
+ bool CanView(Channel* chan, User* user)
+ {
+ // If we are in a channel we can view all users in it.
+ if (chan->HasUser(user))
+ return true;
+
+ // Opers with the users/auspex priv can see everything.
+ if (user->HasPrivPermission("users/auspex"))
+ return true;
+
+ // You can see inside a channel from outside unless it is secret or private.
+ return !chan->IsModeSet(secretmode) && !chan->IsModeSet(privatemode);
+ }
+
+ /** Gets the first channel which is visible between the source and the target users. */
+ Membership* GetFirstVisibleChannel(LocalUser* source, User* user)
+ {
+ for (User::ChanList::iterator iter = user->chans.begin(); iter != user->chans.end(); ++iter)
+ {
+ Membership* memb = *iter;
+
+ // TODO: move the +I check into m_hidechans.
+ bool has_modes = memb->chan->IsModeSet(secretmode) || memb->chan->IsModeSet(privatemode) || user->IsModeSet(hidechansmode);
+ if (source == user || !has_modes || memb->chan->HasUser(source))
+ return memb;
+ }
+ return NULL;
+ }
+
+ /** Determines whether WHO flags match a specific channel user. */
+ static bool MatchChannel(LocalUser* source, Membership* memb, WhoData& data);
+
+ /** Determines whether WHO flags match a specific user. */
+ static bool MatchUser(LocalUser* source, User* target, WhoData& data);
+
+ /** Performs a WHO request on a channel. */
+ void WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* c, WhoData& data);
+
+ /** Template for getting a user from various types of collection. */
+ template<typename T>
+ static User* GetUser(T& t);
+
+ /** Performs a WHO request on a list of users. */
+ template<typename T>
+ void WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data);
+
+ public:
+ CommandWho(Module* parent)
+ : SplitCommand(parent, "WHO", 1, 3)
+ , secretmode(parent, "secret")
+ , privatemode(parent, "private")
+ , hidechansmode(parent, "hidechans")
+ , invisiblemode(parent, "invisible")
+ {
+ allow_empty_last_param = false;
+ syntax = "<server>|<nickname>|<channel>|<realname>|<host>|0 [[Aafhilmnoprstu] <server>|<nickname>|<channel>|<realname>|<host>|0]";
+ }
+
+ /** Sends a WHO reply to a user. */
+ void SendWhoLine(LocalUser* user, const std::vector<std::string>& parameters, Membership* memb, User* u, WhoData& data);
+
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+};
+
+template<> User* CommandWho::GetUser(UserManager::OperList::const_iterator& t) { return *t; }
+template<> User* CommandWho::GetUser(user_hash::const_iterator& t) { return t->second; }
+
+bool CommandWho::MatchChannel(LocalUser* source, Membership* memb, WhoData& data)
+{
+ bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
+ bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
+
+ // The source only wants remote users. This user is eligible if:
+ // (1) The source can't see server information.
+ // (2) The source is not local to the current server.
+ LocalUser* lu = IS_LOCAL(memb->user);
+ if (data.flags['f'] && source_can_see_server && lu)
+ return false;
+
+ // The source only wants local users. This user is eligible if:
+ // (1) The source can't see server information.
+ // (2) The source is local to the current server.
+ if (data.flags['l'] && source_can_see_server && !lu)
+ return false;
+
+ // Only show operators if the oper flag has been specified.
+ if (data.flags['o'] && !memb->user->IsOper())
+ return false;
+
+ // All other flags are ignored for channels.
+ return true;
+}
+
+bool CommandWho::MatchUser(LocalUser* source, User* user, WhoData& data)
+{
+ // Users who are not fully registered can never match.
+ if (user->registered != REG_ALL)
+ return false;
+
+ bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
+ bool source_can_see_target = source == user || source_has_users_auspex;
+ bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
+
+ // The source only wants remote users. This user is eligible if:
+ // (1) The source can't see server information.
+ // (2) The source is not local to the current server.
+ LocalUser* lu = IS_LOCAL(user);
+ if (data.flags['f'] && source_can_see_server && lu)
+ return false;
+
+ // The source only wants local users. This user is eligible if:
+ // (1) The source can't see server information.
+ // (2) The source is local to the current server.
+ if (data.flags['l'] && source_can_see_server && !lu)
+ return false;
+
+ // The source wants to match against users' away messages.
+ bool match = false;
+ if (data.flags['A'])
+ match = user->IsAway() && InspIRCd::Match(user->awaymsg, data.matchtext, ascii_case_insensitive_map);
+
+ // The source wants to match against users' account names.
+ else if (data.flags['a'])
+ {
+ const AccountExtItem* accountext = GetAccountExtItem();
+ const std::string* account = accountext ? accountext->get(user) : NULL;
+ match = account && InspIRCd::Match(*account, data.matchtext);
+ }
+
+ // The source wants to match against users' hostnames.
+ else if (data.flags['h'])
+ {
+ const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
+ match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
+ }
+
+ // The source wants to match against users' IP addresses.
+ else if (data.flags['i'])
+ match = source_can_see_target && InspIRCd::MatchCIDR(user->GetIPString(), data.matchtext, ascii_case_insensitive_map);
+
+ // The source wants to match against users' modes.
+ else if (data.flags['m'])
+ {
+ if (source_can_see_target)
+ {
+ bool set = true;
+ for (std::string::const_iterator iter = data.matchtext.begin(); iter != data.matchtext.end(); ++iter)
+ {
+ unsigned char chr = static_cast<unsigned char>(*iter);
+ switch (chr)
+ {
+ // The following user modes should be set.
+ case '+':
+ set = true;
+ break;
+
+ // The following user modes should be unset.
+ case '-':
+ set = false;
+ break;
+
+ default:
+ if (user->IsModeSet(chr) != set)
+ return false;
+ break;
+ }
+ }
+
+ // All of the modes matched.
+ return true;
+ }
+ }
+
+ // The source wants to match against users' nicks.
+ else if (data.flags['n'])
+ match = InspIRCd::Match(user->nick, data.matchtext);
+
+ // The source wants to match against users' connection ports.
+ else if (data.flags['p'])
+ {
+ if (source_can_see_target && lu)
+ {
+ irc::portparser portrange(data.matchtext, false);
+ long port;
+ while ((port = portrange.GetToken()))
+ {
+ if (port == lu->GetServerPort())
+ {
+ match = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // The source wants to match against users' real names.
+ else if (data.flags['r'])
+ match = InspIRCd::Match(user->fullname, data.matchtext, ascii_case_insensitive_map);
+
+ else if (data.flags['s'])
+ {
+ bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
+ const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
+ match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
+ }
+
+ // The source wants to match against users' connection times.
+ else if (data.flags['t'])
+ {
+ time_t seconds = ServerInstance->Time() - InspIRCd::Duration(data.matchtext);
+ if (user->signon >= seconds)
+ match = true;
+ }
+
+ // The source wants to match against users' idents.
+ else if (data.flags['u'])
+ match = InspIRCd::Match(user->ident, data.matchtext, ascii_case_insensitive_map);
+
+ // The <name> passed to WHO is matched against users' host, server,
+ // real name and nickname if the channel <name> cannot be found.
+ else
+ {
+ const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
+ match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
+
+ if (!match)
+ {
+ bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
+ const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
+ match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
+ }
+
+ if (!match)
+ match = InspIRCd::Match(user->fullname, data.matchtext, ascii_case_insensitive_map);
+
+ if (!match)
+ match = InspIRCd::Match(user->nick, data.matchtext);
+ }
+
+ return match;
+}
+
+void CommandWho::WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* chan, WhoData& data)
+{
+ if (!CanView(chan, source))
+ return;
+
+ bool inside = chan->HasUser(source);
+ const Channel::MemberMap& users = chan->GetUsers();
+ for (Channel::MemberMap::const_iterator iter = users.begin(); iter != users.end(); ++iter)
+ {
+ User* user = iter->first;
+ Membership* memb = iter->second;
+
+ // Only show invisible users if the source is in the channel or has the users/auspex priv.
+ if (!inside && user->IsModeSet(invisiblemode) && !source->HasPrivPermission("users/auspex"))
+ continue;
+
+ // Skip the user if it doesn't match the query.
+ if (!MatchChannel(source, memb, data))
+ continue;
+
+ SendWhoLine(source, parameters, memb, user, data);
+ }
+}
+
+template<typename T>
+void CommandWho::WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data)
+{
+ for (typename T::const_iterator iter = users.begin(); iter != users.end(); ++iter)
+ {
+ User* user = GetUser(iter);
+
+ // Only show users in response to a fuzzy WHO if we can see them normally.
+ bool can_see_normally = user == source || source->SharesChannelWith(user) || !user->IsModeSet(invisiblemode);
+ if (data.fuzzy_match && !can_see_normally && !source->HasPrivPermission("users/auspex"))
+ continue;
+
+ // Skip the user if it doesn't match the query.
+ if (!MatchUser(source, user, data))
+ continue;
+
+ SendWhoLine(source, parameters, NULL, user, data);
+ }
+}
+
+void CommandWho::SendWhoLine(LocalUser* source, const std::vector<std::string>& parameters, Membership* memb, User* user, WhoData& data)
+{
+ if (!memb)
+ memb = GetFirstVisibleChannel(source, user);
+
+ bool source_can_see_target = source == user || source->HasPrivPermission("users/auspex");
+ Numeric::Numeric wholine(data.whox ? RPL_WHOSPCRPL : RPL_WHOREPLY);
+ if (data.whox)
+ {
+ // The source used WHOX so we send a fancy customised response.
+
+ // Include the query type in the reply.
+ if (data.whox_fields['t'])
+ wholine.push(data.whox_querytype.empty() || data.whox_querytype.length() > 3 ? "0" : data.whox_querytype);
+
+ // Include the first channel name.
+ if (data.whox_fields['c'])
+ wholine.push(memb ? memb->chan->name : "*");
+
+ // Include the user's ident.
+ if (data.whox_fields['u'])
+ wholine.push(user->ident);
+
+ // Include the user's IP address.
+ if (data.whox_fields['i'])
+ wholine.push(source_can_see_target ? user->GetIPString() : "255.255.255.255");
+
+ // Include the user's hostname.
+ if (data.whox_fields['h'])
+ wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
+
+ // Include the server name.
+ if (data.whox_fields['s'])
+ {
+ if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
+ wholine.push(user->server->GetName());
+ else
+ wholine.push(ServerInstance->Config->HideServer);
+ }
+
+ // Include the user's nickname.
+ if (data.whox_fields['n'])
+ wholine.push(user->nick);
+
+ // Include the user's flags.
+ if (data.whox_fields['f'])
+ {
+ // Away state.
+ std::string flags(user->IsAway() ? "G" : "H");
+
+ // Operator status.
+ if (user->IsOper())
+ flags.push_back('*');
+
+ // Membership prefix.
+ if (memb)
+ {
+ char prefix = memb->GetPrefixChar();
+ if (prefix)
+ flags.push_back(prefix);
+ }
+
+ wholine.push(flags);
+ }
+
+ // Include the number of hops between the users.
+ if (data.whox_fields['d'])
+ wholine.push("0");
+
+ // Include the user's idle time.
+ if (data.whox_fields['l'])
+ {
+ LocalUser* lu = IS_LOCAL(user);
+ unsigned long idle = lu ? ServerInstance->Time() - lu->idle_lastmsg : 0;
+ wholine.push(ConvToStr(idle));
+ }
+
+ // Include the user's account name.
+ if (data.whox_fields['a'])
+ {
+ const AccountExtItem* accountext = GetAccountExtItem();
+ const std::string* account = accountext ? accountext->get(user) : NULL;
+ wholine.push(account ? *account : "0");
+ }
+
+ // Include the user's operator rank level.
+ if (data.whox_fields['o'])
+ wholine.push(memb ? ConvToStr(memb->getRank()) : "0");
+
+ // Include the user's real name.
+ if (data.whox_fields['r'])
+ wholine.push(user->fullname);
+ }
+ else
+ {
+ // We are not using WHOX so we just send a plain RFC response.
+
+ // Include the channel name.
+ wholine.push(memb ? memb->chan->name : "*");
+
+ // Include the user's ident.
+ wholine.push(user->ident);
+
+ // Include the user's hostname.
+ wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
+
+ // Include the server name.
+ if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
+ wholine.push(user->server->GetName());
+ else
+ wholine.push(ServerInstance->Config->HideServer);
+
+ // Include the user's nick.
+ wholine.push(user->nick);
+
+ // Include the user's flags.
+ {
+ // Away state.
+ std::string flags(user->IsAway() ? "G" : "H");
+
+ // Operator status.
+ if (user->IsOper())
+ flags.push_back('*');
+
+ // Membership prefix.
+ if (memb)
+ {
+ char prefix = memb->GetPrefixChar();
+ if (prefix)
+ flags.push_back(prefix);
+ }
+
+ wholine.push(flags);
+ }
+
+ // Include the number of hops between the users and the user's real name.
+ wholine.push("0 ");
+ wholine.GetParams().back().append(user->fullname);
+ }
+
+ ModResult res;
+ FIRST_MOD_RESULT(OnSendWhoLine, res, (source, parameters, user, memb, wholine));
+ if (res != MOD_RES_DENY)
+ data.results.push_back(wholine);
+}
+
+CmdResult CommandWho::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
+{
+ WhoData data(parameters);
+
+ // Is the source running a WHO on a channel?
+ Channel* chan = ServerInstance->FindChan(data.matchtext);
+ if (chan)
+ WhoChannel(user, parameters, chan, data);
+
+ // If we only want to match against opers we only have to iterate the oper list.
+ else if (data.flags['o'])
+ WhoUsers(user, parameters, ServerInstance->Users->all_opers, data);
+
+ // Otherwise we have to use the global user list.
+ else
+ WhoUsers(user, parameters, ServerInstance->Users->GetUsers(), data);
+
+ // Send the results to the source.
+ for (std::vector<Numeric::Numeric>::const_iterator n = data.results.begin(); n != data.results.end(); ++n)
+ user->WriteNumeric(*n);
+ user->WriteNumeric(RPL_ENDOFWHO, (data.matchtext.empty() ? "*" : data.matchtext.c_str()), "End of /WHO list.");
+
+ // Penalize the source a bit for large queries with one unit of penalty per 200 results.
+ user->CommandFloodPenalty += data.results.size() * 5;
+ return CMD_SUCCESS;
+}
+
+class CoreModWho : public Module
+{
+ private:
+ CommandWho cmd;
+
+ public:
+ CoreModWho()
+ : cmd(this)
+ {
+ }
+
+ void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
+ {
+ tokens["WHOX"];
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the WHO command", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModWho)
diff --git a/src/coremods/core_whois.cpp b/src/coremods/core_whois.cpp
new file mode 100644
index 000000000..4ec592781
--- /dev/null
+++ b/src/coremods/core_whois.cpp
@@ -0,0 +1,370 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "modules/whois.h"
+
+enum
+{
+ // From RFC 1459.
+ RPL_WHOISUSER = 311,
+ RPL_WHOISOPERATOR = 313,
+ RPL_WHOISIDLE = 317,
+ RPL_WHOISCHANNELS = 319,
+
+ // From UnrealIRCd.
+ RPL_WHOISHOST = 378,
+ RPL_WHOISMODES = 379,
+
+ // InspIRCd-specific.
+ RPL_CHANNELSMSG = 651
+};
+
+enum SplitWhoisState
+{
+ // Don't split private/secret channels into a separate RPL_WHOISCHANNELS numeric.
+ SPLITWHOIS_NONE,
+
+ // Split private/secret channels into a separate RPL_WHOISCHANNELS numeric.
+ SPLITWHOIS_SPLIT,
+
+ // Split private/secret channels into a separate RPL_WHOISCHANNELS numeric with RPL_CHANNELSMSG to explain the split.
+ SPLITWHOIS_SPLITMSG
+};
+
+class WhoisContextImpl : public Whois::Context
+{
+ Events::ModuleEventProvider& lineevprov;
+
+ public:
+ WhoisContextImpl(LocalUser* src, User* targ, Events::ModuleEventProvider& evprov)
+ : Whois::Context(src, targ)
+ , lineevprov(evprov)
+ {
+ }
+
+ using Whois::Context::SendLine;
+ void SendLine(Numeric::Numeric& numeric) CXX11_OVERRIDE;
+};
+
+void WhoisContextImpl::SendLine(Numeric::Numeric& numeric)
+{
+ ModResult MOD_RESULT;
+ FIRST_MOD_RESULT_CUSTOM(lineevprov, Whois::LineEventListener, OnWhoisLine, MOD_RESULT, (*this, numeric));
+
+ if (MOD_RESULT != MOD_RES_DENY)
+ source->WriteNumeric(numeric);
+}
+
+/** Handle /WHOIS.
+ */
+class CommandWhois : public SplitCommand
+{
+ ChanModeReference secretmode;
+ ChanModeReference privatemode;
+ UserModeReference snomaskmode;
+ Events::ModuleEventProvider evprov;
+ Events::ModuleEventProvider lineevprov;
+
+ void DoWhois(LocalUser* user, User* dest, time_t signon, unsigned long idle);
+ void SendChanList(WhoisContextImpl& whois);
+
+ public:
+ SplitWhoisState splitwhois;
+
+ /** Constructor for whois.
+ */
+ CommandWhois(Module* parent)
+ : SplitCommand(parent, "WHOIS", 1)
+ , secretmode(parent, "secret")
+ , privatemode(parent, "private")
+ , snomaskmode(parent, "snomask")
+ , evprov(parent, "event/whois")
+ , lineevprov(parent, "event/whoisline")
+ {
+ Penalty = 2;
+ syntax = "<nick>{,<nick>}";
+ }
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+ CmdResult HandleRemote(const std::vector<std::string>& parameters, RemoteUser* target) CXX11_OVERRIDE;
+};
+
+class WhoisNumericSink
+{
+ WhoisContextImpl& whois;
+ public:
+ WhoisNumericSink(WhoisContextImpl& whoisref)
+ : whois(whoisref)
+ {
+ }
+
+ void operator()(Numeric::Numeric& numeric) const
+ {
+ whois.SendLine(numeric);
+ }
+};
+
+class WhoisChanListNumericBuilder : public Numeric::GenericBuilder<' ', false, WhoisNumericSink>
+{
+ public:
+ WhoisChanListNumericBuilder(WhoisContextImpl& whois)
+ : Numeric::GenericBuilder<' ', false, WhoisNumericSink>(WhoisNumericSink(whois), RPL_WHOISCHANNELS, false, whois.GetSource()->nick.size() + whois.GetTarget()->nick.size() + 1)
+ {
+ GetNumeric().push(whois.GetTarget()->nick).push(std::string());
+ }
+};
+
+class WhoisChanList
+{
+ const SplitWhoisState& splitwhois;
+ WhoisChanListNumericBuilder num;
+ WhoisChanListNumericBuilder secretnum;
+ std::string prefixstr;
+
+ void AddMember(Membership* memb, WhoisChanListNumericBuilder& out)
+ {
+ prefixstr.clear();
+ const char prefix = memb->GetPrefixChar();
+ if (prefix)
+ prefixstr.push_back(prefix);
+ out.Add(prefixstr, memb->chan->name);
+ }
+
+ public:
+ WhoisChanList(WhoisContextImpl& whois, const SplitWhoisState& sws)
+ : splitwhois(sws)
+ , num(whois)
+ , secretnum(whois)
+ {
+ }
+
+ void AddVisible(Membership* memb)
+ {
+ AddMember(memb, num);
+ }
+
+ void AddHidden(Membership* memb)
+ {
+ AddMember(memb, splitwhois == SPLITWHOIS_NONE ? num : secretnum);
+ }
+
+ void Flush(WhoisContextImpl& whois)
+ {
+ num.Flush();
+ if (!secretnum.IsEmpty() && splitwhois == SPLITWHOIS_SPLITMSG)
+ whois.SendLine(RPL_CHANNELSMSG, "is on private/secret channels:");
+ secretnum.Flush();
+ }
+};
+
+void CommandWhois::SendChanList(WhoisContextImpl& whois)
+{
+ WhoisChanList chanlist(whois, splitwhois);
+
+ User* const target = whois.GetTarget();
+ bool hasoperpriv = whois.GetSource()->HasPrivPermission("users/channel-spy");
+ for (User::ChanList::iterator i = target->chans.begin(); i != target->chans.end(); ++i)
+ {
+ Membership* memb = *i;
+ Channel* c = memb->chan;
+
+ // Anyone can view channels which are not private or secret.
+ if (!c->IsModeSet(privatemode) && !c->IsModeSet(secretmode))
+ chanlist.AddVisible(memb);
+
+ // Hidden channels are visible when the following conditions are true:
+ // (1) The source user and the target user are the same.
+ // (2) The source user is a member of the hidden channel.
+ // (3) The source user is an oper with the users/channel-spy privilege.
+ else if (whois.IsSelfWhois() || c->HasUser(whois.GetSource()) || hasoperpriv)
+ chanlist.AddHidden(memb);
+ }
+
+ chanlist.Flush(whois);
+}
+
+void CommandWhois::DoWhois(LocalUser* user, User* dest, time_t signon, unsigned long idle)
+{
+ WhoisContextImpl whois(user, dest, lineevprov);
+
+ whois.SendLine(RPL_WHOISUSER, dest->ident, dest->GetDisplayedHost(), '*', dest->fullname);
+ if (whois.IsSelfWhois() || user->HasPrivPermission("users/auspex"))
+ {
+ whois.SendLine(RPL_WHOISHOST, InspIRCd::Format("is connecting from %s@%s %s", dest->ident.c_str(), dest->GetRealHost().c_str(), dest->GetIPString().c_str()));
+ }
+
+ SendChanList(whois);
+
+ if (!whois.IsSelfWhois() && !ServerInstance->Config->HideServer.empty() && !user->HasPrivPermission("servers/auspex"))
+ {
+ whois.SendLine(RPL_WHOISSERVER, ServerInstance->Config->HideServer, ServerInstance->Config->Network);
+ }
+ else
+ {
+ whois.SendLine(RPL_WHOISSERVER, dest->server->GetName(), dest->server->GetDesc());
+ }
+
+ if (dest->IsAway())
+ {
+ whois.SendLine(RPL_AWAY, dest->awaymsg);
+ }
+
+ if (dest->IsOper())
+ {
+ if (ServerInstance->Config->GenericOper)
+ whois.SendLine(RPL_WHOISOPERATOR, "is an IRC operator");
+ else
+ whois.SendLine(RPL_WHOISOPERATOR, InspIRCd::Format("is %s %s on %s", (strchr("AEIOUaeiou",dest->oper->name[0]) ? "an" : "a"), dest->oper->name.c_str(), ServerInstance->Config->Network.c_str()));
+ }
+
+ if (whois.IsSelfWhois() || user->HasPrivPermission("users/auspex"))
+ {
+ if (dest->IsModeSet(snomaskmode))
+ {
+ whois.SendLine(RPL_WHOISMODES, InspIRCd::Format("is using modes %s %s", dest->GetModeLetters().c_str(), snomaskmode->GetUserParameter(dest).c_str()));
+ }
+ else
+ {
+ whois.SendLine(RPL_WHOISMODES, InspIRCd::Format("is using modes %s", dest->GetModeLetters().c_str()));
+ }
+ }
+
+ FOREACH_MOD_CUSTOM(evprov, Whois::EventListener, OnWhois, (whois));
+
+ /*
+ * We only send these if we've been provided them. That is, if hideserver is turned off, and user is local, or
+ * if remote whois is queried, too. This is to keep the user hidden, and also since you can't reliably tell remote time. -- w00t
+ */
+ if ((idle) || (signon))
+ {
+ whois.SendLine(RPL_WHOISIDLE, idle, signon, "seconds idle, signon time");
+ }
+
+ whois.SendLine(RPL_ENDOFWHOIS, "End of /WHOIS list.");
+}
+
+CmdResult CommandWhois::HandleRemote(const std::vector<std::string>& parameters, RemoteUser* target)
+{
+ if (parameters.size() < 2)
+ return CMD_FAILURE;
+
+ User* user = ServerInstance->FindUUID(parameters[0]);
+ if (!user)
+ return CMD_FAILURE;
+
+ // User doing the whois must be on this server
+ LocalUser* localuser = IS_LOCAL(user);
+ if (!localuser)
+ return CMD_FAILURE;
+
+ unsigned long idle = ConvToNum<unsigned long>(parameters.back());
+ DoWhois(localuser, target, target->signon, idle);
+
+ return CMD_SUCCESS;
+}
+
+CmdResult CommandWhois::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
+{
+ User *dest;
+ unsigned int userindex = 0;
+ unsigned long idle = 0;
+ time_t signon = 0;
+
+ if (CommandParser::LoopCall(user, this, parameters, 0))
+ return CMD_SUCCESS;
+
+ /*
+ * If 2 paramters are specified (/whois nick nick), ignore the first one like spanningtree
+ * does, and use the second one, otherwise, use the only paramter. -- djGrrr
+ */
+ if (parameters.size() > 1)
+ userindex = 1;
+
+ dest = ServerInstance->FindNickOnly(parameters[userindex]);
+
+ if ((dest) && (dest->registered == REG_ALL))
+ {
+ /*
+ * Okay. Umpteenth attempt at doing this, so let's re-comment...
+ * For local users (/w localuser), we show idletime if hideserver is disabled
+ * For local users (/w localuser localuser), we always show idletime, hence parameters.size() > 1 check.
+ * For remote users (/w remoteuser), we do NOT show idletime
+ * For remote users (/w remoteuser remoteuser), spanningtree will handle calling do_whois, so we can ignore this case.
+ * Thanks to djGrrr for not being impatient while I have a crap day coding. :p -- w00t
+ */
+ LocalUser* localuser = IS_LOCAL(dest);
+ if (localuser && (ServerInstance->Config->HideServer.empty() || parameters.size() > 1))
+ {
+ idle = labs((long)((localuser->idle_lastmsg)-ServerInstance->Time()));
+ signon = dest->signon;
+ }
+
+ DoWhois(user,dest,signon,idle);
+ }
+ else
+ {
+ /* no such nick/channel */
+ user->WriteNumeric(Numerics::NoSuchNick(!parameters[userindex].empty() ? parameters[userindex] : "*"));
+ user->WriteNumeric(RPL_ENDOFWHOIS, (!parameters[userindex].empty() ? parameters[userindex] : "*"), "End of /WHOIS list.");
+ return CMD_FAILURE;
+ }
+
+ return CMD_SUCCESS;
+}
+
+class CoreModWhois : public Module
+{
+ private:
+ CommandWhois cmd;
+
+ public:
+ CoreModWhois()
+ : cmd(this)
+ {
+ }
+
+ void ReadConfig(ConfigStatus&) CXX11_OVERRIDE
+ {
+ ConfigTag* tag = ServerInstance->Config->ConfValue("options");
+ const std::string splitwhois = tag->getString("splitwhois", "no");
+ if (stdalgo::string::equalsci(splitwhois, "no"))
+ cmd.splitwhois = SPLITWHOIS_NONE;
+ else if (stdalgo::string::equalsci(splitwhois, "split"))
+ cmd.splitwhois = SPLITWHOIS_SPLIT;
+ else if (stdalgo::string::equalsci(splitwhois, "splitmsg"))
+ cmd.splitwhois = SPLITWHOIS_SPLITMSG;
+ else
+ throw ModuleException(splitwhois + " is an invalid <security:splitwhois> value, at " + tag->getTagLocation());
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the WHOIS command", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModWhois)
diff --git a/src/coremods/core_whowas.cpp b/src/coremods/core_whowas.cpp
new file mode 100644
index 000000000..188b0e4d5
--- /dev/null
+++ b/src/coremods/core_whowas.cpp
@@ -0,0 +1,310 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
+ * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "commands/cmd_whowas.h"
+#include "modules/stats.h"
+
+enum
+{
+ // From RFC 1459.
+ RPL_WHOWASUSER = 314,
+ RPL_ENDOFWHOWAS = 369,
+
+ // InspIRCd-specific.
+ RPL_WHOWASIP = 652
+};
+
+CommandWhowas::CommandWhowas( Module* parent)
+ : Command(parent, "WHOWAS", 1)
+{
+ syntax = "<nick>{,<nick>}";
+ Penalty = 2;
+}
+
+CmdResult CommandWhowas::Handle (const std::vector<std::string>& parameters, User* user)
+{
+ /* if whowas disabled in config */
+ if (!manager.IsEnabled())
+ {
+ user->WriteNumeric(ERR_UNKNOWNCOMMAND, name, "This command has been disabled.");
+ return CMD_FAILURE;
+ }
+
+ const WhoWas::Nick* const nick = manager.FindNick(parameters[0]);
+ if (!nick)
+ {
+ user->WriteNumeric(ERR_WASNOSUCHNICK, parameters[0], "There was no such nickname");
+ }
+ else
+ {
+ const WhoWas::Nick::List& list = nick->entries;
+ for (WhoWas::Nick::List::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ WhoWas::Entry* u = *i;
+
+ user->WriteNumeric(RPL_WHOWASUSER, parameters[0], u->ident, u->dhost, '*', u->gecos);
+
+ if (user->HasPrivPermission("users/auspex"))
+ user->WriteNumeric(RPL_WHOWASIP, parameters[0], InspIRCd::Format("was connecting from *@%s", u->host.c_str()));
+
+ std::string signon = InspIRCd::TimeString(u->signon);
+ bool hide_server = (!ServerInstance->Config->HideServer.empty() && !user->HasPrivPermission("servers/auspex"));
+ user->WriteNumeric(RPL_WHOISSERVER, parameters[0], (hide_server ? ServerInstance->Config->HideServer : u->server), signon);
+ }
+ }
+
+ user->WriteNumeric(RPL_ENDOFWHOWAS, parameters[0], "End of WHOWAS");
+ return CMD_SUCCESS;
+}
+
+WhoWas::Manager::Manager()
+ : GroupSize(0), MaxGroups(0), MaxKeep(0)
+{
+}
+
+const WhoWas::Nick* WhoWas::Manager::FindNick(const std::string& nickname) const
+{
+ whowas_users::const_iterator it = whowas.find(nickname);
+ if (it == whowas.end())
+ return NULL;
+ return it->second;
+}
+
+WhoWas::Manager::Stats WhoWas::Manager::GetStats() const
+{
+ size_t entrycount = 0;
+ for (whowas_users::const_iterator i = whowas.begin(); i != whowas.end(); ++i)
+ {
+ WhoWas::Nick::List& list = i->second->entries;
+ entrycount += list.size();
+ }
+
+ Stats stats;
+ stats.entrycount = entrycount;
+ return stats;
+}
+
+void WhoWas::Manager::Add(User* user)
+{
+ if (!IsEnabled())
+ return;
+
+ // Insert nick if it doesn't exist
+ // 'first' will point to the newly inserted element or to the existing element with an equivalent key
+ std::pair<whowas_users::iterator, bool> ret = whowas.insert(std::make_pair(user->nick, static_cast<WhoWas::Nick*>(NULL)));
+
+ if (ret.second) // If inserted
+ {
+ // This nick is new, create a list for it and add the first record to it
+ WhoWas::Nick* nick = new WhoWas::Nick(ret.first->first);
+ nick->entries.push_back(new Entry(user));
+ ret.first->second = nick;
+
+ // Add this nick to the fifo too
+ whowas_fifo.push_back(nick);
+
+ if (whowas.size() > this->MaxGroups)
+ {
+ // Too many nicks, remove the nick which was inserted the longest time ago from both the map and the fifo
+ PurgeNick(whowas_fifo.front());
+ }
+ }
+ else
+ {
+ // We've met this nick before, add a new record to the list
+ WhoWas::Nick::List& list = ret.first->second->entries;
+ list.push_back(new Entry(user));
+
+ // If there are too many records for this nick, remove the oldest (front)
+ if (list.size() > this->GroupSize)
+ {
+ delete list.front();
+ list.pop_front();
+ }
+ }
+}
+
+/* on rehash, refactor maps according to new conf values */
+void WhoWas::Manager::Prune()
+{
+ time_t min = ServerInstance->Time() - this->MaxKeep;
+
+ /* first cut the list to new size (maxgroups) and also prune entries that are timed out. */
+ while (!whowas_fifo.empty())
+ {
+ WhoWas::Nick* nick = whowas_fifo.front();
+ if ((whowas_fifo.size() > this->MaxGroups) || (nick->addtime < min))
+ PurgeNick(nick);
+ else
+ break;
+ }
+
+ /* Then cut the whowas sets to new size (groupsize) */
+ for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); )
+ {
+ WhoWas::Nick::List& list = i->second->entries;
+ while (list.size() > this->GroupSize)
+ {
+ delete list.front();
+ list.pop_front();
+ }
+
+ if (list.empty())
+ PurgeNick(i++);
+ else
+ ++i;
+ }
+}
+
+/* call maintain once an hour to remove expired nicks */
+void WhoWas::Manager::Maintain()
+{
+ time_t min = ServerInstance->Time() - this->MaxKeep;
+ for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); )
+ {
+ WhoWas::Nick::List& list = i->second->entries;
+ while (!list.empty() && list.front()->signon < min)
+ {
+ delete list.front();
+ list.pop_front();
+ }
+
+ if (list.empty())
+ PurgeNick(i++);
+ else
+ ++i;
+ }
+}
+
+WhoWas::Manager::~Manager()
+{
+ for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); ++i)
+ {
+ WhoWas::Nick* nick = i->second;
+ delete nick;
+ }
+}
+
+bool WhoWas::Manager::IsEnabled() const
+{
+ return ((GroupSize != 0) && (MaxGroups != 0));
+}
+
+void WhoWas::Manager::UpdateConfig(unsigned int NewGroupSize, unsigned int NewMaxGroups, unsigned int NewMaxKeep)
+{
+ if ((NewGroupSize == GroupSize) && (NewMaxGroups == MaxGroups) && (NewMaxKeep == MaxKeep))
+ return;
+
+ GroupSize = NewGroupSize;
+ MaxGroups = NewMaxGroups;
+ MaxKeep = NewMaxKeep;
+ Prune();
+}
+
+void WhoWas::Manager::PurgeNick(whowas_users::iterator it)
+{
+ WhoWas::Nick* nick = it->second;
+ whowas_fifo.erase(nick);
+ whowas.erase(it);
+ delete nick;
+}
+
+void WhoWas::Manager::PurgeNick(WhoWas::Nick* nick)
+{
+ whowas_users::iterator it = whowas.find(nick->nick);
+ if (it == whowas.end())
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in whowas database, please report");
+ return;
+ }
+ PurgeNick(it);
+}
+
+WhoWas::Entry::Entry(User* user)
+ : host(user->GetRealHost())
+ , dhost(user->GetDisplayedHost())
+ , ident(user->ident)
+ , server(user->server->GetName())
+ , gecos(user->fullname)
+ , signon(user->signon)
+{
+}
+
+WhoWas::Nick::Nick(const std::string& nickname)
+ : addtime(ServerInstance->Time())
+ , nick(nickname)
+{
+}
+
+WhoWas::Nick::~Nick()
+{
+ stdalgo::delete_all(entries);
+}
+
+class ModuleWhoWas : public Module, public Stats::EventListener
+{
+ CommandWhowas cmd;
+
+ public:
+ ModuleWhoWas()
+ : Stats::EventListener(this)
+ , cmd(this)
+ {
+ }
+
+ void OnGarbageCollect() CXX11_OVERRIDE
+ {
+ // Remove all entries older than MaxKeep
+ cmd.manager.Maintain();
+ }
+
+ void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE
+ {
+ cmd.manager.Add(user);
+ }
+
+ ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE
+ {
+ if (stats.GetSymbol() == 'z')
+ stats.AddRow(249, "Whowas entries: "+ConvToStr(cmd.manager.GetStats().entrycount));
+
+ return MOD_RES_PASSTHRU;
+ }
+
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ ConfigTag* tag = ServerInstance->Config->ConfValue("whowas");
+ unsigned int NewGroupSize = tag->getUInt("groupsize", 10, 0, 10000);
+ unsigned int NewMaxGroups = tag->getUInt("maxgroups", 10240, 0, 1000000);
+ unsigned int NewMaxKeep = tag->getDuration("maxkeep", 3600, 3600);
+
+ cmd.manager.UpdateConfig(NewGroupSize, NewMaxGroups, NewMaxKeep);
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("WHOWAS", VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleWhoWas)
diff --git a/src/coremods/core_xline/cmd_eline.cpp b/src/coremods/core_xline/cmd_eline.cpp
new file mode 100644
index 000000000..26b49894b
--- /dev/null
+++ b/src/coremods/core_xline/cmd_eline.cpp
@@ -0,0 +1,96 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "xline.h"
+#include "core_xline.h"
+
+CommandEline::CommandEline(Module* parent)
+ : Command(parent, "ELINE", 1, 3)
+{
+ flags_needed = 'o';
+ syntax = "<ident@host> [<duration> :<reason>]";
+}
+
+/** Handle /ELINE
+ */
+CmdResult CommandEline::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ std::string target = parameters[0];
+
+ if (parameters.size() >= 3)
+ {
+ IdentHostPair ih;
+ User* find = ServerInstance->FindNick(target);
+ if ((find) && (find->registered == REG_ALL))
+ {
+ ih.first = "*";
+ ih.second = find->GetIPString();
+ target = std::string("*@") + find->GetIPString();
+ }
+ else
+ ih = ServerInstance->XLines->IdentSplit(target);
+
+ if (ih.first.empty())
+ {
+ user->WriteNotice("*** Target not found");
+ return CMD_FAILURE;
+ }
+
+ InsaneBan::IPHostMatcher matcher;
+ if (InsaneBan::MatchesEveryone(ih.first+"@"+ih.second, matcher, user, "E", "hostmasks"))
+ return CMD_FAILURE;
+
+ unsigned long duration = InspIRCd::Duration(parameters[1]);
+ ELine* el = new ELine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), ih.first.c_str(), ih.second.c_str());
+ if (ServerInstance->XLines->AddLine(el, user))
+ {
+ if (!duration)
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added permanent E-line for %s: %s", user->nick.c_str(), target.c_str(), parameters[2].c_str());
+ }
+ else
+ {
+ time_t c_requires_crap = duration + ServerInstance->Time();
+ std::string timestr = InspIRCd::TimeString(c_requires_crap);
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added timed E-line for %s, expires on %s: %s",user->nick.c_str(),target.c_str(),
+ timestr.c_str(), parameters[2].c_str());
+ }
+ }
+ else
+ {
+ delete el;
+ user->WriteNotice("*** E-Line for " + target + " already exists");
+ }
+ }
+ else
+ {
+ if (ServerInstance->XLines->DelLine(target.c_str(), "E", user))
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s removed E-line on %s",user->nick.c_str(),target.c_str());
+ }
+ else
+ {
+ user->WriteNotice("*** E-Line " + target + " not found in list, try /stats e");
+ }
+ }
+
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_xline/cmd_gline.cpp b/src/coremods/core_xline/cmd_gline.cpp
new file mode 100644
index 000000000..49932ba9d
--- /dev/null
+++ b/src/coremods/core_xline/cmd_gline.cpp
@@ -0,0 +1,105 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "xline.h"
+#include "core_xline.h"
+
+CommandGline::CommandGline(Module* parent)
+ : Command(parent, "GLINE", 1, 3)
+{
+ flags_needed = 'o';
+ syntax = "<ident@host> [<duration> :<reason>]";
+}
+
+/** Handle /GLINE
+ */
+CmdResult CommandGline::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ std::string target = parameters[0];
+
+ if (parameters.size() >= 3)
+ {
+ IdentHostPair ih;
+ User* find = ServerInstance->FindNick(target);
+ if ((find) && (find->registered == REG_ALL))
+ {
+ ih.first = "*";
+ ih.second = find->GetIPString();
+ target = std::string("*@") + find->GetIPString();
+ }
+ else
+ ih = ServerInstance->XLines->IdentSplit(target);
+
+ if (ih.first.empty())
+ {
+ user->WriteNotice("*** Target not found");
+ return CMD_FAILURE;
+ }
+
+ InsaneBan::IPHostMatcher matcher;
+ if (InsaneBan::MatchesEveryone(ih.first+"@"+ih.second, matcher, user, "G", "hostmasks"))
+ return CMD_FAILURE;
+
+ else if (target.find('!') != std::string::npos)
+ {
+ user->WriteNotice("*** G-Line cannot operate on nick!user@host masks");
+ return CMD_FAILURE;
+ }
+
+ unsigned long duration = InspIRCd::Duration(parameters[1]);
+ GLine* gl = new GLine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), ih.first.c_str(), ih.second.c_str());
+ if (ServerInstance->XLines->AddLine(gl, user))
+ {
+ if (!duration)
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added permanent G-line for %s: %s",user->nick.c_str(),target.c_str(), parameters[2].c_str());
+ }
+ else
+ {
+ time_t c_requires_crap = duration + ServerInstance->Time();
+ std::string timestr = InspIRCd::TimeString(c_requires_crap);
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added timed G-line for %s, expires on %s: %s",user->nick.c_str(),target.c_str(),
+ timestr.c_str(), parameters[2].c_str());
+ }
+
+ ServerInstance->XLines->ApplyLines();
+ }
+ else
+ {
+ delete gl;
+ user->WriteNotice("** G-Line for " + target + " already exists");
+ }
+
+ }
+ else
+ {
+ if (ServerInstance->XLines->DelLine(target.c_str(),"G",user))
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s removed G-line on %s",user->nick.c_str(),target.c_str());
+ }
+ else
+ {
+ user->WriteNotice("*** G-Line " + target + " not found in list, try /stats g.");
+ }
+ }
+
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_xline/cmd_kline.cpp b/src/coremods/core_xline/cmd_kline.cpp
new file mode 100644
index 000000000..db8862d37
--- /dev/null
+++ b/src/coremods/core_xline/cmd_kline.cpp
@@ -0,0 +1,104 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "xline.h"
+#include "core_xline.h"
+
+CommandKline::CommandKline(Module* parent)
+ : Command(parent, "KLINE", 1, 3)
+{
+ flags_needed = 'o';
+ syntax = "<ident@host> [<duration> :<reason>]";
+}
+
+/** Handle /KLINE
+ */
+CmdResult CommandKline::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ std::string target = parameters[0];
+
+ if (parameters.size() >= 3)
+ {
+ IdentHostPair ih;
+ User* find = ServerInstance->FindNick(target);
+ if ((find) && (find->registered == REG_ALL))
+ {
+ ih.first = "*";
+ ih.second = find->GetIPString();
+ target = std::string("*@") + find->GetIPString();
+ }
+ else
+ ih = ServerInstance->XLines->IdentSplit(target);
+
+ if (ih.first.empty())
+ {
+ user->WriteNotice("*** Target not found");
+ return CMD_FAILURE;
+ }
+
+ InsaneBan::IPHostMatcher matcher;
+ if (InsaneBan::MatchesEveryone(ih.first+"@"+ih.second, matcher, user, "K", "hostmasks"))
+ return CMD_FAILURE;
+
+ if (target.find('!') != std::string::npos)
+ {
+ user->WriteNotice("*** K-Line cannot operate on nick!user@host masks");
+ return CMD_FAILURE;
+ }
+
+ unsigned long duration = InspIRCd::Duration(parameters[1]);
+ KLine* kl = new KLine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), ih.first.c_str(), ih.second.c_str());
+ if (ServerInstance->XLines->AddLine(kl,user))
+ {
+ if (!duration)
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added permanent K-line for %s: %s",user->nick.c_str(),target.c_str(), parameters[2].c_str());
+ }
+ else
+ {
+ time_t c_requires_crap = duration + ServerInstance->Time();
+ std::string timestr = InspIRCd::TimeString(c_requires_crap);
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added timed K-line for %s, expires on %s: %s",user->nick.c_str(),target.c_str(),
+ timestr.c_str(), parameters[2].c_str());
+ }
+
+ ServerInstance->XLines->ApplyLines();
+ }
+ else
+ {
+ delete kl;
+ user->WriteNotice("*** K-Line for " + target + " already exists");
+ }
+ }
+ else
+ {
+ if (ServerInstance->XLines->DelLine(target.c_str(),"K",user))
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s removed K-line on %s",user->nick.c_str(),target.c_str());
+ }
+ else
+ {
+ user->WriteNotice("*** K-Line " + target + " not found in list, try /stats k.");
+ }
+ }
+
+ return CMD_SUCCESS;
+}
diff --git a/src/coremods/core_xline/cmd_qline.cpp b/src/coremods/core_xline/cmd_qline.cpp
new file mode 100644
index 000000000..6dc0da9ba
--- /dev/null
+++ b/src/coremods/core_xline/cmd_qline.cpp
@@ -0,0 +1,89 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "xline.h"
+#include "core_xline.h"
+
+CommandQline::CommandQline(Module* parent)
+ : Command(parent, "QLINE", 1, 3)
+{
+ flags_needed = 'o';
+ syntax = "<nick> [<duration> :<reason>]";
+}
+
+CmdResult CommandQline::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ if (parameters.size() >= 3)
+ {
+ NickMatcher matcher;
+ if (InsaneBan::MatchesEveryone(parameters[0], matcher, user, "Q", "nickmasks"))
+ return CMD_FAILURE;
+
+ if (parameters[0].find('@') != std::string::npos || parameters[0].find('!') != std::string::npos || parameters[0].find('.') != std::string::npos)
+ {
+ user->WriteNotice("*** A Q-Line only bans a nick pattern, not a nick!user@host pattern.");
+ return CMD_FAILURE;
+ }
+
+ unsigned long duration = InspIRCd::Duration(parameters[1]);
+ QLine* ql = new QLine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), parameters[0].c_str());
+ if (ServerInstance->XLines->AddLine(ql,user))
+ {
+ if (!duration)
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added permanent Q-line for %s: %s",user->nick.c_str(), parameters[0].c_str(), parameters[2].c_str());
+ }
+ else
+ {
+ time_t c_requires_crap = duration + ServerInstance->Time();
+ std::string timestr = InspIRCd::TimeString(c_requires_crap);
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added timed Q-line for %s, expires on %s: %s",user->nick.c_str(),parameters[0].c_str(),
+ timestr.c_str(), parameters[2].c_str());
+ }
+ ServerInstance->XLines->ApplyLines();
+ }
+ else
+ {
+ delete ql;
+ user->WriteNotice("*** Q-Line for " + parameters[0] + " already exists");
+ }
+ }
+ else
+ {
+ if (ServerInstance->XLines->DelLine(parameters[0].c_str(), "Q", user))
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s removed Q-line on %s",user->nick.c_str(),parameters[0].c_str());
+ }
+ else
+ {
+ user->WriteNotice("*** Q-Line " + parameters[0] + " not found in list, try /stats q.");
+ return CMD_FAILURE;
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+bool CommandQline::NickMatcher::Check(User* user, const std::string& nick) const
+{
+ return InspIRCd::Match(user->nick, nick);
+}
diff --git a/src/coremods/core_xline/cmd_zline.cpp b/src/coremods/core_xline/cmd_zline.cpp
new file mode 100644
index 000000000..af9d54a5b
--- /dev/null
+++ b/src/coremods/core_xline/cmd_zline.cpp
@@ -0,0 +1,107 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2009 Matt Smith <dz@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "xline.h"
+#include "core_xline.h"
+
+CommandZline::CommandZline(Module* parent)
+ : Command(parent, "ZLINE", 1, 3)
+{
+ flags_needed = 'o';
+ syntax = "<ipmask> [<duration> :<reason>]";
+}
+
+CmdResult CommandZline::Handle (const std::vector<std::string>& parameters, User *user)
+{
+ std::string target = parameters[0];
+
+ if (parameters.size() >= 3)
+ {
+ if (target.find('!') != std::string::npos)
+ {
+ user->WriteNotice("*** You cannot include a nickname in a zline, a zline must ban only an IP mask");
+ return CMD_FAILURE;
+ }
+
+ User *u = ServerInstance->FindNick(target);
+
+ if ((u) && (u->registered == REG_ALL))
+ {
+ target = u->GetIPString();
+ }
+
+ const char* ipaddr = target.c_str();
+
+ if (strchr(ipaddr,'@'))
+ {
+ while (*ipaddr != '@')
+ ipaddr++;
+ ipaddr++;
+ }
+
+ IPMatcher matcher;
+ if (InsaneBan::MatchesEveryone(ipaddr, matcher, user, "Z", "ipmasks"))
+ return CMD_FAILURE;
+
+ unsigned long duration = InspIRCd::Duration(parameters[1]);
+ ZLine* zl = new ZLine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), ipaddr);
+ if (ServerInstance->XLines->AddLine(zl,user))
+ {
+ if (!duration)
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added permanent Z-line for %s: %s", user->nick.c_str(), ipaddr, parameters[2].c_str());
+ }
+ else
+ {
+ time_t c_requires_crap = duration + ServerInstance->Time();
+ std::string timestr = InspIRCd::TimeString(c_requires_crap);
+ ServerInstance->SNO->WriteToSnoMask('x',"%s added timed Z-line for %s, expires on %s: %s",user->nick.c_str(),ipaddr,
+ timestr.c_str(), parameters[2].c_str());
+ }
+ ServerInstance->XLines->ApplyLines();
+ }
+ else
+ {
+ delete zl;
+ user->WriteNotice("*** Z-Line for " + std::string(ipaddr) + " already exists");
+ }
+ }
+ else
+ {
+ if (ServerInstance->XLines->DelLine(target.c_str(),"Z",user))
+ {
+ ServerInstance->SNO->WriteToSnoMask('x',"%s removed Z-line on %s",user->nick.c_str(),target.c_str());
+ }
+ else
+ {
+ user->WriteNotice("*** Z-Line " + target + " not found in list, try /stats Z.");
+ return CMD_FAILURE;
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+bool CommandZline::IPMatcher::Check(User* user, const std::string& ip) const
+{
+ return InspIRCd::MatchCIDR(user->GetIPString(), ip, ascii_case_insensitive_map);
+}
diff --git a/src/coremods/core_xline/core_xline.cpp b/src/coremods/core_xline/core_xline.cpp
new file mode 100644
index 000000000..ab80ab4ed
--- /dev/null
+++ b/src/coremods/core_xline/core_xline.cpp
@@ -0,0 +1,93 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "xline.h"
+#include "core_xline.h"
+
+bool InsaneBan::MatchesEveryone(const std::string& mask, MatcherBase& test, User* user, const char* bantype, const char* confkey)
+{
+ ConfigTag* insane = ServerInstance->Config->ConfValue("insane");
+
+ if (insane->getBool(confkey))
+ return false;
+
+ float itrigger = insane->getFloat("trigger", 95.5, 0.0, 100.0);
+
+ long matches = test.Run(mask);
+
+ if (!matches)
+ return false;
+
+ float percent = ((float)matches / (float)ServerInstance->Users->GetUsers().size()) * 100;
+ if (percent > itrigger)
+ {
+ ServerInstance->SNO->WriteToSnoMask('a', "\2WARNING\2: %s tried to set a %s-line mask of %s, which covers %.2f%% of the network!", user->nick.c_str(), bantype, mask.c_str(), percent);
+ return true;
+ }
+ return false;
+}
+
+bool InsaneBan::IPHostMatcher::Check(User* user, const std::string& mask) const
+{
+ return ((InspIRCd::MatchCIDR(user->MakeHost(), mask, ascii_case_insensitive_map)) ||
+ (InspIRCd::MatchCIDR(user->MakeHostIP(), mask, ascii_case_insensitive_map)));
+}
+
+class CoreModXLine : public Module
+{
+ CommandEline cmdeline;
+ CommandGline cmdgline;
+ CommandKline cmdkline;
+ CommandQline cmdqline;
+ CommandZline cmdzline;
+
+ public:
+ CoreModXLine()
+ : cmdeline(this), cmdgline(this), cmdkline(this), cmdqline(this), cmdzline(this)
+ {
+ }
+
+ ModResult OnUserPreNick(LocalUser* user, const std::string& newnick) CXX11_OVERRIDE
+ {
+ // Check Q-Lines (for local nick changes only, remote servers have our Q-Lines to enforce themselves)
+
+ XLine* xline = ServerInstance->XLines->MatchesLine("Q", newnick);
+ if (!xline)
+ return MOD_RES_PASSTHRU; // No match
+
+ // A Q-Line matched the new nick, tell opers if the user is registered
+ if (user->registered == REG_ALL)
+ {
+ ServerInstance->SNO->WriteGlobalSno('a', "Q-Lined nickname %s from %s: %s",
+ newnick.c_str(), user->GetFullRealHost().c_str(), xline->reason.c_str());
+ }
+
+ // Send a numeric because if we deny then the core doesn't reply anything
+ user->WriteNumeric(ERR_ERRONEUSNICKNAME, newnick, InspIRCd::Format("Invalid nickname: %s", xline->reason.c_str()));
+ return MOD_RES_DENY;
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the ELINE, GLINE, KLINE, QLINE, and ZLINE commands", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModXLine)
diff --git a/src/coremods/core_xline/core_xline.h b/src/coremods/core_xline/core_xline.h
new file mode 100644
index 000000000..11756c06b
--- /dev/null
+++ b/src/coremods/core_xline/core_xline.h
@@ -0,0 +1,163 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "inspircd.h"
+
+class InsaneBan
+{
+ public:
+ class MatcherBase
+ {
+ public:
+ virtual long Run(const std::string& mask) = 0;
+ };
+
+ template <typename T>
+ class Matcher : public MatcherBase
+ {
+ public:
+ long Run(const std::string& mask) CXX11_OVERRIDE
+ {
+ long matches = 0;
+ const T* c = static_cast<T*>(this);
+ const user_hash& users = ServerInstance->Users->GetUsers();
+ for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i)
+ {
+ if (c->Check(i->second, mask))
+ matches++;
+ }
+ return matches;
+ }
+ };
+
+ class IPHostMatcher : public Matcher<IPHostMatcher>
+ {
+ public:
+ bool Check(User* user, const std::string& mask) const;
+ };
+
+ /** Check if the given mask matches too many users according to the config, send an announcement if yes
+ * @param mask A mask to match against
+ * @param test The test that determines if a user matches the mask or not
+ * @param user A user whose nick will be included in the announcement if one is made
+ * @param bantype Type of the ban being set, will be used in the announcement if one is made
+ * @param confkey Name of the config key (inside the insane tag) which if false disables any checking
+ * @return True if the given mask matches too many users, false if not
+ */
+ static bool MatchesEveryone(const std::string& mask, MatcherBase& test, User* user, const char* bantype, const char* confkey);
+};
+
+/** Handle /ELINE.
+ */
+class CommandEline : public Command
+{
+ public:
+ /** Constructor for eline.
+ */
+ CommandEline(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /GLINE.
+ */
+class CommandGline : public Command
+{
+ public:
+ /** Constructor for gline.
+ */
+ CommandGline(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /KLINE.
+ */
+class CommandKline : public Command
+{
+ public:
+ /** Constructor for kline.
+ */
+ CommandKline(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /QLINE.
+ */
+class CommandQline : public Command
+{
+ class NickMatcher : public InsaneBan::Matcher<NickMatcher>
+ {
+ public:
+ bool Check(User* user, const std::string& mask) const;
+ };
+
+ public:
+ /** Constructor for qline.
+ */
+ CommandQline(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};
+
+/** Handle /ZLINE.
+ */
+class CommandZline : public Command
+{
+ class IPMatcher : public InsaneBan::Matcher<IPMatcher>
+ {
+ public:
+ bool Check(User* user, const std::string& mask) const;
+ };
+
+ public:
+ /** Constructor for zline.
+ */
+ CommandZline(Module* parent);
+
+ /** Handle command.
+ * @param parameters The parameters to the command
+ * @param user The user issuing the command
+ * @return A value from CmdResult to indicate command success or failure.
+ */
+ CmdResult Handle(const std::vector<std::string>& parameters, User* user) CXX11_OVERRIDE;
+};