]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Merge insp20
authorAttila Molnar <attilamolnar@hush.com>
Mon, 20 Apr 2015 15:40:12 +0000 (17:40 +0200)
committerAttila Molnar <attilamolnar@hush.com>
Mon, 20 Apr 2015 15:40:12 +0000 (17:40 +0200)
40 files changed:
1  2 
.gitignore
README.md
docs/conf/helpop-full.conf.example
docs/conf/helpop.conf.example
docs/conf/inspircd.conf.example
docs/conf/links.conf.example
docs/conf/modules.conf.example
docs/conf/opers.conf.example
include/modules.h
include/usermanager.h
make/template/main.mk
modulemanager
src/command_parse.cpp
src/configreader.cpp
src/coremods/core_dns.cpp
src/coremods/core_info/cmd_motd.cpp
src/coremods/core_list.cpp
src/coremods/core_reloadmodule.cpp
src/coremods/core_stats.cpp
src/coremods/core_user/cmd_user.cpp
src/coremods/core_user/core_user.cpp
src/coremods/core_userhost.cpp
src/coremods/core_who.cpp
src/hashcomp.cpp
src/modmanager_dynamic.cpp
src/modules/m_abbreviation.cpp
src/modules/m_check.cpp
src/modules/m_dccallow.cpp
src/modules/m_globalload.cpp
src/modules/m_hideoper.cpp
src/modules/m_httpd.cpp
src/modules/m_md5.cpp
src/modules/m_operprefix.cpp
src/modules/m_sasl.cpp
src/modules/m_spanningtree/fjoin.cpp
src/modules/m_spanningtree/treeserver.cpp
src/modules/m_timedbans.cpp
src/threadengines/threadengine_pthread.cpp
src/usermanager.cpp
win/inspircd.rc.cmake

diff --cc .gitignore
index ca364ecfc94f0bef876218a63a9819f6b449552c,f39aa4a55c73c92d57ef9e017ec358a30ad9b4d7..9400478be58d7473a6890b72163bcac10da46104
@@@ -8,10 -7,8 +8,11 @@@
  /BSDmakefile
  /GNUmakefile
  /build
+ /docs/doxygen
  /inspircd
 +/inspircd.1
 +/inspircd-genssl.1
 +/inspircd.service
  /org.inspircd.plist
  /run
  /bin
diff --cc README.md
Simple merge
index 353270c33ab9cdf4eab9041a298a135474d667db,a3529b9dcfd51d6af3e4ac7d4a7c025ce879dde3..7899586f81aad03ac263374e2eb3f94a01ca9d19
@@@ -108,15 -108,12 +108,15 @@@ Removes a user from a channel you speci
  channel halfoperator to remove a user. A removed user will part with
  a message stating they were removed from the channel and by whom.">
  
 +<helpop key="rmode" value="/RMODE [channel] [modeletter] {[pattern]}
 +
 +Removes listmodes from a channel.
 +E.g. /RMODE #Chan b m:* will remove all mute extbans.">
 +
  <helpop key="fpart" value="/FPART <channel> <nick> [<reason>]
  
- This behaves identically to /REMOVE. /REMOVE is a builtin mIRC command
 -This behaves identically to /REMOVE, the only difference is that the
 -<channel> and <nick> parameters are switched around to match /KICK's
 -syntax. Also, /REMOVE is a built-in mIRC command which caused trouble
 -for some users.">
++This behaves identically to /REMOVE. /REMOVE is a built-in mIRC command
 +which caused trouble for some users.">
  
  <helpop key="devoice" value="/DEVOICE <channel>
  
@@@ -767,16 -768,12 +767,22 @@@ server is specified, the local server'
  
  Closes all unregistered connections to the local server.">
  
 +<helpop key="clearchan" value="/CLEARCHAN <channel> [<KILL|KICK|G|Z>] [<reason>]
 +
 +Quits or kicks all non-opers from a channel, optionally G/Z-Lines them.
 +Useful for quickly nuking bot channels.
 +
 +The default method, KILL, simply disconnects the victims from the server,
 +while methods G and Z also add G/Z-Lines for all the targets.
 +
 +When used, the victims won't see each other getting kicked or quitting.">
 +
+ <helpop key="modenotice" value="/MODENOTICE <modeletters> <message>
+ Sends a notice to all users who have the given mode(s) set.
+ If multiple mode letters are given, the notice is only sent to users
+ who have all of them set.">
  ######################
  # User/Channel Modes #
  ######################
Simple merge
index d2f70cb22e3dd9bd68fe85eadb65b27e47ad74f3,9fd0adfd34d1e17e43c3bd379d3eb3c954d5bb54..123ebfd3162c352a1520dfef248e8fe8635f6a07
           # globalmax: Maximum global (network-wide) connections per IP (or CIDR mask, see below).
           globalmax="3"
  
-          # maxconnwarn: Enable warnings when localmax or globalmax is hit (defaults to on)
+          # maxconnwarn: Enable warnings when localmax or globalmax are reached (defaults to on)
           maxconnwarn="off"
  
 +         # resolvehostnames: If disabled, no DNS lookups will be performed on connecting users
 +         # in this class. This can save a lot of resources on very busy servers.
 +         resolvehostnames="yes"
 +
           # usednsbl: Defines whether or not users in this class are subject to DNSBL. Default is yes.
           # This setting only has effect when m_dnsbl is loaded.
           #usednsbl="yes"
index 13754edbd03bcff24821430bcd788e53fd856d2a,a1bab2b3aaebe9241df556136f896d8abc7748ff..0b8b234383a8f7162e101e81d3d020d5adffca0e
@@@ -95,7 -95,7 +95,7 @@@
  # Simple autoconnect block. This enables automatic connection of a server
  # Recommended setup is to have leaves connect to the hub, and have no
  # automatic connections started by the hub.
- <autoconnect period="10m" server="hub.penguin.org">
 -<autoconnect period="300" server="hub.example.org">
++<autoconnect period="10m" server="hub.example.org">
  
  # Failover autoconnect block. If you have multiple hubs, or want your network
  # to automatically link even if the hub is down, you can specify multiple
index 881c5f07730a090a477ed6bae7f7b3881da847ee,71a0fb079b8b58b33c07198987d355abc4f10628..64c9ab0a1557dfab2951b95e01f36697e4022dfd
  #          cooldown="60">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
- # CAP module: Provides the CAP negotiation mechanism seen in
- # ratbox-derived ircds.
+ # CAP module: Provides the CAP negotiation mechanism required by the
+ # m_sasl, m_namesx, m_uhnames, and m_ircv3 modules.
 -# It is also recommended for the STARTTLS support in m_ssl_gnutls.
++# It is also recommended for the STARTTLS support in m_starttls.
  #<module name="m_cap.so">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
  #                                                                     #
  # See http://wiki.inspircd.org/Modules/hostchange for help.           #
  #                                                                     #
- #<host suffix="polarbears.org" separator="." prefix="">
- #<hostchange mask="*@fbi.gov" action="addnick">
- #<hostchange mask="*r00t@*" action="suffix">
- #<hostchange mask="a@b.com" action="set" value="blah.blah.blah">
+ #<host suffix="invalid.org" separator="." prefix="">
+ #<hostchange mask="*@42.theanswer.example.org" action="addnick">
+ #<hostchange mask="*root@*" action="suffix">
+ #<hostchange mask="a@example.com" action="set" value="foo.bar.baz">
  #<hostchange mask="localhost" ports="7000,7001,7005-7007" action="set" value="blahblah.foo">
  
 +# hostcycle: If loaded, when a user gets a host or ident set, it will
 +# cycle them in all their channels. If not loaded it will simply change
 +# their host/ident without cycling them.
 +#<module name="m_hostcycle.so">
 +
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
  # httpd module: Provides HTTP server support for InspIRCd.
  #<module name="m_httpd.so">
Simple merge
index 41dec1194c4046a54e49462fea4e61f38b3597bb,9857012fcfc0ac1c71793198e15f5b0a902d5e0c..fc2aa6324b9dbdf8d0f6a52ec616548b936dcd8e
@@@ -1283,7 -1717,8 +1283,8 @@@ struct AllModuleList 
                                break; \
                } \
                return TRUE; \
-       }
+       } \
 -      extern "C" DllExport const char inspircd_src_version[] = VERSION " r" REVISION;
++      extern "C" DllExport const char inspircd_src_version[] = INSPIRCD_VERSION " " INSPIRCD_REVISION;
  
  #else
  
index 3671e8907e6de89101afa39d9581f15d39a9bb9d,2a9d6b47b2b9ca54c893ba75a07bbe296a74f9e9..a67f90224d628839eda4c05a6f7feac53e08cd11
@@@ -130,53 -112,53 +130,57 @@@ class CoreExport UserManager : public f
         */
        void RemoveCloneCounts(User *user);
  
 -      /** Return the number of global clones of this user
 -       * @param user The user to get a count for
 -       * @return The global clone count of this user
+       /** Rebuild clone counts
+        */
+       void RehashCloneCounts();
 +      /** Return the number of local and global clones of this user
 +       * @param user The user to get the clone counts for
 +       * @return The clone counts of this user. The returned reference is volatile - you
 +       * must assume that it becomes invalid as soon as you call any function other than
 +       * your own.
         */
 -      unsigned long GlobalCloneCount(User *user);
 +      const CloneCounts& GetCloneCounts(User* user) const;
  
 -      /** Return the number of local clones of this user
 -       * @param user The user to get a count for
 -       * @return The local clone count of this user
 +      /** Return a map containg IP addresses and their clone counts
 +       * @return The clone count map
         */
 -      unsigned long LocalCloneCount(User *user);
 +      const CloneMap& GetCloneMap() const { return clonemap; }
  
 -      /** Return a count of users, unknown and known connections
 -       * @return The number of users
 +      /** Return a count of all global users, unknown and known connections
 +       * @return The number of users on the network, including local unregistered users
         */
 -      unsigned int UserCount();
 +      unsigned int UserCount() const { return this->clientlist.size(); }
  
 -      /** Return a count of fully registered connections only
 -       * @return The number of registered users
 +      /** Return a count of fully registered connections on the network
 +       * @return The number of registered users on the network
         */
 -      unsigned int RegisteredUserCount();
 +      unsigned int RegisteredUserCount() { return this->clientlist.size() - this->UnregisteredUserCount(); }
  
 -      /** Return a count of opered (umode +o) users only
 -       * @return The number of opers
 +      /** Return a count of opered (umode +o) users on the network
 +       * @return The number of opers on the network
         */
 -      unsigned int OperCount();
 +      unsigned int OperCount() const { return this->all_opers.size(); }
  
 -      /** Return a count of unregistered (before NICK/USER) users only
 -       * @return The number of unregistered (unknown) connections
 +      /** Return a count of local unregistered (before NICK/USER) users
 +       * @return The number of local unregistered (unknown) connections
         */
 -      unsigned int UnregisteredUserCount();
 +      unsigned int UnregisteredUserCount() const { return this->unregistered_count; }
  
 -      /** Return a count of local users on this server only
 -       * @return The number of local users
 +      /** Return a count of local registered users
 +       * @return The number of registered local users
         */
 -      unsigned int LocalUserCount();
 -
 -
 +      unsigned int LocalUserCount() const { return (this->local_users.size() - this->UnregisteredUserCount()); }
  
 +      /** Get a hash map containing all users, keyed by their nickname
 +       * @return A hash map mapping nicknames to User pointers
 +       */
 +      user_hash& GetUsers() { return clientlist; }
  
 -      /** Number of users with a certain mode set on them
 +      /** Get a list containing all local users
 +       * @return A const list of local users
         */
 -      int ModeCount(const char mode);
 +      const LocalList& GetLocalUsers() const { return local_users; }
  
        /** Send a server notice to all local users
         * @param text The text format string to send
index 9ac43e3bbc14a72a8deda0774cd61b40ff2f9c21,d5705d92876f063424c35c991a829a0c6b342d9c..9630905b19370289c8fbbc649984a88cb7022514
@@@ -108,20 -109,23 +108,21 @@@ DBGOK=
  @ENDIF
  FOOTER = finishmessage
  
 -CXXFLAGS += -Iinclude
 +@TARGET GNU_MAKE MAKEFLAGS += --no-print-directory
  
 -@GNU_ONLY MAKEFLAGS += --no-print-directory
 -
 -@GNU_ONLY SOURCEPATH = $(shell /bin/pwd)
 -@BSD_ONLY SOURCEPATH != /bin/pwd
 +@TARGET GNU_MAKE SOURCEPATH = $(shell /bin/pwd)
 +@TARGET BSD_MAKE SOURCEPATH != /bin/pwd
  
  @IFDEF V
 -  RUNCC = $(CC)
 -  RUNLD = $(CC)
 +  RUNCC = $(CXX)
 +  RUNLD = $(CXX)
    VERBOSE = -v
  @ELSE
 -  @GNU_ONLY MAKEFLAGS += --silent
 -  @BSD_ONLY MAKE += -s
 -  RUNCC = perl $(SOURCEPATH)/make/run-cc.pl $(CC)
 -  RUNLD = perl $(SOURCEPATH)/make/run-cc.pl $(CC)
 +  @TARGET GNU_MAKE MAKEFLAGS += --silent
 +  @TARGET BSD_MAKE MAKE += -s
 +  RUNCC = perl $(SOURCEPATH)/make/run-cc.pl $(CXX)
 +  RUNLD = perl $(SOURCEPATH)/make/run-cc.pl $(CXX)
+   VERBOSE =
  @ENDIF
  
  @IFDEF PURE_STATIC
diff --cc modulemanager
Simple merge
index c93dac65fd386c05bff39ae57307c87dde5117e0,76dfc06ce05eabf887794b76981ef8410c36f992..7998d9cc3a982f0f71732a2f7dfe4af2b831e9f3
@@@ -180,16 -202,28 +180,26 @@@ void CommandParser::ProcessCommand(Loca
        std::transform(command.begin(), command.end(), command.begin(), ::toupper);
  
        /* find the command, check it exists */
 -      Commandtable::iterator cm = cmdlist.find(command);
 +      Command* handler = GetHandler(command);
  
