diff options
Diffstat (limited to 'src/coremods')
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 ¶meter, 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 ¶meter, 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 ¶meter, 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 ¶meter, 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; +}; |