]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Merge branch 'insp20' into master.
authorPeter Powell <petpow@saberuk.com>
Sun, 15 Jul 2018 15:27:48 +0000 (16:27 +0100)
committerPeter Powell <petpow@saberuk.com>
Sun, 15 Jul 2018 15:42:36 +0000 (16:42 +0100)
1  2 
README.md
docs/conf/helpop-full.conf.example
docs/conf/modules.conf.example
src/coremods/core_who.cpp
src/dynamic.cpp

diff --cc README.md
index 6e61106963590ab779cc11dbb4ee83226e46b63a,2e46cea37453b5417182289ea72ae20f2f69e336..8c491cda9f9d0405a9982f64c759e4275d7a9de0
+++ b/README.md
@@@ -1,28 -1,43 +1,49 @@@
- ### Important Notice
+ ## About
  
- The `master` branch contains the latest development version. If you are running
- a server then you probably want the `insp20` branch. You can obtain this from
- the [releases](https://github.com/inspircd/inspircd/releases) page or by running
- `git checkout insp20` if you are installing via Git.
+ InspIRCd is a modular C++ Internet Relay Chat (IRC) server for UNIX-like and Windows systems.
  
- ### About
+ ## Supported Platforms
  
- InspIRCd is a modular Internet Relay Chat (IRC) server written in C++ for Linux,
- BSD, Windows and Mac OS X systems which was created from scratch to be stable,
- modern and lightweight.
+ InspIRCd is supported on on the following platforms:
  
- As InspIRCd is one of the few IRC servers written from scratch, it avoids a
- number of design flaws and performance issues that plague other more established
- projects, such as UnrealIRCd, while providing the same level of feature parity.
 -- Most recent BSD variants using the Clang or GCC compilers and the BSD or GNU toolchains (Make, etc).
++- Most recent BSD variants using the Clang or GCC compilers and the GNU toolchains (Make, etc).
  
- InspIRCd is one of only a few IRC servers to provide a tunable number of
- features through the use of an advanced but well documented module system. By
- keeping core functionality to a minimum we hope to increase the stability,
- security and speed of InspIRCd while also making it customisable to the needs of
- many different users.
+ - Most recent Linux distributions using the Clang or GCC compilers and the GNU toolchain.
  
- ### Links
 -- The most recent three major releases of macOS using the AppleClang, Clang, or GCC (*not* LLVM-GCC) compilers and the BSD or GNU toolchains.
++- The most recent three major releases of macOS using the AppleClang, Clang, or GCC (*not* LLVM-GCC) compilers and the GNU toolchains.
  
- * [Website](http://inspircd.org)
 -- Windows 7 or newer using the MSVC 11 (Visual Studio 2012) compiler and CMake 2.8 or newer.
++- Windows 7 or newer using the MSVC 14 (Visual Studio 2015) compiler and CMake 2.8 or newer.
+ Alternate platforms and toolchains may also work but are not officially supported by the InspIRCd team. Generally speaking if you are using a reasonably modern UNIX-like system you should be able to build InspIRCd on it.
+ If you encounter any bugs then [please file an issue](https://github.com/inspircd/inspircd/issues/new).
+ ## Installation
++**The `master` branch contains the latest development version. If you are running a server then you probably want the `insp20` branch. You can obtain this from [the releases page](https://github.com/inspircd/inspircd/releases) or by running `git checkout insp20` if you are installing via Git.**
++
+ Most InspIRCd users running a UNIX-like system build from source. A guide about how to do this is available on [the InspIRCd wiki](https://wiki.inspircd.org/Installation_From_Source).
 -Building from source on Windows is generally not recommended but [a guide is available](https://github.com/inspircd/inspircd/blob/insp20/win/README.txt) if you wish to do this.
++Building from source on Windows is generally not recommended but [a guide is available](https://github.com/inspircd/inspircd/blob/master/win/README.txt) if you wish to do this.
++
++<!--
++TODO: uncomment this once we have binary packages for v3.
+ If you are running on CentOS 7, Debian 7, or Windows binary packages are available from [the downloads page](https://github.com/inspircd/inspircd/releases/latest).
+ A [Docker](https://www.docker.com) image is also available. See [the inspircd-docker repository](https://github.com/inspircd/inspircd-docker) for more information.
++-->
+ Some distributions ship an InspIRCd package in their package managers. We generally do not recommend the use of such packages as in the past distributions have made broken modifications to InspIRCd and not kept their packages up to date with essential security updates.
+ ## License
+ InspIRCd is licensed under [version 2 of the GNU General Public License](https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html).
+ ## External Links
+ * [Website](https://www.inspircd.org)
+ * [Documentation](https://wiki.inspircd.org)
  * [GitHub](https://github.com/inspircd)
- * IRC: \#inspircd on irc.inspircd.org
+ * [Support IRC channel](https://kiwiirc.com/nextclient/irc.inspircd.org:+6697/#inspircd) &mdash; \#inspircd on irc.inspircd.org
+ * [Development IRC channel](https://kiwiirc.com/nextclient/irc.inspircd.org:+6697/#inspircd.dev) &mdash; \#inspircd.dev on irc.inspircd.org
index 20e03d64a27000cdcc1e3077ffc5d31ceec3eff2,959f2249df37cb05a831524ffb266f5de9f94ffd..5705cad8ec78b55ffe63a50043a98926a2b6eb29
@@@ -226,49 -229,47 +226,84 @@@ given in the command and either the cha
  you are at least a halfoperator, the channel topic will be
  changed to the new one you provide.">
  
- <helpop key="who" value="/WHO <search pattern> [ohurmaiMplf]
 -<helpop key="who" value="/WHO <pattern> [afhilMmoprt]
++<helpop key="who" value="/WHO <pattern> [<flags>][%[<fields>[,<querytype>]]] <pattern>
  
--Looks up the information of users matching the range you provide.
- You may only /WHO nicknames in channels or on servers where you
- share a common channel with them, or ones which are not +i (unless
- you are an IRC operator). The search-pattern may be a special
- sequence of characters determined by the flags given below, or
- it may be one of a nickname, a channel, a hostmask, an ip address
- mask or a server mask.
++Looks up information about users matching the provided pattern. You can specify
++a flag specific pattern, a channel name, user hostname, a user server name, a
++user real name, or a user nickname. Matching users will only be included in the
++WHO response if:
 -You may only /WHO nicknames in channels or on servers where you
 -share a common channel with them, or ones which are not +i (unless
 -you are a server operator). The search pattern may be a special
 -sequence of characters determined by the flags given below, or
 -it may be one of a nickname, a channel, a hostmask, an IP address
 -mask or a server mask.
++ 1) The specified pattern is an exact channel name that does not have the
++    private or secret channel modes set and the user does not have the invisible
++    user mode set.
++ 2) The specified pattern is an exact nickname.
++ 3) You share one or more common channels with the user.
++ 4) The user does not have the invisible user mode set.
++ 5) You are a server operator with the users/auspex privilege.
++
++If you specify any fields the response returned will be a WHOX response rather
++than a RFC 1459 WHO response.
  
  Valid WHO Flags
  ---------------
  
- The following flags after the mask have the following effects:
-  o      Show online IRC operators matching the mask
-  a      Show all users who have an away message matching the given mask
-  i      Show all users who have an ident (username) matching the given mask
-  p      Show all users who are connected on the given port number (IRC
-         operators only)
-  r      Show all users whose real name match the mask. When this
-         flag is set it overrides the meaning of the search-pattern,
-         which must contain a glob pattern intended to match the
-         real name.
-  m      Search for all users with a given set of user modes. When
-         this flag is set it overrides the meaning of the
-         search-pattern, which must contain the mode sequence to
-         search for, for example to find all users with +i and
-         without +s, issue the command WHO +i-s m (IRC operators only)
-  t      Show users connected within this number of seconds
-  M      Show all users who have metadata attached to them with
-         the given key name (IRC operators only)
-  f      Show only remote (far) users
-  l      Show only local users
-  h      Show real hostnames rather than masked hostnames (IRC
-         operators only)
-  u      Unlimit the results past the maximum /who results value
-         (IRC operators only)
- You may combine multiple flags in one WHO command except where stated in the table above.">
+ The following flags use <pattern> to match against the specified user data:
 - a     Show users who have an away message matching <pattern>.
 - i     Show users who have an ident (username) matching <pattern>.
 - M     Show users who have metadata attached to them with a key name matching
 -       <pattern> (server operators only).
++ A     Show users who have an away message matching <pattern>.
++ a     Show users who have an account name matching <pattern>.
++ h     Show users who have a hostname matching <pattern>. If the 'x' modifier
++       is specified then this will match against the real hostname instead of
++       the display hostname.
++ i     Show users who have an IP address matching <pattern>.
+  m     Show users who have the modes listed in <pattern>. The pattern
+        should be in the same format as a mode change e.g. +ow-i (server
+        operators only).
++ n     Show users who have a nickname matching <pattern>.
+  p     Show users who are connected to a port in the <pattern> range (server
+        operators only).
+  r     Show users who have a real name matching <pattern>.
++ s     Show users who are on a server with a name matching <pattern>. If the 'x'
++       modifier is specified then this will match against the real server name
++       instead of the masked server name.
+  t     Show users who have connected in the last <pattern> seconds.
++ u     Show users who have an ident (username) matching <pattern>.
+ The following flags filter users by their status:
+  f     Only show users on remote (far) servers.
+  l     Only show users on the local server.
+  o     Only show server operators.
+ The following flags modify the command output:
 - h     Show real hostnames rather than display hostnames (server operators
 -       only).
++ x     Show sensitive data like real user hostnames and, when hideserver is
++       enabled, real server hostnames.
+ You may combine one flag from the first group and multiple from the others in
 -one WHO command.">
++one WHO command.
++
++Valid WHO Fields
++----------------
++
++ a     Include the user's account name in the response.
++ c     Include the first common channel name in the response.
++ d     Include the user's server distance from you in the response.
++ f     Include the user's away status, oper status, and highest channel prefix
++       in the response.
++ h     Include the user's hostname in the response. If the 'x' flag was
++       specified then this is the real host rather than the display host.
++ i     Include the user's IP address in the response.
++ l     Include the user's idle time in the response.
++ n     Include the user's nickname in the response.
++ o     Include the user's channel operator rank level in the response.
++ r     Include the user's real name in the response.
++ s     Include the user's server name in the response. If the 'x' flag was
++       specified then this is the real server name rather than the masked server
++       name.
++ t     Include the query type in the response.
++ u     Include the user's ident in the response.
++
++">
  
  <helpop key="motd" value="/MOTD [<server>]
  
index 5f219da76d92272057e9705554fa7b3861a6108d,a538ea879fe6618f936768ded4a7cfcf844744cd..1b35c4a1e548c167e3f437b85242457a98a00ea0
  #
  #-#-#-#-#-#-#-#-#-#-#-#-  HTTPD   CONFIGURATION  -#-#-#-#-#-#-#-#-#-#-#
  #
 -# If you choose to use the m_httpd.so module, then you will need to add
 +# If you choose to use the httpd module, then you will need to add
  # a <bind> tag with type "httpd", and load at least one of the other
 -# m_httpd_* modules to provide pages to display.
 +# httpd_* modules to provide pages to display.
+ # <bind address="127.0.0.1" port="8067" type="httpd">
+ # <bind address="127.0.0.1" port="8097" type="httpd" ssl="gnutls">
  #
  # You can adjust the timeout for HTTP connections below. All HTTP
 -# connections will be closed after (roughly) this many seconds.
 +# connections will be closed after (roughly) this time period.
  #<httpd timeout="20">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
  # <httpdacl path="/*" types="blacklist" blacklist="*">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
- # HTTP config module: Allows the configuration of the server to be
- # viewed over HTTP. Requires httpd to be loaded for it to function.
+ # HTTP config module: Allows the server configuration to be viewed over
 -# HTTP via the /config path. Requires m_httpd.so to be loaded for it to
 -# function.
++# HTTP via the /config path. Requires the httpd module to be loaded for
++# it to function.
+ #
+ # IMPORTANT: This module exposes extremely sensitive information about
+ # your server and users so you *MUST* protect it using a local-only
 -# <bind> tag and/or the m_httpd_acl.so module. See above for details.
 -#<module name="m_httpd_config.so">
++# <bind> tag and/or the httpd_acl module. See above for details.
 +#<module name="httpd_config">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
- # HTTP stats module: Provides basic stats pages over HTTP.
- # Requires httpd to be loaded for it to function.
+ # HTTP stats module: Provides server statistics over HTTP via the /stats
 -# path. Requires m_httpd.so to be loaded for it to function.
++# path. Requires the httpd module to be loaded for it to function.
+ #
+ # IMPORTANT: This module exposes extremely sensitive information about
+ # your server and users so you *MUST* protect it using a local-only
 -# <bind> tag and/or the m_httpd_acl.so module. See above for details.
 -#<module name="m_httpd_stats.so">
++# <bind> tag and/or the httpd_acl module. See above for details.
 +#<module name="httpd_stats">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
  # Ident: Provides RFC 1413 ident lookup support.
index 4757375c77b0750b8d1fe78d515501e994f3b9bc,0000000000000000000000000000000000000000..305733e03e90694bb2397a27a9c91b96b82166b2
mode 100644,000000..100644
--- /dev/null
@@@ -1,592 -1,0 +1,591 @@@
-       ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "MATCH: %u", match);
 +/*
 + * InspIRCd -- Internet Relay Chat Daemon
 + *
 + *   Copyright (C) 2018 Peter Powell <petpow@saberuk.com>
 + *   Copyright (C) 2014 Adam <Adam@anope.org>
 + *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
 + *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
 + *
 + * This file is part of InspIRCd.  InspIRCd is free software: you can
 + * redistribute it and/or modify it under the terms of the GNU General Public
 + * License as published by the Free Software Foundation, version 2.
 + *
 + * This program is distributed in the hope that it will be useful, but WITHOUT
 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 + * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 + * details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 + */
 +
 +
 +#include "inspircd.h"
 +#include "modules/account.h"
 +
 +enum
 +{
 +      // From RFC 1459.
 +      RPL_ENDOFWHO = 315,
 +      RPL_WHOREPLY = 352,
 +
 +      // From ircu.
 +      RPL_WHOSPCRPL = 354
 +};
 +
 +struct WhoData
 +{
 +      // The flags for matching users to include.
 +      std::bitset<UCHAR_MAX> flags;
 +
 +      // Whether we are matching using a wildcard or a flag.
 +      bool fuzzy_match;
 +
 +      // The text to match against.
 +      std::string matchtext;
 +
 +      // The WHO/WHOX responses we will send to the source.
 +      std::vector<Numeric::Numeric> results;
 +
 +      // Whether the source requested a WHOX response.
 +      bool whox;
 +
 +      // The fields to include in the WHOX response.
 +      std::bitset<UCHAR_MAX> whox_fields;
 +
 +      // A user specified label for the WHOX response.
 +      std::string whox_querytype;
 +
 +      WhoData(const std::vector<std::string>& parameters)
 +              : whox(false)
 +      {
 +              // Find the matchtext and swap the 0 for a * so we can use InspIRCd::Match on it.
 +              matchtext = parameters.size() > 2 ? parameters[2] : parameters[0];
 +              if (matchtext == "0")
 +                      matchtext = "*";
 +
 +              // Fuzzy matches are when the source has not specified a specific user.
 +              fuzzy_match = (parameters.size() > 1) || (matchtext.find_first_of("*?.") != std::string::npos);
 +
 +              // If flags have been specified by the source.
 +              if (parameters.size() > 1)
 +              {
 +                      std::bitset<UCHAR_MAX>* current_bitset = &flags;
 +                      for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter)
 +                      {
 +                              unsigned char chr = static_cast<unsigned char>(*iter);
 +
 +                              // If the source specifies a percentage the rest of the flags are WHOX fields.
 +                              if (chr == '%')
 +                              {
 +                                      whox = true;
 +                                      current_bitset = &whox_fields;
 +                                      continue;
 +                              }
 +
 +                              // If we are in WHOX mode and the source specifies a comma
 +                              // the rest of the parameter is the query type.
 +                              if (whox && chr == ',')
 +                              {
 +                                      whox_querytype.assign(++iter, parameters[1].end());
 +                                      break;
 +                              }
 +
 +                              // The source specified a matching flag.
 +                              current_bitset->set(chr);
 +                      }
 +              }
 +      }
 +};
 +
 +class CommandWho : public SplitCommand
 +{
 + private:
 +      ChanModeReference secretmode;
 +      ChanModeReference privatemode;
 +      UserModeReference hidechansmode;
 +      UserModeReference invisiblemode;
 +
 +      /** Determines whether a user can view the users of a channel. */
 +      bool CanView(Channel* chan, User* user)
 +      {
 +              // If we are in a channel we can view all users in it.
 +              if (chan->HasUser(user))
 +                      return true;
 +
 +              // Opers with the users/auspex priv can see everything.
 +              if (user->HasPrivPermission("users/auspex"))
 +                      return true;
 +
 +              // You can see inside a channel from outside unless it is secret or private.
 +              return !chan->IsModeSet(secretmode) && !chan->IsModeSet(privatemode);
 +      }
 +
 +      /** Gets the first channel which is visible between the source and the target users. */
 +      Membership* GetFirstVisibleChannel(LocalUser* source, User* user)
 +      {
 +              for (User::ChanList::iterator iter = user->chans.begin(); iter != user->chans.end(); ++iter)
 +              {
 +                      Membership* memb = *iter;
 +
 +                      // TODO: move the +I check into m_hidechans.
 +                      bool has_modes = memb->chan->IsModeSet(secretmode) || memb->chan->IsModeSet(privatemode) || user->IsModeSet(hidechansmode);
 +                      if (source == user || !has_modes || memb->chan->HasUser(source))
 +                              return memb;
 +              }
 +              return NULL;
 +      }
 +
 +      /** Determines whether WHO flags match a specific channel user. */
 +      static bool MatchChannel(LocalUser* source, Membership* memb, WhoData& data);
 +
 +      /** Determines whether WHO flags match a specific user. */
 +      static bool MatchUser(LocalUser* source, User* target, WhoData& data);
 +
 +      /** Performs a WHO request on a channel. */
 +      void WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* c, WhoData& data);
 +
 +      /** Template for getting a user from various types of collection. */
 +      template<typename T>
 +      static User* GetUser(T& t);
 +
 +      /** Performs a WHO request on a list of users. */
 +      template<typename T>
 +      void WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data);
 +
 + public:
 +      CommandWho(Module* parent)
 +              : SplitCommand(parent, "WHO", 1, 3)
 +              , secretmode(parent, "secret")
 +              , privatemode(parent, "private")
 +              , hidechansmode(parent, "hidechans")
 +              , invisiblemode(parent, "invisible")
 +      {
 +              allow_empty_last_param = false;
 +              syntax = "<server>|<nickname>|<channel>|<realname>|<host>|0 [[Aafhilmnoprstu] <server>|<nickname>|<channel>|<realname>|<host>|0]";
 +      }
 +
 +      /** Sends a WHO reply to a user. */
 +      void SendWhoLine(LocalUser* user, const std::vector<std::string>& parameters, Membership* memb, User* u, WhoData& data);
 +
 +      CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
 +};
 +
 +template<> User* CommandWho::GetUser(UserManager::OperList::const_iterator& t) { return *t; }
 +template<> User* CommandWho::GetUser(user_hash::const_iterator& t) { return t->second; }
 +
 +bool CommandWho::MatchChannel(LocalUser* source, Membership* memb, WhoData& data)
 +{
 +      bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
 +      bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
 +
 +      // The source only wants remote users. This user is eligible if:
 +      //   (1) The source can't see server information.
 +      //   (2) The source is not local to the current server.
 +      LocalUser* lu = IS_LOCAL(memb->user);
 +      if (data.flags['f'] && source_can_see_server && lu)
 +              return false;
 +
 +      // The source only wants local users. This user is eligible if:
 +      //   (1) The source can't see server information.
 +      //   (2) The source is local to the current server.
 +      if (data.flags['l'] && source_can_see_server && !lu)
 +              return false;
 +
 +      // Only show operators if the oper flag has been specified.
 +      if (data.flags['o'] && !memb->user->IsOper())
 +              return false;
 +
 +      // All other flags are ignored for channels.
 +      return true;
 +}
 +
 +bool CommandWho::MatchUser(LocalUser* source, User* user, WhoData& data)
 +{
 +      // Users who are not fully registered can never match.
 +      if (user->registered != REG_ALL)
 +              return false;
 +
 +      bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
 +      bool source_can_see_target = source == user || source_has_users_auspex;
 +      bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
 +
 +      // The source only wants remote users. This user is eligible if:
 +      //   (1) The source can't see server information.
 +      //   (2) The source is not local to the current server.
 +      LocalUser* lu = IS_LOCAL(user);
 +      if (data.flags['f'] && source_can_see_server && lu)
 +              return false;
 +
 +      // The source only wants local users. This user is eligible if:
 +      //   (1) The source can't see server information.
 +      //   (2) The source is local to the current server.
 +      if (data.flags['l'] && source_can_see_server && !lu)
 +              return false;
 +
 +      // The source wants to match against users' away messages.
 +      bool match = false;
 +      if (data.flags['A'])
 +              match = user->IsAway() && InspIRCd::Match(user->awaymsg, data.matchtext, ascii_case_insensitive_map);
 +
 +      // The source wants to match against users' account names.
 +      else if (data.flags['a'])
 +      {
 +              const AccountExtItem* accountext = GetAccountExtItem();
 +              const std::string* account = accountext ? accountext->get(user) : NULL;
 +              match = account && InspIRCd::Match(*account, data.matchtext);
 +      }
 +
 +      // The source wants to match against users' hostnames.
 +      else if (data.flags['h'])
 +      {
 +              const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
 +              match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
 +      }
 +
 +      // The source wants to match against users' IP addresses.
 +      else if (data.flags['i'])
 +              match = source_can_see_target && InspIRCd::MatchCIDR(user->GetIPString(), data.matchtext, ascii_case_insensitive_map);
 +
 +      // The source wants to match against users' modes.
 +      else if (data.flags['m'])
 +      {
 +              if (source_can_see_target)
 +              {
 +                      bool set = true;
 +                      for (std::string::const_iterator iter = data.matchtext.begin(); iter != data.matchtext.end(); ++iter)
 +                      {
 +                              unsigned char chr = static_cast<unsigned char>(*iter);
 +                              switch (chr)
 +                              {
 +                                      // The following user modes should be set.
 +                                      case '+':
 +                                              set = true;
 +                                              break;
 +
 +                                      // The following user modes should be unset.
 +                                      case '-':
 +                                              set = false;
 +                                              break;
 +
 +                                      default:
 +                                              if (user->IsModeSet(chr) != set)
 +                                                      return false;
 +                                              break;
 +                              }
 +                      }
 +
 +                      // All of the modes matched.
 +                      return true;
 +              }
 +      }
 +
 +      // The source wants to match against users' nicks.
 +      else if (data.flags['n'])
 +              match = InspIRCd::Match(user->nick, data.matchtext);
 +
 +      // The source wants to match against users' connection ports.
 +      else if (data.flags['p'])
 +      {
 +              if (source_can_see_target && lu)
 +              {
 +                      irc::portparser portrange(data.matchtext, false);
 +                      long port;
 +                      while ((port = portrange.GetToken()))
 +                      {
 +                              if (port == lu->GetServerPort())
 +                              {
 +                                      match = true;
 +                                      break;
 +                              }
 +                      }
 +              }
 +      }
 +
 +      // The source wants to match against users' real names.
 +      else if (data.flags['r'])
 +              match = InspIRCd::Match(user->fullname, data.matchtext, ascii_case_insensitive_map);
 +
 +      else if (data.flags['s'])
 +      {
 +              bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
 +              const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
 +              match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
 +      }
 +
 +      // The source wants to match against users' connection times.
 +      else if (data.flags['t'])
 +      {
 +              time_t seconds = ServerInstance->Time() - InspIRCd::Duration(data.matchtext);
 +              if (user->signon >= seconds)
 +                      match = true;
 +      }
 +
 +      // The source wants to match against users' idents.
 +      else if (data.flags['u'])
 +              match = InspIRCd::Match(user->ident, data.matchtext, ascii_case_insensitive_map);
 +
 +      // The <name> passed to WHO is matched against users' host, server,
 +      // real name and nickname if the channel <name> cannot be found.
 +      else
 +      {
 +              const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
 +              match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
 +
 +              if (!match)
 +              {
 +                      bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
 +                      const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
 +                      match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
 +              }
 +
 +              if (!match)
 +                      match = InspIRCd::Match(user->fullname, data.matchtext, ascii_case_insensitive_map);
 +
 +              if (!match)
 +                      match = InspIRCd::Match(user->nick, data.matchtext);
 +      }
 +
 +      return match;
 +}
 +
 +void CommandWho::WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* chan, WhoData& data)
 +{
 +      if (!CanView(chan, source))
 +              return;
 +
 +      bool inside = chan->HasUser(source);
 +      const Channel::MemberMap& users = chan->GetUsers();
 +      for (Channel::MemberMap::const_iterator iter = users.begin(); iter != users.end(); ++iter)
 +      {
 +              User* user = iter->first;
 +              Membership* memb = iter->second;
 +
 +              // Only show invisible users if the source is in the channel or has the users/auspex priv.
 +              if (!inside && user->IsModeSet(invisiblemode) && !source->HasPrivPermission("users/auspex"))
 +                      continue;
 +
 +              // Skip the user if it doesn't match the query.
 +              if (!MatchChannel(source, memb, data))
 +                      continue;
 +
 +              SendWhoLine(source, parameters, memb, user, data);
 +      }
 +}
 +
 +template<typename T>
 +void CommandWho::WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data)
 +{
 +      for (typename T::const_iterator iter = users.begin(); iter != users.end(); ++iter)
 +      {
 +              User* user = GetUser(iter);
 +
 +              // Only show users in response to a fuzzy WHO if we can see them normally.
 +              bool can_see_normally = user == source || source->SharesChannelWith(user) || !user->IsModeSet(invisiblemode);
 +              if (data.fuzzy_match && !can_see_normally && !source->HasPrivPermission("users/auspex"))
 +                      continue;
 +
 +              // Skip the user if it doesn't match the query.
 +              if (!MatchUser(source, user, data))
 +                      continue;
 +
 +              SendWhoLine(source, parameters, NULL, user, data);
 +      }
 +}
 +
 +void CommandWho::SendWhoLine(LocalUser* source, const std::vector<std::string>& parameters, Membership* memb, User* user, WhoData& data)
 +{
 +      if (!memb)
 +              memb = GetFirstVisibleChannel(source, user);
 +
 +      bool source_can_see_target = source == user || source->HasPrivPermission("users/auspex");
 +      Numeric::Numeric wholine(data.whox ? RPL_WHOSPCRPL : RPL_WHOREPLY);
 +      if (data.whox)
 +      {
 +              // The source used WHOX so we send a fancy customised response.
 +
 +              // Include the query type in the reply.
 +              if (data.whox_fields['t'])
 +                      wholine.push(data.whox_querytype.empty() || data.whox_querytype.length() > 3 ? "0" : data.whox_querytype);
 +
 +              // Include the first channel name.
 +              if (data.whox_fields['c'])
 +                      wholine.push(memb ? memb->chan->name : "*");
 +
 +              // Include the user's ident.
 +              if (data.whox_fields['u'])
 +                      wholine.push(user->ident);
 +
 +              // Include the user's IP address.
 +              if (data.whox_fields['i'])
 +                      wholine.push(source_can_see_target ? user->GetIPString() : "255.255.255.255");
 +
 +              // Include the user's hostname.
 +              if (data.whox_fields['h'])
 +                      wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
 +
 +              // Include the server name.
 +              if (data.whox_fields['s'])
 +              {
 +                      if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
 +                              wholine.push(user->server->GetName());
 +                      else
 +                              wholine.push(ServerInstance->Config->HideServer);
 +              }
 +
 +              // Include the user's nickname.
 +              if (data.whox_fields['n'])
 +                      wholine.push(user->nick);
 +
 +              // Include the user's flags.
 +              if (data.whox_fields['f'])
 +              {
 +                      // Away state.
 +                      std::string flags(user->IsAway() ? "G" : "H");
 +
 +                      // Operator status.
 +                      if (user->IsOper())
 +                              flags.push_back('*');
 +
 +                      // Membership prefix.
 +                      if (memb)
 +                      {
 +                              char prefix = memb->GetPrefixChar();
 +                              if (prefix)
 +                                      flags.push_back(prefix);
 +                      }
 +
 +                      wholine.push(flags);
 +              }
 +
 +              // Include the number of hops between the users.
 +              if (data.whox_fields['d'])
 +                      wholine.push("0");
 +
 +              // Include the user's idle time.
 +              if (data.whox_fields['l'])
 +              {
 +                      LocalUser* lu = IS_LOCAL(user);
 +                      unsigned long idle = lu ? ServerInstance->Time() - lu->idle_lastmsg : 0;
 +                      wholine.push(ConvToStr(idle));
 +              }
 +
 +              // Include the user's account name.
 +              if (data.whox_fields['a'])
 +              {
 +                      const AccountExtItem* accountext = GetAccountExtItem();
 +                      const std::string* account = accountext ? accountext->get(user) : NULL;
 +                      wholine.push(account ? *account : "0");
 +              }
 +
 +              // Include the user's operator rank level.
 +              if (data.whox_fields['o'])
 +                      wholine.push(memb ? ConvToStr(memb->getRank()) : "0");
 +
 +              // Include the user's real name.
 +              if (data.whox_fields['r'])
 +                      wholine.push(user->fullname);
 +      }
 +      else
 +      {
 +              // We are not using WHOX so we just send a plain RFC response.
 +
 +              // Include the channel name.
 +              wholine.push(memb ? memb->chan->name : "*");
 +
 +              // Include the user's ident.
 +              wholine.push(user->ident);
 +
 +              // Include the user's hostname.
 +              wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
 +
 +              // Include the server name.
 +              if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
 +                      wholine.push(user->server->GetName());
 +              else
 +                      wholine.push(ServerInstance->Config->HideServer);
 +
 +              // Include the user's nick.
 +              wholine.push(user->nick);
 +
 +              // Include the user's flags.
 +              {
 +                      // Away state.
 +                      std::string flags(user->IsAway() ? "G" : "H");
 +
 +                      // Operator status.
 +                      if (user->IsOper())
 +                              flags.push_back('*');
 +
 +                      // Membership prefix.
 +                      if (memb)
 +                      {
 +                              char prefix = memb->GetPrefixChar();
 +                              if (prefix)
 +                                      flags.push_back(prefix);
 +                      }
 +
 +                      wholine.push(flags);
 +              }
 +
 +              // Include the number of hops between the users and the user's real name.
 +              wholine.push("0 ");
 +              wholine.GetParams().back().append(user->fullname);
 +      }
 +
 +      ModResult res;
 +      FIRST_MOD_RESULT(OnSendWhoLine, res, (source, parameters, user, memb, wholine));
 +      if (res != MOD_RES_DENY)
 +              data.results.push_back(wholine);
 +}
 +
 +CmdResult CommandWho::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
 +{
 +      WhoData data(parameters);
 +
 +      // Is the source running a WHO on a channel?
 +      Channel* chan = ServerInstance->FindChan(data.matchtext);
 +      if (chan)
 +              WhoChannel(user, parameters, chan, data);
 +
 +      // If we only want to match against opers we only have to iterate the oper list.
 +      else if (data.flags['o'])
 +              WhoUsers(user, parameters, ServerInstance->Users->all_opers, data);
 +
 +      // Otherwise we have to use the global user list.
 +      else
 +              WhoUsers(user, parameters, ServerInstance->Users->GetUsers(), data);
 +
 +      // Send the results to the source.
 +      for (std::vector<Numeric::Numeric>::const_iterator n = data.results.begin(); n != data.results.end(); ++n)
 +              user->WriteNumeric(*n);
 +      user->WriteNumeric(RPL_ENDOFWHO, (data.matchtext.empty() ? "*" : data.matchtext.c_str()), "End of /WHO list.");
 +
 +      // Penalize the source a bit for large queries with one unit of penalty per 200 results.
 +      user->CommandFloodPenalty += data.results.size() * 5;
 +      return CMD_SUCCESS;
 +}
 +
 +class CoreModWho : public Module
 +{
 + private:
 +      CommandWho cmd;
 +
 + public:
 +      CoreModWho()
 +              : cmd(this)
 +      {
 +      }
 +
 +      void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
 +      {
 +              tokens["WHOX"];
 +      }
 +
 +      Version GetVersion() CXX11_OVERRIDE
 +      {
 +              return Version("Provides the WHO command", VF_VENDOR|VF_CORE);
 +      }
 +};
 +
 +MODULE_INIT(CoreModWho)
diff --cc src/dynamic.cpp
Simple merge