+       // Penalty to give if the command fails before the handler is executed
+       unsigned int failpenalty = 0;
        /* Modify the user's penalty regardless of whether or not the command exists */
 -      bool do_more = true;
        if (!user->HasPrivPermission("users/flood/no-throttle"))
        {
                // If it *doesn't* exist, give it a slightly heftier penalty than normal to deter flooding us crap
-               user->CommandFloodPenalty += handler ? handler->Penalty * 1000 : 2000;
 -              unsigned int penalty = (cm != cmdlist.end() ? cm->second->Penalty * 1000 : 2000);
++              unsigned int penalty = (handler ? handler->Penalty * 1000 : 2000);
+               user->CommandFloodPenalty += penalty;
+               // Increase their penalty later if we fail and the command has 0 penalty by default (i.e. in Command::Penalty) to
+               // throttle sending ERR_* from the command parser. If the command does have a non-zero penalty then this is not
+               // needed because we've increased their penalty above.
+               if (penalty == 0)
+                       failpenalty = 1000;
        }
  
 -
 -      if (cm == cmdlist.end())
 +      if (!handler)
        {
                ModResult MOD_RESULT;
                FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false, cmd));
        /* activity resets the ping pending timer */
        user->nping = ServerInstance->Time() + user->MyClass->GetPingTime();
  
 -      if (cm->second->flags_needed)
 +      if (handler->flags_needed)
        {
 -              if (!user->IsModeSet(cm->second->flags_needed))
 +              if (!user->IsModeSet(handler->flags_needed))
                {
 -                      user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - You do not have the required operator privileges",user->nick.c_str());
 -                      return do_more;
+                       user->CommandFloodPenalty += failpenalty;
 +                      user->WriteNumeric(ERR_NOPRIVILEGES, ":Permission Denied - You do not have the required operator privileges");
 +                      return;
                }
 +
                if (!user->HasPermission(command))
                {
 -                      user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper type %s does not have access to command %s",
 -                              user->nick.c_str(), user->oper->NameStr(), command.c_str());
 -                      return do_more;
+                       user->CommandFloodPenalty += failpenalty;
 +                      user->WriteNumeric(ERR_NOPRIVILEGES, ":Permission Denied - Oper type %s does not have access to command %s",
 +                              user->oper->name.c_str(), command.c_str());
 +                      return;
                }
        }
 -      if ((user->registered == REG_ALL) && (!IS_OPER(user)) && (cm->second->IsDisabled()))
 +
 +      if ((user->registered == REG_ALL) && (!user->IsOper()) && (handler->IsDisabled()))
        {
                /* command is disabled! */
+               user->CommandFloodPenalty += failpenalty;
                if (ServerInstance->Config->DisabledDontExist)
                {
 -                      user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :Unknown command",user->nick.c_str(),command.c_str());
 +                      user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s :Unknown command", command.c_str());
                }
                else
                {
  
                ServerInstance->SNO->WriteToSnoMask('a', "%s denied for %s (%s@%s)",
                                command.c_str(), user->nick.c_str(), user->ident.c_str(), user->host.c_str());
 -              return do_more;
 +              return;
        }
  
 -      if ((!command_p.empty()) && (command_p.back().empty()) && (!cm->second->allow_empty_last_param))
 +      if ((!command_p.empty()) && (command_p.back().empty()) && (!handler->allow_empty_last_param))
                command_p.pop_back();
  
 -      if (command_p.size() < cm->second->min_params)
 +      if (command_p.size() < handler->min_params)
        {
 -              user->WriteNumeric(ERR_NEEDMOREPARAMS, "%s %s :Not enough parameters.", user->nick.c_str(), command.c_str());
 -              if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (cm->second->syntax.length()))
 -                      user->WriteNumeric(RPL_SYNTAX, "%s :SYNTAX %s %s", user->nick.c_str(), cm->second->name.c_str(), cm->second->syntax.c_str());
 -              return do_more;
+               user->CommandFloodPenalty += failpenalty;
 +              user->WriteNumeric(ERR_NEEDMOREPARAMS, "%s :Not enough parameters.", command.c_str());
 +              if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (handler->syntax.length()))
 +                      user->WriteNumeric(RPL_SYNTAX, ":SYNTAX %s %s", handler->name.c_str(), handler->syntax.c_str());
 +              return;
        }
 -      if ((user->registered != REG_ALL) && (!cm->second->WorksBeforeReg()))
 +
 +      if ((user->registered != REG_ALL) && (!handler->WorksBeforeReg()))
        {
 -              user->WriteNumeric(ERR_NOTREGISTERED, "%s %s :You have not registered", user->nick.c_str(), command.c_str());
 -              return do_more;
+               user->CommandFloodPenalty += failpenalty;
 +              user->WriteNumeric(ERR_NOTREGISTERED, "%s :You have not registered",command.c_str());
        }
        else
        {
index d52f3de13fdc3f03690947555f0f4a6d92b6337e,bcee938d55208e1aefc3dcc5d0db93cf81820175..68495623c778b1a03ad9a02fad1f3e7ac3f87e01
  
  
  #include "inspircd.h"
 -#include <fstream>
  #include "xline.h"
 +#include "listmode.h"
  #include "exitcodes.h"
 -#include "commands/cmd_whowas.h"
  #include "configparser.h"
  #include <iostream>
 -#ifdef _WIN32
 -#include <Iphlpapi.h>
 -#pragma comment(lib, "Iphlpapi.lib")
 -#endif
 +
 +ServerLimits::ServerLimits(ConfigTag* tag)
 +      : NickMax(tag->getInt("maxnick", 32))
 +      , ChanMax(tag->getInt("maxchan", 64))
 +      , MaxModes(tag->getInt("maxmodes", 20))
 +      , IdentMax(tag->getInt("maxident", 11))
 +      , MaxQuit(tag->getInt("maxquit", 255))
 +      , MaxTopic(tag->getInt("maxtopic", 307))
 +      , MaxKick(tag->getInt("maxkick", 255))
 +      , MaxGecos(tag->getInt("maxgecos", 128))
 +      , MaxAway(tag->getInt("maxaway", 200))
 +      , MaxLine(tag->getInt("maxline", 512))
 +      , MaxHost(tag->getInt("maxhost", 64))
 +{
 +}
 +
 +static ConfigTag* CreateEmptyTag()
 +{
 +      std::vector<KeyVal>* items;
 +      return ConfigTag::create("empty", "<auto>", 0, items);
 +}
  
  ServerConfig::ServerConfig()
 -      : NoSnoticeStack(false)
 +      : EmptyTag(CreateEmptyTag())
 +      , Limits(EmptyTag)
++      , NoSnoticeStack(false)
  {
 -      WhoWasGroupSize = WhoWasMaxGroups = WhoWasMaxKeep = 0;
 -      RawLog = NoUserDns = HideBans = HideSplits = UndernetMsgPrefix = false;
 -      WildcardIPv6 = CycleHosts = InvBypassModes = true;
 +      RawLog = HideBans = HideSplits = UndernetMsgPrefix = false;
 +      WildcardIPv6 = InvBypassModes = true;
        dns_timeout = 5;
        MaxTargets = 20;
        NetBufferSize = 10240;
@@@ -570,12 -730,7 +571,12 @@@ void ServerConfig::Apply(ServerConfig* 
        if (valid)
                ServerInstance->WritePID(this->PID);
  
-       if (old)
 +      ConfigTagList binds = ConfTags("bind");
 +      if (binds.first == binds.second)
 +               errstr << "Possible configuration error: you have not defined any <bind> blocks." << std::endl
 +                       << "You will need to do this if you want clients to be able to connect!" << std::endl;
 +
+       if (old && valid)
        {
                // On first run, ports are bound later on
                FailedPortList pl;
@@@ -784,22 -963,15 +785,23 @@@ void ConfigReaderThread::Finish(
                 * XXX: The order of these is IMPORTANT, do not reorder them without testing
                 * thoroughly!!!
                 */
 -              ServerInstance->Users->RehashCloneCounts();
++              ServerInstance->Users.RehashCloneCounts();
                ServerInstance->XLines->CheckELines();
                ServerInstance->XLines->ApplyLines();
 -              ServerInstance->Res->Rehash();
 -              ServerInstance->ResetMaxBans();
 +              ChanModeReference ban(NULL, "ban");
 +              static_cast<ListModeBase*>(*ban)->DoRehash();
                Config->ApplyDisabledCommands(Config->DisabledCommands);
                User* user = ServerInstance->FindNick(TheUserUID);
 -              FOREACH_MOD(I_OnRehash, OnRehash(user));
 -              ServerInstance->BuildISupport();
 +
 +              ConfigStatus status(user);
 +              const ModuleManager::ModuleMap& mods = ServerInstance->Modules->GetModules();
 +              for (ModuleManager::ModuleMap::const_iterator i = mods.begin(); i != mods.end(); ++i)
 +                      i->second->ReadConfig(status);
 +
 +              // The description of this server may have changed - update it for WHOIS etc.
 +              ServerInstance->FakeClient->server->description = Config->ServerDesc;
 +
 +              ServerInstance->ISupport.Build();
  
                ServerInstance->Logs->CloseLogs();
                ServerInstance->Logs->OpenFileLogs();
index de8dedd4ac41e260eb0f773d25ce261020275137,0000000000000000000000000000000000000000..9aca8b3384aacf8a83a5d73fdfc5864fe7f7a292
mode 100644,000000..100644
--- /dev/null
@@@ -1,832 -1,0 +1,840 @@@
 +/*
 + * 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
 +
 +using namespace DNS;
 +
 +/** A full packet sent or recieved to/from the nameserver
 + */
 +class Packet : public Query
 +{
++      static bool IsValidName(const std::string& name)
++      {
++              return (name.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") == std::string::npos);
++      }
++
 +      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 question;
 +
 +              question.name = this->UnpackName(input, input_size, pos);
 +
 +              if (pos + 4 > input_size)
 +                      throw Exception("Unable to unpack question");
 +
 +              question.type = static_cast<QueryType>(input[pos] << 8 | input[pos + 1]);
 +              pos += 2;
 +
 +              question.qclass = input[pos] << 8 | input[pos + 1];
 +              pos += 2;
 +
 +              return question;
 +      }
 +
 +      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;
 +
 +              //record.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 (!IsValidName(record.rdata))
++                                      throw Exception("Invalid name"); // XXX: Causes the request to time out
++
 +                              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 */
 +      unsigned short 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;
 +
 +              if (this->id >= MAX_REQUEST_ID)
 +                      throw Exception("Query ID too large?");
 +
 +              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));
 +
 +              for (unsigned i = 0; i < qdcount; ++i)
 +                      this->questions.push_back(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++] = this->questions.size() >> 8;
 +              output[pos++] = this->questions.size() & 0xFF;
 +              output[pos++] = this->answers.size() >> 8;
 +              output[pos++] = this->answers.size() & 0xFF;
 +              output[pos++] = 0;
 +              output[pos++] = 0;
 +              output[pos++] = 0;
 +              output[pos++] = 0;
 +
 +              for (unsigned i = 0; i < this->questions.size(); ++i)
 +              {
 +                      Question& q = this->questions[i];
 +
 +                      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;
 +
 +                      s = htons(q.qclass);
 +                      memcpy(&output[pos], &s, 2);
 +                      pos += 2;
 +              }
 +
 +              for (unsigned int i = 0; i < answers.size(); i++)
 +              {
 +                      ResourceRecord& rr = answers[i];
 +
 +                      this->PackName(output, output_size, pos, rr.name);
 +
 +                      if (pos + 8 >= output_size)
 +                              throw Exception("Unable to pack packet");
 +
 +                      short s = htons(rr.type);
 +                      memcpy(&output[pos], &s, 2);
 +                      pos += 2;
 +
 +                      s = htons(rr.qclass);
 +                      memcpy(&output[pos], &s, 2);
 +                      pos += 2;
 +
 +                      long l = htonl(rr.ttl);
 +                      memcpy(&output[pos], &l, 4);
 +                      pos += 4;
 +
 +                      switch (rr.type)
 +                      {
 +                              case QUERY_A:
 +                              {
 +                                      if (pos + 6 > output_size)
 +                                              throw Exception("Unable to pack packet");
 +
 +                                      irc::sockets::sockaddrs a;
 +                                      irc::sockets::aptosa(rr.rdata, 0, a);
 +
 +                                      s = htons(4);
 +                                      memcpy(&output[pos], &s, 2);
 +                                      pos += 2;
 +
 +                                      memcpy(&output[pos], &a.in4.sin_addr, 4);
 +                                      pos += 4;
 +                                      break;
 +                              }
 +                              case QUERY_AAAA:
 +                              {
 +                                      if (pos + 18 > output_size)
 +                                              throw Exception("Unable to pack packet");
 +
 +                                      irc::sockets::sockaddrs a;
 +                                      irc::sockets::aptosa(rr.rdata, 0, a);
 +
 +                                      s = htons(16);
 +                                      memcpy(&output[pos], &s, 2);
 +                                      pos += 2;
 +
 +                                      memcpy(&output[pos], &a.in6.sin6_addr, 16);
 +                                      pos += 16;
 +                                      break;
 +                              }
 +                              case QUERY_CNAME:
 +                              case QUERY_PTR:
 +                              {
 +                                      if (pos + 2 >= output_size)
 +                                              throw Exception("Unable to pack packet");
 +
 +                                      unsigned short packet_pos_save = pos;
 +                                      pos += 2;
 +
 +                                      this->PackName(output, output_size, pos, rr.rdata);
 +
 +                                      s = htons(pos - packet_pos_save - 2);
 +                                      memcpy(&output[packet_pos_save], &s, 2);
 +                                      break;
 +                              }
 +                              default:
 +                                      break;
 +                      }
 +              }
 +
 +              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;
 +
 +      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)
 +      {
 +              const ResourceRecord& rr = r.answers[0];
 +              ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "cache: added cache for " + rr.name + " -> " + rr.rdata + " ttl: " + ConvToStr(rr.ttl));
 +              this->cache[r.questions[0]] = r;
 +      }
 +
 + public:
 +      DNS::Request* requests[MAX_REQUEST_ID];
 +
 +      MyManager(Module* c) : Manager(c), Timer(3600, true)
 +      {
 +              for (int i = 0; i < MAX_REQUEST_ID; ++i)
 +                      requests[i] = NULL;
 +              ServerInstance->Timers.AddTimer(this);
 +      }
 +
 +      ~MyManager()
 +      {
 +              for (int i = 0; i < MAX_REQUEST_ID; ++i)
 +              {
 +                      DNS::Request* request = requests[i];
 +                      if (!request)
 +                              continue;
 +
 +                      Query rr(*request);
 +                      rr.error = ERROR_UNKNOWN;
 +                      request->OnError(&rr);
 +
 +                      delete request;
 +              }
 +      }
 +
 +      void Process(DNS::Request* req)
 +      {
 +              ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Processing request to lookup " + req->name + " of type " + ConvToStr(req->type) + " to " + this->myserver.addr());
 +
 +              /* Create an id */
 +              unsigned int tries = 0;
 +              do
 +              {
 +                      req->id = ServerInstance->GenRandomInt(DNS::MAX_REQUEST_ID);
 +
 +                      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
 +                              req->id = 0;
 +                              for (int i = 1; i < DNS::MAX_REQUEST_ID; i++)
 +                              {
 +                                      if (!this->requests[i])
 +                                      {
 +                                              req->id = i;
 +                                              break;
 +                                      }
 +                              }
 +
 +                              if (req->id == 0)
 +                                      throw Exception("DNS: All ids are in use");
 +
 +                              break;
 +                      }
 +              }
 +              while (!req->id || this->requests[req->id]);
 +
 +              this->requests[req->id] = req;
 +
 +              Packet p;
 +              p.flags = QUERYFLAGS_RD;
 +              p.id = req->id;
 +              p.questions.push_back(*req);
 +
 +              unsigned char buffer[524];
 +              unsigned short len = p.Pack(buffer, sizeof(buffer));
 +
 +              /* Note that calling Pack() above can actually change the contents of p.questions[0].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.questions[0]))
 +              {
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Using cached result");
 +                      delete req;
 +                      return;
 +              }
 +
 +              if (SocketEngine::SendTo(this, buffer, len, 0, &this->myserver.sa, this->myserver.sa_size()) != len)
 +                      throw Exception("DNS: Unable to send query");
 +      }
 +
 +      void RemoveRequest(DNS::Request* req)
 +      {
 +              this->requests[req->id] = NULL;
 +      }
 +
 +      std::string GetErrorStr(Error e)
 +      {
 +              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:
 +                              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;
 +
 +              Packet recv_packet;
 +
 +              try
 +              {
 +                      recv_packet.Fill(buffer, length);
 +              }
 +              catch (Exception& ex)
 +              {
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, ex.GetReason());
 +                      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;
 +              }
 +
 +              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 (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_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.questions.empty() || 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->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)
 +      {
 +              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)
 +      {
 +              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;
 +                      memset(&bindto, 0, sizeof(bindto));
 +                      bindto.sa.sa_family = myserver.sa.sa_family;
 +
 +                      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);
 +                      }
 +              }
 +              else
 +              {
 +                      ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Error creating DNS socket - hostnames will NOT resolve");
 +              }
 +      }
 +};
 +
 +class ModuleDNS : public Module
 +{
 +      MyManager manager;
 +      std::string DNSServer;
 +
 +      void FindDNSServer()
 +      {
 +#ifdef _WIN32
 +              // attempt to look up their nameserver from the system
 +              ServerInstance->Logs->Log("CONFIG", 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("CONFIG", LOG_DEFAULT, "<dns:server> set to '%s' as first active resolver in the system settings.", DNSServer.c_str());
 +                              return;
 +                      }
 +              }
 +
 +              ServerInstance->Logs->Log("CONFIG", 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("CONFIG", 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)
 +                              {
 +                                      ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "<dns:server> set to '%s' as first resolver in /etc/resolv.conf.",DNSServer.c_str());
 +                                      return;
 +                              }
 +                      }
 +              }
 +
 +              ServerInstance->Logs->Log("CONFIG", 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)
 +      {
 +      }
 +
 +      void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
 +      {
 +              std::string oldserver = DNSServer;
 +              DNSServer = ServerInstance->Config->ConfValue("dns")->getString("server");
 +              if (DNSServer.empty())
 +                      FindDNSServer();
 +
 +              if (oldserver != DNSServer)
 +                      this->manager.Rehash(DNSServer);
 +      }
 +
 +      void OnUnloadModule(Module* mod)
 +      {
 +              for (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);
 +                              rr.error = ERROR_UNLOADED;
 +                              req->OnError(&rr);
 +
 +                              delete req;
 +                      }
 +              }
 +      }
 +
 +      Version GetVersion()
 +      {
 +              return Version("DNS support", VF_CORE|VF_VENDOR);
 +      }
 +};
 +
 +MODULE_INIT(ModuleDNS)
 +
index 2d396858f536dbc20280d8bd9d0d4e51dcb13733,0000000000000000000000000000000000000000..57616094ede4c9976291e24cd0c94bfcd83e275d
mode 100644,000000..100644
--- /dev/null
@@@ -1,66 -1,0 +1,72 @@@
 +/*
 + * 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)
 +      : Command(parent, "MOTD", 0, 1)
 +{
 +      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->SendText(":%s %03d %s :Message of the day file is missing.",
 +                      ServerInstance->Config->ServerName.c_str(), ERR_NOMOTD, user->nick.c_str());
 +              return CMD_SUCCESS;
 +      }
 +
 +      user->SendText(":%s %03d %s :%s message of the day", ServerInstance->Config->ServerName.c_str(),
 +              RPL_MOTDSTART, user->nick.c_str(), ServerInstance->Config->ServerName.c_str());
 +
 +      for (file_cache::iterator i = motd->second.begin(); i != motd->second.end(); i++)
 +              user->SendText(":%s %03d %s :- %s", ServerInstance->Config->ServerName.c_str(), RPL_MOTD, user->nick.c_str(), i->c_str());
 +
 +      user->SendText(":%s %03d %s :End of message of the day.", ServerInstance->Config->ServerName.c_str(), RPL_ENDOFMOTD, user->nick.c_str());
 +
 +      return CMD_SUCCESS;
 +}
 +
 +RouteDescriptor CommandMotd::GetRouting(User* user, const std::vector<std::string>& parameters)
 +{
 +      if (parameters.size() > 0)
 +              return ROUTE_UNICAST(parameters[0]);
 +      return ROUTE_LOCALONLY;
 +}
index 745f019f2274c518fe6e2f2df216bb25b1b9758b,0000000000000000000000000000000000000000..278e6044d0e31fd2792d14a6ad89229849b0bbe6
mode 100644,000000..100644
--- /dev/null
@@@ -1,117 -1,0 +1,118 @@@
-               if ((!n) && (chan->IsModeSet(privatemode)))
 +/*
 + * 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
 +{
 +      ChanModeReference secretmode;
 +      ChanModeReference privatemode;
 +
 + 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);
 +};
 +
 +
 +/** Handle /LIST
 + */
 +CmdResult CommandList::Handle (const std::vector<std::string>& parameters, User *user)
 +{
 +      int minusers = 0, maxusers = 0;
 +
 +      user->WriteNumeric(RPL_LISTSTART, "Channel :Users Name");
 +
 +      /* Work around mIRC suckyness. YOU SUCK, KHALED! */
 +      if (parameters.size() == 1)
 +      {
 +              if (parameters[0][0] == '<')
 +              {
 +                      maxusers = atoi((parameters[0].c_str())+1);
 +              }
 +              else if (parameters[0][0] == '>')
 +              {
 +                      minusers = atoi((parameters[0].c_str())+1);
 +              }
 +      }
 +
 +      const bool has_privs = user->HasPrivPermission("channels/auspex");
 +      const bool match_name_topic = ((!parameters.empty()) && (!parameters[0].empty()) && (parameters[0][0] != '<') && (parameters[0][0] != '>'));
 +
 +      const chan_hash& chans = ServerInstance->GetChans();
 +      for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i)
 +      {
 +              Channel* const chan = i->second;
 +
 +              // attempt to match a glob pattern
 +              long users = chan->GetUserCounter();
 +
 +              bool too_few = (minusers && (users <= minusers));
 +              bool too_many = (maxusers && (users >= maxusers));
 +
 +              if (too_many || too_few)
 +                      continue;
 +
 +              if (match_name_topic)
 +              {
 +                      if (!InspIRCd::Match(chan->name, parameters[0]) && !InspIRCd::Match(chan->topic, parameters[0]))
 +                              continue;
 +              }
 +
 +              // if the channel is not private/secret, OR the user is on the channel anyway
 +              bool n = (has_privs || chan->HasUser(user));
 +
-                       /* Channel is +p and user is outside/not privileged */
-                       user->WriteNumeric(RPL_LIST, "* %ld :", users);
-               }
-               else
-               {
-                       if ((n) || (!chan->IsModeSet(secretmode)))
++              // 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, "* %ld :", users);
++                      }
++                      else
 +                      {
 +                              /* User is in the channel/privileged, channel is not +s */
 +                              user->WriteNumeric(RPL_LIST, "%s %ld :[+%s] %s", chan->name.c_str(), users, chan->ChanModes(n), chan->topic.c_str());
 +                      }
 +              }
 +      }
 +      user->WriteNumeric(RPL_LISTEND, ":End of channel list.");
 +
 +      return CMD_SUCCESS;
 +}
 +
 +
 +COMMAND_INIT(CommandList)
index 1561131dc71365ff43375530c8fc51bbd8db5e24,0000000000000000000000000000000000000000..7f0f15e7735169817acbf2823e73cf98f526a146
mode 100644,000000..100644
--- /dev/null
@@@ -1,78 -1,0 +1,78 @@@
-               ServerInstance->Modules->Reload(m, new ReloadModuleWorker(user->uuid, parameters[0]));
 +/*
 + * 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"
 +
 +class CommandReloadmodule : public Command
 +{
 + public:
 +      /** Constructor for reloadmodule.
 +       */
 +      CommandReloadmodule ( Module* parent) : Command( parent, "RELOADMODULE",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);
 +};
 +
 +class ReloadModuleWorker : public HandlerBase1<void, bool>
 +{
 + public:
 +      const std::string name;
 +      const std::string uid;
 +      ReloadModuleWorker(const std::string& uuid, const std::string& modn)
 +              : name(modn), uid(uuid) {}
 +      void Call(bool result)
 +      {
 +              ServerInstance->SNO->WriteGlobalSno('a', "RELOAD MODULE: %s %ssuccessfully reloaded",
 +                      name.c_str(), result ? "" : "un");
 +              User* user = ServerInstance->FindNick(uid);
 +              if (user)
 +                      user->WriteNumeric(RPL_LOADEDMODULE, "%s :Module %ssuccessfully reloaded.",
 +                              name.c_str(), 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, "%s :You cannot reload core_reloadmodule.so (unload and load it)",
 +                      parameters[0].c_str());
 +              return CMD_FAILURE;
 +      }
 +
 +      if (m)
 +      {
++              ServerInstance->Modules->Reload(m, (creator->dying ? NULL : new ReloadModuleWorker(user->uuid, parameters[0])));
 +              return CMD_SUCCESS;
 +      }
 +      else
 +      {
 +              user->WriteNumeric(RPL_LOADEDMODULE, "%s :Could not find module by that name", parameters[0].c_str());
 +              return CMD_FAILURE;
 +      }
 +}
 +
 +COMMAND_INIT(CommandReloadmodule)
index 997dd3afee71cd628ef48593f01484fdca7696a3,0000000000000000000000000000000000000000..180ece9b3b1f56ee883bc19aa42d35792524f85d
mode 100644,000000..100644
--- /dev/null
@@@ -1,393 -1,0 +1,399 @@@
-               results.push_back("211 "+user->nick+" "+u->nick+"["+u->ident+"@"+(c == 'l' ? u->dhost : 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->age));
 +/*
 + * 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"
 +
 +#ifdef _WIN32
 +#include <psapi.h>
 +#pragma comment(lib, "psapi.lib") // For GetProcessMemoryInfo()
 +#endif
 +
 +/** Handle /STATS.
 + */
 +class CommandStats : public Command
 +{
 +      void DoStats(char statschar, User* user, string_list &results);
 + public:
 +      /** Constructor for stats.
 +       */
 +      CommandStats ( Module* parent) : Command(parent,"STATS",1,2) { 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);
 +      RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters)
 +      {
 +              if (parameters.size() > 1)
 +                      return ROUTE_UNICAST(parameters[1]);
 +              return ROUTE_LOCALONLY;
 +      }
 +};
 +
 +static void GenerateStatsLl(User* user, string_list& results, char c)
 +{
 +      results.push_back(InspIRCd::Format("211 %s nick[ident@%s] sendq cmds_out bytes_out cmds_in bytes_in time_open", user->nick.c_str(), (c == '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;
++              results.push_back("211 "+user->nick+" "+u->nick+"["+u->ident+"@"+(c == 'l' ? u->dhost : 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(char statschar, User* user, string_list &results)
 +{
 +      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->host.c_str());
 +              results.push_back("481 " + user->nick + " :Permission Denied - STATS " + statschar + " requires the servers/auspex priv.");
 +              return;
 +      }
 +
 +      ModResult MOD_RESULT;
 +      FIRST_MOD_RESULT(OnStats, MOD_RESULT, (statschar, user, results));
 +      if (MOD_RESULT == MOD_RES_DENY)
 +      {
 +              results.push_back("219 "+user->nick+" "+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->host.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 ip = ls->bind_addr;
 +                              if (ip.empty())
 +                                      ip.assign("*");
 +                              std::string type = ls->bind_tag->getString("type", "clients");
 +                              std::string hook = ls->bind_tag->getString("ssl", "plaintext");
 +
 +                              results.push_back("249 "+user->nick+" :"+ ip + ":"+ConvToStr(ls->bind_port)+
 +                                      " (" + 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;
 +                              std::stringstream res;
 +                              res << "215 " << user->nick << " I " << c->name << ' ';
 +                              if (c->type == CC_ALLOW)
 +                                      res << '+';
 +                              if (c->type == CC_DENY)
 +                                      res << '-';
 +
 +                              if (c->type == CC_NAMED)
 +                                      res << '*';
 +                              else
 +                                      res << c->host;
 +
 +                              res << ' ' << c->config->getString("port", "*") << ' ';
 +
 +                              res << c->GetRecvqMax() << ' ' << c->GetSendqSoftMax() << ' ' << c->GetSendqHardMax()
 +                                      << ' ' << c->GetCommandRate() << ' ' << c->GetPenaltyThreshold();
 +                              if (c->fakelag)
 +                                      res << '*';
 +                              results.push_back(res.str());
 +                      }
 +              }
 +              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;
 +                              results.push_back("215 "+user->nick+" i NOMATCH * "+c->GetHost()+" "+ConvToStr(c->limit ? c->limit : SocketEngine::GetMaxFds())+" "+ConvToStr(idx)+" "+ServerInstance->Config->ServerName+" *");
 +                              results.push_back("218 "+user->nick+" Y "+ConvToStr(idx)+" "+ConvToStr(c->GetPingTime())+" 0 "+ConvToStr(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);
 +                                      results.push_back("249 " + user->nick + " :" + oper->nick + " (" + oper->ident + "@" + oper->dhost + ") Idle: " +
 +                                                      (lu ? ConvToStr(ServerInstance->Time() - lu->idle_lastmsg) + " secs" : "unavailable"));
 +                                      idx++;
 +                              }
 +                      }
 +                      results.push_back("249 "+user->nick+" :"+ConvToStr(idx)+" OPER(s)");
 +              }
 +              break;
 +
 +              case 'k':
 +                      ServerInstance->XLines->InvokeStats("K",216,user,results);
 +              break;
 +              case 'g':
 +                      ServerInstance->XLines->InvokeStats("G",223,user,results);
 +              break;
 +              case 'q':
 +                      ServerInstance->XLines->InvokeStats("Q",217,user,results);
 +              break;
 +              case 'Z':
 +                      ServerInstance->XLines->InvokeStats("Z",223,user,results);
 +              break;
 +              case 'e':
 +                      ServerInstance->XLines->InvokeStats("E",223,user,results);
 +              break;
 +              case 'E':
 +              {
 +                      const SocketEngine::Statistics& stats = SocketEngine::GetStats();
 +                      results.push_back("249 "+user->nick+" :Total events: "+ConvToStr(stats.TotalEvents));
 +                      results.push_back("249 "+user->nick+" :Read events:  "+ConvToStr(stats.ReadEvents));
 +                      results.push_back("249 "+user->nick+" :Write events: "+ConvToStr(stats.WriteEvents));
 +                      results.push_back("249 "+user->nick+" :Error events: "+ConvToStr(stats.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 */
 +                                      results.push_back("212 "+user->nick+" "+i->second->name+" "+ConvToStr(i->second->use_count));
 +                              }
 +                      }
 +              }
 +              break;
 +
 +              /* stats z (debug and memory info) */
 +              case 'z':
 +              {
 +                      results.push_back("249 "+user->nick+" :Users: "+ConvToStr(ServerInstance->Users->GetUsers().size()));
 +                      results.push_back("249 "+user->nick+" :Channels: "+ConvToStr(ServerInstance->GetChans().size()));
 +                      results.push_back("249 "+user->nick+" :Commands: "+ConvToStr(ServerInstance->Parser.GetCommands().size()));
 +
 +                      float kbitpersec_in, kbitpersec_out, kbitpersec_total;
 +                      char kbitpersec_in_s[30], kbitpersec_out_s[30], kbitpersec_total_s[30];
 +
 +                      SocketEngine::GetStats().GetBandwidth(kbitpersec_in, kbitpersec_out, kbitpersec_total);
 +
 +                      snprintf(kbitpersec_total_s, 30, "%03.5f", kbitpersec_total);
 +                      snprintf(kbitpersec_out_s, 30, "%03.5f", kbitpersec_out);
 +                      snprintf(kbitpersec_in_s, 30, "%03.5f", kbitpersec_in);
 +
 +                      results.push_back("249 "+user->nick+" :Bandwidth total:  "+ConvToStr(kbitpersec_total_s)+" kilobits/sec");
 +                      results.push_back("249 "+user->nick+" :Bandwidth out:    "+ConvToStr(kbitpersec_out_s)+" kilobits/sec");
 +                      results.push_back("249 "+user->nick+" :Bandwidth in:     "+ConvToStr(kbitpersec_in_s)+" kilobits/sec");
 +
 +#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 */
 +                      {
 +                              results.push_back("249 "+user->nick+" :Total allocation: "+ConvToStr(R.ru_maxrss)+"K");
 +                              results.push_back("249 "+user->nick+" :Signals:          "+ConvToStr(R.ru_nsignals));
 +                              results.push_back("249 "+user->nick+" :Page faults:      "+ConvToStr(R.ru_majflt));
 +                              results.push_back("249 "+user->nick+" :Swaps:            "+ConvToStr(R.ru_nswap));
 +                              results.push_back("249 "+user->nick+" :Context Switches: Voluntary; "+ConvToStr(R.ru_nvcsw)+" Involuntary; "+ConvToStr(R.ru_nivcsw));
 +
 +                              char percent[30];
 +
 +                              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;
 +
 +                              snprintf(percent, 30, "%03.5f%%", per);
 +                              results.push_back("249 "+user->nick+" :CPU Use (now):    "+percent);
 +
 +                              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;
 +                              snprintf(percent, 30, "%03.5f%%", per);
 +                              results.push_back("249 "+user->nick+" :CPU Use (total):  "+percent);
 +                      }
 +#else
 +                      PROCESS_MEMORY_COUNTERS MemCounters;
 +                      if (GetProcessMemoryInfo(GetCurrentProcess(), &MemCounters, sizeof(MemCounters)))
 +                      {
 +                              results.push_back("249 "+user->nick+" :Total allocation: "+ConvToStr((MemCounters.WorkingSetSize + MemCounters.PagefileUsage) / 1024)+"K");
 +                              results.push_back("249 "+user->nick+" :Pagefile usage:   "+ConvToStr(MemCounters.PagefileUsage / 1024)+"K");
 +                              results.push_back("249 "+user->nick+" :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);
 +
 +                              char percent[30];
 +
 +                              snprintf(percent, 30, "%03.5f%%", per);
 +                              results.push_back("249 "+user->nick+" :CPU Use (now):    "+percent);
 +
 +                              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);
 +                              snprintf(percent, 30, "%03.5f%%", per);
 +                              results.push_back("249 "+user->nick+" :CPU Use (total):  "+percent);
 +                      }
 +#endif
 +              }
 +              break;
 +
 +              case 'T':
 +              {
 +                      results.push_back("249 "+user->nick+" :accepts "+ConvToStr(ServerInstance->stats.Accept)+" refused "+ConvToStr(ServerInstance->stats.Refused));
 +                      results.push_back("249 "+user->nick+" :unknown commands "+ConvToStr(ServerInstance->stats.Unknown));
 +                      results.push_back("249 "+user->nick+" :nick collisions "+ConvToStr(ServerInstance->stats.Collisions));
 +                      results.push_back("249 "+user->nick+" :dns requests "+ConvToStr(ServerInstance->stats.DnsGood+ServerInstance->stats.DnsBad)+" succeeded "+ConvToStr(ServerInstance->stats.DnsGood)+" failed "+ConvToStr(ServerInstance->stats.DnsBad));
 +                      results.push_back("249 "+user->nick+" :connection count "+ConvToStr(ServerInstance->stats.Connects));
 +                      results.push_back(InspIRCd::Format("249 %s :bytes sent %5.2fK recv %5.2fK", user->nick.c_str(),
 +                              ServerInstance->stats.Sent / 1024.0, ServerInstance->stats.Recv / 1024.0));
 +              }
 +              break;
 +
 +              /* stats o */
 +              case 'o':
 +              {
 +                      ConfigTagList tags = ServerInstance->Config->ConfTags("oper");
 +                      for(ConfigIter i = tags.first; i != tags.second; ++i)
 +                      {
 +                              ConfigTag* tag = i->second;
 +                              results.push_back("243 "+user->nick+" 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);
 +                              }
 +                              results.push_back("243 "+user->nick+" O "+tag->name.c_str() + " " + 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(user, results, statschar);
 +              break;
 +
 +              /* stats u (show server uptime) */
 +              case 'u':
 +              {
 +                      unsigned int up = static_cast<unsigned int>(ServerInstance->Time() - ServerInstance->startup_time);
 +                      results.push_back(InspIRCd::Format("242 %s :Server up %u days, %.2u:%.2u:%.2u", user->nick.c_str(),
 +                              up / 86400, (up / 3600) % 24, (up / 60) % 60, up % 60));
 +              }
 +              break;
 +
 +              default:
 +              break;
 +      }
 +
 +      results.push_back("219 "+user->nick+" "+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->host.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;
++      }
 +      string_list values;
 +      char search = parameters[0][0];
 +      DoStats(search, user, values);
 +
 +      const std::string p = ":" + ServerInstance->Config->ServerName + " ";
 +      for (size_t i = 0; i < values.size(); i++)
 +              user->SendText(p + values[i]);
 +
 +      return CMD_SUCCESS;
 +}
 +
 +COMMAND_INIT(CommandStats)
index d593d7f4b8f7974f6f2f341b5b1f8ff84b4b5d82,0000000000000000000000000000000000000000..cbf4f5e08b97ae1487561c28946d96304ca06ec2
mode 100644,000000..100644
--- /dev/null
@@@ -1,81 -1,0 +1,82 @@@
 +/*
 + * 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"
 +
 +CommandUser::CommandUser(Module* parent)
 +      : SplitCommand(parent, "USER", 4, 4)
 +{
 +      works_before_reg = true;
 +      Penalty = 0;
 +      syntax = "<username> <localhost> <remotehost> <GECOS>";
 +}
 +
 +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]))
 +              {
 +                      /*
 +                       * RFC says we must use this numeric, so we do. Let's make it a little more nub friendly though. :)
 +                       *  -- Craig, and then w00t.
 +                       */
 +                      user->WriteNumeric(ERR_NEEDMOREPARAMS, "USER :Your username is not valid");
 +                      return CMD_FAILURE;
 +              }
 +              else
 +              {
 +                      /*
 +                       * The ident field is IDENTMAX+2 in size to account for +1 for the optional
 +                       * ~ character, and +1 for null termination, therefore we can safely use up to
 +                       * IDENTMAX here.
 +                       */
 +                      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;
 +}
index ffa6aa2ff3de81c2ef37a3c6b976a3a8761b50c7,0000000000000000000000000000000000000000..dd778548ab95514a37e87157c92756ff70b9e9e6
mode 100644,000000..100644
--- /dev/null
@@@ -1,163 -1,0 +1,170 @@@
-               Penalty = 0;
 +/*
 + * 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)
 +      {
 +              // 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)
 +      {
-               if (IS_LOCAL(user))
-                       IS_LOCAL(user)->lastping = 1;
 +              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)
 +      {
 +              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)
 +      {
 +              // 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;
 +
 + public:
 +      CoreModUser()
 +              : cmdaway(this), cmdmode(this), cmdnick(this), cmdpart(this), cmdpass(this), cmdping(this)
 +              , cmdpong(this), cmdquit(this), cmduser(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)
index a6782419496a0710f13bc8480a4248d7e07c87d7,0000000000000000000000000000000000000000..eae6e51ce3f0f3a1eaa2b61f343d57e9edce3652
mode 100644,000000..100644
--- /dev/null
@@@ -1,72 -1,0 +1,85 @@@
-       CommandUserhost ( Module* parent) : Command(parent,"USERHOST", 1, 5) {
 +/*
 + * 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.
 +       */
-       for (unsigned int i = 0; i < parameters.size(); i++)
++      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);
 +};
 +
 +CmdResult CommandUserhost::Handle (const std::vector<std::string>& parameters, User *user)
 +{
 +      const bool has_privs = user->HasPrivPermission("users/auspex");
 +
 +      std::string retbuf = "302 " + user->nick + " :";
 +
-                               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 == user) || (has_privs)) ? u->host : u->dhost);
 +                      retbuf += ' ';
 +              }
 +      }
 +
 +      user->WriteServ(retbuf);
 +
 +      return CMD_SUCCESS;
 +}
 +
 +COMMAND_INIT(CommandUserhost)
index 39ea347dc8292b5c2b92f7cd908ddaa9c88e7acf,0000000000000000000000000000000000000000..8b9258d71d374160f2bbb8a3e792a93697436ad6
mode 100644,000000..100644
--- /dev/null
@@@ -1,397 -1,0 +1,397 @@@
-                       if (user->age >= ServerInstance->Time() - seconds)
 +/*
 + * InspIRCd -- Internet Relay Chat Daemon
 + *
 + *   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"
 +
 +/** Handle /WHO.
 + */
 +class CommandWho : public Command
 +{
 +      bool CanView(Channel* chan, User* user);
 +      bool opt_viewopersonly;
 +      bool opt_showrealhost;
 +      bool opt_realname;
 +      bool opt_mode;
 +      bool opt_ident;
 +      bool opt_metadata;
 +      bool opt_port;
 +      bool opt_away;
 +      bool opt_local;
 +      bool opt_far;
 +      bool opt_time;
 +      ChanModeReference secretmode;
 +      ChanModeReference privatemode;
 +      UserModeReference invisiblemode;
 +
 +      Membership* get_first_visible_channel(User* u)
 +      {
 +              for (User::ChanList::iterator i = u->chans.begin(); i != u->chans.end(); ++i)
 +              {
 +                      Membership* memb = *i;
 +                      if (!memb->chan->IsModeSet(secretmode))
 +                              return memb;
 +              }
 +              return NULL;
 +      }
 +
 + public:
 +      /** Constructor for who.
 +       */
 +      CommandWho(Module* parent)
 +              : Command(parent, "WHO", 1)
 +              , secretmode(parent, "secret")
 +              , privatemode(parent, "private")
 +              , invisiblemode(parent, "invisible")
 +      {
 +              syntax = "<server>|<nickname>|<channel>|<realname>|<host>|0 [ohurmMiaplf]";
 +      }
 +
 +      void SendWhoLine(User* user, const std::vector<std::string>& parms, const std::string& initial, Membership* memb, User* u, std::vector<std::string>& whoresults);
 +      /** 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);
 +      bool whomatch(User* cuser, User* user, const char* matchtext);
 +};
 +
 +bool CommandWho::whomatch(User* cuser, User* user, const char* matchtext)
 +{
 +      bool match = false;
 +      bool positive = false;
 +
 +      if (user->registered != REG_ALL)
 +              return false;
 +
 +      if (opt_local && !IS_LOCAL(user))
 +              return false;
 +      else if (opt_far && IS_LOCAL(user))
 +              return false;
 +
 +      if (opt_mode)
 +      {
 +              for (const char* n = matchtext; *n; n++)
 +              {
 +                      if (*n == '+')
 +                      {
 +                              positive = true;
 +                              continue;
 +                      }
 +                      else if (*n == '-')
 +                      {
 +                              positive = false;
 +                              continue;
 +                      }
 +                      if (user->IsModeSet(*n) != positive)
 +                              return false;
 +              }
 +              return true;
 +      }
 +      else
 +      {
 +              /*
 +               * This was previously one awesome pile of ugly nested if, when really, it didn't need
 +               * to be, since only one condition was ever checked, a chained if works just fine.
 +               * -- w00t
 +               */
 +              if (opt_metadata)
 +              {
 +                      match = false;
 +                      const Extensible::ExtensibleStore& list = user->GetExtList();
 +                      for(Extensible::ExtensibleStore::const_iterator i = list.begin(); i != list.end(); ++i)
 +                              if (InspIRCd::Match(i->first->name, matchtext))
 +                                      match = true;
 +              }
 +              else if (opt_realname)
 +                      match = InspIRCd::Match(user->fullname, matchtext);
 +              else if (opt_showrealhost)
 +                      match = InspIRCd::Match(user->host, matchtext, ascii_case_insensitive_map);
 +              else if (opt_ident)
 +                      match = InspIRCd::Match(user->ident, matchtext, ascii_case_insensitive_map);
 +              else if (opt_port)
 +              {
 +                      irc::portparser portrange(matchtext, false);
 +                      long portno = -1;
 +                      while ((portno = portrange.GetToken()))
 +                              if (IS_LOCAL(user) && portno == IS_LOCAL(user)->GetServerPort())
 +                              {
 +                                      match = true;
 +                                      break;
 +                              }
 +              }
 +              else if (opt_away)
 +                      match = InspIRCd::Match(user->awaymsg, matchtext);
 +              else if (opt_time)
 +              {
 +                      long seconds = InspIRCd::Duration(matchtext);
 +
 +                      // Okay, so time matching, we want all users connected `seconds' ago
++                      if (user->signon >= ServerInstance->Time() - seconds)
 +                              match = true;
 +              }
 +
 +              /*
 +               * Once the conditionals have been checked, only check dhost/nick/server
 +               * if they didn't match this user -- and only match if we don't find a match.
 +               *
 +               * This should make things minutely faster, and again, less ugly.
 +               * -- w00t
 +               */
 +              if (!match)
 +                      match = InspIRCd::Match(user->dhost, matchtext, ascii_case_insensitive_map);
 +
 +              if (!match)
 +                      match = InspIRCd::Match(user->nick, matchtext);
 +
 +              /* Don't allow server name matches if HideWhoisServer is enabled, unless the command user has the priv */
 +              if (!match && (ServerInstance->Config->HideWhoisServer.empty() || cuser->HasPrivPermission("users/auspex")))
 +                      match = InspIRCd::Match(user->server->GetName(), matchtext);
 +
 +              return match;
 +      }
 +}
 +
 +bool CommandWho::CanView(Channel* chan, User* user)
 +{
 +      /* Bug #383 - moved higher up the list, because if we are in the channel
 +       * we can see all its users
 +       */
 +      if (chan->HasUser(user))
 +              return true;
 +      /* Opers see all */
 +      if (user->HasPrivPermission("users/auspex"))
 +              return true;
 +      /* Cant see inside a +s or a +p channel unless we are a member (see above) */
 +      else if (!chan->IsModeSet(secretmode) && !chan->IsModeSet(privatemode))
 +              return true;
 +
 +      return false;
 +}
 +
 +void CommandWho::SendWhoLine(User* user, const std::vector<std::string>& parms, const std::string& initial, Membership* memb, User* u, std::vector<std::string>& whoresults)
 +{
 +      if (!memb)
 +              memb = get_first_visible_channel(u);
 +
 +      std::string wholine = initial + (memb ? memb->chan->name : "*") + " " + u->ident + " " +
 +              (opt_showrealhost ? u->host : u->dhost) + " ";
 +      if (!ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex"))
 +              wholine.append(ServerInstance->Config->HideWhoisServer);
 +      else
 +              wholine.append(u->server->GetName());
 +
 +      wholine.append(" " + u->nick + " ");
 +
 +      /* away? */
 +      if (u->IsAway())
 +      {
 +              wholine.append("G");
 +      }
 +      else
 +      {
 +              wholine.append("H");
 +      }
 +
 +      /* oper? */
 +      if (u->IsOper())
 +      {
 +              wholine.push_back('*');
 +      }
 +
 +      if (memb)
 +      {
 +              char prefix = memb->GetPrefixChar();
 +              if (prefix)
 +                      wholine.push_back(prefix);
 +      }
 +
 +      wholine.append(" :0 " + u->fullname);
 +
 +      FOREACH_MOD(OnSendWhoLine, (user, parms, u, memb, wholine));
 +
 +      if (!wholine.empty())
 +              whoresults.push_back(wholine);
 +}
 +
 +CmdResult CommandWho::Handle (const std::vector<std::string>& parameters, User *user)
 +{
 +      /*
 +       * XXX - RFC says:
 +       *   The <name> passed to WHO is matched against users' host, server, real
 +       *   name and nickname
 +       * Currently, we support WHO #chan, WHO nick, WHO 0, WHO *, and the addition of a 'o' flag, as per RFC.
 +       */
 +
 +      /* WHO options */
 +      opt_viewopersonly = false;
 +      opt_showrealhost = false;
 +      opt_realname = false;
 +      opt_mode = false;
 +      opt_ident = false;
 +      opt_metadata = false;
 +      opt_port = false;
 +      opt_away = false;
 +      opt_local = false;
 +      opt_far = false;
 +      opt_time = false;
 +
 +      std::vector<std::string> whoresults;
 +      std::string initial = "352 " + user->nick + " ";
 +
 +      /* Change '0' into '*' so the wildcard matcher can grok it */
 +      std::string matchtext = ((parameters[0] == "0") ? "*" : parameters[0]);
 +
 +      // WHO flags count as a wildcard
 +      bool usingwildcards = ((parameters.size() > 1) || (matchtext.find_first_of("*?.") != std::string::npos));
 +
 +      if (parameters.size() > 1)
 +      {
 +              for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter)
 +              {
 +                      switch (*iter)
 +                      {
 +                              case 'o':
 +                                      opt_viewopersonly = true;
 +                                      break;
 +                              case 'h':
 +                                      if (user->HasPrivPermission("users/auspex"))
 +                                              opt_showrealhost = true;
 +                                      break;
 +                              case 'r':
 +                                      opt_realname = true;
 +                                      break;
 +                              case 'm':
 +                                      if (user->HasPrivPermission("users/auspex"))
 +                                              opt_mode = true;
 +                                      break;
 +                              case 'M':
 +                                      if (user->HasPrivPermission("users/auspex"))
 +                                              opt_metadata = true;
 +                                      break;
 +                              case 'i':
 +                                      opt_ident = true;
 +                                      break;
 +                              case 'p':
 +                                      if (user->HasPrivPermission("users/auspex"))
 +                                              opt_port = true;
 +                                      break;
 +                              case 'a':
 +                                      opt_away = true;
 +                                      break;
 +                              case 'l':
 +                                      if (user->HasPrivPermission("users/auspex") || ServerInstance->Config->HideWhoisServer.empty())
 +                                              opt_local = true;
 +                                      break;
 +                              case 'f':
 +                                      if (user->HasPrivPermission("users/auspex") || ServerInstance->Config->HideWhoisServer.empty())
 +                                              opt_far = true;
 +                                      break;
 +                              case 't':
 +                                      opt_time = true;
 +                                      break;
 +                      }
 +              }
 +      }
 +
 +
 +      /* who on a channel? */
 +      Channel* ch = ServerInstance->FindChan(matchtext);
 +
 +      if (ch)
 +      {
 +              if (CanView(ch,user))
 +              {
 +                      bool inside = ch->HasUser(user);
 +
 +                      /* who on a channel. */
 +                      const Channel::MemberMap& cu = ch->GetUsers();
 +                      for (Channel::MemberMap::const_iterator i = cu.begin(); i != cu.end(); ++i)
 +                      {
 +                              /* None of this applies if we WHO ourselves */
 +                              if (user != i->first)
 +                              {
 +                                      /* opers only, please */
 +                                      if (opt_viewopersonly && !i->first->IsOper())
 +                                              continue;
 +
 +                                      /* If we're not inside the channel, hide +i users */
 +                                      if (i->first->IsModeSet(invisiblemode) && !inside && !user->HasPrivPermission("users/auspex"))
 +                                              continue;
 +                              }
 +
 +                              SendWhoLine(user, parameters, initial, i->second, i->first, whoresults);
 +                      }
 +              }
 +      }
 +      else
 +      {
 +              /* Match against wildcard of nick, server or host */
 +              if (opt_viewopersonly)
 +              {
 +                      /* Showing only opers */
 +                      const UserManager::OperList& opers = ServerInstance->Users->all_opers;
 +                      for (UserManager::OperList::const_iterator i = opers.begin(); i != opers.end(); ++i)
 +                      {
 +                              User* oper = *i;
 +
 +                              if (whomatch(user, oper, matchtext.c_str()))
 +                              {
 +                                      if (!user->SharesChannelWith(oper))
 +                                      {
 +                                              if (usingwildcards && (oper->IsModeSet(invisiblemode)) && (!user->HasPrivPermission("users/auspex")))
 +                                                      continue;
 +                                      }
 +
 +                                      SendWhoLine(user, parameters, initial, NULL, oper, whoresults);
 +                              }
 +                      }
 +              }
 +              else
 +              {
 +                      const user_hash& users = ServerInstance->Users->GetUsers();
 +                      for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i)
 +                      {
 +                              if (whomatch(user, i->second, matchtext.c_str()))
 +                              {
 +                                      if (!user->SharesChannelWith(i->second))
 +                                      {
 +                                              if (usingwildcards && (i->second->IsModeSet(invisiblemode)) && (!user->HasPrivPermission("users/auspex")))
 +                                                      continue;
 +                                      }
 +
 +                                      SendWhoLine(user, parameters, initial, NULL, i->second, whoresults);
 +                              }
 +                      }
 +              }
 +      }
 +      /* Send the results out */
 +      for (std::vector<std::string>::const_iterator n = whoresults.begin(); n != whoresults.end(); n++)
 +              user->WriteServ(*n);
 +      user->WriteNumeric(RPL_ENDOFWHO, "%s :End of /WHO list.", *parameters[0].c_str() ? parameters[0].c_str() : "*");
 +
 +      // Penalize the user a bit for large queries
 +      // (add one unit of penalty per 200 results)
 +      if (IS_LOCAL(user))
 +              IS_LOCAL(user)->CommandFloodPenalty += whoresults.size() * 5;
 +      return CMD_SUCCESS;
 +}
 +
 +COMMAND_INIT(CommandWho)
index 42e25f8f6a6248bfb0efc19294857cd409d34e19,e0347421b29d01fb89e7cedd13d89aacde04fb74..35e5f3671cec15aa37c1387bfc460a6eaf54eb76
@@@ -169,39 -165,6 +169,39 @@@ bool irc::StrHashComp::operator()(cons
        return (national_case_insensitive_map[*n1] == national_case_insensitive_map[*n2]);
  }
  
-       register size_t t = 0;
 +bool irc::insensitive_swo::operator()(const std::string& a, const std::string& b) const
 +{
 +      const unsigned char* charmap = national_case_insensitive_map;
 +      std::string::size_type asize = a.size();
 +      std::string::size_type bsize = b.size();
 +      std::string::size_type maxsize = std::min(asize, bsize);
 +
 +      for (std::string::size_type i = 0; i < maxsize; i++)
 +      {
 +              unsigned char A = charmap[(unsigned char)a[i]];
 +              unsigned char B = charmap[(unsigned char)b[i]];
 +              if (A > B)
 +                      return false;
 +              else if (A < B)
 +                      return true;
 +      }
 +      return (asize < bsize);
 +}
 +
 +size_t irc::insensitive::operator()(const std::string &s) const
 +{
 +      /* XXX: NO DATA COPIES! :)
 +       * The hash function here is practically
 +       * a copy of the one in STL's hash_fun.h,
 +       * only with *x replaced with national_case_insensitive_map[*x].
 +       * This avoids a copy to use hash<const char*>
 +       */
++      size_t t = 0;
 +      for (std::string::const_iterator x = s.begin(); x != s.end(); ++x) /* ++x not x++, as its faster */
 +              t = 5 * t + national_case_insensitive_map[(unsigned char)*x];
 +      return t;
 +}
 +
  /******************************************************
   *
   * This is the implementation of our special irc::string
index 184013a4e0db3777957a85c9a636fe3fb6fbe5e5,050f41c758379e7581fea0054287a3532d885c7e..fc6161e3175f2320d22e331bd3d52c22a6369295
@@@ -31,14 -36,18 +31,17 @@@ bool ModuleManager::Load(const std::str
  {
        /* Don't allow people to specify paths for modules, it doesn't work as expected */
        if (filename.find('/') != std::string::npos)
+       {
+               LastModuleError = "You can't load modules with a path: " + filename;
                return false;
+       }
  
 -      char modfile[MAXBUF];
 -      snprintf(modfile,MAXBUF,"%s/%s",ServerInstance->Config->ModPath.c_str(),filename.c_str());
 +      const std::string moduleFile = ServerInstance->Config->Paths.PrependModule(filename);
  
 -      if (!ServerConfig::FileExists(modfile))
 +      if (!FileSystem::FileExists(moduleFile))
        {
                LastModuleError = "Module file could not be found: " + filename;
 -              ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError);
 +              ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError);
                return false;
        }
  
index 77d86cb3167ba5de06b8ed1a37197c8b88e3718b,1e8f7117669083465047b6094584acb5233e5628..d2fa09c4ebad1106a5f37776c50c85a5832ea6e7
@@@ -67,7 -79,7 +67,7 @@@ class ModuleAbbreviation : public Modul
                /* Ambiguous command, list the matches */
                if (!matchlist.empty())
                {
-                       user->WriteNumeric(420, ":Ambiguous abbreviation, posssible matches: %s%s", foundcommand.c_str(), matchlist.c_str());
 -                      user->WriteNumeric(420, "%s :Ambiguous abbreviation, possible matches: %s%s", user->nick.c_str(), foundcommand.c_str(), matchlist.c_str());
++                      user->WriteNumeric(420, ":Ambiguous abbreviation, possible matches: %s%s", foundcommand.c_str(), matchlist.c_str());
                        return MOD_RES_DENY;
                }
  
Simple merge
Simple merge
Simple merge
index 5b226f3b8baf733036314a2b814130ca552342b8,88b0c4cdffbf544f5ed12a1a5746162cdf152a6d..81b9b888f4fff7724b8e1c8acb0d31bcc0194ff7
@@@ -63,12 -77,16 +63,16 @@@ class ModuleHideOper : public Modul
                return MOD_RES_PASSTHRU;
        }
  
 -      void OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, std::string& line)
 +      void OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, Membership* memb, std::string& line) CXX11_OVERRIDE
        {
 -              if (user->IsModeSet('H') && !source->HasPrivPermission("users/auspex"))
 +              if (user->IsModeSet(hm) && !source->HasPrivPermission("users/auspex"))
                {
                        // hide the "*" that marks the user as an oper from the /WHO line
-                       std::string::size_type pos = line.find("*");
+                       std::string::size_type spcolon = line.find(" :");
+                       if (spcolon == std::string::npos)
+                               return; // Another module hid the user completely
+                       std::string::size_type sp = line.rfind(' ', spcolon-1);
+                       std::string::size_type pos = line.find('*', sp);
                        if (pos != std::string::npos)
                                line.erase(pos, 1);
                        // hide the line completely if doing a "/who * o" query
index e09ca3fa2c03c604f644736e7c839065ff08be00,2b079c6ff9fe19d017d8347332f38d4b0a66fe95..aa83b120cee1b6a51c97500bffb6c390389ce74b
@@@ -407,12 -379,47 +407,26 @@@ class ModuleHttpServer : public Modul
                return MOD_RES_ALLOW;
        }
  
 -      void OnBackgroundTimer(time_t curtime)
 -      {
 -              if (!timeoutsec)
 -                      return;
 -
 -              time_t oldest_allowed = curtime - timeoutsec;
 -              for (std::set<HttpServerSocket*>::const_iterator i = sockets.begin(); i != sockets.end(); )
 -              {
 -                      HttpServerSocket* sock = *i;
 -                      ++i;
 -                      if (sock->createtime < oldest_allowed)
 -                      {
 -                              sock->cull();
 -                              delete sock;
 -                      }
 -              }
 -      }
 -
+       void OnUnloadModule(Module* mod)
+       {
 -              for (std::set<HttpServerSocket*>::const_iterator i = sockets.begin(); i != sockets.end(); )
++              for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
+               {
+                       HttpServerSocket* sock = *i;
+                       ++i;
 -                      if (sock->GetIOHook() == mod)
++                      if (sock->GetIOHook() && sock->GetIOHook()->prov->creator == mod)
+                       {
+                               sock->cull();
+                               delete sock;
+                       }
+               }
+       }
 -      CullResult cull()
 +      CullResult cull() CXX11_OVERRIDE
        {
 -              std::set<HttpServerSocket*> local;
 -              local.swap(sockets);
 -              for (std::set<HttpServerSocket*>::const_iterator i = local.begin(); i != local.end(); ++i)
 +              for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
                {
                        HttpServerSocket* sock = *i;
 -                      sock->cull();
 -                      delete sock;
 +                      sock->AddToCull();
                }
                return Module::cull();
        }
Simple merge
index 4c63e53d1b109153d45560eacd2924c76a0c508b,9f1f6cc5e8f3f25863a940abde3513d133767d46..51281a528b6867e102c8ccd0dfdad43ed5835528
@@@ -72,17 -110,38 +72,31 @@@ class ModuleOperPrefixMode : public Mod
                return MOD_RES_PASSTHRU;
        }
  
 -              if ((!IS_LOCAL(memb->user)) || (!IS_OPER(memb->user)) || (((mw_added) && (memb->user->IsModeSet('H')))))
+       void OnPostJoin(Membership* memb)
+       {
 -              std::vector<std::string> modechange;
 -              modechange.push_back(memb->chan->name);
 -              modechange.push_back("+y");
 -              modechange.push_back(memb->user->nick);
 -              ServerInstance->SendGlobalMode(modechange, ServerInstance->FakeClient);
++              if ((!IS_LOCAL(memb->user)) || (!memb->user->IsOper()) || (memb->user->IsModeSet(hideopermode)))
+                       return;
+               if (memb->hasMode(opm.GetModeChar()))
+                       return;
+               // The user was force joined and OnUserPreJoin() did not run. Set the operprefix now.
++              Modes::ChangeList changelist;
++              changelist.push_add(&opm, memb->user->nick);
++              ServerInstance->Modes.Process(ServerInstance->FakeClient, memb->chan, NULL, changelist);
+       }
        void SetOperPrefix(User* user, bool add)
        {
 -              std::vector<std::string> modechange;
 -              modechange.push_back("");
 -              modechange.push_back(add ? "+y" : "-y");
 -              modechange.push_back(user->nick);
 -              for (UCListIter v = user->chans.begin(); v != user->chans.end(); v++)
 -              {
 -                      modechange[0] = (*v)->name;
 -                      ServerInstance->SendGlobalMode(modechange, ServerInstance->FakeClient);
 -              }
 +              Modes::ChangeList changelist;
 +              changelist.push(&opm, add, user->nick);
 +              for (User::ChanList::iterator v = user->chans.begin(); v != user->chans.end(); v++)
 +                      ServerInstance->Modes->Process(ServerInstance->FakeClient, (*v)->chan, NULL, changelist);
        }
  
 -      void OnPostOper(User* user, const std::string& opername, const std::string& opertype)
 +      void OnPostOper(User* user, const std::string& opername, const std::string& opertype) CXX11_OVERRIDE
        {
 -              if (IS_LOCAL(user) && (!mw_added || !user->IsModeSet('H')))
 +              if (IS_LOCAL(user) && (!user->IsModeSet(hideopermode)))
                        SetOperPrefix(user, true);
        }
  
index c96b8703400c75ebd3d4bc66ddf3fbff0730692f,32c9afc79ec05e36be5eeecd07f91f6f75e94ca3..341b3aea7166139068f75039e4ebc1b101e98bb4
@@@ -283,10 -291,15 +286,10 @@@ class ModuleSASL : public Modul
                return MOD_RES_PASSTHRU;
        }
  
 -      Version GetVersion()
 +      Version GetVersion() CXX11_OVERRIDE
        {
-               return Version("Provides support for IRC Authentication Layer (aka: atheme SASL) via AUTHENTICATE.",VF_VENDOR);
+               return Version("Provides support for IRC Authentication Layer (aka: SASL) via AUTHENTICATE.", VF_VENDOR);
        }
 -
 -      void OnEvent(Event &ev)
 -      {
 -              cap.HandleEvent(ev);
 -      }
  };
  
  MODULE_INIT(ModuleSASL)
index 25c1f66781f5716b1eabbbbe790f0fd8e5622cf1,47b39452268dfa8cc34923a98cfc36184726bb25..e29aa09d7ae5d9aa0c5fce8f59656dcd24c3707b
@@@ -127,219 -84,174 +127,219 @@@ CmdResult CommandFJoin::Handle(User* sr
        else
        {
                time_t ourTS = chan->age;
 -
                if (TS != ourTS)
-                       ServerInstance->SNO->WriteToSnoMask('d', "Merge FJOIN received for %s, ourTS: %lu, TS: %lu, difference: %lu",
-                               chan->name.c_str(), (unsigned long)ourTS, (unsigned long)TS, (unsigned long)(ourTS - TS));
 +              {
 -              /* If our TS is less than theirs, we dont accept their modes */
 -              if (ourTS < TS)
 -              {
 -                      ServerInstance->SNO->WriteToSnoMask('d', "NOT Applying modes from other side");
 -                      apply_other_sides_modes = false;
 -              }
 -              else if (ourTS > TS)
 -              {
 -                      /* Our TS greater than theirs, clear all our modes from the channel, accept theirs. */
 -                      ServerInstance->SNO->WriteToSnoMask('d', "Removing our modes, accepting remote");
 -                      parameterlist param_list;
 -                      if (Utils->AnnounceTSChange)
 -                              chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :TS for %s changed from %lu to %lu", chan->name.c_str(), channel.c_str(), (unsigned long) ourTS, (unsigned long) TS);
 -                      // while the name is equal in case-insensitive compare, it might differ in case; use the remote version
 -                      chan->name = channel;
 -                      chan->age = TS;
 -                      chan->ClearInvites();
 -                      param_list.push_back(channel);
 -                      this->RemoveStatus(ServerInstance->FakeClient, param_list);
 -
 -                      // XXX: If the channel does not exist in the chan hash at this point, create it so the remote modes can be applied on it.
 -                      // This happens to 0-user permanent channels on the losing side, because those are removed (from the chan hash, then
 -                      // deleted later) as soon as the permchan mode is removed from them.
 -                      if (ServerInstance->FindChan(channel) == NULL)
+                       ServerInstance->SNO->WriteToSnoMask('d', "Merge FJOIN received for %s, ourTS: %lu, TS: %lu, difference: %ld",
+                               chan->name.c_str(), (unsigned long)ourTS, (unsigned long)TS, (long)(ourTS - TS));
 +                      /* If our TS is less than theirs, we dont accept their modes */
 +                      if (ourTS < TS)
 +                      {
 +                              apply_other_sides_modes = false;
 +                      }
 +                      else if (ourTS > TS)
                        {
 -                              chan = new Channel(channel, TS);
 +                              // Our TS is greater than theirs, remove all modes, extensions, etc. from the channel
 +                              LowerTS(chan, TS, channel);
 +
 +                              // XXX: If the channel does not exist in the chan hash at this point, create it so the remote modes can be applied on it.
 +                              // This happens to 0-user permanent channels on the losing side, because those are removed (from the chan hash, then
 +                              // deleted later) as soon as the permchan mode is removed from them.
 +                              if (ServerInstance->FindChan(channel) == NULL)
 +                              {
 +                                      chan = new Channel(channel, TS);
 +                              }
                        }
                }
 -              // The silent case here is ourTS == TS, we don't need to remove modes here, just to merge them later on.
        }
  
 -      /* First up, apply their modes if they won the TS war */
 +      /* First up, apply their channel modes if they won the TS war */
 +      Modes::ChangeList modechangelist;
        if (apply_other_sides_modes)
        {
 -              // Need to use a modestacker here due to maxmodes
 -              irc::modestacker stack(true);
 -              std::vector<std::string>::const_iterator paramit = params.begin() + 3;
 -              const std::vector<std::string>::const_iterator lastparamit = ((params.size() > 3) ? (params.end() - 1) : params.end());
 -              for (std::string::const_iterator i = params[2].begin(); i != params[2].end(); ++i)
 -              {
 -                      ModeHandler* mh = ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL);
 -                      if (!mh)
 -                              continue;
 +              ServerInstance->Modes.ModeParamsToChangeList(srcuser, MODETYPE_CHANNEL, params, modechangelist, 2, params.size() - 1);
 +              ServerInstance->Modes->Process(srcuser, chan, NULL, modechangelist, ModeParser::MODE_LOCALONLY | ModeParser::MODE_MERGE);
 +              // Reuse for prefix modes
 +              modechangelist.clear();
 +      }
  
 -                      std::string modeparam;
 -                      if ((paramit != lastparamit) && (mh->GetNumParams(true)))
 -                      {
 -                              modeparam = *paramit;
 -                              ++paramit;
 -                      }
 +      TreeServer* const sourceserver = TreeServer::Get(srcuser);
  
 -                      stack.Push(*i, modeparam);
 -              }
 +      // Build a new FJOIN for forwarding. Put the correct TS in it and the current modes of the channel
 +      // after applying theirs. If they lost, the prefix modes from their message are not forwarded.
 +      FwdFJoinBuilder fwdfjoin(chan, sourceserver);
  
 -              std::vector<std::string> modelist;
 +      /* Now, process every 'modes,uuid' pair */
 +      irc::tokenstream users(params.back());
 +      std::string item;
 +      Modes::ChangeList* modechangelistptr = (apply_other_sides_modes ? &modechangelist : NULL);
 +      while (users.GetToken(item))
 +      {
 +              ProcessModeUUIDPair(item, sourceserver, chan, modechangelistptr, fwdfjoin);
 +      }
  
 -              // Mode parser needs to know what channel to act on.
 -              modelist.push_back(params[0]);
 +      fwdfjoin.finalize();
 +      fwdfjoin.Forward(sourceserver);
  
 -              while (stack.GetStackedLine(modelist))
 -              {
 -                      ServerInstance->Modes->Process(modelist, srcuser, true);
 -                      modelist.erase(modelist.begin() + 1, modelist.end());
 -              }
 +      // Set prefix modes on their users if we lost the FJOIN or had equal TS
 +      if (apply_other_sides_modes)
 +              ServerInstance->Modes->Process(srcuser, chan, NULL, modechangelist, ModeParser::MODE_LOCALONLY);
 +
 +      return CMD_SUCCESS;
 +}
  
 -              ServerInstance->Modes->Process(modelist, srcuser, true);
 +void CommandFJoin::ProcessModeUUIDPair(const std::string& item, TreeServer* sourceserver, Channel* chan, Modes::ChangeList* modechangelist, FwdFJoinBuilder& fwdfjoin)
 +{
 +      std::string::size_type comma = item.find(',');
 +
 +      // Comma not required anymore if the user has no modes
 +      const std::string::size_type ubegin = (comma == std::string::npos ? 0 : comma+1);
 +      std::string uuid(item, ubegin, UIDGenerator::UUID_LENGTH);
 +      User* who = ServerInstance->FindUUID(uuid);
 +      if (!who)
 +      {
 +              // Probably KILLed, ignore
 +              return;
        }
  
 -      /* Now, process every 'modes,nick' pair */
 -      while (users.GetToken(item))
 +      TreeSocket* src_socket = sourceserver->GetSocket();
 +      /* Check that the user's 'direction' is correct */
 +      TreeServer* route_back_again = TreeServer::Get(who);
 +      if (route_back_again->GetSocket() != src_socket)
 +      {
 +              return;
 +      }
 +
 +      std::string::const_iterator modeendit = item.begin(); // End of the "ov" mode string
 +      /* Check if the user received at least one mode */
 +      if ((modechangelist) && (comma != std::string::npos))
        {
 -              const char* usr = item.c_str();
 -              if (usr && *usr)
 +              modeendit += comma;
 +              /* Iterate through the modes and see if they are valid here, if so, apply */
 +              for (std::string::const_iterator i = item.begin(); i != modeendit; ++i)
                {
 -                      const char* unparsedmodes = usr;
 -                      std::string modes;
 +                      ModeHandler* mh = ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL);
 +                      if (!mh)
 +                              throw ProtocolException("Unrecognised mode '" + std::string(1, *i) + "'");
  
 +                      /* Add any modes this user had to the mode stack */
 +                      modechangelist->push_add(mh, who->nick);
 +              }
 +      }
  
 -                      /* Iterate through all modes for this user and check they are valid. */
 -                      while ((*unparsedmodes) && (*unparsedmodes != ','))
 -                      {
 -                              ModeHandler *mh = ServerInstance->Modes->FindMode(*unparsedmodes, MODETYPE_CHANNEL);
 -                              if (!mh)
 -                              {
 -                                      ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Unrecognised mode %c, dropping link", *unparsedmodes);
 -                                      return CMD_INVALID;
 -                              }
 +      Membership* memb = chan->ForceJoin(who, NULL, sourceserver->IsBursting());
 +      if (!memb)
 +      {
 +              // User was already on the channel, forward because of the modes they potentially got
 +              memb = chan->GetUser(who);
 +              if (memb)
 +                      fwdfjoin.add(memb, item.begin(), modeendit);
 +              return;
 +      }
  
 -                              modes += *unparsedmodes;
 -                              usr++;
 -                              unparsedmodes++;
 -                      }
 +      // Assign the id to the new Membership
 +      Membership::Id membid = 0;
 +      const std::string::size_type colon = item.rfind(':');
 +      if (colon != std::string::npos)
 +              membid = Membership::IdFromString(item.substr(colon+1));
 +      memb->id = membid;
  
 -                      /* Advance past the comma, to the nick */
 -                      usr++;
 +      // Add member to fwdfjoin with prefix modes
 +      fwdfjoin.add(memb, item.begin(), modeendit);
 +}
  
 -                      /* Check the user actually exists */
 -                      who = ServerInstance->FindUUID(usr);
 -                      if (who)
 -                      {
 -                              /* Check that the user's 'direction' is correct */
 -                              TreeServer* route_back_again = Utils->BestRouteTo(who->server);
 -                              if ((!route_back_again) || (route_back_again->GetSocket() != src_socket))
 -                                      continue;
 +void CommandFJoin::RemoveStatus(Channel* c)
 +{
 +      Modes::ChangeList changelist;
  
 -                              /* Add any modes this user had to the mode stack */
 -                              for (std::string::iterator x = modes.begin(); x != modes.end(); ++x)
 -                                      modestack.Push(*x, who->nick);
 +      const ModeParser::ModeHandlerMap& mhs = ServerInstance->Modes->GetModes(MODETYPE_CHANNEL);
 +      for (ModeParser::ModeHandlerMap::const_iterator i = mhs.begin(); i != mhs.end(); ++i)
 +      {
 +              ModeHandler* mh = i->second;
  
 -                              Channel::JoinUser(who, channel.c_str(), true, "", src_server->bursting, TS);
 -                      }
 -                      else
 -                      {
 -                              ServerInstance->Logs->Log("m_spanningtree",SPARSE, "Ignored nonexistant user %s in fjoin to %s (probably quit?)", usr, channel.c_str());
 -                              continue;
 -                      }
 -              }
 +              /* Passing a pointer to a modestacker here causes the mode to be put onto the mode stack,
 +               * rather than applied immediately. Module unloads require this to be done immediately,
 +               * for this function we require tidyness instead. Fixes bug #493
 +               */
 +              mh->RemoveMode(c, changelist);
        }
  
 -      /* Flush mode stacker if we lost the FJOIN or had equal TS */
 -      if (apply_other_sides_modes)
 -      {
 -              parameterlist stackresult;
 -              stackresult.push_back(channel);
 +      ServerInstance->Modes->Process(ServerInstance->FakeClient, c, NULL, changelist, ModeParser::MODE_LOCALONLY);
 +}
  
 -              while (modestack.GetStackedLine(stackresult))
 -              {
 -                      ServerInstance->SendMode(stackresult, srcuser);
 -                      stackresult.erase(stackresult.begin() + 1, stackresult.end());
 -              }
 +void CommandFJoin::LowerTS(Channel* chan, time_t TS, const std::string& newname)
 +{
 +      if (Utils->AnnounceTSChange)
 +              chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :TS for %s changed from %lu to %lu", chan->name.c_str(), newname.c_str(), (unsigned long) chan->age, (unsigned long) TS);
 +
 +      // While the name is equal in case-insensitive compare, it might differ in case; use the remote version
 +      chan->name = newname;
 +      chan->age = TS;
 +
 +      // Remove all pending invites
 +      chan->ClearInvites();
 +
 +      // Clear all modes
 +      CommandFJoin::RemoveStatus(chan);
 +
 +      // Unset all extensions
 +      chan->FreeAllExtItems();
 +
 +      // Clear the topic, if it isn't empty then send a topic change message to local users
 +      if (!chan->topic.empty())
 +      {
 +              chan->topic.clear();
 +              chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "TOPIC %s :", chan->name.c_str());
        }
 -      return CMD_SUCCESS;
 +      chan->setby.clear();
 +      chan->topicset = 0;
  }
  
 -void CommandFJoin::RemoveStatus(User* srcuser, parameterlist &params)
 +CommandFJoin::Builder::Builder(Channel* chan, TreeServer* source)
 +      : CmdBuilder(source->GetID(), "FJOIN")
  {
 -      if (params.size() < 1)
 -              return;
 +      push(chan->name).push_int(chan->age).push_raw(" +");
 +      pos = str().size();
 +      push_raw(chan->ChanModes(true)).push_raw(" :");
 +}
  
 -      Channel* c = ServerInstance->FindChan(params[0]);
 +void CommandFJoin::Builder::add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend)
 +{
 +      push_raw(mbegin, mend).push_raw(',').push_raw(memb->user->uuid);
 +      push_raw(':').push_raw_int(memb->id);
 +      push_raw(' ');
 +}
  
 -      if (c)
 -      {
 -              irc::modestacker stack(false);
 -              parameterlist stackresult;
 -              stackresult.push_back(c->name);
 +bool CommandFJoin::Builder::has_room(std::string::size_type nummodes) const
 +{
 +      return ((str().size() + nummodes + UIDGenerator::UUID_LENGTH + 2 + membid_max_digits + 1) <= maxline);
 +}
  
 -              for (char modeletter = 'A'; modeletter <= 'z'; ++modeletter)
 -              {
 -                      ModeHandler* mh = ServerInstance->Modes->FindMode(modeletter, MODETYPE_CHANNEL);
 -
 -                      /* Passing a pointer to a modestacker here causes the mode to be put onto the mode stack,
 -                       * rather than applied immediately. Module unloads require this to be done immediately,
 -                       * for this function we require tidyness instead. Fixes bug #493
 -                       */
 -                      if (mh)
 -                              mh->RemoveMode(c, &stack);
 -              }
 +void CommandFJoin::Builder::clear()
 +{
 +      content.erase(pos);
 +      push_raw(" :");
 +}
  
 -              while (stack.GetStackedLine(stackresult))
 -              {
 -                      ServerInstance->SendMode(stackresult, srcuser);
 -                      stackresult.erase(stackresult.begin() + 1, stackresult.end());
 -              }
 -      }
 +const std::string& CommandFJoin::Builder::finalize()
 +{
 +      if (*content.rbegin() == ' ')
 +              content.erase(content.size()-1);
 +      return str();
  }
  
 +void FwdFJoinBuilder::add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend)
 +{
 +      // Pseudoserver compatibility:
 +      // Some pseudoservers do not handle lines longer than 512 so we split long FJOINs into multiple messages.
 +      // The forwarded FJOIN can end up being longer than the original one if we have more modes set and won, for example.
 +
 +      // Check if the member fits into the current message. If not, send it and prepare a new one.
 +      if (!has_room(std::distance(mbegin, mend)))
 +      {
 +              finalize();
 +              Forward(sourceserver);
 +              clear();
 +      }
 +      // Add the member and their modes exactly as they sent them
 +      CommandFJoin::Builder::add(memb, mbegin, mend);
 +}
index d0c6401cd4fc6c0667f03fc69622e226fb1f0e89,493b05ebf78f6dda096e3c8b3ab04f86200dc16e..afd86c0ce530976254090618ae2afe6f81094eec
   * represents our own server. Therefore, it has no route, no parent, and
   * no socket associated with it. Its version string is our own local version.
   */
 -TreeServer::TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id)
 -      : ServerName(Name.c_str()), ServerDesc(Desc), Utils(Util), ServerUser(ServerInstance->FakeClient)
 +TreeServer::TreeServer()
 +      : Server(ServerInstance->Config->ServerName, ServerInstance->Config->ServerDesc)
 +      , Parent(NULL), Route(NULL)
 +      , VersionString(ServerInstance->GetVersionString())
 +      , fullversion(ServerInstance->GetVersionString(true))
 +      , Socket(NULL), sid(ServerInstance->Config->GetSID()), behind_bursting(0), isdead(false)
 +      , pingtimer(this)
 +      , ServerUser(ServerInstance->FakeClient)
-       , age(ServerInstance->Time()), UserCount(ServerInstance->Users.GetLocalUsers().size())
++      , age(ServerInstance->Time()), UserCount(ServerInstance->Users.LocalUserCount())
 +      , OperCount(0), rtt(0), StartBurst(0), Hidden(false)
  {
 -      age = ServerInstance->Time();
 -      bursting = false;
 -      Parent = NULL;
 -      VersionString.clear();
 -      ServerUserCount = ServerOperCount = 0;
 -      VersionString = ServerInstance->GetVersionString();
 -      Route = NULL;
 -      Socket = NULL; /* Fix by brain */
 -      StartBurst = rtt = 0;
 -      Warned = Hidden = false;
        AddHashEntry();
 -      SetID(id);
  }
  
  /** When we create a new server, we call this constructor to initialize it.
index 803156446a2e50d86e544a204f16f1097cc6dfdf,b473277046c0af470fbd3afea566c89711090711..8196d37ba6299a01f509c9ed70279a2a99fcec9c
@@@ -20,7 -20,9 +20,8 @@@
   */
  
  
 -/* $ModDesc: Adds timed bans */
 -
  #include "inspircd.h"
++#include "listmode.h"
  
  /** Holds a timed ban
   */
@@@ -39,10 -42,21 +41,30 @@@ timedbans TimedBanList
   */
  class CommandTban : public Command
  {
 -      static bool IsBanSet(Channel* chan, const std::string& mask)
++      ChanModeReference banmode;
++
++      bool IsBanSet(Channel* chan, const std::string& mask)
+       {
 -              for (BanList::const_iterator i = chan->bans.begin(); i != chan->bans.end(); ++i)
++              ListModeBase* banlm = static_cast<ListModeBase*>(*banmode);
++              const ListModeBase::ModeList* bans = banlm->GetList(chan);
++              if (bans)
+               {
 -                      if (!strcasecmp(i->data.c_str(), mask.c_str()))
 -                              return true;
++                      for (ListModeBase::ModeList::const_iterator i = bans->begin(); i != bans->end(); ++i)
++                      {
++                              const ListModeBase::ListItem& ban = *i;
++                              if (!strcasecmp(ban.mask.c_str(), mask.c_str()))
++                                      return true;
++                      }
+               }
++
+               return false;
+       }
   public:
        CommandTban(Module* Creator) : Command(Creator,"TBAN", 3)
++              , banmode(Creator, "ban")
        {
                syntax = "<channel> <duration> <banmask>";
 -              TRANSLATE4(TR_TEXT, TR_TEXT, TR_TEXT, TR_END);
        }
  
        CmdResult Handle (const std::vector<std::string> &parameters, User *user)
                        return CMD_FAILURE;
                }
                std::string mask = parameters[2];
 -              std::vector<std::string> setban;
 -              setban.push_back(parameters[0]);
 -              setban.push_back("+b");
                bool isextban = ((mask.size() > 2) && (mask[1] == ':'));
 -              if (!isextban && !ServerInstance->IsValidMask(mask))
 +              if (!isextban && !InspIRCd::IsValidMask(mask))
                        mask.append("!*@*");
 -              if ((mask.length() > 250) || (!ServerInstance->IsValidMask(mask) && !isextban))
 -              {
 -                      user->WriteServ("NOTICE "+user->nick+" :Invalid ban mask");
 -                      return CMD_FAILURE;
 -              }
  
 -                      user->WriteServ("NOTICE %s :Ban already set", user->nick.c_str());
+               if (IsBanSet(channel, mask))
+               {
 -              setban.push_back(mask);
 -              // use CallHandler to make it so that the user sets the mode
 -              // themselves
 -              ServerInstance->Parser->CallHandler("MODE",setban,user);
 -              if (!IsBanSet(channel, mask))
++                      user->WriteNotice("Ban already set");
+                       return CMD_FAILURE;
+               }
 +              Modes::ChangeList setban;
 +              setban.push_add(ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL), mask);
 +              // Pass the user (instead of ServerInstance->FakeClient) to ModeHandler::Process() to
 +              // make it so that the user sets the mode themselves
 +              ServerInstance->Modes->Process(user, channel, NULL, setban);
 +              if (ServerInstance->Modes->GetLastParse().empty())
 +              {
 +                      user->WriteNotice("Invalid ban mask");
                        return CMD_FAILURE;
 +              }
  
                CUList tmp;
                T.channel = channelname;
        }
  };
  
 +class BanWatcher : public ModeWatcher
 +{
 + public:
 +      BanWatcher(Module* parent)
 +              : ModeWatcher(parent, "ban", MODETYPE_CHANNEL)
 +      {
 +      }
 +
 +      void AfterMode(User* source, User* dest, Channel* chan, const std::string& banmask, bool adding)
 +      {
 +              if (adding)
 +                      return;
 +
 +              irc::string listitem = banmask.c_str();
 +              irc::string thischan = chan->name.c_str();
 +              for (timedbans::iterator i = TimedBanList.begin(); i != TimedBanList.end(); ++i)
 +              {
 +                      irc::string target = i->mask.c_str();
 +                      irc::string tchan = i->channel.c_str();
 +                      if ((listitem == target) && (tchan == thischan))
 +                      {
 +                              TimedBanList.erase(i);
 +                              break;
 +                      }
 +              }
 +      }
 +};
 +
+ class ChannelMatcher
+ {
+       Channel* const chan;
+  public:
+       ChannelMatcher(Channel* ch)
+               : chan(ch)
+       {
+       }
+       bool operator()(const TimedBan& tb) const
+       {
+               return (tb.chan == chan);
+       }
+ };
  class ModuleTimedBans : public Module
  {
        CommandTban cmd;
                }
        }
  
 -      virtual Version GetVersion()
+       void OnChannelDelete(Channel* chan)
+       {
+               // Remove all timed bans affecting the channel from internal bookkeeping
+               TimedBanList.erase(std::remove_if(TimedBanList.begin(), TimedBanList.end(), ChannelMatcher(chan)), TimedBanList.end());
+       }
 +      Version GetVersion() CXX11_OVERRIDE
        {
                return Version("Adds timed bans", VF_COMMON | VF_VENDOR);
        }
index 1966c9b4729f86193e8c8fa87694a3bf9395c936,76446c5b57fb07371adaa9b6730e6f335d7e0805..4ebc3b5834be57142002bb52b983e6a579279b81
@@@ -90,17 -69,19 +90,17 @@@ void UserManager::AddUser(int socket, L
  
        /* The users default nick is their UUID */
        New->nick = New->uuid;
 -      (*(this->clientlist))[New->nick] = New;
 +      this->clientlist[New->nick] = New;
  
        New->registered = REG_NONE;
-       New->signon = ServerInstance->Time() + ServerInstance->Config->dns_timeout;
+       New->signon = ServerInstance->Time();
        New->lastping = 1;
  
 -      ServerInstance->Users->AddLocalClone(New);
 -      ServerInstance->Users->AddGlobalClone(New);
 +      this->AddClone(New);
  
 -      New->localuseriter = this->local_users.insert(local_users.end(), New);
 -      local_count++;
 +      this->local_users.push_front(New);
  
 -      if ((this->local_users.size() > ServerInstance->Config->SoftLimit) || (this->local_users.size() >= (unsigned int)ServerInstance->SE->GetMaxFds()))
 +      if (this->local_users.size() > ServerInstance->Config->SoftLimit)
        {
                ServerInstance->SNO->WriteToSnoMask('a', "Warning: softlimit value has been reached: %d clients", ServerInstance->Config->SoftLimit);
                this->QuitUser(New,"No more connections allowed");
@@@ -254,13 -287,74 +254,25 @@@ void UserManager::RemoveCloneCounts(Use
        }
  }
  
 -      local_clones.clear();
 -      global_clones.clear();
+ void UserManager::RehashCloneCounts()
+ {
 -      const user_hash& hash = *ServerInstance->Users->clientlist;
++      clonemap.clear();
 -
 -              if (IS_LOCAL(u))
 -                      AddLocalClone(u);
 -              AddGlobalClone(u);
++      const user_hash& hash = ServerInstance->Users.GetUsers();
+       for (user_hash::const_iterator i = hash.begin(); i != hash.end(); ++i)
+       {
+               User* u = i->second;
 -unsigned long UserManager::GlobalCloneCount(User *user)
++              AddClone(u);
+       }
+ }
 +const UserManager::CloneCounts& UserManager::GetCloneCounts(User* user) const
  {
 -      clonemap::iterator x = global_clones.find(user->GetCIDRMask());
 -      if (x != global_clones.end())
 -              return x->second;
 +      CloneMap::const_iterator it = clonemap.find(user->GetCIDRMask());
 +      if (it != clonemap.end())
 +              return it->second;
        else
 -              return 0;
 -}
 -
 -unsigned long UserManager::LocalCloneCount(User *user)
 -{
 -      clonemap::iterator x = local_clones.find(user->GetCIDRMask());
 -      if (x != local_clones.end())
 -              return x->second;
 -      else
 -              return 0;
 -}
 -
 -/* this function counts all users connected, wether they are registered or NOT. */
 -unsigned int UserManager::UserCount()
 -{
 -      /*
 -       * XXX: Todo:
 -       *  As part of this restructuring, move clientlist/etc fields into usermanager.
 -       *      -- w00t
 -       */
 -      return this->clientlist->size();
 -}
 -
 -/* this counts only registered users, so that the percentages in /MAP don't mess up */
 -unsigned int UserManager::RegisteredUserCount()
 -{
 -      return this->clientlist->size() - this->UnregisteredUserCount();
 -}
 -
 -/* return how many users are opered */
 -unsigned int UserManager::OperCount()
 -{
 -      return this->all_opers.size();
 -}
 -
 -/* return how many users are unregistered */
 -unsigned int UserManager::UnregisteredUserCount()
 -{
 -      return this->unregistered_count;
 -}
 -
 -/* return how many local registered users there are */
 -unsigned int UserManager::LocalUserCount()
 -{
 -      /* Doesnt count unregistered clients */
 -      return (this->local_count - this->UnregisteredUserCount());
 +              return zeroclonecounts;
  }
  
  void UserManager::ServerNoticeAll(const char* text, ...)
@@@ -287,78 -399,16 +299,83 @@@ void UserManager::GarbageCollect(
        }
  }
  
 +/* this returns true when all modules are satisfied that the user should be allowed onto the irc server
 + * (until this returns true, a user will block in the waiting state, waiting to connect up to the
 + * registration timeout maximum seconds)
 + */
 +bool UserManager::AllModulesReportReady(LocalUser* user)
 +{
 +      ModResult res;
 +      FIRST_MOD_RESULT(OnCheckReady, res, (user));
 +      return (res == MOD_RES_PASSTHRU);
 +}
  
 -/* return how many users have a given mode e.g. 'a' */
 -int UserManager::ModeCount(const char mode)
 +/**
 + * This function is called once a second from the mainloop.
 + * It is intended to do background checking on all the user structs, e.g.
 + * stuff like ping checks, registration timeouts, etc.
 + */
 +void UserManager::DoBackgroundUserStuff()
  {
 -      int c = 0;
 -      for(user_hash::iterator i = clientlist->begin(); i != clientlist->end(); ++i)
 +      /*
 +       * loop over all local users..
 +       */
 +      for (LocalList::iterator i = local_users.begin(); i != local_users.end(); ++i)
        {
 -              User* u = i->second;
 -              if (u->modes[mode-65])
 -                      c++;
 +              LocalUser* curr = *i;
 +
 +              if (curr->CommandFloodPenalty || curr->eh.getSendQSize())
 +              {
 +                      unsigned int rate = curr->MyClass->GetCommandRate();
 +                      if (curr->CommandFloodPenalty > rate)
 +                              curr->CommandFloodPenalty -= rate;
 +                      else
 +                              curr->CommandFloodPenalty = 0;
 +                      curr->eh.OnDataReady();
 +              }
 +
 +              switch (curr->registered)
 +              {
 +                      case REG_ALL:
 +                              if (ServerInstance->Time() >= curr->nping)
 +                              {
 +                                      // This user didn't answer the last ping, remove them
 +                                      if (!curr->lastping)
 +                                      {
 +                                              time_t time = ServerInstance->Time() - (curr->nping - curr->MyClass->GetPingTime());
 +                                              const std::string message = "Ping timeout: " + ConvToStr(time) + (time != 1 ? " seconds" : " second");
 +                                              this->QuitUser(curr, message);
 +                                              continue;
 +                                      }
 +
 +                                      curr->Write("PING :" + ServerInstance->Config->ServerName);
 +                                      curr->lastping = 0;
 +                                      curr->nping = ServerInstance->Time() + curr->MyClass->GetPingTime();
 +                              }
 +                              break;
 +                      case REG_NICKUSER:
 +                              if (AllModulesReportReady(curr))
 +                              {
 +                                      /* User has sent NICK/USER, modules are okay, DNS finished. */
 +                                      curr->FullConnect();
 +                                      continue;
 +                              }
++
++                              // If the user has been quit in OnCheckReady then we shouldn't
++                              // quit them again for having a registration timeout.
++                              if (curr->quitting)
++                                      continue;
 +                              break;
 +              }
 +
-               if (curr->registered != REG_ALL && (ServerInstance->Time() > (curr->age + curr->MyClass->GetRegTimeout())))
++              if (curr->registered != REG_ALL && curr->MyClass && (ServerInstance->Time() > (curr->signon + curr->MyClass->GetRegTimeout())))
 +              {
 +                      /*
 +                       * registration timeout -- didnt send USER/NICK/HOST
 +                       * in the time specified in their connection class.
 +                       */
 +                      this->QuitUser(curr, "Registration timeout");
 +                      continue;
 +              }
        }
 -      return c;
  }
index 975be5e2c7e9556b780f89aed38198f578f7f730,ba52ad5d2dec3a719ec7d408fd704ee8705005eb..06012b3f58ec0c88845da08e71f35a6389a5e35a
@@@ -22,9 -22,9 +22,9 @@@ BEGI
              VALUE "FileDescription", "InspIRCd"\r
              VALUE "FileVersion", "@FULL_VERSION@"\r
              VALUE "InternalName", "InspIRCd"\r
-             VALUE "LegalCopyright", "Copyright (c) 2014 InspIRCd Development Team"\r
+             VALUE "LegalCopyright", "Copyright (c) 2015 InspIRCd Development Team"\r
              VALUE "OriginalFilename", "inspircd.exe"\r
 -            VALUE "ProductName", "InspIRCd - The Inspire IRC Daemon"\r
 +            VALUE "ProductName", "InspIRCd - Internet Relay Chat Daemon"\r
              VALUE "ProductVersion", "@FULL_VERSION@"\r
          END\r
      END\r