]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Merge insp20
authorAttila Molnar <attilamolnar@hush.com>
Wed, 17 Aug 2016 10:49:48 +0000 (12:49 +0200)
committerAttila Molnar <attilamolnar@hush.com>
Wed, 17 Aug 2016 10:49:48 +0000 (12:49 +0200)
20 files changed:
1  2 
docs/conf/inspircd.conf.example
include/configreader.h
include/inspircd.h
make/template/inspircd
make/template/main.mk
src/configreader.cpp
src/coremods/core_list.cpp
src/coremods/core_oper/cmd_kill.cpp
src/coremods/core_stats.cpp
src/inspircd.cpp
src/mode.cpp
src/modules/extra/m_ssl_gnutls.cpp
src/modules/extra/m_ssl_openssl.cpp
src/modules/m_censor.cpp
src/modules/m_dccallow.cpp
src/modules/m_sasl.cpp
src/modules/m_securelist.cpp
src/modules/m_spanningtree/postcommand.cpp
src/usermanager.cpp
tools/travis-ci.sh

index 16c34cc249824d6d95972cdae10d4d79190a4183,7099cefe262a6a7c4b53e4c7ca6dfa6696c1a9fb..d56ac55ac22075359a44b4d55671f0610606a613
  #                                                                      #
  ########################################################################
  
 +#-#-#-#-#-#-#-#-#-#  CONFIGURATION FORMAT  #-#-#-#-#-#-#-#-#-#-#-#-#-#-
 +#                                                                     #
 +# In order to maintain compatibility with older configuration files,  #
 +# you can change the configuration parser to parse as it did in       #
 +# previous releases. When using the "compat" format, you need to use  #
 +# C++ escape sequences (e.g. \n) instead of XML ones (e.g. &nl;) and  #
 +# can not use <define> to create macros.                              #
 +#<config format="compat">
 +
  #-#-#-#-#-#-#-#-#-#  INCLUDE CONFIGURATION  #-#-#-#-#-#-#-#-#-#-#-#-#-#
  #                                                                     #
  # This optional tag allows you to include another config file         #
  #                                                                     #
  # Variables may be redefined and may reference other variables.       #
  # Value expansion happens at the time the tag is read.                #
 -#                                                                     #
 -# Using variable definitions REQUIRES that the config format be       #
 -# changed to "xml" from the default "compat" that uses escape         #
 -# sequences such as "\"" and "\n", and does not support <define>      #
 -<config format="xml">
  <define name="bindip" value="1.2.2.3">
  <define name="localips" value="&bindip;/24">
  
  #   |_| \_\___|\__,_|\__,_|   |_| |_| |_|_|___/ |____/|_|\__(_)       #
  #                                                                     #
  #  If you want to link servers to InspIRCd you must load the          #
 -#  m_spanningtree.so module! Please see the modules list for          #
 +#  spanningtree module! Please see the modules list for               #
  #  information on how to load this module! If you do not load this    #
  #  module, server ports will NOT work!                                #
  
        # loaded for SSL to work. If you do not want the port(s) in this bind
        # tag to support SSL, just remove or comment out this option.
        ssl="gnutls"
 +
 +      # defer: When this is non-zero, connections will not be handed over to
 +      # the daemon from the operating system before data is ready.
 +      # In Linux, the value indicates the number of seconds we'll wait for a
 +      # connection to come up with data. Don't set it too low!
 +      # In BSD the value is ignored; only zero and non-zero is possible.
 +      # Windows ignores this parameter completely.
 +      # Note: This does not take effect on rehash.
 +      # To change it on a running bind, you'll have to comment it out,
 +      # rehash, comment it in and rehash again.
 +      defer="0"
  >
  
  <bind address="" port="6660-6669" type="clients">
  
 +# Listener accepting HTML5 WebSocket connections.
 +# Requires the websocket module and SHA-1 hashing support (provided by the sha1
 +# module).
 +#<bind address="" port="7002" type="clients" hook="websocket">
 +
  # When linking servers, the OpenSSL and GnuTLS implementations are completely
  # link-compatible and can be used alongside each other
  # on each end of the link without any significant issues.
  # Supported SSL types are: "openssl" and "gnutls".
 -# You must load m_ssl_openssl for OpenSSL or m_ssl_gnutls for GnuTLS.
 +# You must load the ssl_openssl module for OpenSSL or ssl_gnutls for GnuTLS.
  
  <bind address="" port="7000,7001" type="servers">
  <bind address="1.2.3.4" port="7005" type="servers" ssl="openssl">
  
  <power
         # hash: what hash these passwords are hashed with.
 -       # Requires the module for selected hash (m_md5.so, m_sha256.so
 -       # or m_ripemd160.so) be loaded and the password hashing module
 -       # (m_password_hash.so) loaded.
 +       # Requires the module for selected hash (md5, sha256, or
 +       # ripemd160) be loaded and the password hashing module
 +       # (password_hash) loaded.
         # Options here are: "md5", "sha256" and "ripemd160", or one of
         # these prefixed with "hmac-", e.g.: "hmac-sha256".
         # Optional, but recommended. Create hashed passwords with:
           allow="203.0.113.*"
  
           # hash: what hash this password is hashed with. requires the module
 -         # for selected hash (m_md5.so, m_sha256.so or m_ripemd160.so) be
 -         # loaded and the password hashing module (m_password_hash.so)
 -         # loaded. Options here are: "md5", "sha256" and "ripemd160".
 +         # for selected hash (md5, sha256 or ripemd160) be loaded and the
 +         # password hashing module (password_hash) loaded.
           # Optional, but recommended. Create hashed passwords with:
           # /mkpasswd <hash> <password>
           #hash="sha256"
           password="secret"
  
           # maxchans: Maximum number of channels a user in this class
 -         # be in at one time. This overrides every other maxchans setting.
 -         #maxchans="30"
 +         # be in at one time.
 +         maxchans="20"
  
           # timeout: How long (in seconds) the server will wait before
           # disconnecting a user if they do not do anything on connect.
           # 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.
 +         # This setting only has effect when the dnsbl module is loaded.
           #usednsbl="yes"
  
           # useident: Defines if users in this class MUST respond to a ident query or not.
           limit="5000"
  
           # modes: Usermodes that are set on users in this block on connect.
 -         # Enabling this option requires that the m_conn_umodes module be loaded.
 +         # Enabling this option requires that the conn_umodes module be loaded.
           # This entry is highly recommended to use for/with IP Cloaking/masking.
 -         # For the example to work, this also requires that the m_cloaking
 +         # For the example to work, this also requires that the "cloaking"
           # module be loaded as well.
           modes="+x"
  
           # requireident, requiressl, requireaccount: require that users of this
           # block have a valid ident response, use SSL, or have authenticated.
 -         # Requires m_ident, m_sslinfo, or m_services_account respectively.
 +         # Requires ident, sslinfo, or the services_account module, respectively.
           requiressl="on"
           # NOTE: For requireaccount, you must complete the signon prior to full
           # connection. Currently, this is only possible by using SASL
           allow="*"
  
           # maxchans: Maximum number of channels a user in this class
 -         # be in at one time. This overrides every other maxchans setting.
 -         #maxchans="30"
 +         # be in at one time.
 +         maxchans="20"
  
           # timeout: How long (in seconds) the server will wait before
           # disconnecting a user if they do not do anything on connect.
           # globalmax: Maximum global (network-wide) connections per IP.
           globalmax="3"
  
 +         # 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"
 +
           # useident: Defines if users in this class must respond to a ident query or not.
           useident="no"
  
           limit="5000"
  
           # modes: Usermodes that are set on users in this block on connect.
 -         # Enabling this option requires that the m_conn_umodes module be loaded.
 +         # Enabling this option requires that the conn_umodes module be loaded.
           # This entry is highly recommended to use for/with IP Cloaking/masking.
 -         # For the example to work, this also requires that the m_cloaking
 +         # For the example to work, this also requires that the cloaking
           # module be loaded as well.
           modes="+x">
  
  
  # This file has all the information about oper classes, types and o:lines.
  # You *MUST* edit it.
 -<include file="conf/examples/opers.conf.example">
 +<include file="examples/opers.conf.example">
  
  # This file has all the information about server links and ulined servers.
  # You *MUST* edit it if you intend to link servers.
 -<include file="conf/examples/links.conf.example">
 +<include file="examples/links.conf.example">
  
  #-#-#-#-#-#-#-#-#-#-  MISCELLANEOUS CONFIGURATION  -#-#-#-#-#-#-#-#-#-#
  #                                                                     #
  # Files block - contains files whose contents are used by the ircd
  #
  #   motd - displayed on connect and when a user executes /MOTD
 -#   rules - displayed when the user executes /RULES
  # Modules can also define their own files
 -<files motd="conf/examples/motd.txt.example" rules="conf/examples/rules.txt.example">
 +<files motd="examples/motd.txt.example">
  
  # Example of an executable file include. Note this will be read on rehash,
  # not when the command is run.
 -#<execfiles rules="wget -O - http://www.example.com/rules.txt">
 -
 -#-#-#-#-#-#-#-#-#-#-#-# MAXIMUM CHANNELS -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
 -#                                                                     #
 -
 -<channels
 -          # users: Maximum number of channels a user can be in at once.
 -          users="20"
 -
 -          # opers: Maximum number of channels an oper can be in at once.
 -          opers="60">
 +#<execfiles motd="wget -O - http://www.example.com/motd.txt">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-# DNS SERVER -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
  # If these values are not defined, InspIRCd uses the default DNS resolver
           # the correct parameters are.
           syntaxhints="no"
  
 -         # cyclehosts: If enabled, when a user gets a host set, it will cycle
 -         # them in all their channels. If not, it will simply change their host
 -         # without cycling them.
 -         cyclehosts="yes"
 -
           # cyclehostsfromuser: If enabled, the source of the mode change for
           # cyclehosts will be the user who cycled. This can look nicer, but
           # triggers anti-takeover mechanisms of some obsolete bots.
           cyclehostsfromuser="no"
  
 -         # ircumsgprefix: Use undernet-style message prefixing for NOTICE and
 -         # PRIVMSG. If enabled, it will add users' prefix to the line, if not,
 -         # it will just message the user normally.
 -         ircumsgprefix="no"
 -
           # announcets: If set to yes, when the timestamp on a channel changes, all users
           # in the channel will be sent a NOTICE about it.
           announcets="yes"
  
           # defaultmodes: What modes are set on a empty channel when a user
           # joins it and it is unregistered.
 -         defaultmodes="nt"
 +         defaultmodes="not"
  
 -         # moronbanner: This is the text that is sent to a user when they are
 +         # xlinemessage: This is the text that is sent to a user when they are
           # banned from the server.
 -         moronbanner="You're banned! Email abuse@example.com with the ERROR line below for help."
 +         xlinemessage="You're banned! Email irc@example.com with the ERROR line below for help."
  
           # exemptchanops: exemptions for channel access restrictions based on prefix.
           exemptchanops="nonick:v flood:o"
  
           # nosnoticestack: This prevents snotices from 'stacking' and giving you
           # the message saying '(last message repeated X times)'. Defaults to no.
 -         nosnoticestack="no"
 -
 -         # welcomenotice: When turned on, this sends a NOTICE to connecting users
 -         # with the text Welcome to <networkname>! after successful registration.
 -         # Defaults to yes.
 -         welcomenotice="yes">
 +         nosnoticestack="no">
  
  
  #-#-#-#-#-#-#-#-#-#-#-# PERFORMANCE CONFIGURATION #-#-#-#-#-#-#-#-#-#-#
               # in the accept queue. This is *NOT* the total maximum number of
               # connections per server. Some systems may only allow this to be up
               # to 5, while others (such as Linux and *BSD) default to 128.
 +             # Setting this above the limit imposed by your OS can have undesired
 +             # effects.
               somaxconn="128"
  
 -             # limitsomaxconn: By default, somaxconn (see above) is limited to a
 -             # safe maximum value in the 2.0 branch for compatibility reasons.
 -             # This setting can be used to disable this limit, forcing InspIRCd
 -             # to use the value specified above.
 -             limitsomaxconn="true"
 -
               # softlimit: This optional feature allows a defined softlimit for
               # connections. If defined, it sets a soft max connections value.
               softlimit="12800"
  
 +             # clonesonconnect: If this is set to false, we won't check for clones
 +             # on initial connection, but only after the DNS check is done.
 +             # This can be useful where your main class is more restrictive
 +             # than some other class a user can be assigned after DNS lookup is complete.
 +             # Turning this option off will make the server spend more time on users we may
 +             # potentially not want. Normally this should be neglible, though.
 +             # Default value is true
 +             clonesonconnect="true"
 +
               # quietbursts: When syncing or splitting from a network, a server
               # can generate a lot of connect and quit messages to opers with
               # +C and +Q snomasks. Setting this to yes squelches those messages,
               # which makes it easier for opers, but degrades the functionality of
               # bots like BOPM during netsplits.
 -             quietbursts="yes"
 -
 -             # nouserdns: If enabled, no DNS lookups will be performed on
 -             # connecting users. This can save a lot of resources on very busy servers.
 -             nouserdns="no">
 +             quietbursts="yes">
  
  #-#-#-#-#-#-#-#-#-#-#-# SECURITY CONFIGURATION  #-#-#-#-#-#-#-#-#-#-#-#
  #                                                                     #
  
  <security
 +          # allowcoreunload: If this value is set to yes, Opers will be able to
 +          # unload core modules (e.g. cmd_privmsg.so).
 +          allowcoreunload="no"
  
            # announceinvites: This option controls which members of the channel
            # receive an announcement when someone is INVITEd. Available values:
            #             higher ranked users. This is the recommended setting.
            announceinvites="dynamic"
  
 -          # hidemodes: If enabled, then the listmodes given will be hidden
 -          # from users below halfop. This is not recommended to be set on +b
 -          # as it may break some functionality in popular clients such as mIRC.
 -          hidemodes="eI"
 -
            # hideulines: If this value is set to yes, U-lined servers will
            # be hidden from non-opers in /links and /map.
            hideulines="no"
            # hidekills: If defined, replaces who set a /kill with a custom string.
            hidekills=""
  
+           # hideulinekills: Hide kills from clients of ulined servers from server notices.
+           hideulinekills="yes"
            # hidesplits: If enabled, non-opers will not be able to see which
            # servers split in a netsplit, they will only be able to see that one
            # occurred (If their client has netsplit detection).
            # (Commands like /notice, /privmsg, /kick, etc)
            maxtargets="20"
  
 -          # customversion: Displays a custom string when a user /version's
 -          # the ircd. This may be set for security reasons or vanity reasons.
 +          # customversion: A custom message to be displayed in the comments field
 +          # of the VERSION command response. This does not hide the InspIRCd version.
            customversion=""
  
            # operspywhois: show opers (users/auspex) the +s channels a user is in. Values:
          # maxident: Maximum length of a ident/username.
          maxident="11"
  
 +        # maxhost: Maximum length of a hostname.
 +        maxhost="64"
 +
          # maxquit: Maximum length of a quit message.
          maxquit="255"
  
          # maxaway: Maximum length of an away message.
          maxaway="200">
  
 +#-#-#-#-#-#-#-#-#-#-#-#-# PATHS CONFIGURATION #-#-#-#-#-#-#-#-#-#-#-#-#
 +#                                                                     #
 +# This configuration tag defines the location that InspIRCd stores    #
 +# various types of files such as configuration files, log files and   #
 +# modules. You will probably not need to change these from the values #
 +# set when InspIRCd was built unless you are using a binary package   #
 +# where you do not have the ability to set build time configuration.  #
 +#<path configdir="conf" datadir="data" logdir="logs" moduledir="modules">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
  # Logging
  # to do what they want.
  #
  # An example log tag would be:
 -#  <log method="file" type="OPER" level="default" target="logs/opers.log">
 +#  <log method="file" type="OPER" level="default" target="opers.log">
  # which would log all information on /oper (failed and successful) to
  # a file called opers.log.
  #
  #  - OPER - succesful and failed oper attempts
  #  - KILL - kill related messages
  #  - snomask - server notices (*all* snomasks will be logged)
 -#  - FILTER - messages related to filter matches (m_filter)
 +#  - FILTER - messages related to filter matches (filter module)
  #  - CONFIG - configuration related messages
  #  - COMMAND - die and restart messages, and messages related to unknown user types
  #  - SOCKET - socket engine informational/error messages
  # The following log tag is highly default and uncustomised. It is recommended you
  # sort out your own log tags. This is just here so you get some output.
  
 -<log method="file" type="* -USERINPUT -USEROUTPUT" level="default" target="logs/ircd.log">
 +<log method="file" type="* -USERINPUT -USEROUTPUT" level="default" target="ircd.log">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-  WHOWAS OPTIONS   -#-#-#-#-#-#-#-#-#-#-#-#-#
  #                                                                     #
  # provide almost all the features of InspIRCd. :)                     #
  #                                                                     #
  # The default does nothing -- we include it for simplicity for you.   #
 -<include file="conf/examples/modules.conf.example">
 +<include file="examples/modules.conf.example">
  
  # Here are some pre-built modules.conf files that closely match the
  # default configurations of some popular IRCd's. You still may want to
  # recommended that you make your own modules file based on modules.conf.example.
  
  # Settings similar to UnrealIRCd defaults.
 -#<include file="conf/examples/modules/unrealircd.conf.example">
 +#<include file="examples/modules/unrealircd.conf.example">
  
  # Settings similar to Charybdis IRCd defaults.
 -#<include file="conf/examples/modules/charybdis.conf.example">
 +#<include file="examples/modules/charybdis.conf.example">
  
  
  #########################################################################
diff --combined include/configreader.h
index f2ba16902293cf9a32b1057062c2ccb2da8162a9,4a697d05c314a1edbe5de12ec9240978f2cb3b43..fb93adafd65383d11b01c1dd01c5979c5552e230
@@@ -21,7 -21,8 +21,7 @@@
   */
  
  
 -#ifndef INSPIRCD_CONFIGREADER
 -#define INSPIRCD_CONFIGREADER
 +#pragma once
  
  #include <sstream>
  #include <string>
@@@ -44,22 -45,12 +44,22 @@@ class CoreExport ConfigTag : public ref
        /** Get the value of an option, using def if it does not exist */
        std::string getString(const std::string& key, const std::string& def = "");
        /** Get the value of an option, using def if it does not exist */
 -      long getInt(const std::string& key, long def = 0);
 +      long getInt(const std::string& key, long def = 0, long min = LONG_MIN, long max = LONG_MAX);
        /** Get the value of an option, using def if it does not exist */
        double getFloat(const std::string& key, double def = 0);
        /** Get the value of an option, using def if it does not exist */
        bool getBool(const std::string& key, bool def = false);
  
 +      /** Get the value in seconds of a duration that is in the user-friendly "1h2m3s" format,
 +       * using a default value if it does not exist or is out of bounds.
 +       * @param key The config key name
 +       * @param def Default value (optional)
 +       * @param min Minimum acceptable value (optional)
 +       * @param max Maximum acceptable value (optional)
 +       * @return The duration in seconds
 +       */
 +      long getDuration(const std::string& key, long def = 0, long min = LONG_MIN, long max = LONG_MAX);
 +
        /** Get the value of an option
         * @param key The option to get
         * @param value The location to store the value (unmodified if does not exist)
         */
        bool readString(const std::string& key, std::string& value, bool allow_newline = false);
  
 +      /** Check for an out of range value. If the value falls outside the boundaries a warning is
 +       * logged and the value is corrected (set to def).
 +       * @param key The key name, used in the warning message
 +       * @param res The value to verify and modify if needed
 +       * @param def The default value, res will be set to this if (min <= res <= max) doesn't hold true
 +       * @param min Minimum accepted value for res
 +       * @param max Maximum accepted value for res
 +       */
 +      void CheckRange(const std::string& key, long& res, long def, long min, long max);
 +
        std::string getTagLocation();
  
        inline const std::vector<KeyVal>& getItems() const { return items; }
@@@ -112,18 -93,14 +112,18 @@@ class ServerLimit
        size_t MaxGecos;
        /** Maximum away message length */
        size_t MaxAway;
 +      /** Maximum line length */
 +      size_t MaxLine;
 +      /** Maximum hostname length */
 +      size_t MaxHost;
  
 -      /** Creating the class initialises it to the defaults
 -       * as in 1.1's ./configure script. Reading other values
 -       * from the config will change these values.
 +      /** Read all limits from a config tag. Limits which aren't specified in the tag are set to a default value.
 +       * @param tag Configuration tag to read the limits from
         */
 -      ServerLimits() : NickMax(31), ChanMax(64), MaxModes(20), IdentMax(12), MaxQuit(255), MaxTopic(307), MaxKick(255), MaxGecos(128), MaxAway(200)
 -      {
 -      }
 +      ServerLimits(ConfigTag* tag);
 +
 +      /** Maximum length of a n!u@h mask */
 +      size_t GetMaxMask() const { return NickMax + 1 + IdentMax + 1 + MaxHost; }
  };
  
  struct CommandLineConf
         */
        bool writelog;
  
 -      /** True if we have been told to run the testsuite from the commandline,
 -       * rather than entering the mainloop.
 -       */
 -      bool TestSuite;
 -
        /** Saved argc from startup
         */
        int argc;
        /** Saved argv from startup
         */
        char** argv;
 -
 -      std::string startup_log;
  };
  
  class CoreExport OperInfo : public refcountbase
  {
   public:
 -      std::set<std::string> AllowedOperCommands;
 -      std::set<std::string> AllowedPrivs;
 +      typedef insp::flat_set<std::string> PrivSet;
 +      PrivSet AllowedOperCommands;
 +      PrivSet AllowedPrivs;
  
        /** Allowed user modes from oper classes. */
        std::bitset<64> AllowedUserModes;
        /** Get a configuration item, searching in the oper, type, and class blocks (in that order) */
        std::string getConfig(const std::string& key);
        void init();
 -
 -      inline const char* NameStr()
 -      {
 -              return irc::Spacify(name.c_str());
 -      }
  };
  
  /** This class holds the bulk of the runtime configuration for the ircd.
@@@ -201,40 -189,6 +201,40 @@@ class CoreExport ServerConfi
        void CrossCheckConnectBlocks(ServerConfig* current);
  
   public:
 +      class ServerPaths
 +      {
 +       public:
 +              /** Config path */
 +              std::string Config;
 +
 +              /** Data path */
 +              std::string Data;
 +
 +              /** Log path */
 +              std::string Log;
 +
 +              /** Module path */
 +              std::string Module;
 +
 +              ServerPaths()
 +                      : Config(INSPIRCD_CONFIG_PATH)
 +                      , Data(INSPIRCD_DATA_PATH)
 +                      , Log(INSPIRCD_LOG_PATH)
 +                      , Module(INSPIRCD_MODULE_PATH) { }
 +
 +              std::string PrependConfig(const std::string& fn) const { return FileSystem::ExpandPath(Config, fn); }
 +              std::string PrependData(const std::string& fn) const { return FileSystem::ExpandPath(Data, fn); }
 +              std::string PrependLog(const std::string& fn) const { return FileSystem::ExpandPath(Log, fn); }
 +              std::string PrependModule(const std::string& fn) const { return FileSystem::ExpandPath(Module, fn); }
 +      };
 +
 +      /** Holds a complete list of all connect blocks
 +       */
 +      typedef std::vector<reference<ConnectClass> > ClassVector;
 +
 +      /** Index of valid oper blocks and types
 +       */
 +      typedef insp::flat_map<std::string, reference<OperInfo> > OperIndex;
  
        /** Get a configuration tag
         * @param tag The name of the tag to get
         */
        ServerLimits Limits;
  
 +      /** Locations of various types of file (config, module, etc). */
 +      ServerPaths Paths;
 +
        /** Configuration parsed from the command line.
         */
        CommandLineConf cmdline;
         */
        int c_ipv6_range;
  
 -      /** Max number of WhoWas entries per user.
 -       */
 -      int WhoWasGroupSize;
 -
 -      /** Max number of cumulative user-entries in WhoWas.
 -       *  When max reached and added to, push out oldest entry FIFO style.
 -       */
 -      int WhoWasMaxGroups;
 -
 -      /** Max seconds a user is kept in WhoWas before being pruned.
 -       */
 -      int WhoWasMaxKeep;
 -
        /** Holds the server name of the local server
         * as defined by the administrator.
         */
        std::string ServerName;
  
 -      /** Notice to give to users when they are Xlined
 +      /** Notice to give to users when they are banned by an XLine
         */
 -      std::string MoronBanner;
 +      std::string XLineMessage;
  
        /* Holds the network name the local server
         * belongs to. This is an arbitary field defined
         */
        std::string ServerDesc;
  
 -      /** Holds the admin's name, for output in
 -       * the /ADMIN command.
 -       */
 -      std::string AdminName;
 -
 -      /** Holds the email address of the admin,
 -       * for output in the /ADMIN command.
 -       */
 -      std::string AdminEmail;
 -
 -      /** Holds the admin's nickname, for output
 -       * in the /ADMIN command
 -       */
 -      std::string AdminNick;
 -
 -      /** The admin-configured /DIE password
 -       */
 -      std::string diepass;
 -
 -      /** The admin-configured /RESTART password
 -       */
 -      std::string restartpass;
 -
 -      /** The hash method for *BOTH* the die and restart passwords.
 -       */
 -      std::string powerhash;
 -
 -      /** The pathname and filename of the message of the
 -       * day file, as defined by the administrator.
 -       */
 -      std::string motd;
 -
 -      /** The pathname and filename of the rules file,
 -       * as defined by the administrator.
 -       */
 -      std::string rules;
 -
 -      /** The quit prefix in use, or an empty string
 -       */
 -      std::string PrefixQuit;
 -
 -      /** The quit suffix in use, or an empty string
 -       */
 -      std::string SuffixQuit;
 -
 -      /** The fixed quit message in use, or an empty string
 -       */
 -      std::string FixedQuit;
 -
 -      /** The part prefix in use, or an empty string
 -       */
 -      std::string PrefixPart;
 -
 -      /** The part suffix in use, or an empty string
 -       */
 -      std::string SuffixPart;
 -
 -      /** The fixed part message in use, or an empty string
 -       */
 -      std::string FixedPart;
 -
 -      /** The DNS server to use for DNS queries
 -       */
 -      std::string DNSServer;
 -
        /** Pretend disabled commands don't exist.
         */
        bool DisabledDontExist;
         */
        char DisabledCModes[64];
  
 -      /** The full path to the modules directory.
 -       * This is either set at compile time, or
 -       * overridden in the configuration file via
 -       * the \<path> tag.
 -       */
 -      std::string ModPath;
 -
        /** If set to true, then all opers on this server are
         * shown with a generic 'is an IRC operator' line rather
         * than the oper type. Oper types are still used internally.
         */
        bool RestrictBannedUsers;
  
 -      /** If this is set to true, then mode lists (e.g
 -       * MODE \#chan b) are hidden from unprivileged
 -       * users.
 -       */
 -      bool HideModeLists[256];
 -
        /** The number of seconds the DNS subsystem
         * will wait before timing out any request.
         */
         */
        int MaxConn;
  
 +      /** If we should check for clones during CheckClass() in AddUser()
 +       * Setting this to false allows to not trigger on maxclones for users
 +       * that may belong to another class after DNS-lookup is complete.
 +       * It does, however, make the server spend more time on users we may potentially not want.
 +       */
 +      bool CCOnConnect;
 +
        /** The soft limit value assigned to the irc server.
         * The IRC server will not allow more than this
         * number of local users.
         */
        std::string HideKillsServer;
  
+       /** Set to hide kills from clients of ulined servers in snotices.
+        */
+       bool HideULineKills;
        /** The full pathname and filename of the PID
         * file as defined in the configuration.
         */
         */
        ClassVector Classes;
  
 -      /** The 005 tokens of this server (ISUPPORT)
 -       * populated/repopulated upon loading or unloading
 -       * modules.
 -       */
 -      std::string data005;
 -
 -      /** isupport strings
 -       */
 -      std::vector<std::string> isupport;
 -
        /** STATS characters in this list are available
         * only to operators.
         */
         */
        std::string CustomVersion;
  
 -      /** List of u-lined servers
 -       */
 -      std::map<irc::string, bool> ulines;
 -
 -      /** Max banlist sizes for channels (the std::string is a glob)
 -       */
 -      std::map<std::string, int> maxbans;
 -
 -      /** If set to true, no user DNS lookups are to be performed
 -       */
 -      bool NoUserDns;
 -
        /** If set to true, provide syntax hints for unknown commands
         */
        bool SyntaxHints;
  
 -      /** If set to true, users appear to quit then rejoin when their hosts change.
 -       * This keeps clients synchronized properly.
 -       */
 -      bool CycleHosts;
 -
        /** If set to true, the CycleHosts mode change will be sourced from the user,
         * rather than the server
         */
        bool CycleHostsFromUser;
  
 -      /** If set to true, prefixed channel NOTICEs and PRIVMSGs will have the prefix
 -       *  added to the outgoing text for undernet style msg prefixing.
 -       */
 -      bool UndernetMsgPrefix;
 -
        /** If set to true, the full nick!user\@host will be shown in the TOPIC command
         * for who set the topic last. If false, only the nick is shown.
         */
        bool FullHostInTopic;
  
 -      /** Oper block and type index.
 -       * For anonymous oper blocks (type only), prefix with a space.
 +      /** Oper blocks keyed by their name
         */
        OperIndex oper_blocks;
  
 -      /** Max channels per user
 +      /** Oper types keyed by their name
 +       */
 +      OperIndex OperTypes;
 +
 +      /** Default value for <connect:maxchans>, deprecated in 2.2
         */
        unsigned int MaxChans;
  
 -      /** Oper max channels per user
 +      /** Default value for <oper:maxchans>, deprecated in 2.2
         */
        unsigned int OperMaxChans;
  
  
        /** Get server ID as string with required leading zeroes
         */
 -      const std::string& GetSID();
 -
 -      /** Update the 005 vector
 -       */
 -      void Update005();
 -
 -      /** Send the 005 numerics (ISUPPORT) to a user
 -       */
 -      void Send005(User* user);
 +      const std::string& GetSID() const { return sid; }
  
        /** Read the entire configuration into memory
         * and initialize this class. All other methods
  
        void Fill();
  
 -      /** Returns true if the given string starts with a windows drive letter
 -       */
 -      bool StartsWithWindowsDriveLetter(const std::string &path);
 -
        bool ApplyDisabledCommands(const std::string& data);
  
 -      /** Clean a filename, stripping the directories (and drives) from string.
 -       * @param name Directory to tidy
 -       * @return The cleaned filename
 +      /** Escapes a value for storage in a configuration key.
 +       * @param str The string to escape.
 +       * @param xml Are we using the XML config format?
         */
 -      static const char* CleanFilename(const char* name);
 -
 -      /** Check if a file exists.
 -       * @param file The full path to a file
 -       * @return True if the file exists and is readable.
 -       */
 -      static bool FileExists(const char* file);
 -
 -      /** If this value is true, invites will bypass more than just +i
 -       */
 -      bool InvBypassModes;
 +      static std::string Escape(const std::string& str, bool xml = true);
  
        /** If this value is true, snotices will not stack when repeats are sent
         */
        bool NoSnoticeStack;
 -
 -      /** If true, a "Welcome to <networkname>!" NOTICE will be sent to
 -       * connecting users
 -       */
 -      bool WelcomeNotice;
  };
  
  /** The background thread for config reading, so that reading from executable includes
@@@ -523,13 -618,4 +527,13 @@@ class CoreExport ConfigReaderThread : p
        bool IsDone() { return done; }
  };
  
 -#endif
 +class CoreExport ConfigStatus
 +{
 + public:
 +      User* const srcuser;
 +
 +      ConfigStatus(User* user = NULL)
 +              : srcuser(user)
 +      {
 +      }
 +};
diff --combined include/inspircd.h
index ee09070f858799d5ffca7c534769c9a96dadc3ca,78348ed54c4957d411c97a6a657d0c13eee8f4c7..303d24745664ef2936e7c145e4ad17b1cf24a809
   */
  
  
 -#ifndef INSPIRCD_H
 -#define INSPIRCD_H
 +#pragma once
  
 -#define _FILE_OFFSET_BITS 64
 -#ifndef _LARGEFILE_SOURCE
 -#define _LARGEFILE_SOURCE
 -#endif
 -
 -#ifndef _WIN32
 -#define DllExport
 -#define CoreExport
 -#else
 -#include "inspircd_win32wrapper.h"
 -/** Windows defines these already */
 -#undef ERROR
 -#endif
 -
 -#ifdef __GNUC__
 -#define CUSTOM_PRINTF(STRING, FIRST) __attribute__((format(printf, STRING, FIRST)))
 -#else
 -#define CUSTOM_PRINTF(STRING, FIRST)
 -#endif
 -
 -// Required system headers.
 +#include <climits>
 +#include <cmath>
  #include <csignal>
 -#include <ctime>
  #include <cstdarg>
 -#include <algorithm>
 -#include <cmath>
 -#include <cstring>
 -#include <climits>
  #include <cstdio>
 -#ifndef _WIN32
 -#include <unistd.h>
 -#endif
 +#include <cstring>
 +#include <ctime>
 +#include <stdint.h>
  
 -#include <sstream>
 -#include <string>
 -#include <vector>
 -#include <list>
 +#include <algorithm>
 +#include <bitset>
  #include <deque>
 +#include <list>
  #include <map>
 -#include <bitset>
  #include <set>
 -#include <time.h>
 -#include "inspircd_config.h"
 -#include "inspircd_version.h"
 +#include <sstream>
 +#include <string>
 +#include <vector>
 +
 +#include "intrusive_list.h"
 +#include "flat_map.h"
 +#include "compat.h"
 +#include "aligned_storage.h"
  #include "typedefs.h"
 -#include "consolecolors.h"
 +#include "stdalgo.h"
  
  CoreExport extern InspIRCd* ServerInstance;
  
 +/** Base class for manager classes that are still accessed using -> but are no longer pointers
 + */
 +template <typename T>
 +struct fakederef
 +{
 +      T* operator->()
 +      {
 +              return static_cast<T*>(this);
 +      }
 +};
 +
 +#include "config.h"
 +#include "convto.h"
 +#include "dynref.h"
 +#include "consolecolors.h"
  #include "caller.h"
  #include "cull_list.h"
  #include "extensible.h"
 +#include "fileutils.h"
  #include "numerics.h"
 +#include "numeric.h"
  #include "uid.h"
 +#include "server.h"
  #include "users.h"
  #include "channels.h"
  #include "timer.h"
  #include "configreader.h"
  #include "inspstring.h"
  #include "protocol.h"
 -
 -#ifndef PATH_MAX
 -#warning Potentially broken system, PATH_MAX undefined
 -#define PATH_MAX 4096
 -#endif
 -
 -/**
 - * Used to define the maximum number of parameters a command may have.
 - */
 -#define MAXPARAMETERS 127
 -
 -/** Returned by some functions to indicate failure.
 - */
 -#define ERROR -1
 -
 -/** Support for librodent -
 - * see http://www.chatspike.net/index.php?z=64
 - */
 -#define ETIREDHAMSTERS EAGAIN
 -
 -/** Template function to convert any input type to std::string
 - */
 -template<typename T> inline std::string ConvNumeric(const T &in)
 -{
 -      if (in == 0) return "0";
 -      char res[MAXBUF];
 -      char* out = res;
 -      T quotient = in;
 -      while (quotient) {
 -              *out = "0123456789"[ std::abs( (long)quotient % 10 ) ];
 -              ++out;
 -              quotient /= 10;
 -      }
 -      if (in < 0)
 -              *out++ = '-';
 -      *out = 0;
 -      std::reverse(res,out);
 -      return res;
 -}
 -
 -/** Template function to convert any input type to std::string
 - */
 -inline std::string ConvToStr(const int in)
 -{
 -      return ConvNumeric(in);
 -}
 -
 -/** Template function to convert any input type to std::string
 - */
 -inline std::string ConvToStr(const long in)
 -{
 -      return ConvNumeric(in);
 -}
 -
 -/** Template function to convert any input type to std::string
 - */
 -inline std::string ConvToStr(const char* in)
 -{
 -      return in;
 -}
 -
 -/** Template function to convert any input type to std::string
 - */
 -inline std::string ConvToStr(const bool in)
 -{
 -      return (in ? "1" : "0");
 -}
 -
 -/** Template function to convert any input type to std::string
 - */
 -inline std::string ConvToStr(char in)
 -{
 -      return std::string(1, in);
 -}
 -
 -/** Template function to convert any input type to std::string
 - */
 -template <class T> inline std::string ConvToStr(const T &in)
 -{
 -      std::stringstream tmp;
 -      if (!(tmp << in)) return std::string();
 -      return tmp.str();
 -}
 -
 -/** Template function to convert any input type to any other type
 - * (usually an integer or numeric type)
 - */
 -template<typename T> inline long ConvToInt(const T &in)
 -{
 -      std::stringstream tmp;
 -      if (!(tmp << in)) return 0;
 -      return atol(tmp.str().c_str());
 -}
 +#include "bancache.h"
 +#include "isupportmanager.h"
  
  /** This class contains various STATS counters
   * It is used by the InspIRCd class, which internally
@@@ -106,38 -201,38 +106,38 @@@ class serverstat
    public:
        /** Number of accepted connections
         */
 -      unsigned long statsAccept;
 +      unsigned long Accept;
        /** Number of failed accepts
         */
 -      unsigned long statsRefused;
 +      unsigned long Refused;
        /** Number of unknown commands seen
         */
 -      unsigned long statsUnknown;
 +      unsigned long Unknown;
        /** Number of nickname collisions handled
         */
 -      unsigned long statsCollisions;
 +      unsigned long Collisions;
        /** Number of DNS queries sent out
         */
 -      unsigned long statsDns;
 +      unsigned long Dns;
        /** Number of good DNS replies received
         * NOTE: This may not tally to the number sent out,
         * due to timeouts and other latency issues.
         */
 -      unsigned long statsDnsGood;
 +      unsigned long DnsGood;
        /** Number of bad (negative) DNS replies received
         * NOTE: This may not tally to the number sent out,
         * due to timeouts and other latency issues.
         */
 -      unsigned long statsDnsBad;
 +      unsigned long DnsBad;
        /** Number of inbound connections seen
         */
 -      unsigned long statsConnects;
 +      unsigned long Connects;
        /** Total bytes of data transmitted
         */
 -      unsigned long statsSent;
 +      unsigned long Sent;
        /** Total bytes of data received
         */
 -      unsigned long statsRecv;
 +      unsigned long Recv;
  #ifdef _WIN32
        /** Cpu usage at last sample
        */
        /** The constructor initializes all the counts to zero
         */
        serverstats()
 -              : statsAccept(0), statsRefused(0), statsUnknown(0), statsCollisions(0), statsDns(0),
 -              statsDnsGood(0), statsDnsBad(0), statsConnects(0), statsSent(0), statsRecv(0)
 +              : Accept(0), Refused(0), Unknown(0), Collisions(0), Dns(0),
 +              DnsGood(0), DnsBad(0), Connects(0), Sent(0), Recv(0)
        {
        }
  };
  
 -DEFINE_HANDLER2(IsNickHandler, bool, const char*, size_t);
 +DEFINE_HANDLER1(IsNickHandler, bool, const std::string&);
  DEFINE_HANDLER2(GenRandomHandler, void, char*, size_t);
 -DEFINE_HANDLER1(IsIdentHandler, bool, const char*);
 -DEFINE_HANDLER1(FloodQuitUserHandler, void, User*);
 -DEFINE_HANDLER2(IsChannelHandler, bool, const char*, size_t);
 -DEFINE_HANDLER1(IsSIDHandler, bool, const std::string&);
 -DEFINE_HANDLER1(RehashHandler, void, const std::string&);
 +DEFINE_HANDLER1(IsIdentHandler, bool, const std::string&);
 +DEFINE_HANDLER1(IsChannelHandler, bool, const std::string&);
  DEFINE_HANDLER3(OnCheckExemptionHandler, ModResult, User*, Channel*, const std::string&);
  
 -class TestSuite;
 -
  /** The main class of the irc server.
   * This class contains instances of all the other classes in this software.
   * Amongst other things, it contains a ModeParser, a DNS object, a CommandParser
  class CoreExport InspIRCd
  {
   private:
 -      /** Holds the current UID. Used to generate the next one.
 -       */
 -      char current_uid[UUID_LENGTH];
 -
        /** Set up the signal handlers
         */
        void SetSignals();
         */
        bool DaemonSeed();
  
 -      /** Iterate the list of BufferedSocket objects, removing ones which have timed out
 -       * @param TIME the current time
 -       */
 -      void DoSocketTimeouts(time_t TIME);
 -
 -      /** Increments the current UID by one.
 -       */
 -      void IncrementUID(int pos);
 -
 -      /** Perform background user events such as PING checks
 -       */
 -      void DoBackgroundUserStuff();
 -
 -      /** Returns true when all modules have done pre-registration checks on a user
 -       * @param user The user to verify
 -       * @return True if all modules have finished checking this user
 -       */
 -      bool AllModulesReportReady(LocalUser* user);
 -
        /** The current time, updated in the mainloop
         */
        struct timespec TIME;
         */
        char ReadBuffer[65535];
  
 +      /** Check we aren't running as root, and exit if we are
 +       * with exit code EXIT_STATUS_ROOT.
 +       */
 +      void CheckRoot();
 +
   public:
  
 +      UIDGenerator UIDGen;
 +
        /** Global cull list, will be processed on next iteration
         */
        CullList GlobalCulls;
  
        IsNickHandler HandleIsNick;
        IsIdentHandler HandleIsIdent;
 -      FloodQuitUserHandler HandleFloodQuitUser;
        OnCheckExemptionHandler HandleOnCheckExemption;
        IsChannelHandler HandleIsChannel;
 -      IsSIDHandler HandleIsSID;
 -      RehashHandler HandleRehash;
        GenRandomHandler HandleGenRandom;
  
        /** Globally accessible fake user record. This is used to force mode changes etc across s2s, etc.. bit ugly, but.. better than how this was done in 1.1
         */
        FakeUser* FakeClient;
  
 -      /** Returns the next available UID for this server.
 -       */
 -      std::string GetUID();
 -
 -      static const char LogHeader[];
 -
        /** Find a user in the UUID hash
         * @param uid The UUID to find
         * @return A pointer to the user, or NULL if the user does not exist
         */
        User* FindUUID(const std::string &uid);
  
 -      /** Find a user in the UUID hash
 -       * @param uid The UUID to find
 -       * @return A pointer to the user, or NULL if the user does not exist
 -       */
 -      User* FindUUID(const char *uid);
 -
 -      /** Build the ISUPPORT string by triggering all modules On005Numeric events
 -       */
 -      void BuildISupport();
 -
        /** Time this ircd was booted
         */
        time_t startup_time;
  
        /** Mode handler, handles mode setting and removal
         */
 -      ModeParser* Modes;
 +      ModeParser Modes;
  
        /** Command parser, handles client to server commands
         */
 -      CommandParser* Parser;
 -
 -      /** Socket engine, handles socket activity events
 -       */
 -      SocketEngine* SE;
 +      CommandParser Parser;
  
        /** Thread engine, Handles threading where required
         */
 -      ThreadEngine* Threads;
 +      ThreadEngine Threads;
  
        /** The thread/class used to read config files in REHASH and on startup
         */
  
        /** LogManager handles logging.
         */
 -      LogManager *Logs;
 +      LogManager Logs;
  
        /** ModuleManager contains everything related to loading/unloading
         * modules.
         */
 -      ModuleManager* Modules;
 +      ModuleManager Modules;
  
        /** BanCacheManager is used to speed up checking of restrictions on connection
         * to the IRCd.
         */
 -      BanCacheManager *BanCache;
 +      BanCacheManager BanCache;
  
        /** Stats class, holds miscellaneous stats counters
         */
 -      serverstats* stats;
 +      serverstats stats;
  
        /**  Server Config class, holds configuration file data
         */
        /** Snomask manager - handles routing of snomask messages
         * to opers.
         */
 -      SnomaskManager* SNO;
 -
 -      /** DNS class, provides resolver facilities to the core and modules
 -       */
 -      DNS* Res;
 +      SnomaskManager SNO;
  
        /** Timer manager class, triggers Timer timer events
         */
 -      TimerManager* Timers;
 +      TimerManager Timers;
  
        /** X-Line manager. Handles G/K/Q/E line setting, removal and matching
         */
  
        /** User manager. Various methods and data associated with users.
         */
 -      UserManager *Users;
 +      UserManager Users;
  
        /** Channel list, a hash_map containing all channels XXX move to channel manager class
         */
 -      chan_hash* chanlist;
 +      chan_hash chanlist;
  
        /** List of the open ports
         */
         */
        ProtocolInterface* PI;
  
 -      /** Holds extensible for user nickforced
 +      /** Default implementation of the ProtocolInterface, does nothing
         */
 -      LocalIntExt NICKForced;
 +      ProtocolInterface DefaultProtocolInterface;
  
        /** Holds extensible for user operquit
         */
 -      LocalStringExt OperQuit;
 +      StringExtItem OperQuit;
 +
 +      /** Manages the generation and transmission of ISUPPORT. */
 +      ISupportManager ISupport;
  
        /** Get the current time
         * Because this only calls time() once every time around the mainloop,
         */
        int BindPorts(FailedPortList &failed_ports);
  
 -      /** Binds a socket on an already open file descriptor
 -       * @param sockfd A valid file descriptor of an open socket
 -       * @param port The port number to bind to
 -       * @param addr The address to bind to (IP only)
 -       * @param dolisten Should this port be listened on?
 -       * @return True if the port was bound successfully
 -       */
 -      bool BindSocket(int sockfd, int port, const char* addr, bool dolisten = true);
 -
 -      /** Gets the GECOS (description) field of the given server.
 -       * If the servername is not that of the local server, the name
 -       * is passed to handling modules which will attempt to determine
 -       * the GECOS that bleongs to the given servername.
 -       * @param servername The servername to find the description of
 -       * @return The description of this server, or of the local server
 -       */
 -      std::string GetServerDescription(const std::string& servername);
 -
        /** Find a user in the nick hash.
         * If the user cant be found in the nick hash check the uuid hash
         * @param nick The nickname to find
         */
        User* FindNick(const std::string &nick);
  
 -      /** Find a user in the nick hash.
 -       * If the user cant be found in the nick hash check the uuid hash
 -       * @param nick The nickname to find
 -       * @return A pointer to the user, or NULL if the user does not exist
 -       */
 -      User* FindNick(const char* nick);
 -
 -      /** Find a user in the nick hash ONLY
 -       */
 -      User* FindNickOnly(const char* nick);
 -
        /** Find a user in the nick hash ONLY
         */
        User* FindNickOnly(const std::string &nick);
         */
        Channel* FindChan(const std::string &chan);
  
 -      /** Find a channel in the channels hash
 -       * @param chan The channel to find
 -       * @return A pointer to the channel, or NULL if the channel does not exist
 -       */
 -      Channel* FindChan(const char* chan);
 -
 -      /** Check we aren't running as root, and exit if we are
 -       * @return Depending on the configuration, this function may never return
 +      /** Get a hash map containing all channels, keyed by their name
 +       * @return A hash map mapping channel names to Channel pointers
         */
 -      void CheckRoot();
 -
 -      /** Determine the right path for, and open, the logfile
 -       * @param argv The argv passed to main() initially, used to calculate program path
 -       * @param argc The argc passed to main() initially, used to calculate program path
 -       * @return True if the log could be opened, false if otherwise
 -       */
 -      bool OpenLog(char** argv, int argc);
 +      chan_hash& GetChans() { return chanlist; }
  
        /** Return true if a channel name is valid
         * @param chname A channel name to verify
         * @return True if the name is valid
         */
 -      caller2<bool, const char*, size_t> IsChannel;
 +      caller1<bool, const std::string&> IsChannel;
  
        /** Return true if str looks like a server ID
 -       * @param string to check against
 +       * @param sid string to check against
         */
 -      caller1<bool, const std::string&> IsSID;
 -
 -      /** Rehash the local server
 -       */
 -      caller1<void, const std::string&> Rehash;
 +      static bool IsSID(const std::string& sid);
  
        /** Handles incoming signals after being set
         * @param signal the signal recieved
         */
        static void QuickExit(int status);
  
 -      /** Return a count of channels on the network
 -       * @return The number of channels
 -       */
 -      long ChannelCount();
 -
 -      /** Send an error notice to all local users, opered and unopered
 -       * @param s The error string to send
 -       */
 -      void SendError(const std::string &s);
 +      /** Formats the input string with the specified arguments.
 +      * @param formatString The string to format
 +      * @param ... A variable number of format arguments.
 +      * @return The formatted string
 +      */
 +      static const char* Format(const char* formatString, ...) CUSTOM_PRINTF(1, 2);
 +      static const char* Format(va_list &vaList, const char* formatString) CUSTOM_PRINTF(2, 0);
  
        /** Return true if a nickname is valid
         * @param n A nickname to verify
         * @return True if the nick is valid
         */
 -      caller2<bool, const char*, size_t> IsNick;
 +      caller1<bool, const std::string&> IsNick;
  
        /** Return true if an ident is valid
         * @param An ident to verify
         * @return True if the ident is valid
         */
 -      caller1<bool, const char*> IsIdent;
 -
 -      /** Add a dns Resolver class to this server's active set
 -       * @param r The resolver to add
 -       * @param cached If this value is true, then the cache will
 -       * be searched for the DNS result, immediately. If the value is
 -       * false, then a request will be sent to the nameserver, and the
 -       * result will not be immediately available. You should usually
 -       * use the boolean value which you passed to the Resolver
 -       * constructor, which Resolver will set appropriately depending
 -       * on if cached results are available and haven't expired. It is
 -       * however safe to force this value to false, forcing a remote DNS
 -       * lookup, but not an update of the cache.
 -       * @return True if the operation completed successfully. Note that
 -       * if this method returns true, you should not attempt to access
 -       * the resolver class you pass it after this call, as depending upon
 -       * the request given, the object may be deleted!
 -       */
 -      bool AddResolver(Resolver* r, bool cached);
 -
 -      /** Add a command to this server's command parser
 -       * @param f A Command command handler object to add
 -       * @throw ModuleException Will throw ModuleExcption if the command already exists
 -       */
 -      inline void AddCommand(Command *f)
 -      {
 -              Modules->AddService(*f);
 -      }
 -
 -      /** Send a modechange.
 -       * The parameters provided are identical to that sent to the
 -       * handler for class cmd_mode.
 -       * @param parameters The mode parameters
 -       * @param user The user to send error messages to
 -       */
 -      void SendMode(const std::vector<std::string>& parameters, User *user);
 -
 -      /** Send a modechange and route it to the network.
 -       * The parameters provided are identical to that sent to the
 -       * handler for class cmd_mode.
 -       * @param parameters The mode parameters
 -       * @param user The user to send error messages to
 -       */
 -      void SendGlobalMode(const std::vector<std::string>& parameters, User *user);
 +      caller1<bool, const std::string&> IsIdent;
  
        /** Match two strings using pattern matching, optionally, with a map
         * to check case against (may be NULL). If map is null, match will be case insensitive.
         * @param mask The glob pattern to match against.
         * @param map The character map to use when matching.
         */
 -      static bool Match(const std::string &str, const std::string &mask, unsigned const char *map = NULL);
 -      static bool Match(const  char *str, const char *mask, unsigned const char *map = NULL);
 +      static bool Match(const std::string& str, const std::string& mask, unsigned const char* map = NULL);
 +      static bool Match(const char* str, const char* mask, unsigned const char* map = NULL);
  
        /** Match two strings using pattern matching, optionally, with a map
         * to check case against (may be NULL). If map is null, match will be case insensitive.
         * @param mask The glob or CIDR pattern to match against.
         * @param map The character map to use when matching.
         */
 -      static bool MatchCIDR(const std::string &str, const std::string &mask, unsigned const char *map = NULL);
 -      static bool MatchCIDR(const  char *str, const char *mask, unsigned const char *map = NULL);
 +      static bool MatchCIDR(const std::string& str, const std::string& mask, unsigned const char* map = NULL);
 +      static bool MatchCIDR(const char* str, const char* mask, unsigned const char* map = NULL);
  
 -      /** Call the handler for a given command.
 -       * @param commandname The command whos handler you wish to call
 -       * @param parameters The mode parameters
 -       * @param user The user to execute the command as
 -       * @return True if the command handler was called successfully
 +      /** Matches a hostname and IP against a space delimited list of hostmasks.
 +       * @param masks The space delimited masks to match against.
 +       * @param hostname The hostname to try and match.
 +       * @param ipaddr The IP address to try and match.
         */
 -      CmdResult CallCommandHandler(const std::string &commandname, const std::vector<std::string>& parameters, User* user);
 -
 -      /** Return true if the command is a module-implemented command and the given parameters are valid for it
 -       * @param commandname The command name to check
 -       * @param pcnt The parameter count
 -       * @param user The user to test-execute the command as
 -       * @return True if the command handler is a module command, and there are enough parameters and the user has permission to the command
 -       */
 -      bool IsValidModuleCommand(const std::string &commandname, int pcnt, User* user);
 +      static bool MatchMask(const std::string& masks, const std::string& hostname, const std::string& ipaddr);
  
        /** Return true if the given parameter is a valid nick!user\@host mask
         * @param mask A nick!user\@host masak to match against
         * @return True i the mask is valid
         */
 -      bool IsValidMask(const std::string &mask);
 +      static bool IsValidMask(const std::string& mask);
  
 -      /** Strips all color codes from the given string
 +      /** Strips all color and control codes except 001 from the given string
         * @param sentence The string to strip from
         */
        static void StripColor(std::string &sentence);
        static void ProcessColors(file_cache& input);
  
        /** Rehash the local server
 +       * @param uuid The uuid of the user who started the rehash, can be empty
         */
 -      void RehashServer();
 -
 -      /** Check if the given nickmask matches too many users, send errors to the given user
 -       * @param nick A nickmask to match against
 -       * @param user A user to send error text to
 -       * @return True if the nick matches too many users
 -       */
 -      bool NickMatchesEveryone(const std::string &nick, User* user);
 -
 -      /** Check if the given IP mask matches too many users, send errors to the given user
 -       * @param ip An ipmask to match against
 -       * @param user A user to send error text to
 -       * @return True if the ip matches too many users
 -       */
 -      bool IPMatchesEveryone(const std::string &ip, User* user);
 -
 -      /** Check if the given hostmask matches too many users, send errors to the given user
 -       * @param mask A hostmask to match against
 -       * @param user A user to send error text to
 -       * @return True if the host matches too many users
 -       */
 -      bool HostMatchesEveryone(const std::string &mask, User* user);
 +      void Rehash(const std::string& uuid = "");
  
        /** Calculate a duration in seconds from a string in the form 1y2w3d4h6m5s
         * @param str A string containing a time in the form 1y2w3d4h6m5s
         * (one year, two weeks, three days, four hours, six minutes and five seconds)
         * @return The total number of seconds
         */
 -      long Duration(const std::string &str);
 +      static unsigned long Duration(const std::string& str);
  
        /** Attempt to compare a password to a string from the config file.
         * This will be passed to handling modules which will compare the data
         * @param data The data from the config file
         * @param input The data input by the oper
         * @param hashtype The hash from the config file
 -       * @return 0 if the strings match, 1 or -1 if they do not
 +       * @return True if the strings match, false if they do not
         */
 -      int PassCompare(Extensible* ex, const std::string &data, const std::string &input, const std::string &hashtype);
 -
 -      /** Check if a given server is a uline.
 -       * An empty string returns true, this is by design.
 -       * @param server The server to check for uline status
 -       * @return True if the server is a uline OR the string is empty
 -       */
 -      bool ULine(const std::string& server);
 -
 -      /** Returns true if the uline is 'silent' (doesnt generate
 -       * remote connect notices etc).
 -       */
 -      bool SilentULine(const std::string& server);
 +      bool PassCompare(Extensible* ex, const std::string& data, const std::string& input, const std::string& hashtype);
  
        /** Returns the full version string of this ircd
         * @return The version string
         */
 -      std::string GetVersionString(bool rawversion = false);
 +      std::string GetVersionString(bool getFullVersion = false);
  
        /** Attempt to write the process id to a given file
         * @param filename The PID file to attempt to write to
+        * @param exitonfail If true and the PID fail cannot be written log to stdout and exit, otherwise only log on failure
         * @return This function may bail if the file cannot be written
         */
-       void WritePID(const std::string &filename);
+       void WritePID(const std::string& filename, bool exitonfail = true);
  
        /** This constructor initialises all the subsystems and reads the config file.
         * @param argc The argument count passed to main()
         */
        InspIRCd(int argc, char** argv);
  
 -      /** Send a line of WHOIS data to a user.
 -       * @param user user to send the line to
 -       * @param dest user being WHOISed
 -       * @param numeric Numeric to send
 -       * @param text Text of the numeric
 -       */
 -      void SendWhoisLine(User* user, User* dest, int numeric, const std::string &text);
 -
 -      /** Send a line of WHOIS data to a user.
 -       * @param user user to send the line to
 -       * @param dest user being WHOISed
 -       * @param numeric Numeric to send
 -       * @param format Format string for the numeric
 -       * @param ... Parameters for the format string
 -       */
 -      void SendWhoisLine(User* user, User* dest, int numeric, const char* format, ...) CUSTOM_PRINTF(5, 6);
 -
 -      /** Handle /WHOIS
 -       */
 -      void DoWhois(User* user, User* dest,unsigned long signon, unsigned long idle, const char* nick);
 -
 -      /** Quit a user for excess flood, and if they are not
 -       * fully registered yet, temporarily zline their IP.
 -       * @param current user to quit
 -       */
 -      caller1<void, User*> FloodQuitUser;
 -
        /** Called to check whether a channel restriction mode applies to a user
         * @param User that is attempting some action
         * @param Channel that the action is being performed on
         */
        caller3<ModResult, User*, Channel*, const std::string&> OnCheckExemption;
  
 -      /** Restart the server.
 -       * This function will not return. If an error occurs,
 -       * it will throw an instance of CoreException.
 -       * @param reason The restart reason to show to all clients
 -       * @throw CoreException An instance of CoreException indicating the error from execv().
 -       */
 -      void Restart(const std::string &reason);
 -
        /** Prepare the ircd for restart or shutdown.
         * This function unloads all modules which can be unloaded,
         * closes all open sockets, and closes the logfile.
         */
        void Cleanup();
  
 -      /** This copies the user and channel hash_maps into new hash maps.
 -       * This frees memory used by the hash_map allocator (which it neglects
 -       * to free, most of the time, using tons of ram)
 -       */
 -      void RehashUsersAndChans();
 -
 -      /** Resets the cached max bans value on all channels.
 -       * Called by rehash.
 +      /** Return a time_t as a human-readable string.
 +       * @param format The format to retrieve the date/time in. See `man 3 strftime`
 +       * for more information. If NULL, "%a %b %d %T %Y" is assumed.
 +       * @param utc True to convert the time to string as-is, false to convert it to local time first.
 +       * @return A string representing the given date/time.
         */
 -      void ResetMaxBans();
 +      static std::string TimeString(time_t curtime, const char* format = NULL, bool utc = false);
  
 -      /** Return a time_t as a human-readable string.
 +      /** Compare two strings in a timing-safe way. If the lengths of the strings differ, the function
 +       * returns false immediately (leaking information about the length), otherwise it compares each
 +       * character and only returns after all characters have been compared.
 +       * @param one First string
 +       * @param two Second string
 +       * @return True if the strings match, false if they don't
         */
 -      std::string TimeString(time_t curtime);
 +      static bool TimingSafeCompare(const std::string& one, const std::string& two);
  
        /** Begin execution of the server.
         * NOTE: this function NEVER returns. Internally,
         * it will repeatedly loop.
 -       * @return The return value for this function is undefined.
 -       */
 -      int Run();
 -
 -      /** Adds an extban char to the 005 token.
         */
 -      void AddExtBanChar(char c);
 +      void Run();
  
        char* GetReadBuffer()
        {
                return this->ReadBuffer;
        }
 -
 -      friend class TestSuite;
  };
  
  ENTRYPOINT;
@@@ -573,18 -886,15 +574,18 @@@ class CommandModule : public Modul
        {
        }
  
 -      void init()
 -      {
 -              ServerInstance->Modules->AddService(cmd);
 -      }
 -
        Version GetVersion()
        {
                return Version(cmd.name, VF_VENDOR|VF_CORE);
        }
  };
  
 -#endif
 +inline void stdalgo::culldeleter::operator()(classbase* item)
 +{
 +      if (item)
 +              ServerInstance->GlobalCulls.AddItem(item);
 +}
 +
 +#include "numericbuilder.h"
 +#include "modules/whois.h"
 +#include "modules/stats.h"
diff --combined make/template/inspircd
index 138de29a9cfdb4ef6eb060bff65fe11bbfca4007,b43ad60c9ecbb67981cf36258dc765964c36b860..cb2d2902d520eca9752a7cf0da4b4f4004c9a2c4
@@@ -1,4 -1,3 +1,4 @@@
 +%mode 0750
  #!/usr/bin/env perl
  
  #
@@@ -30,35 -29,17 +30,35 @@@ use strict
  use POSIX;
  use Fcntl;
  
 +# From http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
 +use constant {
 +    STATUS_EXIT_SUCCESS => 0,
 +    STATUS_EXIT_DEAD_WITH_PIDFILE => 1,
 +    STATUS_EXIT_DEAD_WITH_LOCKFILE => 2,
 +    STATUS_EXIT_NOT_RUNNING => 3,
 +    STATUS_EXIT_UNKNOWN => 4,
 +
 +    GENERIC_EXIT_SUCCESS => 0,
 +    GENERIC_EXIT_UNSPECIFIED => 1,
 +    GENERIC_EXIT_INVALID_ARGUMENTS => 2,
 +    GENERIC_EXIT_UNIMPLEMENTED => 3,
 +    GENERIC_EXIT_INSUFFICIENT_PRIVILEGE => 4,
 +    GENERIC_EXIT_NOT_INSTALLED => 5,
 +    GENERIC_EXIT_NOT_CONFIGURED => 6,
 +    GENERIC_EXIT_NOT_RUNNING => 7
 +};
 +
  my $basepath  =       "@BASE_DIR@";
  my $confpath  =       "@CONFIG_DIR@/";
  my $binpath   =       "@BINARY_DIR@";
  my $runpath   =       "@BASE_DIR@";
  my $datadir   =       "@DATA_DIR@";
  my $valgrindlogpath   =       "$basepath/valgrindlogs";
 -my $executable        =       "@EXECUTABLE@";
 -my $version   =       "@VERSION@";
 +my $executable        =       "inspircd";
 +my $version   =       "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@+@VERSION_LABEL@";
  my $uid = "@UID@";
  
 -if ($< == 0 || $> == 0) {
 +if (!("--runasroot" ~~ @ARGV) && ($< == 0 || $> == 0)) {
        if ($uid !~ /^\d+$/) {
                # Named UID, look it up
                $uid = getpwnam $uid;
@@@ -106,11 -87,12 +106,11 @@@ if (!defined($sub)
  {
        print STDERR "Invalid command or none given.\n";
        cmd_help();
 -      exit 1;
 +      exit GENERIC_EXIT_UNIMPLEMENTED;
  }
  else
  {
 -      $sub->(@ARGV);
 -      exit 0;
 +      exit $sub->(@ARGV); # Error code passed through return value
  }
  
  sub cmd_help()
        $_ =~ s/_/-/g foreach (@cmds, @devs);
        print STDERR "Usage: ./inspircd (" . join("|", @cmds) . ")\n";
        print STDERR "Developer arguments: (" . join("|", @devs) . ")\n";
 -      exit 0;
 +      exit GENERIC_EXIT_SUCCESS;
  }
  
  sub cmd_status()
        if (getstatus() == 1) {
                my $pid = getprocessid();
                print "InspIRCd is running (PID: $pid)\n";
 -              exit();
 +              exit STATUS_EXIT_SUCCESS;
        } else {
                print "InspIRCd is not running. (Or PID File not found)\n";
 -              exit();
 +              exit STATUS_EXIT_NOT_RUNNING;
        }
  }
  
@@@ -144,43 -126,43 +144,43 @@@ sub cmd_rehash(
                my $pid = getprocessid();
                system("kill -HUP $pid >/dev/null 2>&1");
                print "InspIRCd rehashed (pid: $pid).\n";
 -              exit();
 +              exit GENERIC_EXIT_SUCCESS;
        } else {
                print "InspIRCd is not running. (Or PID File not found)\n";
 -              exit();
 +              exit GENERIC_EXIT_NOT_RUNNING;
        }
  }
  
  sub cmd_cron()
  {
-       if (getstatus() == 0) { goto &cmd_start(); }
+       if (getstatus() == 0) { goto &cmd_start(@_); }
 -      exit();
 +      exit GENERIC_EXIT_UNSPECIFIED;
  }
  
  sub cmd_version()
  {
        print "InspIRCd version: $version\n";
 -      exit();
 +      exit GENERIC_EXIT_SUCCESS;
  }
  
  sub cmd_restart(@)
  {
        cmd_stop();
        unlink($pidfile) if (-e $pidfile);
-       goto &cmd_start;
+       goto &cmd_start(@_);
  }
  
  sub hid_cheese_sandwich()
  {
        print "Creating Cheese Sandwich..\n";
        print "Done.\n";
 -      exit();
 +      exit GENERIC_EXIT_SUCCESS;
  }
  
  sub cmd_start(@)
  {
        # Check to see its not 'running' already.
 -      if (getstatus() == 1) { print "InspIRCd is already running.\n"; return 0; }
 +      if (getstatus() == 1) { print "InspIRCd is already running.\n"; exit GENERIC_EXIT_SUCCESS; }
        # If we are still alive here.. Try starting the IRCd..
        chdir $runpath;
        print "$binpath/$executable doesn't exist\n" and return 0 unless(-e "$binpath/$executable");
@@@ -242,7 -224,7 +242,7 @@@ sub dev_valdebug(@
        # If we are still alive here.. Try starting the IRCd..
        # May want to do something with these args at some point: --suppressions=.inspircd.sup --gen-suppressions=yes
        # Could be useful when we want to stop it complaining about things we're sure aren't issues.
 -      exec qw(valgrind -v --tool=memcheck --leak-check=yes --db-attach=yes --num-callers=10), "$binpath/$executable", qw(--nofork --debug --nolog), @_;
 +      exec qw(valgrind -v --tool=memcheck --leak-check=yes --db-attach=yes --num-callers=30), "$binpath/$executable", qw(--nofork --debug --nolog), @_;
        die "Failed to start valgrind: $!\n";
  }
  
@@@ -276,7 -258,7 +276,7 @@@ sub dev_valdebug_unattended(@
                sysopen STDERR, "$valgrindlogpath/valdebug.$suffix", O_WRONLY | O_CREAT | O_NOCTTY | O_APPEND, 0666 or die "Can't open $valgrindlogpath/valdebug.$suffix: $!\n";
        # May want to do something with these args at some point: --suppressions=.inspircd.sup --gen-suppressions=yes
        # Could be useful when we want to stop it complaining about things we're sure aren't issues.
 -              exec qw(valgrind -v --tool=memcheck --leak-check=full --show-reachable=yes --num-callers=15 --track-fds=yes),
 +              exec qw(valgrind -v --tool=memcheck --leak-check=full --show-reachable=yes --num-callers=30 --track-fds=yes),
                        "--suppressions=$binpath/valgrind.sup", qw(--gen-suppressions=all),
                        qw(--leak-resolution=med --time-stamp=yes --log-fd=2 --),
                        "$binpath/$executable", qw(--nofork --debug --nolog), @_;
@@@ -301,13 -283,13 +301,13 @@@ sub dev_screenvaldebug(@
        # If we are still alive here.. Try starting the IRCd..
        print "Starting InspIRCd in `screen`, type `screen -r` when the ircd crashes to view the valgrind and gdb output and get a backtrace.\n";
        print "Once you're inside the screen session press ^C + d to re-detach from the session\n";
 -      exec qw(screen -m -d valgrind -v --tool=memcheck --leak-check=yes --db-attach=yes --num-callers=10), "$binpath/$executable", qw(--nofork --debug --nolog), @_;
 +      exec qw(screen -m -d valgrind -v --tool=memcheck --leak-check=yes --db-attach=yes --num-callers=30), "$binpath/$executable", qw(--nofork --debug --nolog), @_;
        die "Failed to start screen: $!\n";
  }
  
  sub cmd_stop()
  {
 -      if (getstatus() == 0) { print "InspIRCd is not running. (Or PID File not found)\n"; return 0; }
 +      if (getstatus() == 0) { print "InspIRCd is not running. (Or PID File not found)\n"; return GENERIC_EXIT_SUCCESS; }
        # Get to here, we have something to kill.
        my $pid = getprocessid();
        print "Stopping InspIRCd (pid: $pid)...\n";
                sleep 1;
                if (getstatus() == 0) {
                        print "InspIRCd Stopped.\n";
 -                      return;
 +                      return GENERIC_EXIT_SUCCESS;
                }
        }
        print "InspIRCd not dying quietly -- forcing kill\n";
        kill KILL => $pid;
 -      return 0;
 +      return GENERIC_EXIT_SUCCESS;
  }
  
  ###
@@@ -433,7 -415,7 +433,7 @@@ sub checkvalgrin
        unless(`valgrind --version`)
        {
                print "Couldn't start valgrind: $!\n";
 -              exit;
 +              exit GENERIC_EXIT_UNSPECIFIED;
        }
  }
  
@@@ -442,7 -424,7 +442,7 @@@ sub checkgd
        unless(`gdb --version`)
        {
                print "Couldn't start gdb: $!\n";
 -              exit;
 +              exit GENERIC_EXIT_UNSPECIFIED;
        }
  }
  
@@@ -451,6 -433,6 +451,6 @@@ sub checkscree
        unless(`screen --version`)
        {
                print "Couldn't start screen: $!\n";
 -              exit;
 +              exit GENERIC_EXIT_UNSPECIFIED;
        }
  }
diff --combined make/template/main.mk
index bff0657362364e0bb80a0f324d0e4a2ca67b8920,23daa7efc02a0d2069cc70f694b56958f1870571..818b4139d8681a7fbab038f6fa75fd12a83878de
@@@ -1,5 -1,3 +1,5 @@@
 +%target BSD_MAKE BSDmakefile
 +%target GNU_MAKE GNUmakefile
  #
  # InspIRCd -- Internet Relay Chat Daemon
  #
  #
  
  
 -CC = @CC@
 -SYSTEM = @SYSTEM@
 -BUILDPATH = @BUILD_DIR@
 +CXX = @CXX@
 +COMPILER = @COMPILER_NAME@
 +SYSTEM = @SYSTEM_NAME@
 +BUILDPATH ?= $(PWD)/build
  SOCKETENGINE = @SOCKETENGINE@
 -CXXFLAGS = -pipe -fPIC -DPIC
 -LDLIBS = -pthread -lstdc++
 -LDFLAGS = 
 +CORECXXFLAGS = -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -pipe -Iinclude -Wall -Wextra -Wfatal-errors -Wno-unused-parameter -Wshadow
 +LDLIBS = -lstdc++
  CORELDFLAGS = -rdynamic -L. $(LDFLAGS)
  PICLDFLAGS = -fPIC -shared -rdynamic $(LDFLAGS)
  BASE = "$(DESTDIR)@BASE_DIR@"
  CONPATH = "$(DESTDIR)@CONFIG_DIR@"
 +MANPATH = "$(DESTDIR)@MANUAL_DIR@"
  MODPATH = "$(DESTDIR)@MODULE_DIR@"
  LOGPATH = "$(DESTDIR)@LOG_DIR@"
  DATPATH = "$(DESTDIR)@DATA_DIR@"
  BINPATH = "$(DESTDIR)@BINARY_DIR@"
  INSTALL = install
  INSTUID = @UID@
 -INSTMODE_DIR = 0755
 -INSTMODE_BIN = 0755
 -INSTMODE_LIB = 0644
 -
 -@IFEQ $(CC) icpc
 -  CXXFLAGS += -Wshadow
 -@ELSE
 -  CXXFLAGS += -pedantic -Woverloaded-virtual -Wshadow -Wformat=2 -Wmissing-format-attribute -Wall
 +INSTMODE_DIR = 0750
 +INSTMODE_BIN = 0750
 +INSTMODE_LIB = 0640
 +
 +@IFNEQ $(COMPILER) ICC
 +  CORECXXFLAGS += -Woverloaded-virtual -Wshadow
 +@IFNEQ $(SYSTEM) openbsd
 +    CORECXXFLAGS += -pedantic -Wformat=2 -Wmissing-format-attribute
 +@ENDIF
  @ENDIF
  
 +@IFNEQ $(SYSTEM) darwin
 +  LDLIBS += -pthread
 +@ENDIF
  
  @IFEQ $(SYSTEM) linux
    LDLIBS += -ldl -lrt
    LDLIBS += -lsocket -lnsl -lrt -lresolv
    INSTALL = ginstall
  @ENDIF
 -@IFEQ $(SYSTEM) sunos
 -  LDLIBS += -lsocket -lnsl -lrt -lresolv
 -      INSTALL = ginstall
 -@ENDIF
  @IFEQ $(SYSTEM) darwin
 -  CXXFLAGS += -DDARWIN -frtti
    LDLIBS += -ldl
    CORELDFLAGS = -dynamic -bind_at_load -L. $(LDFLAGS)
    PICLDFLAGS = -fPIC -shared -twolevel_namespace -undefined dynamic_lookup $(LDFLAGS)
  @ENDIF
 -@IFEQ $(SYSTEM) interix
 -  CXXFLAGS += -D_ALL_SOURCE -I/usr/local/include
 -@ENDIF
 -
 -@IFNDEF D
 -  D=0
 -@ENDIF
  
 -GCC6=@GCC6@
 -@IFEQ $(GCC6) true
 -  CXXFLAGS += -fno-delete-null-pointer-checks
 +@IFNDEF INSPIRCD_DEBUG
 +  INSPIRCD_DEBUG=0
  @ENDIF
  
  DBGOK=0
 -@IFEQ $(D) 0
 -  CXXFLAGS += -O2
 -@IFEQ $(CC) g++
 -    CXXFLAGS += -g1
 +@IFEQ $(INSPIRCD_DEBUG) 0
 +  CORECXXFLAGS += -fno-rtti -O2
 +@IFEQ $(COMPILER) GCC
 +    CORECXXFLAGS += -g1
  @ENDIF
    HEADER = std-header
    DBGOK=1
  @ENDIF
 -@IFEQ $(D) 1
 -  CXXFLAGS += -O0 -g3 -Werror
 +@IFEQ $(INSPIRCD_DEBUG) 1
 +  CORECXXFLAGS += -O0 -g3 -Werror -DINSPIRCD_ENABLE_RTTI
    HEADER = debug-header
    DBGOK=1
  @ENDIF
 -@IFEQ $(D) 2
 -  CXXFLAGS += -O2 -g3
 +@IFEQ $(INSPIRCD_DEBUG) 2
 +  CORECXXFLAGS += -fno-rtti -O2 -g3
    HEADER = debug-header
    DBGOK=1
  @ENDIF
  FOOTER = finishmessage
  
 -CXXFLAGS += -Iinclude
 +@TARGET GNU_MAKE MAKEFLAGS += --no-print-directory
  
 -@GNU_ONLY MAKEFLAGS += --no-print-directory
 +@TARGET GNU_MAKE SOURCEPATH = $(shell /bin/pwd)
 +@TARGET BSD_MAKE SOURCEPATH != /bin/pwd
  
 -@GNU_ONLY SOURCEPATH = $(shell /bin/pwd)
 -@BSD_ONLY SOURCEPATH != /bin/pwd
 -
 -@IFDEF V
 -  RUNCC = $(CC)
 -  RUNLD = $(CC)
 -  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)
 -  VERBOSE =
 +@IFNDEF INSPIRCD_VERBOSE
 +  @TARGET GNU_MAKE MAKEFLAGS += --silent
 +  @TARGET BSD_MAKE MAKE += -s
  @ENDIF
  
 -@IFDEF PURE_STATIC
 -  CXXFLAGS += -DPURE_STATIC
 +@IFDEF INSPIRCD_STATIC
 +  CORECXXFLAGS += -DINSPIRCD_STATIC
  @ENDIF
  
 -@DO_EXPORT RUNCC RUNLD CXXFLAGS LDLIBS PICLDFLAGS VERBOSE SOCKETENGINE CORELDFLAGS
 -@DO_EXPORT SOURCEPATH BUILDPATH PURE_STATIC SPLIT_CC
 +# Add the users CXXFLAGS to the base ones to allow them to override
 +# things like -Wfatal-errors if they wish to.
 +CORECXXFLAGS += $(CXXFLAGS)
 +
 +@DO_EXPORT CXX CORECXXFLAGS LDLIBS PICLDFLAGS INSPIRCD_VERBOSE SOCKETENGINE CORELDFLAGS
 +@DO_EXPORT SOURCEPATH BUILDPATH INSPIRCD_STATIC
  
  # Default target
  TARGET = all
  
 -@IFDEF M
 +@IFDEF INSPIRCD_MODULE
      HEADER = mod-header
      FOOTER = mod-footer
 -    @BSD_ONLY TARGET = modules/${M:S/.so$//}.so
 -    @GNU_ONLY TARGET = modules/$(M:.so=).so
 +    @TARGET BSD_MAKE TARGET = modules/${INSPIRCD_MODULE:S/.so$//}.so
 +    @TARGET GNU_MAKE TARGET = modules/$(INSPIRCD_MODULE:.so=).so
  @ENDIF
  
 -@IFDEF T
 +@IFDEF INSPIRCD_TARGET
      HEADER =
      FOOTER = target
 -    TARGET = $(T)
 +    TARGET = $(INSPIRCD_TARGET)
  @ENDIF
  
  @IFEQ $(DBGOK) 0
@@@ -154,10 -165,10 +154,10 @@@ all: $(FOOTER
  
  target: $(HEADER)
        $(MAKEENV) perl make/calcdep.pl
-       cd $(BUILDPATH); $(MAKEENV) $(MAKE) -f real.mk $(TARGET)
+       cd "$(BUILDPATH)"; $(MAKEENV) $(MAKE) -f real.mk $(TARGET)
  
  debug:
 -      @${MAKE} D=1 all
 +      @${MAKE} INSPIRCD_DEBUG=1 all
  
  debug-header:
        @echo "*************************************"
        @echo "*************************************"
  
  mod-header:
 -@IFDEF PURE_STATIC
 +@IFDEF INSPIRCD_STATIC
        @echo 'Cannot build single modules in pure-static build'
        @exit 1
  @ENDIF
@@@ -220,25 -231,14 +220,25 @@@ install: targe
        @-$(INSTALL) -d -m $(INSTMODE_DIR) $(BINPATH)
        @-$(INSTALL) -d -m $(INSTMODE_DIR) $(CONPATH)/examples/aliases
        @-$(INSTALL) -d -m $(INSTMODE_DIR) $(CONPATH)/examples/modules
 +      @-$(INSTALL) -d -m $(INSTMODE_DIR) $(MANPATH)
        @-$(INSTALL) -d -m $(INSTMODE_DIR) $(MODPATH)
-       [ $(BUILDPATH)/bin/ -ef $(BINPATH) ] || $(INSTALL) -m $(INSTMODE_BIN) $(BUILDPATH)/bin/inspircd $(BINPATH)
+       [ "$(BUILDPATH)/bin/" -ef $(BINPATH) ] || $(INSTALL) -m $(INSTMODE_BIN) "$(BUILDPATH)/bin/inspircd" $(BINPATH)
 -@IFNDEF PURE_STATIC
 +@IFNDEF INSPIRCD_STATIC
-       [ $(BUILDPATH)/modules/ -ef $(MODPATH) ] || $(INSTALL) -m $(INSTMODE_LIB) $(BUILDPATH)/modules/*.so $(MODPATH)
+       [ "$(BUILDPATH)/modules/" -ef $(MODPATH) ] || $(INSTALL) -m $(INSTMODE_LIB) "$(BUILDPATH)/modules/"*.so $(MODPATH)
  @ENDIF
 -      -$(INSTALL) -m $(INSTMODE_BIN) @STARTSCRIPT@ $(BASE) 2>/dev/null
 -      -$(INSTALL) -m $(INSTMODE_LIB) tools/gdbargs $(BASE)/.gdbargs 2>/dev/null
 +      -$(INSTALL) -m $(INSTMODE_BIN) @CONFIGURE_DIRECTORY@/inspircd $(BASE) 2>/dev/null
 +      -$(INSTALL) -m $(INSTMODE_LIB) .gdbargs $(BASE)/.gdbargs 2>/dev/null
 +@IFEQ $(SYSTEM) darwin
 +      -$(INSTALL) -m $(INSTMODE_BIN) @CONFIGURE_DIRECTORY@/org.inspircd.plist $(BASE) 2>/dev/null
 +@ENDIF
 +@IFEQ $(SYSTEM) linux
 +      -$(INSTALL) -m $(INSTMODE_LIB) @CONFIGURE_DIRECTORY@/inspircd.service $(BASE) 2>/dev/null
 +@ENDIF
 +      -$(INSTALL) -m $(INSTMODE_LIB) @CONFIGURE_DIRECTORY@/inspircd.1 $(MANPATH) 2>/dev/null
 +      -$(INSTALL) -m $(INSTMODE_LIB) @CONFIGURE_DIRECTORY@/inspircd-genssl.1 $(MANPATH) 2>/dev/null
 +      -$(INSTALL) -m $(INSTMODE_BIN) tools/genssl $(BINPATH)/inspircd-genssl 2>/dev/null
        -$(INSTALL) -m $(INSTMODE_LIB) docs/conf/*.example $(CONPATH)/examples
 +      -$(INSTALL) -m $(INSTMODE_LIB) *.pem $(CONPATH) 2>/dev/null
        -$(INSTALL) -m $(INSTMODE_LIB) docs/conf/aliases/*.example $(CONPATH)/examples/aliases
        -$(INSTALL) -m $(INSTMODE_LIB) docs/conf/modules/*.example $(CONPATH)/examples/modules
        @echo ""
        @echo 'Remember to create your config file:' $(CONPATH)/inspircd.conf
        @echo 'Examples are available at:' $(CONPATH)/examples/
  
 -@GNU_ONLY RCS_FILES = $(wildcard .git/index src/version.sh)
 -@BSD_ONLY RCS_FILES = src/version.sh
 -GNUmakefile BSDmakefile: make/template/main.mk configure $(RCS_FILES)
 -      ./configure -update
 -@BSD_ONLY .MAKEFILEDEPS: BSDmakefile
 +GNUmakefile BSDmakefile: make/template/main.mk src/version.sh configure @CONFIGURE_CACHE_FILE@
 +      ./configure --update
 +@TARGET BSD_MAKE .MAKEFILEDEPS: BSDmakefile
  
  clean:
        @echo Cleaning...
-       -rm -f $(BUILDPATH)/bin/inspircd $(BUILDPATH)/include $(BUILDPATH)/real.mk
-       -rm -rf $(BUILDPATH)/obj $(BUILDPATH)/modules
-       @-rmdir $(BUILDPATH)/bin 2>/dev/null
-       @-rmdir $(BUILDPATH) 2>/dev/null
+       -rm -f "$(BUILDPATH)/bin/inspircd" "$(BUILDPATH)/include" "$(BUILDPATH)/real.mk"
+       -rm -rf "$(BUILDPATH)/obj" "$(BUILDPATH)/modules"
+       @-rmdir "$(BUILDPATH)/bin" 2>/dev/null
+       @-rmdir "$(BUILDPATH)" 2>/dev/null
        @echo Completed.
  
  deinstall:
        -rm -f $(BINPATH)/inspircd
        -rm -rf $(CONPATH)/examples
 -      -rm -f $(MODPATH)/cmd_*.so
 +      -rm -f $(MANPATH)/inspircd.1
 +      -rm -f $(MANPATH)/inspircd-genssl.1
-       -rm -f $(MODPATH)/*.so
+       -rm -f $(MODPATH)/m_*.so
++      -rm -f $(MODPATH)/core_*.so
        -rm -f $(BASE)/.gdbargs
 +      -rm -f $(BASE)/inspircd.service
        -rm -f $(BASE)/org.inspircd.plist
  
 -squeakyclean: distclean
 -
  configureclean:
 -      rm -f .config.cache
 +      rm -f .gdbargs
        rm -f BSDmakefile
        rm -f GNUmakefile
 -      rm -f include/inspircd_config.h
 -      rm -f include/inspircd_version.h
 -      rm -f inspircd
 -      -rm -f org.inspircd.plist
 +      rm -f include/config.h
 +      rm -rf @CONFIGURE_DIRECTORY@
  
  distclean: clean configureclean
-       -rm -rf $(SOURCEPATH)/run
-       find $(SOURCEPATH)/src/modules -type l | xargs rm -f
+       -rm -rf "$(SOURCEPATH)/run"
+       find "$(SOURCEPATH)/src/modules" -type l | xargs rm -f
  
  help:
        @echo 'InspIRCd Makefile'
        @echo 'Use: ${MAKE} [flags] [targets]'
        @echo ''
        @echo 'Flags:'
 -      @echo ' V=1       Show the full command being executed instead of "BUILD: dns.cpp"'
 -      @echo ' D=1       Enable debug build, for module development or crash tracing'
 -      @echo ' D=2       Enable debug build with optimizations, for detailed backtraces'
 -      @echo ' DESTDIR=  Specify a destination root directory (for tarball creation)'
 -      @echo ' -j <N>    Run a parallel build using N jobs'
 +      @echo ' INSPIRCD_VERBOSE=1  Show the full command being executed instead of "BUILD: dns.cpp"'
 +      @echo ' INSPIRCD_DEBUG=1    Enable debug build, for module development or crash tracing'
 +      @echo ' INSPIRCD_DEBUG=2    Enable debug build with optimizations, for detailed backtraces'
 +      @echo ' DESTDIR=            Specify a destination root directory (for tarball creation)'
 +      @echo ' -j <N>              Run a parallel build using N jobs'
        @echo ''
        @echo 'Targets:'
        @echo ' all       Complete build of InspIRCd, without installing (default)'
        @echo '           Currently installs to ${BASE}'
        @echo ' debug     Compile a debug build. Equivalent to "make D=1 all"'
        @echo ''
 -      @echo ' M=m_foo   Builds a single module (cmd_foo also works here)'
 -      @echo ' T=target  Builds a user-specified target, such as "inspircd" or "modules"'
 -      @echo '           Other targets are specified by their path in the build directory'
 -      @echo '           Multiple targets may be separated by a space'
 +      @echo ' INSPIRCD_MODULE=m_foo   Builds a single module (core_foo also works here)'
 +      @echo ' INSPIRCD_TARGET=target  Builds a user-specified target, such as "inspircd" or "modules"'
 +      @echo '                         Other targets are specified by their path in the build directory'
 +      @echo '                         Multiple targets may be separated by a space'
        @echo ''
        @echo ' clean     Cleans object files produced by the compile'
        @echo ' distclean Cleans all generated files (build, configure, run, etc)'
  
  .NOTPARALLEL:
  
 -.PHONY: all target debug debug-header mod-header mod-footer std-header finishmessage install clean deinstall squeakyclean configureclean help
 +.PHONY: all target debug debug-header mod-header mod-footer std-header finishmessage install clean deinstall configureclean help
diff --combined src/configreader.cpp
index e607c6e7d72e635564cbbae5ab7cdf9b3d968b4e,301db14e87187c849db0150bd8221461ee7f1386..8a432e82f6a74f836038a4ebfdfb1627dd473196
  
  
  #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 = false;
 +      WildcardIPv6 = true;
        dns_timeout = 5;
        MaxTargets = 20;
        NetBufferSize = 10240;
 -      SoftLimit = ServerInstance->SE->GetMaxFds();
        MaxConn = SOMAXCONN;
        MaxChans = 20;
        OperMaxChans = 30;
        c_ipv4_range = 32;
        c_ipv6_range = 128;
 -
 -      std::vector<KeyVal>* items;
 -      EmptyTag = ConfigTag::create("empty", "<auto>", 0, items);
  }
  
  ServerConfig::~ServerConfig()
        delete EmptyTag;
  }
  
 -void ServerConfig::Update005()
 -{
 -      std::stringstream out(data005);
 -      std::vector<std::string> data;
 -      std::string token;
 -      while (out >> token)
 -              data.push_back(token);
 -      sort(data.begin(), data.end());
 -
 -      std::string line5;
 -      isupport.clear();
 -      for(unsigned int i=0; i < data.size(); i++)
 -      {
 -              token = data[i];
 -              line5 = line5 + token + " ";
 -              if (i % 13 == 12)
 -              {
 -                      line5.append(":are supported by this server");
 -                      isupport.push_back(line5);
 -                      line5.clear();
 -              }
 -      }
 -      if (!line5.empty())
 -      {
 -              line5.append(":are supported by this server");
 -              isupport.push_back(line5);
 -      }
 -}
 -
 -void ServerConfig::Send005(User* user)
 -{
 -      for (std::vector<std::string>::iterator line = ServerInstance->Config->isupport.begin(); line != ServerInstance->Config->isupport.end(); line++)
 -              user->WriteNumeric(RPL_ISUPPORT, "%s %s", user->nick.c_str(), line->c_str());
 -}
 -
 -template<typename T, typename V>
 -static void range(T& value, V min, V max, V def, const char* msg)
 -{
 -      if (value >= (T)min && value <= (T)max)
 -              return;
 -      ServerInstance->Logs->Log("CONFIG", DEFAULT,
 -              "WARNING: %s value of %ld is not between %ld and %ld; set to %ld.",
 -              msg, (long)value, (long)min, (long)max, (long)def);
 -      value = def;
 -}
 -
 -
 -static void ValidIP(const std::string& ip, const std::string& key)
 -{
 -      irc::sockets::sockaddrs dummy;
 -      if (!irc::sockets::aptosa(ip, 0, dummy))
 -              throw CoreException("The value of "+key+" is not an IP address");
 -}
 -
  static void ValidHost(const std::string& p, const std::string& msg)
  {
        int num_dots = 0;
@@@ -98,20 -139,79 +98,20 @@@ bool ServerConfig::ApplyDisabledCommand
        std::string thiscmd;
  
        /* Enable everything first */
 -      for (Commandtable::iterator x = ServerInstance->Parser->cmdlist.begin(); x != ServerInstance->Parser->cmdlist.end(); x++)
 +      const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands();
 +      for (CommandParser::CommandMap::const_iterator x = commands.begin(); x != commands.end(); ++x)
                x->second->Disable(false);
  
        /* Now disable all the ones which the user wants disabled */
        while (dcmds >> thiscmd)
        {
 -              Commandtable::iterator cm = ServerInstance->Parser->cmdlist.find(thiscmd);
 -              if (cm != ServerInstance->Parser->cmdlist.end())
 -              {
 -                      cm->second->Disable(true);
 -              }
 +              Command* handler = ServerInstance->Parser.GetHandler(thiscmd);
 +              if (handler)
 +                      handler->Disable(true);
        }
        return true;
  }
  
 -static void FindDNS(std::string& server)
 -{
 -      if (!server.empty())
 -              return;
 -#ifdef _WIN32
 -      // attempt to look up their nameserver from the system
 -      ServerInstance->Logs->Log("CONFIG",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)
 -                              server = pFixedInfo->DnsServerList.IpAddress.String;
 -
 -                      HeapFree(GetProcessHeap(), 0, pFixedInfo);
 -              }
 -
 -              if(!server.empty())
 -              {
 -                      ServerInstance->Logs->Log("CONFIG",DEFAULT,"<dns:server> set to '%s' as first active resolver in the system settings.", server.c_str());
 -                      return;
 -              }
 -      }
 -
 -      ServerInstance->Logs->Log("CONFIG",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",DEFAULT,"WARNING: <dns:server> not defined, attempting to find working server in /etc/resolv.conf...");
 -
 -      std::ifstream resolv("/etc/resolv.conf");
 -
 -      while (resolv >> server)
 -      {
 -              if (server == "nameserver")
 -              {
 -                      resolv >> server;
 -                      if (server.find_first_not_of("0123456789.") == std::string::npos)
 -                      {
 -                              ServerInstance->Logs->Log("CONFIG",DEFAULT,"<dns:server> set to '%s' as first resolver in /etc/resolv.conf.",server.c_str());
 -                              return;
 -                      }
 -              }
 -      }
 -
 -      ServerInstance->Logs->Log("CONFIG",DEFAULT,"/etc/resolv.conf contains no viable nameserver entries! Defaulting to nameserver '127.0.0.1'!");
 -#endif
 -      server = "127.0.0.1";
 -}
 -
  static void ReadXLine(ServerConfig* conf, const std::string& tag, const std::string& key, XLineFactory* make)
  {
        ConfigTagList tags = conf->ConfTags(tag);
@@@ -150,11 -250,13 +150,11 @@@ void ServerConfig::CrossCheckOperClassT
                std::string name = tag->getString("name");
                if (name.empty())
                        throw CoreException("<type:name> is missing from tag at " + tag->getTagLocation());
 -              if (!ServerInstance->IsNick(name.c_str(), Limits.NickMax))
 -                      throw CoreException("<type:name> is invalid (value '" + name + "')");
 -              if (oper_blocks.find(" " + name) != oper_blocks.end())
 +              if (OperTypes.find(name) != OperTypes.end())
                        throw CoreException("Duplicate type block with name " + name + " at " + tag->getTagLocation());
  
                OperInfo* ifo = new OperInfo;
 -              oper_blocks[" " + name] = ifo;
 +              OperTypes[name] = ifo;
                ifo->name = name;
                ifo->type_block = tag;
  
                        throw CoreException("<oper:name> missing from tag at " + tag->getTagLocation());
  
                std::string type = tag->getString("type");
 -              OperIndex::iterator tblk = oper_blocks.find(" " + type);
 -              if (tblk == oper_blocks.end())
 +              OperIndex::iterator tblk = OperTypes.find(type);
 +              if (tblk == OperTypes.end())
                        throw CoreException("Oper block " + name + " has missing type " + type);
                if (oper_blocks.find(name) != oper_blocks.end())
                        throw CoreException("Duplicate oper block with name " + name + " at " + tag->getTagLocation());
@@@ -203,7 -305,7 +203,7 @@@ void ServerConfig::CrossCheckConnectBlo
                for(ClassVector::iterator i = current->Classes.begin(); i != current->Classes.end(); ++i)
                {
                        ConnectClass* c = *i;
 -                      if (c->name.substr(0, 8) != "unnamed-")
 +                      if (c->name.compare(0, 8, "unnamed-", 8))
                        {
                                oldBlocksByMask["n" + c->name] = c;
                        }
                        me->maxchans = tag->getInt("maxchans", me->maxchans);
                        me->maxconnwarn = tag->getBool("maxconnwarn", me->maxconnwarn);
                        me->limit = tag->getInt("limit", me->limit);
 +                      me->resolvehostnames = tag->getBool("resolvehostnames", me->resolvehostnames);
  
                        ClassMap::iterator oldMask = oldBlocksByMask.find(typeMask);
                        if (oldMask != oldBlocksByMask.end())
  
  /** Represents a deprecated configuration tag.
   */
 -struct Deprecated
 +struct DeprecatedConfig
  {
 -      /** Tag name
 -       */
 -      const char* tag;
 -      /** Tag value
 -       */
 -      const char* value;
 -      /** Reason for deprecation
 -       */
 -      const char* reason;
 +      /** Tag name. */
 +      std::string tag;
 +
 +      /** Attribute key. */
 +      std::string key;
 +
 +      /** Attribute value. */
 +      std::string value;
 +
 +      /** Reason for deprecation. */
 +      std::string reason;
  };
  
 -static const Deprecated ChangedConfig[] = {
 -      {"options", "hidelinks",                "has been moved to <security:hidelinks> as of 1.2a3"},
 -      {"options", "hidewhois",                "has been moved to <security:hidewhois> as of 1.2a3"},
 -      {"options", "userstats",                "has been moved to <security:userstats> as of 1.2a3"},
 -      {"options", "customversion",    "has been moved to <security:customversion> as of 1.2a3"},
 -      {"options", "hidesplits",               "has been moved to <security:hidesplits> as of 1.2a3"},
 -      {"options", "hidebans",         "has been moved to <security:hidebans> as of 1.2a3"},
 -      {"options", "hidekills",                "has been moved to <security:hidekills> as of 1.2a3"},
 -      {"options", "operspywhois",             "has been moved to <security:operspywhois> as of 1.2a3"},
 -      {"options", "announceinvites",  "has been moved to <security:announceinvites> as of 1.2a3"},
 -      {"options", "hidemodes",                "has been moved to <security:hidemodes> as of 1.2a3"},
 -      {"options", "maxtargets",               "has been moved to <security:maxtargets> as of 1.2a3"},
 -      {"options",     "nouserdns",            "has been moved to <performance:nouserdns> as of 1.2a3"},
 -      {"options",     "maxwho",               "has been moved to <performance:maxwho> as of 1.2a3"},
 -      {"options",     "softlimit",            "has been moved to <performance:softlimit> as of 1.2a3"},
 -      {"options", "somaxconn",                "has been moved to <performance:somaxconn> as of 1.2a3"},
 -      {"options", "netbuffersize",    "has been moved to <performance:netbuffersize> as of 1.2a3"},
 -      {"options", "maxwho",           "has been moved to <performance:maxwho> as of 1.2a3"},
 -      {"options",     "loglevel",             "1.2+ does not use the loglevel value. Please define <log> tags instead."},
 -      {"die",     "value",            "you need to reread your config"},
 -      {"bind",    "transport",                "has been moved to <bind:ssl> as of 2.0a1"},
 -      {"link",    "transport",                "has been moved to <link:ssl> as of 2.0a1"},
 -      {"link",        "autoconnect",          "2.0+ does not use the autoconnect value. Please define <autoconnect> tags instead."},
 +static const DeprecatedConfig ChangedConfig[] = {
 +      { "bind",        "transport",   "",                 "has been moved to <bind:ssl> as of 2.0" },
 +      { "die",         "value",       "",                 "you need to reread your config" },
 +      { "gnutls",      "starttls",    "",                 "has been replaced with m_starttls as of 2.2" },
 +      { "link",        "autoconnect", "",                 "2.0+ does not use this attribute - define <autoconnect> tags instead" },
 +      { "link",        "transport",   "",                 "has been moved to <link:ssl> as of 2.0" },
 +      { "module",      "name",        "m_chanprotect.so", "has been replaced with m_customprefix as of 2.2" },
 +      { "module",      "name",        "m_halfop.so",      "has been replaced with m_customprefix as of 2.2" },
 +      { "options",     "cyclehosts",  "",                 "has been replaced with m_hostcycle as of 2.2" },
 +      { "performance", "nouserdns",   "",                 "has been moved to <connect:resolvehostnames> as of 2.2" }
  };
  
  void ServerConfig::Fill()
        ConfigTag* security = ConfValue("security");
        if (sid.empty())
        {
 -              ServerName = ConfValue("server")->getString("name");
 -              sid = ConfValue("server")->getString("id");
 +              ServerName = ConfValue("server")->getString("name", "irc.example.com");
                ValidHost(ServerName, "<server:name>");
 -              if (!sid.empty() && !ServerInstance->IsSID(sid))
 +
 +              sid = ConfValue("server")->getString("id");
 +              if (!sid.empty() && !InspIRCd::IsSID(sid))
                        throw CoreException(sid + " is not a valid server ID. A server ID must be 3 characters long, with the first character a digit and the next two characters a digit or letter.");
        }
        else
        {
                if (ServerName != ConfValue("server")->getString("name"))
 -                      throw CoreException("You must restart to change the server name or SID");
 +                      throw CoreException("You must restart to change the server name");
 +
                std::string nsid = ConfValue("server")->getString("id");
                if (!nsid.empty() && nsid != sid)
 -                      throw CoreException("You must restart to change the server name or SID");
 -      }
 -      diepass = ConfValue("power")->getString("diepass");
 -      restartpass = ConfValue("power")->getString("restartpass");
 -      powerhash = ConfValue("power")->getString("hash");
 -      PrefixQuit = options->getString("prefixquit");
 -      SuffixQuit = options->getString("suffixquit");
 -      FixedQuit = options->getString("fixedquit");
 -      PrefixPart = options->getString("prefixpart");
 -      SuffixPart = options->getString("suffixpart");
 -      FixedPart = options->getString("fixedpart");
 -      SoftLimit = ConfValue("performance")->getInt("softlimit", ServerInstance->SE->GetMaxFds());
 +                      throw CoreException("You must restart to change the server id");
 +      }
 +      SoftLimit = ConfValue("performance")->getInt("softlimit", (SocketEngine::GetMaxFds() > 0 ? SocketEngine::GetMaxFds() : LONG_MAX), 10);
 +      CCOnConnect = ConfValue("performance")->getBool("clonesonconnect", true);
        MaxConn = ConfValue("performance")->getInt("somaxconn", SOMAXCONN);
 -      MoronBanner = options->getString("moronbanner", "You're banned!");
 +      XLineMessage = options->getString("xlinemessage", options->getString("moronbanner", "You're banned!"));
        ServerDesc = ConfValue("server")->getString("description", "Configure Me");
        Network = ConfValue("server")->getString("network", "Network");
 -      AdminName = ConfValue("admin")->getString("name", "");
 -      AdminEmail = ConfValue("admin")->getString("email", "null@example.com");
 -      AdminNick = ConfValue("admin")->getString("nick", "admin");
 -      ModPath = ConfValue("path")->getString("moduledir", MOD_PATH);
 -      NetBufferSize = ConfValue("performance")->getInt("netbuffersize", 10240);
 +      NetBufferSize = ConfValue("performance")->getInt("netbuffersize", 10240, 1024, 65534);
        dns_timeout = ConfValue("dns")->getInt("timeout", 5);
        DisabledCommands = ConfValue("disabled")->getString("commands", "");
        DisabledDontExist = ConfValue("disabled")->getBool("fakenonexistant");
        UserStats = security->getString("userstats");
 -      CustomVersion = security->getString("customversion", Network + " IRCd");
 +      CustomVersion = security->getString("customversion");
        HideSplits = security->getBool("hidesplits");
        HideBans = security->getBool("hidebans");
        HideWhoisServer = security->getString("hidewhois");
        HideKillsServer = security->getString("hidekills");
+       HideULineKills = security->getBool("hideulinekills");
        RestrictBannedUsers = security->getBool("restrictbannedusers", true);
        GenericOper = security->getBool("genericoper");
 -      NoUserDns = ConfValue("performance")->getBool("nouserdns");
        SyntaxHints = options->getBool("syntaxhints");
 -      CycleHosts = options->getBool("cyclehosts");
        CycleHostsFromUser = options->getBool("cyclehostsfromuser");
 -      UndernetMsgPrefix = options->getBool("ircumsgprefix");
        FullHostInTopic = options->getBool("hostintopic");
 -      MaxTargets = security->getInt("maxtargets", 20);
 -      DefaultModes = options->getString("defaultmodes", "nt");
 +      MaxTargets = security->getInt("maxtargets", 20, 1, 31);
 +      DefaultModes = options->getString("defaultmodes", "not");
        PID = ConfValue("pid")->getString("file");
 -      WhoWasGroupSize = ConfValue("whowas")->getInt("groupsize");
 -      WhoWasMaxGroups = ConfValue("whowas")->getInt("maxgroups");
 -      WhoWasMaxKeep = ServerInstance->Duration(ConfValue("whowas")->getString("maxkeep"));
        MaxChans = ConfValue("channels")->getInt("users", 20);
 -      OperMaxChans = ConfValue("channels")->getInt("opers", 60);
 +      OperMaxChans = ConfValue("channels")->getInt("opers");
        c_ipv4_range = ConfValue("cidr")->getInt("ipv4clone", 32);
        c_ipv6_range = ConfValue("cidr")->getInt("ipv6clone", 128);
 -      Limits.NickMax = ConfValue("limits")->getInt("maxnick", 32);
 -      Limits.ChanMax = ConfValue("limits")->getInt("maxchan", 64);
 -      Limits.MaxModes = ConfValue("limits")->getInt("maxmodes", 20);
 -      Limits.IdentMax = ConfValue("limits")->getInt("maxident", 11);
 -      Limits.MaxQuit = ConfValue("limits")->getInt("maxquit", 255);
 -      Limits.MaxTopic = ConfValue("limits")->getInt("maxtopic", 307);
 -      Limits.MaxKick = ConfValue("limits")->getInt("maxkick", 255);
 -      Limits.MaxGecos = ConfValue("limits")->getInt("maxgecos", 128);
 -      Limits.MaxAway = ConfValue("limits")->getInt("maxaway", 200);
 -      InvBypassModes = options->getBool("invitebypassmodes", true);
 +      Limits = ServerLimits(ConfValue("limits"));
 +      Paths.Config = ConfValue("path")->getString("configdir", INSPIRCD_CONFIG_PATH);
 +      Paths.Data = ConfValue("path")->getString("datadir", INSPIRCD_DATA_PATH);
 +      Paths.Log = ConfValue("path")->getString("logdir", INSPIRCD_LOG_PATH);
 +      Paths.Module = ConfValue("path")->getString("moduledir", INSPIRCD_MODULE_PATH);
        NoSnoticeStack = options->getBool("nosnoticestack", false);
 -      WelcomeNotice = options->getBool("welcomenotice", true);
 -
 -      range(SoftLimit, 10, ServerInstance->SE->GetMaxFds(), ServerInstance->SE->GetMaxFds(), "<performance:softlimit>");
 -      if (ConfValue("performance")->getBool("limitsomaxconn", true))
 -              range(MaxConn, 0, SOMAXCONN, SOMAXCONN, "<performance:somaxconn>");
 -      range(MaxTargets, 1, 31, 20, "<security:maxtargets>");
 -      range(NetBufferSize, 1024, 65534, 10240, "<performance:netbuffersize>");
 -      range(WhoWasGroupSize, 0, 10000, 10, "<whowas:groupsize>");
 -      range(WhoWasMaxGroups, 0, 1000000, 10240, "<whowas:maxgroups>");
 -      range(WhoWasMaxKeep, 3600, INT_MAX, 3600, "<whowas:maxkeep>");
  
 -      ValidIP(DNSServer, "<dns:server>");
 +      if (Network.find(' ') != std::string::npos)
 +              throw CoreException(Network + " is not a valid network name. A network name must not contain spaces.");
  
        std::string defbind = options->getString("defaultbind");
        if (assign(defbind) == "ipv4")
                if (socktest < 0)
                        WildcardIPv6 = false;
                else
 -                      ServerInstance->SE->Close(socktest);
 -      }
 -      ConfigTagList tags = ConfTags("uline");
 -      for(ConfigIter i = tags.first; i != tags.second; ++i)
 -      {
 -              ConfigTag* tag = i->second;
 -              std::string server;
 -              if (!tag->readString("server", server))
 -                      throw CoreException("<uline> tag missing server at " + tag->getTagLocation());
 -              ulines[assign(server)] = tag->getBool("silent");
 -      }
 -
 -      tags = ConfTags("banlist");
 -      for(ConfigIter i = tags.first; i != tags.second; ++i)
 -      {
 -              ConfigTag* tag = i->second;
 -              std::string chan;
 -              if (!tag->readString("chan", chan))
 -                      throw CoreException("<banlist> tag missing chan at " + tag->getTagLocation());
 -              maxbans[chan] = tag->getInt("limit");
 +                      SocketEngine::Close(socktest);
        }
  
        ReadXLine(this, "badip", "ipmask", ServerInstance->XLines->GetFactory("Z"));
                DisabledCModes[*p - 'A'] = 1;
        }
  
 -      memset(HideModeLists, 0, sizeof(HideModeLists));
 -      modes = ConfValue("security")->getString("hidemodes");
 -      for (std::string::const_iterator p = modes.begin(); p != modes.end(); ++p)
 -              HideModeLists[(unsigned char) *p] = true;
 -
        std::string v = security->getString("announceinvites");
  
        if (v == "ops")
@@@ -508,7 -675,12 +509,7 @@@ void ServerConfig::Read(
        catch (CoreException& err)
        {
                valid = false;
 -              errstr << err.GetReason();
 -      }
 -      if (valid)
 -      {
 -              DNSServer = ConfValue("dns")->getString("server");
 -              FindDNS(DNSServer);
 +              errstr << err.GetReason() << std::endl;
        }
  }
  
@@@ -528,26 -700,16 +529,26 @@@ void ServerConfig::Apply(ServerConfig* 
        /* The stuff in here may throw CoreException, be sure we're in a position to catch it. */
        try
        {
 -              for (int Index = 0; Index * sizeof(Deprecated) < sizeof(ChangedConfig); Index++)
 +              for (int index = 0; index * sizeof(DeprecatedConfig) < sizeof(ChangedConfig); index++)
                {
 -                      std::string dummy;
 -                      ConfigTagList tags = ConfTags(ChangedConfig[Index].tag);
 +                      std::string value;
 +                      ConfigTagList tags = ConfTags(ChangedConfig[index].tag);
                        for(ConfigIter i = tags.first; i != tags.second; ++i)
                        {
 -                              if (i->second->readString(ChangedConfig[Index].value, dummy, true))
 -                                      errstr << "Your configuration contains a deprecated value: <"
 -                                              << ChangedConfig[Index].tag << ":" << ChangedConfig[Index].value << "> - " << ChangedConfig[Index].reason
 -                                              << " (at " << i->second->getTagLocation() << ")\n";
 +                              if (i->second->readString(ChangedConfig[index].key, value, true)
 +                                      && (ChangedConfig[index].value.empty() || value == ChangedConfig[index].value))
 +                              {
 +                                      errstr << "Your configuration contains a deprecated value: <"  << ChangedConfig[index].tag;
 +                                      if (ChangedConfig[index].value.empty())
 +                                      {
 +                                              errstr << ':' << ChangedConfig[index].key;
 +                                      }
 +                                      else
 +                                      {
 +                                              errstr << ' ' << ChangedConfig[index].key << "=\"" << ChangedConfig[index].value << "\"";
 +                                      }
 +                                      errstr << "> - " << ChangedConfig[index].reason << " (at " << i->second->getTagLocation() << ")" << std::endl;
 +                              }
                        }
                }
  
  
        // write once here, to try it out and make sure its ok
        if (valid)
-               ServerInstance->WritePID(this->PID);
+               ServerInstance->WritePID(this->PID, !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
                ServerInstance->BindPorts(pl);
                if (pl.size())
                {
 -                      errstr << "Not all your client ports could be bound.\nThe following port(s) failed to bind:\n";
 +                      errstr << "Not all your client ports could be bound." << std::endl
 +                              << "The following port(s) failed to bind:" << std::endl;
  
                        int j = 1;
                        for (FailedPortList::iterator i = pl.begin(); i != pl.end(); i++, j++)
                        {
 -                              char buf[MAXBUF];
 -                              snprintf(buf, MAXBUF, "%d.   Address: %s   Reason: %s\n", j, i->first.empty() ? "<all>" : i->first.c_str(), i->second.c_str());
 -                              errstr << buf;
 +                              errstr << j << ".\tAddress: " << (i->first.empty() ? "<all>" : i->first.c_str()) << "\tReason: "
 +                                      << i->second << std::endl;
                        }
                }
        }
  
        if (!valid)
        {
 -              ServerInstance->Logs->Log("CONFIG",DEFAULT, "There were errors in your configuration file:");
 +              ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "There were errors in your configuration file:");
                Classes.clear();
        }
  
                        std::cout << line << std::endl;
                // If a user is rehashing, tell them directly
                if (user)
 -                      user->SendText(":%s NOTICE %s :*** %s", ServerInstance->Config->ServerName.c_str(), user->nick.c_str(), line.c_str());
 +                      user->WriteRemoteNotice(InspIRCd::Format("*** %s", line.c_str()));
                // Also tell opers
                ServerInstance->SNO->WriteGlobalSno('a', line);
        }
                ConfigTag *tag = (*it)->config;
                // Make sure our connection class allows motd colors
                if(!tag->getBool("allowmotdcolors"))
 -                    continue;
 +                      continue;
  
                ConfigFileCache::iterator file = this->Files.find(tag->getString("motd", "motd"));
                if (file != this->Files.end())
 -                    InspIRCd::ProcessColors(file->second);
 -
 -              file = this->Files.find(tag->getString("rules", "rules"));
 -              if (file != this->Files.end())
 -                    InspIRCd::ProcessColors(file->second);
 +                      InspIRCd::ProcessColors(file->second);
        }
  
        /* No old configuration -> initial boot, nothing more to do here */
        ApplyModules(user);
  
        if (user)
 -              user->SendText(":%s NOTICE %s :*** Successfully rehashed server.",
 -                      ServerInstance->Config->ServerName.c_str(), user->nick.c_str());
 +              user->WriteRemoteNotice("*** Successfully rehashed server.");
        ServerInstance->SNO->WriteGlobalSno('a', "*** Successfully rehashed server.");
  }
  
  void ServerConfig::ApplyModules(User* user)
  {
 -      Module* whowas = ServerInstance->Modules->Find("cmd_whowas.so");
 -      if (whowas)
 -              WhowasRequest(NULL, whowas, WhowasRequest::WHOWAS_PRUNE).Send();
 -
 -      const std::vector<std::string> v = ServerInstance->Modules->GetAllModuleNames(0);
        std::vector<std::string> added_modules;
 -      std::set<std::string> removed_modules(v.begin(), v.end());
 +      ModuleManager::ModuleMap removed_modules = ServerInstance->Modules->GetModules();
  
        ConfigTagList tags = ConfTags("module");
        for(ConfigIter i = tags.first; i != tags.second; ++i)
                std::string name;
                if (tag->readString("name", name))
                {
 +                      name = ModuleManager::ExpandModName(name);
                        // if this module is already loaded, the erase will succeed, so we need do nothing
                        // otherwise, we need to add the module (which will be done later)
                        if (removed_modules.erase(name) == 0)
                }
        }
  
 -      if (ConfValue("options")->getBool("allowhalfop") && removed_modules.erase("m_halfop.so") == 0)
 -              added_modules.push_back("m_halfop.so");
 -
 -      for (std::set<std::string>::iterator removing = removed_modules.begin(); removing != removed_modules.end(); removing++)
 +      for (ModuleManager::ModuleMap::iterator i = removed_modules.begin(); i != removed_modules.end(); ++i)
        {
 -              // Don't remove cmd_*.so, just remove m_*.so
 -              if (removing->c_str()[0] == 'c')
 +              const std::string& modname = i->first;
 +              // Don't remove core_*.so, just remove m_*.so
 +              if (modname.c_str()[0] == 'c')
                        continue;
 -              Module* m = ServerInstance->Modules->Find(*removing);
 -              if (m && ServerInstance->Modules->Unload(m))
 +              if (ServerInstance->Modules->Unload(i->second))
                {
 -                      ServerInstance->SNO->WriteGlobalSno('a', "*** REHASH UNLOADED MODULE: %s",removing->c_str());
 +                      ServerInstance->SNO->WriteGlobalSno('a', "*** REHASH UNLOADED MODULE: %s", modname.c_str());
  
                        if (user)
 -                              user->WriteNumeric(RPL_UNLOADEDMODULE, "%s %s :Module %s successfully unloaded.",user->nick.c_str(), removing->c_str(), removing->c_str());
 +                              user->WriteNumeric(RPL_UNLOADEDMODULE, modname, InspIRCd::Format("Module %s successfully unloaded.", modname.c_str()));
                        else
 -                              ServerInstance->SNO->WriteGlobalSno('a', "Module %s successfully unloaded.", removing->c_str());
 +                              ServerInstance->SNO->WriteGlobalSno('a', "Module %s successfully unloaded.", modname.c_str());
                }
                else
                {
                        if (user)
 -                              user->WriteNumeric(ERR_CANTUNLOADMODULE, "%s %s :Failed to unload module %s: %s",user->nick.c_str(), removing->c_str(), removing->c_str(), ServerInstance->Modules->LastError().c_str());
 +                              user->WriteNumeric(ERR_CANTUNLOADMODULE, modname, InspIRCd::Format("Failed to unload module %s: %s", modname.c_str(), ServerInstance->Modules->LastError().c_str()));
                        else
 -                               ServerInstance->SNO->WriteGlobalSno('a', "Failed to unload module %s: %s", removing->c_str(), ServerInstance->Modules->LastError().c_str());
 +                              ServerInstance->SNO->WriteGlobalSno('a', "Failed to unload module %s: %s", modname.c_str(), ServerInstance->Modules->LastError().c_str());
                }
        }
  
        for (std::vector<std::string>::iterator adding = added_modules.begin(); adding != added_modules.end(); adding++)
        {
 -              if (ServerInstance->Modules->Load(adding->c_str()))
 +              if (ServerInstance->Modules->Load(*adding))
                {
                        ServerInstance->SNO->WriteGlobalSno('a', "*** REHASH LOADED MODULE: %s",adding->c_str());
                        if (user)
 -                              user->WriteNumeric(RPL_LOADEDMODULE, "%s %s :Module %s successfully loaded.",user->nick.c_str(), adding->c_str(), adding->c_str());
 +                              user->WriteNumeric(RPL_LOADEDMODULE, *adding, InspIRCd::Format("Module %s successfully loaded.", adding->c_str()));
                        else
                                ServerInstance->SNO->WriteGlobalSno('a', "Module %s successfully loaded.", adding->c_str());
                }
                else
                {
                        if (user)
 -                              user->WriteNumeric(ERR_CANTLOADMODULE, "%s %s :Failed to load module %s: %s",user->nick.c_str(), adding->c_str(), adding->c_str(), ServerInstance->Modules->LastError().c_str());
 +                              user->WriteNumeric(ERR_CANTLOADMODULE, *adding, InspIRCd::Format("Failed to load module %s: %s", adding->c_str(), ServerInstance->Modules->LastError().c_str()));
                        else
                                ServerInstance->SNO->WriteGlobalSno('a', "Failed to load module %s: %s", adding->c_str(), ServerInstance->Modules->LastError().c_str());
                }
        }
  }
  
 -bool ServerConfig::StartsWithWindowsDriveLetter(const std::string &path)
 -{
 -      return (path.length() > 2 && isalpha(path[0]) && path[1] == ':');
 -}
 -
  ConfigTag* ServerConfig::ConfValue(const std::string &tag)
  {
        ConfigTagList found = config_data.equal_range(tag);
        ConfigTag* rv = found.first->second;
        found.first++;
        if (found.first != found.second)
 -              ServerInstance->Logs->Log("CONFIG",DEFAULT, "Multiple <" + tag + "> tags found; only first will be used "
 +              ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Multiple <" + tag + "> tags found; only first will be used "
                        "(first at " + rv->getTagLocation() + "; second at " + found.first->second->getTagLocation() + ")");
        return rv;
  }
@@@ -738,28 -912,35 +739,28 @@@ ConfigTagList ServerConfig::ConfTags(co
        return config_data.equal_range(tag);
  }
  
 -bool ServerConfig::FileExists(const char* file)
 +std::string ServerConfig::Escape(const std::string& str, bool xml)
  {
 -      struct stat sb;
 -      if (stat(file, &sb) == -1)
 -              return false;
 -
 -      if ((sb.st_mode & S_IFDIR) > 0)
 -              return false;
 -
 -      FILE *input = fopen(file, "r");
 -      if (input == NULL)
 -              return false;
 -      else
 +      std::string escaped;
 +      for (std::string::const_iterator it = str.begin(); it != str.end(); ++it)
        {
 -              fclose(input);
 -              return true;
 +              switch (*it)
 +              {
 +                      case '"':
 +                              escaped += xml ? "&quot;" : "\"";
 +                              break;
 +                      case '&':
 +                              escaped += xml ? "&amp;" : "&";
 +                              break;
 +                      case '\\':
 +                              escaped += xml ? "\\" : "\\\\";
 +                              break;
 +                      default:
 +                              escaped += *it;
 +                              break;
 +              }
        }
 -}
 -
 -const char* ServerConfig::CleanFilename(const char* name)
 -{
 -      const char* p = name + strlen(name);
 -      while ((p != name) && (*p != '/') && (*p != '\\')) p--;
 -      return (p != name ? ++p : p);
 -}
 -
 -const std::string& ServerConfig::GetSID()
 -{
 -      return sid;
 +      return escaped;
  }
  
  void ConfigReaderThread::Run()
  void ConfigReaderThread::Finish()
  {
        ServerConfig* old = ServerInstance->Config;
 -      ServerInstance->Logs->Log("CONFIG",DEBUG,"Switching to new configuration...");
 +      ServerInstance->Logs->Log("CONFIG", LOG_DEBUG, "Switching to new configuration...");
        ServerInstance->Config = this->Config;
        Config->Apply(old, TheUserUID);
  
                 * 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 67829a55edf472715d7075e5c592ede7c6f2612a,0000000000000000000000000000000000000000..6a62d122f660e1e932b4e7d28463a19cbe13f61a
mode 100644,000000..100644
--- /dev/null
@@@ -1,118 -1,0 +1,117 @@@
-       /* Work around mIRC suckyness. YOU SUCK, KHALED! */
-       if (parameters.size() == 1)
 +/*
 + * 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");
 +
++      if ((parameters.size() == 1) && (!parameters[0].empty()))
 +      {
 +              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));
 +
 +              // If we're not in the channel and +s is set on it, we want to ignore it
 +              if ((n) || (!chan->IsModeSet(secretmode)))
 +              {
 +                      if ((!n) && (chan->IsModeSet(privatemode)))
 +                      {
 +                              // Channel is private (+p) and user is outside/not privileged
 +                              user->WriteNumeric(RPL_LIST, '*', users, "");
 +                      }
 +                      else
 +                      {
 +                              /* User is in the channel/privileged, channel is not +s */
 +                              user->WriteNumeric(RPL_LIST, chan->name, users, InspIRCd::Format("[+%s] %s", chan->ChanModes(n), chan->topic.c_str()));
 +                      }
 +              }
 +      }
 +      user->WriteNumeric(RPL_LISTEND, "End of channel list.");
 +
 +      return CMD_SUCCESS;
 +}
 +
 +
 +COMMAND_INIT(CommandList)
index 0d111ce02e911f5cd13a3758b547c39b4b6a3efb,0000000000000000000000000000000000000000..e75566e2fcea19bb079eb8bf53c1d870607a0903
mode 100644,000000..100644
--- /dev/null
@@@ -1,156 -1,0 +1,156 @@@
-                       if (!user->server->IsULine())
 +/*
 + * InspIRCd -- Internet Relay Chat Daemon
 + *
 + *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
 + *   Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
 + *   Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
 + *
 + * This file is part of InspIRCd.  InspIRCd is free software: you can
 + * redistribute it and/or modify it under the terms of the GNU General Public
 + * License as published by the Free Software Foundation, version 2.
 + *
 + * This program is distributed in the hope that it will be useful, but WITHOUT
 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 + * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 + * details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 + */
 +
 +
 +#include "inspircd.h"
 +#include "core_oper.h"
 +
 +CommandKill::CommandKill(Module* parent)
 +      : Command(parent, "KILL", 2, 2)
 +{
 +      flags_needed = 'o';
 +      syntax = "<nickname> <reason>";
 +      TRANSLATE2(TR_CUSTOM, TR_CUSTOM);
 +}
 +
 +
 +/** Handle /KILL
 + */
 +CmdResult CommandKill::Handle (const std::vector<std::string>& parameters, User *user)
 +{
 +      /* Allow comma seperated lists of users for /KILL (thanks w00t) */
 +      if (CommandParser::LoopCall(user, this, parameters, 0))
 +      {
 +              // If we got a colon delimited list of nicks then the handler ran for each nick,
 +              // and KILL commands were broadcast for remote targets.
 +              return CMD_FAILURE;
 +      }
 +
 +      User *u = ServerInstance->FindNick(parameters[0]);
 +      if (u)
 +      {
 +              /*
 +               * Here, we need to decide how to munge kill messages. Whether to hide killer, what to show opers, etc.
 +               * We only do this when the command is being issued LOCALLY, for remote KILL, we just copy the message we got.
 +               *
 +               * This conditional is so that we only append the "Killed (" prefix ONCE. If killer is remote, then the kill
 +               * just gets processed and passed on, otherwise, if they are local, it gets prefixed. Makes sense :-) -- w00t
 +               */
 +
 +              if (IS_LOCAL(user))
 +              {
 +                      /*
 +                       * Moved this event inside the IS_LOCAL check also, we don't want half the network killing a user
 +                       * and the other half not. This would be a bad thing. ;p -- w00t
 +                       */
 +                      ModResult MOD_RESULT;
 +                      FIRST_MOD_RESULT(OnKill, MOD_RESULT, (user, u, parameters[1]));
 +
 +                      if (MOD_RESULT == MOD_RES_DENY)
 +                              return CMD_FAILURE;
 +
 +                      killreason = "Killed (";
 +                      if (!ServerInstance->Config->HideKillsServer.empty())
 +                      {
 +                              // hidekills is on, use it
 +                              killreason += ServerInstance->Config->HideKillsServer;
 +                      }
 +                      else
 +                      {
 +                              // hidekills is off, do nothing
 +                              killreason += user->nick;
 +                      }
 +
 +                      killreason += " (" + parameters[1] + "))";
 +              }
 +              else
 +              {
 +                      /* Leave it alone, remote server has already formatted it */
 +                      killreason.assign(parameters[1], 0, ServerInstance->Config->Limits.MaxQuit);
 +              }
 +
 +              /*
 +               * Now we need to decide whether or not to send a local or remote snotice. Currently this checking is a little flawed.
 +               * No time to fix it right now, so left a note. -- w00t
 +               */
 +              if (!IS_LOCAL(u))
 +              {
 +                      // remote kill
-                       if (!user->server->IsULine())
++                      if ((!ServerInstance->Config->HideULineKills) || (!user->server->IsULine()))
 +                              ServerInstance->SNO->WriteToSnoMask('K', "Remote kill by %s: %s (%s)", user->nick.c_str(), u->GetFullRealHost().c_str(), parameters[1].c_str());
 +                      this->lastuuid = u->uuid;
 +              }
 +              else
 +              {
 +                      // local kill
 +                      /*
 +                       * XXX - this isn't entirely correct, servers A - B - C, oper on A, client on C. Oper kills client, A and B will get remote kill
 +                       * snotices, C will get a local kill snotice. this isn't accurate, and needs fixing at some stage. -- w00t
 +                       */
++                      if ((!ServerInstance->Config->HideULineKills) || (!user->server->IsULine()))
 +                      {
 +                              if (IS_LOCAL(user))
 +                                      ServerInstance->SNO->WriteGlobalSno('k',"Local Kill by %s: %s (%s)", user->nick.c_str(), u->GetFullRealHost().c_str(), parameters[1].c_str());
 +                              else
 +                                      ServerInstance->SNO->WriteToSnoMask('k',"Local Kill by %s: %s (%s)", user->nick.c_str(), u->GetFullRealHost().c_str(), parameters[1].c_str());
 +                      }
 +
 +                      ServerInstance->Logs->Log("KILL", LOG_DEFAULT, "LOCAL KILL: %s :%s!%s!%s (%s)", u->nick.c_str(), ServerInstance->Config->ServerName.c_str(), user->dhost.c_str(), user->nick.c_str(), parameters[1].c_str());
 +
 +                      u->Write(":%s KILL %s :%s!%s!%s (%s)", ServerInstance->Config->HideKillsServer.empty() ? user->GetFullHost().c_str() : ServerInstance->Config->HideKillsServer.c_str(),
 +                                      u->nick.c_str(),
 +                                      ServerInstance->Config->ServerName.c_str(),
 +                                      ServerInstance->Config->HideKillsServer.empty() ? user->dhost.c_str() : ServerInstance->Config->HideKillsServer.c_str(),
 +                                      ServerInstance->Config->HideKillsServer.empty() ? user->nick.c_str() : ServerInstance->Config->HideKillsServer.c_str(),
 +                                      parameters[1].c_str());
 +
 +                      this->lastuuid.clear();
 +              }
 +
 +              // send the quit out
 +              ServerInstance->Users->QuitUser(u, killreason);
 +      }
 +      else
 +      {
 +              user->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
 +              return CMD_FAILURE;
 +      }
 +
 +      return CMD_SUCCESS;
 +}
 +
 +RouteDescriptor CommandKill::GetRouting(User* user, const std::vector<std::string>& parameters)
 +{
 +      // FindNick() doesn't work here because we quit the target user in Handle() which
 +      // removes it from the nicklist, so we check lastuuid: if it's empty then this KILL
 +      // was for a local user, otherwise it contains the uuid of the user who was killed.
 +      if (lastuuid.empty())
 +              return ROUTE_LOCALONLY;
 +      return ROUTE_BROADCAST;
 +}
 +
 +
 +void CommandKill::EncodeParameter(std::string& param, int index)
 +{
 +      // Manually translate the nick -> uuid (see above), and also the reason (params[1])
 +      // because we decorate it if the oper is local and want remote servers to see the
 +      // decorated reason not the original.
 +      param = ((index == 0) ? lastuuid : killreason);
 +}
index d890d19ea2f93e1516a611b6d97b92d048ddad81,0000000000000000000000000000000000000000..ee0c50db2c5378068a04d2d41a27c152c0a9cb7a
mode 100644,000000..100644
--- /dev/null
@@@ -1,409 -1,0 +1,409 @@@
-       CommandStats ( Module* parent) : Command(parent,"STATS",1,2) { syntax = "<stats-symbol> [<servername>]"; }
 +/*
 + * 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(Stats::Context& stats);
 + public:
 +      /** Constructor for stats.
 +       */
++      CommandStats ( Module* parent) : Command(parent,"STATS",1,2) { allow_empty_last_param = false; syntax = "<stats-symbol> [<servername>]"; }
 +      /** Handle command.
 +       * @param parameters The parameters to the command
 +       * @param user The user issuing the command
 +       * @return A value from CmdResult to indicate command success or failure.
 +       */
 +      CmdResult Handle(const std::vector<std::string>& parameters, User *user);
 +      RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters)
 +      {
 +              if ((parameters.size() > 1) && (parameters[1].find('.') != std::string::npos))
 +                      return ROUTE_UNICAST(parameters[1]);
 +              return ROUTE_LOCALONLY;
 +      }
 +};
 +
 +static void GenerateStatsLl(Stats::Context& stats)
 +{
 +      stats.AddRow(211, InspIRCd::Format("nick[ident@%s] sendq cmds_out bytes_out cmds_in bytes_in time_open", (stats.GetSymbol() == 'l' ? "host" : "ip")));
 +
 +      const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
 +      for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
 +      {
 +              LocalUser* u = *i;
 +              stats.AddRow(211, u->nick+"["+u->ident+"@"+(stats.GetSymbol() == 'l' ? u->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(Stats::Context& stats)
 +{
 +      User* const user = stats.GetSource();
 +      const char statschar = stats.GetSymbol();
 +
 +      bool isPublic = ServerInstance->Config->UserStats.find(statschar) != std::string::npos;
 +      bool isRemoteOper = IS_REMOTE(user) && (user->IsOper());
 +      bool isLocalOperWithPrivs = IS_LOCAL(user) && user->HasPrivPermission("servers/auspex");
 +
 +      if (!isPublic && !isRemoteOper && !isLocalOperWithPrivs)
 +      {
 +              ServerInstance->SNO->WriteToSnoMask('t',
 +                              "%s '%c' denied for %s (%s@%s)",
 +                              (IS_LOCAL(user) ? "Stats" : "Remote stats"),
 +                              statschar, user->nick.c_str(), user->ident.c_str(), user->host.c_str());
 +              stats.AddRow(481, (std::string("Permission Denied - STATS ") + statschar + " requires the servers/auspex priv."));
 +              return;
 +      }
 +
 +      ModResult MOD_RESULT;
 +      FIRST_MOD_RESULT(OnStats, MOD_RESULT, (stats));
 +      if (MOD_RESULT == MOD_RES_DENY)
 +      {
 +              stats.AddRow(219, statschar, "End of /STATS report");
 +              ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)",
 +                      (IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->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("*");
 +                              else if (ip.find_first_of(':') != std::string::npos)
 +                              {
 +                                      ip.insert(ip.begin(), '[');
 +                                      ip.insert(ip.end(),  ']');
 +                              }
 +                              std::string type = ls->bind_tag->getString("type", "clients");
 +                              std::string hook = ls->bind_tag->getString("ssl", "plaintext");
 +
 +                              stats.AddRow(249, 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;
 +                              Stats::Row row(215);
 +                              row.push("I").push(c->name);
 +
 +                              std::string param;
 +                              if (c->type == CC_ALLOW)
 +                                      param.push_back('+');
 +                              if (c->type == CC_DENY)
 +                                      param.push_back('-');
 +
 +                              if (c->type == CC_NAMED)
 +                                      param.push_back('*');
 +                              else
 +                                      param.append(c->host);
 +
 +                              row.push(param).push(c->config->getString("port", "*"));
 +                              row.push(ConvToStr(c->GetRecvqMax())).push(ConvToStr(c->GetSendqSoftMax())).push(ConvToStr(c->GetSendqHardMax())).push(ConvToStr(c->GetCommandRate()));
 +
 +                              param = ConvToStr(c->GetPenaltyThreshold());
 +                              if (c->fakelag)
 +                                      param.push_back('*');
 +                              row.push(param);
 +
 +                              stats.AddRow(row);
 +                      }
 +              }
 +              break;
 +
 +              case 'Y':
 +              {
 +                      int idx = 0;
 +                      for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++)
 +                      {
 +                              ConnectClass* c = *i;
 +                              stats.AddRow(215, 'i', "NOMATCH", '*', c->GetHost(), (c->limit ? c->limit : SocketEngine::GetMaxFds()), idx, ServerInstance->Config->ServerName, '*');
 +                              stats.AddRow(218, 'Y', idx, c->GetPingTime(), '0', c->GetSendqHardMax(), ConvToStr(c->GetRecvqMax())+" "+ConvToStr(c->GetRegTimeout()));
 +                              idx++;
 +                      }
 +              }
 +              break;
 +
 +              case 'P':
 +              {
 +                      unsigned int idx = 0;
 +                      const UserManager::OperList& opers = ServerInstance->Users->all_opers;
 +                      for (UserManager::OperList::const_iterator i = opers.begin(); i != opers.end(); ++i)
 +                      {
 +                              User* oper = *i;
 +                              if (!oper->server->IsULine())
 +                              {
 +                                      LocalUser* lu = IS_LOCAL(oper);
 +                                      stats.AddRow(249, oper->nick + " (" + oper->ident + "@" + oper->dhost + ") Idle: " +
 +                                                      (lu ? ConvToStr(ServerInstance->Time() - lu->idle_lastmsg) + " secs" : "unavailable"));
 +                                      idx++;
 +                              }
 +                      }
 +                      stats.AddRow(249, ConvToStr(idx)+" OPER(s)");
 +              }
 +              break;
 +
 +              case 'k':
 +                      ServerInstance->XLines->InvokeStats("K",216,stats);
 +              break;
 +              case 'g':
 +                      ServerInstance->XLines->InvokeStats("G",223,stats);
 +              break;
 +              case 'q':
 +                      ServerInstance->XLines->InvokeStats("Q",217,stats);
 +              break;
 +              case 'Z':
 +                      ServerInstance->XLines->InvokeStats("Z",223,stats);
 +              break;
 +              case 'e':
 +                      ServerInstance->XLines->InvokeStats("E",223,stats);
 +              break;
 +              case 'E':
 +              {
 +                      const SocketEngine::Statistics& sestats = SocketEngine::GetStats();
 +                      stats.AddRow(249, "Total events: "+ConvToStr(sestats.TotalEvents));
 +                      stats.AddRow(249, "Read events:  "+ConvToStr(sestats.ReadEvents));
 +                      stats.AddRow(249, "Write events: "+ConvToStr(sestats.WriteEvents));
 +                      stats.AddRow(249, "Error events: "+ConvToStr(sestats.ErrorEvents));
 +                      break;
 +              }
 +
 +              /* stats m (list number of times each command has been used, plus bytecount) */
 +              case 'm':
 +              {
 +                      const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands();
 +                      for (CommandParser::CommandMap::const_iterator i = commands.begin(); i != commands.end(); ++i)
 +                      {
 +                              if (i->second->use_count)
 +                              {
 +                                      /* RPL_STATSCOMMANDS */
 +                                      stats.AddRow(212, i->second->name, i->second->use_count);
 +                              }
 +                      }
 +              }
 +              break;
 +
 +              /* stats z (debug and memory info) */
 +              case 'z':
 +              {
 +                      stats.AddRow(249, "Users: "+ConvToStr(ServerInstance->Users->GetUsers().size()));
 +                      stats.AddRow(249, "Channels: "+ConvToStr(ServerInstance->GetChans().size()));
 +                      stats.AddRow(249, "Commands: "+ConvToStr(ServerInstance->Parser.GetCommands().size()));
 +
 +                      float kbitpersec_in, kbitpersec_out, kbitpersec_total;
 +                      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);
 +
 +                      stats.AddRow(249, "Bandwidth total:  "+ConvToStr(kbitpersec_total_s)+" kilobits/sec");
 +                      stats.AddRow(249, "Bandwidth out:    "+ConvToStr(kbitpersec_out_s)+" kilobits/sec");
 +                      stats.AddRow(249, "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 */
 +                      {
 +                              stats.AddRow(249, "Total allocation: "+ConvToStr(R.ru_maxrss)+"K");
 +                              stats.AddRow(249, "Signals:          "+ConvToStr(R.ru_nsignals));
 +                              stats.AddRow(249, "Page faults:      "+ConvToStr(R.ru_majflt));
 +                              stats.AddRow(249, "Swaps:            "+ConvToStr(R.ru_nswap));
 +                              stats.AddRow(249, "Context Switches: Voluntary; "+ConvToStr(R.ru_nvcsw)+" Involuntary; "+ConvToStr(R.ru_nivcsw));
 +
 +                              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);
 +                              stats.AddRow(249, std::string("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);
 +                              stats.AddRow(249, std::string("CPU Use (total):  ")+percent);
 +                      }
 +#else
 +                      PROCESS_MEMORY_COUNTERS MemCounters;
 +                      if (GetProcessMemoryInfo(GetCurrentProcess(), &MemCounters, sizeof(MemCounters)))
 +                      {
 +                              stats.AddRow(249, "Total allocation: "+ConvToStr((MemCounters.WorkingSetSize + MemCounters.PagefileUsage) / 1024)+"K");
 +                              stats.AddRow(249, "Pagefile usage:   "+ConvToStr(MemCounters.PagefileUsage / 1024)+"K");
 +                              stats.AddRow(249, "Page faults:      "+ConvToStr(MemCounters.PageFaultCount));
 +                      }
 +
 +                      FILETIME CreationTime;
 +                      FILETIME ExitTime;
 +                      FILETIME KernelTime;
 +                      FILETIME UserTime;
 +                      LARGE_INTEGER ThisSample;
 +                      if(GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime) &&
 +                              QueryPerformanceCounter(&ThisSample))
 +                      {
 +                              KernelTime.dwHighDateTime += UserTime.dwHighDateTime;
 +                              KernelTime.dwLowDateTime += UserTime.dwLowDateTime;
 +                              double n_eaten = (double)( ( (uint64_t)(KernelTime.dwHighDateTime - ServerInstance->stats.LastCPU.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime - ServerInstance->stats.LastCPU.dwLowDateTime) )/100000;
 +                              double n_elapsed = (double)(ThisSample.QuadPart - ServerInstance->stats.LastSampled.QuadPart) / ServerInstance->stats.QPFrequency.QuadPart;
 +                              double per = (n_eaten/n_elapsed);
 +
 +                              char percent[30];
 +
 +                              snprintf(percent, 30, "%03.5f%%", per);
 +                              stats.AddRow(249, std::string("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);
 +                              stats.AddRow(249, std::string("CPU Use (total):  ")+percent));
 +                      }
 +#endif
 +              }
 +              break;
 +
 +              case 'T':
 +              {
 +                      stats.AddRow(249, "accepts "+ConvToStr(ServerInstance->stats.Accept)+" refused "+ConvToStr(ServerInstance->stats.Refused));
 +                      stats.AddRow(249, "unknown commands "+ConvToStr(ServerInstance->stats.Unknown));
 +                      stats.AddRow(249, "nick collisions "+ConvToStr(ServerInstance->stats.Collisions));
 +                      stats.AddRow(249, "dns requests "+ConvToStr(ServerInstance->stats.DnsGood+ServerInstance->stats.DnsBad)+" succeeded "+ConvToStr(ServerInstance->stats.DnsGood)+" failed "+ConvToStr(ServerInstance->stats.DnsBad));
 +                      stats.AddRow(249, "connection count "+ConvToStr(ServerInstance->stats.Connects));
 +                      stats.AddRow(249, InspIRCd::Format("bytes sent %5.2fK recv %5.2fK",
 +                              ServerInstance->stats.Sent / 1024.0, ServerInstance->stats.Recv / 1024.0));
 +              }
 +              break;
 +
 +              /* stats o */
 +              case 'o':
 +              {
 +                      ConfigTagList tags = ServerInstance->Config->ConfTags("oper");
 +                      for(ConfigIter i = tags.first; i != tags.second; ++i)
 +                      {
 +                              ConfigTag* tag = i->second;
 +                              stats.AddRow(243, 'O', tag->getString("host"), '*', tag->getString("name"), tag->getString("type"), '0');
 +                      }
 +              }
 +              break;
 +              case 'O':
 +              {
 +                      for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->OperTypes.begin(); i != ServerInstance->Config->OperTypes.end(); ++i)
 +                      {
 +                              OperInfo* tag = i->second;
 +                              tag->init();
 +                              std::string umodes;
 +                              std::string cmodes;
 +                              for(char c='A'; c <= 'z'; c++)
 +                              {
 +                                      ModeHandler* mh = ServerInstance->Modes->FindMode(c, MODETYPE_USER);
 +                                      if (mh && mh->NeedsOper() && tag->AllowedUserModes[c - 'A'])
 +                                              umodes.push_back(c);
 +                                      mh = ServerInstance->Modes->FindMode(c, MODETYPE_CHANNEL);
 +                                      if (mh && mh->NeedsOper() && tag->AllowedChanModes[c - 'A'])
 +                                              cmodes.push_back(c);
 +                              }
 +                              stats.AddRow(243, 'O', tag->name, umodes, cmodes);
 +                      }
 +              }
 +              break;
 +
 +              /* stats l (show user I/O stats) */
 +              case 'l':
 +              /* stats L (show user I/O stats with IP addresses) */
 +              case 'L':
 +                      GenerateStatsLl(stats);
 +              break;
 +
 +              /* stats u (show server uptime) */
 +              case 'u':
 +              {
 +                      unsigned int up = static_cast<unsigned int>(ServerInstance->Time() - ServerInstance->startup_time);
 +                      stats.AddRow(242, InspIRCd::Format("Server up %u days, %.2u:%.2u:%.2u",
 +                              up / 86400, (up / 3600) % 24, (up / 60) % 60, up % 60));
 +              }
 +              break;
 +
 +              default:
 +              break;
 +      }
 +
 +      stats.AddRow(219, statschar, "End of /STATS report");
 +      ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)",
 +              (IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->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;
 +      }
 +      Stats::Context stats(user, parameters[0][0]);
 +      DoStats(stats);
 +      const std::vector<Stats::Row>& rows = stats.GetRows();
 +      for (std::vector<Stats::Row>::const_iterator i = rows.begin(); i != rows.end(); ++i)
 +      {
 +              const Stats::Row& row = *i;
 +              user->WriteRemoteNumeric(row);
 +      }
 +
 +      return CMD_SUCCESS;
 +}
 +
 +COMMAND_INIT(CommandStats)
diff --combined src/inspircd.cpp
index eb24cdea0a2b0d924e8a116f6e4bbd7639c17bd5,0fa90fca5b6028ed764f94f6878d470b530320cb..5d4d8081f02772909c4b1a5cd934fc16da05bf43
@@@ -26,7 -26,9 +26,7 @@@
   */
  
  
 -/* $Core */
  #include "inspircd.h"
 -#include "inspircd_version.h"
  #include <signal.h>
  
  #ifndef _WIN32
  #include <fstream>
  #include <iostream>
  #include "xline.h"
 -#include "bancache.h"
 -#include "socketengine.h"
 -#include "socket.h"
 -#include "command_parse.h"
  #include "exitcodes.h"
 -#include "caller.h"
  #include "testsuite.h"
  
  InspIRCd* ServerInstance = NULL;
@@@ -71,26 -78,28 +71,26 @@@ unsigned const char *national_case_inse
   */
  const char* ExitCodes[] =
  {
 -              "No error", /* 0 */
 -              "DIE command", /* 1 */
 -              "execv() failed", /* 2 */
 -              "Internal error", /* 3 */
 -              "Config file error", /* 4 */
 -              "Logfile error", /* 5 */
 -              "POSIX fork failed", /* 6 */
 -              "Bad commandline parameters", /* 7 */
 -              "No ports could be bound", /* 8 */
 -              "Can't write PID file", /* 9 */
 -              "SocketEngine could not initialize", /* 10 */
 -              "Refusing to start up as root", /* 11 */
 -              "Found a <die> tag!", /* 12 */
 -              "Couldn't load module on startup", /* 13 */
 -              "Could not create windows forked process", /* 14 */
 -              "Received SIGTERM", /* 15 */
 -              "Bad command handler loaded", /* 16 */
 -              "RegisterServiceCtrlHandler failed", /* 17 */
 -              "UpdateSCMStatus failed", /* 18 */
 -              "CreateEvent failed" /* 19 */
 +              "No error",                                                             // 0
 +              "DIE command",                                                  // 1
 +              "Config file error",                                    // 2
 +              "Logfile error",                                                // 3
 +              "POSIX fork failed",                                    // 4
 +              "Bad commandline parameters",                   // 5
 +              "Can't write PID file",                                 // 6
 +              "SocketEngine could not initialize",    // 7
 +              "Refusing to start up as root",                 // 8
 +              "Couldn't load module on startup",              // 9
 +              "Received SIGTERM"                                              // 10
  };
  
 +#ifdef INSPIRCD_ENABLE_TESTSUITE
 +/** True if we have been told to run the testsuite from the commandline,
 + * rather than entering the mainloop.
 + */
 +static int do_testsuite = 0;
 +#endif
 +
  template<typename T> static void DeleteZero(T*&n)
  {
        T* t = n;
  
  void InspIRCd::Cleanup()
  {
 +      // Close all listening sockets
        for (unsigned int i = 0; i < ports.size(); i++)
        {
 -              /* This calls the constructor and closes the listening socket */
                ports[i]->cull();
                delete ports[i];
        }
        ports.clear();
  
 -      /* Close all client sockets, or the new process inherits them */
 -      LocalUserList::reverse_iterator i = Users->local_users.rbegin();
 -      while (i != this->Users->local_users.rend())
 -      {
 -              User* u = *i++;
 -              Users->QuitUser(u, "Server shutdown");
 -      }
 -
        GlobalCulls.Apply();
        Modules->UnloadAll();
  
        /* Delete objects dynamically allocated in constructor (destructor would be more appropriate, but we're likely exiting) */
        /* Must be deleted before modes as it decrements modelines */
        if (FakeClient)
 +      {
 +              delete FakeClient->server;
                FakeClient->cull();
 -      if (Res)
 -              Res->cull();
 +      }
        DeleteZero(this->FakeClient);
 -      DeleteZero(this->Users);
 -      DeleteZero(this->Modes);
        DeleteZero(this->XLines);
 -      DeleteZero(this->Parser);
 -      DeleteZero(this->stats);
 -      DeleteZero(this->Modules);
 -      DeleteZero(this->BanCache);
 -      DeleteZero(this->SNO);
        DeleteZero(this->Config);
 -      DeleteZero(this->Res);
 -      DeleteZero(this->chanlist);
 -      DeleteZero(this->PI);
 -      DeleteZero(this->Threads);
 -      DeleteZero(this->Timers);
 -      DeleteZero(this->SE);
 -      /* Close logging */
 -      this->Logs->CloseLogs();
 -      DeleteZero(this->Logs);
 -}
 -
 -void InspIRCd::Restart(const std::string &reason)
 -{
 -      /* SendError flushes each client's queue,
 -       * regardless of writeability state
 -       */
 -      this->SendError(reason);
 -
 -      /* Figure out our filename (if theyve renamed it, we're boned) */
 -      std::string me;
 -
 -      char** argv = Config->cmdline.argv;
 -
 -#ifdef _WIN32
 -      char module[MAX_PATH];
 -      if (GetModuleFileNameA(NULL, module, MAX_PATH))
 -              me = module;
 -#else
 -      me = argv[0];
 -#endif
 -
 -      this->Cleanup();
 -
 -      if (execv(me.c_str(), argv) == -1)
 -      {
 -              /* Will raise a SIGABRT if not trapped */
 -              throw CoreException(std::string("Failed to execv()! error: ") + strerror(errno));
 -      }
 -}
 -
 -void InspIRCd::ResetMaxBans()
 -{
 -      for (chan_hash::const_iterator i = chanlist->begin(); i != chanlist->end(); i++)
 -              i->second->ResetMaxBans();
 -}
 -
 -/** Because hash_map doesn't free its buckets when we delete items, we occasionally
 - * recreate the hash to free them up.
 - * We do this by copying the entries from the old hash to a new hash, causing all
 - * empty buckets to be weeded out of the hash.
 - * Since this is quite expensive, it's not done very often.
 - */
 -void InspIRCd::RehashUsersAndChans()
 -{
 -      user_hash* old_users = Users->clientlist;
 -      Users->clientlist = new user_hash;
 -      for (user_hash::const_iterator n = old_users->begin(); n != old_users->end(); n++)
 -              Users->clientlist->insert(*n);
 -      delete old_users;
 -
 -      user_hash* old_uuid = Users->uuidlist;
 -      Users->uuidlist = new user_hash;
 -      for (user_hash::const_iterator n = old_uuid->begin(); n != old_uuid->end(); n++)
 -              Users->uuidlist->insert(*n);
 -      delete old_uuid;
 -
 -      chan_hash* old_chans = chanlist;
 -      chanlist = new chan_hash;
 -      for (chan_hash::const_iterator n = old_chans->begin(); n != old_chans->end(); n++)
 -              chanlist->insert(*n);
 -      delete old_chans;
 -
 -      // Reset the already_sent IDs so we don't wrap it around and drop a message
 -      LocalUser::already_sent_id = 0;
 -      for (LocalUserList::const_iterator i = Users->local_users.begin(); i != Users->local_users.end(); i++)
 -      {
 -              (**i).already_sent = 0;
 -              (**i).RemoveExpiredInvites();
 -      }
 +      SocketEngine::Deinit();
 +      Logs->CloseLogs();
  }
  
  void InspIRCd::SetSignals()
@@@ -158,8 -258,8 +158,8 @@@ bool InspIRCd::DaemonSeed(
        // Do not use QuickExit here: It will exit with status SIGTERM which would break e.g. daemon scripts
        signal(SIGTERM, VoidSignalHandler);
  
 -      int childpid;
 -      if ((childpid = fork ()) < 0)
 +      int childpid = fork();
 +      if (childpid < 0)
                return false;
        else if (childpid > 0)
        {
        rlimit rl;
        if (getrlimit(RLIMIT_CORE, &rl) == -1)
        {
 -              this->Logs->Log("STARTUP",DEFAULT,"Failed to getrlimit()!");
 +              this->Logs->Log("STARTUP", LOG_DEFAULT, "Failed to getrlimit()!");
                return false;
        }
        rl.rlim_cur = rl.rlim_max;
  
        if (setrlimit(RLIMIT_CORE, &rl) == -1)
 -                      this->Logs->Log("STARTUP",DEFAULT,"setrlimit() failed, cannot increase coredump size.");
 +                      this->Logs->Log("STARTUP", LOG_DEFAULT, "setrlimit() failed, cannot increase coredump size.");
  
        return true;
  #endif
  }
  
- void InspIRCd::WritePID(const std::string &filename)
+ void InspIRCd::WritePID(const std::string& filename, bool exitonfail)
  {
  #ifndef _WIN32
        std::string fname(filename);
        if (fname.empty())
 -              fname = DATA_PATH "/inspircd.pid";
 +              fname = ServerInstance->Config->Paths.PrependData("inspircd.pid");
        std::ofstream outfile(fname.c_str());
        if (outfile.is_open())
        {
                outfile.close();
        }
        else
 -      {\r
 -              if (exitonfail)\r
 +      {
-               std::cout << "Failed to write PID-file '" << fname << "', exiting." << std::endl;
-               this->Logs->Log("STARTUP", LOG_DEFAULT, "Failed to write PID-file '%s', exiting.",fname.c_str());
-               Exit(EXIT_STATUS_PID);
++              if (exitonfail)
+                       std::cout << "Failed to write PID-file '" << fname << "', exiting." << std::endl;
 -              this->Logs->Log("STARTUP",DEFAULT,"Failed to write PID-file '%s'%s",fname.c_str(), (exitonfail ? ", exiting." : ""));\r
++              this->Logs->Log("STARTUP", LOG_DEFAULT, "Failed to write PID-file '%s'%s", fname.c_str(), (exitonfail ? ", exiting." : ""));
+               if (exitonfail)
 -                      Exit(EXIT_STATUS_PID);\r
++                      Exit(EXIT_STATUS_PID);
        }
  #endif
  }
  
  InspIRCd::InspIRCd(int argc, char** argv) :
 -       ConfigFileName(CONFIG_PATH "/inspircd.conf"),
 +       ConfigFileName(INSPIRCD_CONFIG_PATH "/inspircd.conf"),
 +       PI(&DefaultProtocolInterface),
  
         /* Functor pointer initialisation.
          *
          * THIS MUST MATCH THE ORDER OF DECLARATION OF THE FUNCTORS, e.g. the methods
          * themselves within the class.
          */
 -       NICKForced("NICKForced", NULL),
 -       OperQuit("OperQuit", NULL),
 +       OperQuit("operquit", ExtensionItem::EXT_USER, NULL),
         GenRandom(&HandleGenRandom),
         IsChannel(&HandleIsChannel),
 -       IsSID(&HandleIsSID),
 -       Rehash(&HandleRehash),
         IsNick(&HandleIsNick),
         IsIdent(&HandleIsIdent),
 -       FloodQuitUser(&HandleFloodQuitUser),
         OnCheckExemption(&HandleOnCheckExemption)
  {
        ServerInstance = this;
  
 -      Extensions.Register(&NICKForced);
        Extensions.Register(&OperQuit);
  
        FailedPortList pl;
 +      // Flag variables passed to getopt_long() later
        int do_version = 0, do_nofork = 0, do_debug = 0,
 -          do_nolog = 0, do_root = 0, do_testsuite = 0;    /* flag variables */
 -      int c = 0;
 +          do_nolog = 0, do_root = 0;
  
        // Initialize so that if we exit before proper initialization they're not deleted
 -      this->Logs = 0;
 -      this->Threads = 0;
 -      this->PI = 0;
 -      this->Users = 0;
 -      this->chanlist = 0;
        this->Config = 0;
 -      this->SNO = 0;
 -      this->BanCache = 0;
 -      this->Modules = 0;
 -      this->stats = 0;
 -      this->Timers = 0;
 -      this->Parser = 0;
        this->XLines = 0;
 -      this->Modes = 0;
 -      this->Res = 0;
        this->ConfigThread = NULL;
        this->FakeClient = NULL;
  
        UpdateTime();
        this->startup_time = TIME.tv_sec;
  
 -      // This must be created first, so other parts of Insp can use it while starting up
 -      this->Logs = new LogManager;
 -
 -      SE = CreateSocketEngine();
 -
 -      this->Threads = new ThreadEngine;
 -
 -      /* Default implementation does nothing */
 -      this->PI = new ProtocolInterface;
 -
 -      this->s_signal = 0;
 -
 -      // Create base manager classes early, so nothing breaks
 -      this->Users = new UserManager;
 -
 -      this->Users->clientlist = new user_hash();
 -      this->Users->uuidlist = new user_hash();
 -      this->chanlist = new chan_hash();
 +      SocketEngine::Init();
  
        this->Config = new ServerConfig;
 -      this->SNO = new SnomaskManager;
 -      this->BanCache = new BanCacheManager;
 -      this->Modules = new ModuleManager();
 -      this->stats = new serverstats();
 -      this->Timers = new TimerManager;
 -      this->Parser = new CommandParser;
 +      dynamic_reference_base::reset_all();
        this->XLines = new XLineManager;
  
        this->Config->cmdline.argv = argv;
        struct option longopts[] =
        {
                { "nofork",     no_argument,            &do_nofork,     1       },
 -              { "logfile",    required_argument,      NULL,           'f'     },
                { "config",     required_argument,      NULL,           'c'     },
                { "debug",      no_argument,            &do_debug,      1       },
                { "nolog",      no_argument,            &do_nolog,      1       },
                { "runasroot",  no_argument,            &do_root,       1       },
                { "version",    no_argument,            &do_version,    1       },
 +#ifdef INSPIRCD_ENABLE_TESTSUITE
                { "testsuite",  no_argument,            &do_testsuite,  1       },
 +#endif
                { 0, 0, 0, 0 }
        };
  
 +      int c;
        int index;
 -      while ((c = getopt_long(argc, argv, ":c:f:", longopts, &index)) != -1)
 +      while ((c = getopt_long(argc, argv, ":c:", longopts, &index)) != -1)
        {
                switch (c)
                {
 -                      case 'f':
 -                              /* Log filename was set */
 -                              Config->cmdline.startup_log = optarg;
 -                      break;
                        case 'c':
                                /* Config filename was set */
 -                              ConfigFileName = optarg;
 +                              ConfigFileName = ServerInstance->Config->Paths.PrependConfig(optarg);
                        break;
                        case 0:
                                /* getopt_long_only() set an int variable, just keep going */
                        default:
                                /* Fall through to handle other weird values too */
                                std::cout << "Unknown parameter '" << argv[optind-1] << "'" << std::endl;
 -                              std::cout << "Usage: " << argv[0] << " [--nofork] [--nolog] [--debug] [--logfile <filename>] " << std::endl <<
 -                                      std::string(static_cast<int>(8+strlen(argv[0])), ' ') << "[--runasroot] [--version] [--config <config>] [--testsuite]" << std::endl;
 +                              std::cout << "Usage: " << argv[0] << " [--nofork] [--nolog] [--debug] [--config <config>]" << std::endl <<
 +                                      std::string(static_cast<int>(8+strlen(argv[0])), ' ') << "[--runasroot] [--version]" << std::endl;
                                Exit(EXIT_STATUS_ARGV);
                        break;
                }
        }
  
 +#ifdef INSPIRCD_ENABLE_TESTSUITE
        if (do_testsuite)
                do_nofork = do_debug = true;
 +#endif
  
        if (do_version)
        {
 -              std::cout << std::endl << VERSION << " r" << REVISION << std::endl;
 +              std::cout << std::endl << INSPIRCD_VERSION << " " << INSPIRCD_REVISION << std::endl;
                Exit(EXIT_STATUS_NOERROR);
        }
  
        Config->cmdline.nofork = (do_nofork != 0);
        Config->cmdline.forcedebug = (do_debug != 0);
        Config->cmdline.writelog = !do_nolog;
 -      Config->cmdline.TestSuite = (do_testsuite != 0);
  
        if (do_debug)
        {
                FileWriter* fw = new FileWriter(stdout);
 -              FileLogStream* fls = new FileLogStream(RAWIO, fw);
 +              FileLogStream* fls = new FileLogStream(LOG_RAWIO, fw);
                Logs->AddLogTypes("*", fls, true);
        }
 -      else if (!this->OpenLog(argv, argc))
 -      {
 -              std::cout << "ERROR: Could not open initial logfile " << Config->cmdline.startup_log << ": " << strerror(errno) << std::endl << std::endl;
 -              Exit(EXIT_STATUS_LOG);
 -      }
  
 -      if (!ServerConfig::FileExists(ConfigFileName.c_str()))
 +      if (!FileSystem::FileExists(ConfigFileName))
        {
  #ifdef _WIN32
                /* Windows can (and defaults to) hide file extensions, so let's play a bit nice for windows users. */
                std::string txtconf = this->ConfigFileName;
                txtconf.append(".txt");
  
 -              if (ServerConfig::FileExists(txtconf.c_str()))
 +              if (FileSystem::FileExists(txtconf))
                {
                        ConfigFileName = txtconf;
                }
  #endif
                {
                        std::cout << "ERROR: Cannot open config file: " << ConfigFileName << std::endl << "Exiting..." << std::endl;
 -                      this->Logs->Log("STARTUP",DEFAULT,"Unable to open config file %s", ConfigFileName.c_str());
 +                      this->Logs->Log("STARTUP", LOG_DEFAULT, "Unable to open config file %s", ConfigFileName.c_str());
                        Exit(EXIT_STATUS_CONFIG);
                }
        }
  
 -      std::cout << con_green << "Inspire Internet Relay Chat Server" << con_reset << ", compiled on " __DATE__ " at " __TIME__ << std::endl;
 -      std::cout << con_green << "(C) InspIRCd Development Team." << con_reset << std::endl << std::endl;
 -      std::cout << "Developers:" << std::endl;
 -      std::cout << con_green << "\tBrain, FrostyCoolSlug, w00t, Om, Special, peavey" << std::endl;
 -      std::cout << "\taquanight, psychon, dz, danieldg, jackmcbarn" << std::endl;\r
 -      std::cout << "\tAttila" << con_reset << std::endl << std::endl;
 -      std::cout << "Others:\t\t\t" << con_green << "See /INFO Output" << con_reset << std::endl;
 -
 -      this->Modes = new ModeParser;
 +      std::cout << con_green << "InspIRCd - Internet Relay Chat Daemon" << con_reset << ", compiled on " __DATE__ " at " __TIME__ << std::endl;
 +      std::cout << "For contributors & authors: " << con_green << "See /INFO Output" << con_reset << std::endl;
  
  #ifndef _WIN32
        if (!do_root)
                this->CheckRoot();
        else
        {
 -              std::cout << "* WARNING * WARNING * WARNING * WARNING * WARNING *" << std::endl\r
 -              << "YOU ARE RUNNING INSPIRCD AS ROOT. THIS IS UNSUPPORTED" << std::endl\r
 -              << "AND IF YOU ARE HACKED, CRACKED, SPINDLED OR MUTILATED" << std::endl\r
 -              << "OR ANYTHING ELSE UNEXPECTED HAPPENS TO YOU OR YOUR" << std::endl\r
 -              << "SERVER, THEN IT IS YOUR OWN FAULT. IF YOU DID NOT MEAN" << std::endl\r
 -              << "TO START INSPIRCD AS ROOT, HIT CTRL+C NOW AND RESTART" << std::endl\r
 -              << "THE PROGRAM AS A NORMAL USER. YOU HAVE BEEN WARNED!" << std::endl << std::endl\r
 -              << "InspIRCd starting in 20 seconds, ctrl+c to abort..." << std::endl;\r
 +              std::cout << "* WARNING * WARNING * WARNING * WARNING * WARNING *" << std::endl
 +              << "YOU ARE RUNNING INSPIRCD AS ROOT. THIS IS UNSUPPORTED" << std::endl
 +              << "AND IF YOU ARE HACKED, CRACKED, SPINDLED OR MUTILATED" << std::endl
 +              << "OR ANYTHING ELSE UNEXPECTED HAPPENS TO YOU OR YOUR" << std::endl
 +              << "SERVER, THEN IT IS YOUR OWN FAULT. IF YOU DID NOT MEAN" << std::endl
 +              << "TO START INSPIRCD AS ROOT, HIT CTRL+C NOW AND RESTART" << std::endl
 +              << "THE PROGRAM AS A NORMAL USER. YOU HAVE BEEN WARNED!" << std::endl << std::endl
 +              << "InspIRCd starting in 20 seconds, ctrl+c to abort..." << std::endl;
                sleep(20);
        }
  #endif
                if (!this->DaemonSeed())
                {
                        std::cout << "ERROR: could not go into daemon mode. Shutting down." << std::endl;
 -                      Logs->Log("STARTUP", DEFAULT, "ERROR: could not go into daemon mode. Shutting down.");
 +                      Logs->Log("STARTUP", LOG_DEFAULT, "ERROR: could not go into daemon mode. Shutting down.");
                        Exit(EXIT_STATUS_FORK);
                }
        }
  
 -      SE->RecoverFromFork();
 +      SocketEngine::RecoverFromFork();
  
 -      /* During startup we don't actually initialize this
 -       * in the thread engine.
 +      /* During startup we read the configuration now, not in
 +       * a seperate thread
         */
        this->Config->Read();
        this->Config->Apply(NULL, "");
        Logs->OpenFileLogs();
 +      ModeParser::InitBuiltinModes();
  
 -      this->Res = new DNS();
 -
 -      /*
 -       * Initialise SID/UID.
 -       * For an explanation as to exactly how this works, and why it works this way, see GetUID().
 -       *   -- w00t
 -       */
 +      // If we don't have a SID, generate one based on the server name and the server description
        if (Config->sid.empty())
 -      {
 -              // Generate one
 -              unsigned int sid = 0;
 -              char sidstr[4];
 +              Config->sid = UIDGenerator::GenerateSID(Config->ServerName, Config->ServerDesc);
  
 -              for (const char* x = Config->ServerName.c_str(); *x; ++x)
 -                      sid = 5 * sid + *x;
 -              for (const char* y = Config->ServerDesc.c_str(); *y; ++y)
 -                      sid = 5 * sid + *y;
 -              sprintf(sidstr, "%03d", sid % 1000);
 +      // Initialize the UID generator with our sid
 +      this->UIDGen.init(Config->sid);
  
 -              Config->sid = sidstr;
 -      }
 +      // Create the server user for this server
 +      this->FakeClient = new FakeUser(Config->sid, Config->ServerName, Config->ServerDesc);
  
 -      /* set up fake client again this time with the correct uid */
 -      this->FakeClient = new FakeUser(Config->sid, Config->ServerName);
 -
 -      // Get XLine to do it's thing.
 -      this->XLines->CheckELines();
 +      // This is needed as all new XLines are marked pending until ApplyLines() is called
        this->XLines->ApplyLines();
  
        int bounditems = BindPorts(pl);
  
        this->Modules->LoadAll();
  
 -      /* Just in case no modules were loaded - fix for bug #101 */
 -      this->BuildISupport();
 +      // Build ISupport as ModuleManager::LoadAll() does not do it
 +      this->ISupport.Build();
        Config->ApplyDisabledCommands(Config->DisabledCommands);
  
        if (!pl.empty())
                std::cout << std::endl << "Hint: Try using a public IP instead of blank or *" << std::endl;
        }
  
 -      std::cout << "InspIRCd is now running as '" << Config->ServerName << "'[" << Config->GetSID() << "] with " << SE->GetMaxFds() << " max open sockets" << std::endl;
 +      std::cout << "InspIRCd is now running as '" << Config->ServerName << "'[" << Config->GetSID() << "] with " << SocketEngine::GetMaxFds() << " max open sockets" << std::endl;
  
  #ifndef _WIN32
        if (!Config->cmdline.nofork)
                if (kill(getppid(), SIGTERM) == -1)
                {
                        std::cout << "Error killing parent process: " << strerror(errno) << std::endl;
 -                      Logs->Log("STARTUP", DEFAULT, "Error killing parent process: %s",strerror(errno));
 +                      Logs->Log("STARTUP", LOG_DEFAULT, "Error killing parent process: %s",strerror(errno));
                }
        }
  
         *
         *    -- nenolod
         */
 -      if ((!do_nofork) && (!do_testsuite) && (!Config->cmdline.forcedebug))
 +      if ((!do_nofork) && (!Config->cmdline.forcedebug))
        {
                int fd = open("/dev/null", O_RDWR);
  
                fclose(stdout);
  
                if (dup2(fd, STDIN_FILENO) < 0)
 -                      Logs->Log("STARTUP", DEFAULT, "Failed to dup /dev/null to stdin.");
 +                      Logs->Log("STARTUP", LOG_DEFAULT, "Failed to dup /dev/null to stdin.");
                if (dup2(fd, STDOUT_FILENO) < 0)
 -                      Logs->Log("STARTUP", DEFAULT, "Failed to dup /dev/null to stdout.");
 +                      Logs->Log("STARTUP", LOG_DEFAULT, "Failed to dup /dev/null to stdout.");
                if (dup2(fd, STDERR_FILENO) < 0)
 -                      Logs->Log("STARTUP", DEFAULT, "Failed to dup /dev/null to stderr.");
 +                      Logs->Log("STARTUP", LOG_DEFAULT, "Failed to dup /dev/null to stderr.");
                close(fd);
        }
        else
        {
 -              Logs->Log("STARTUP", DEFAULT,"Keeping pseudo-tty open as we are running in the foreground.");
 +              Logs->Log("STARTUP", LOG_DEFAULT, "Keeping pseudo-tty open as we are running in the foreground.");
        }
  #else
        /* Set win32 service as running, if we are running as a service */
                FreeConsole();
        }
  
 -      QueryPerformanceFrequency(&stats->QPFrequency);
 +      QueryPerformanceFrequency(&stats.QPFrequency);
  #endif
  
 -      Logs->Log("STARTUP", DEFAULT, "Startup complete as '%s'[%s], %d max open sockets", Config->ServerName.c_str(),Config->GetSID().c_str(), SE->GetMaxFds());
 +      Logs->Log("STARTUP", LOG_DEFAULT, "Startup complete as '%s'[%s], %d max open sockets", Config->ServerName.c_str(),Config->GetSID().c_str(), SocketEngine::GetMaxFds());
  
  #ifndef _WIN32
        std::string SetUser = Config->ConfValue("security")->getString("runasuser");
  
                if (ret == -1)
                {
 -                      this->Logs->Log("SETGROUPS", DEFAULT, "setgroups() failed (wtf?): %s", strerror(errno));
 +                      this->Logs->Log("STARTUP", LOG_DEFAULT, "setgroups() failed (wtf?): %s", strerror(errno));
                        this->QuickExit(0);
                }
  
  
                if (!g)
                {
 -                      this->Logs->Log("SETGUID", DEFAULT, "getgrnam(%s) failed (wrong group?): %s", SetGroup.c_str(), strerror(errno));
 +                      this->Logs->Log("STARTUP", LOG_DEFAULT, "getgrnam(%s) failed (wrong group?): %s", SetGroup.c_str(), strerror(errno));
                        this->QuickExit(0);
                }
  
  
                if (ret == -1)
                {
 -                      this->Logs->Log("SETGUID", DEFAULT, "setgid() failed (wrong group?): %s", strerror(errno));
 +                      this->Logs->Log("STARTUP", LOG_DEFAULT, "setgid() failed (wrong group?): %s", strerror(errno));
                        this->QuickExit(0);
                }
        }
  
                if (!u)
                {
 -                      this->Logs->Log("SETGUID", DEFAULT, "getpwnam(%s) failed (wrong user?): %s", SetUser.c_str(), strerror(errno));
 +                      this->Logs->Log("STARTUP", LOG_DEFAULT, "getpwnam(%s) failed (wrong user?): %s", SetUser.c_str(), strerror(errno));
                        this->QuickExit(0);
                }
  
  
                if (ret == -1)
                {
 -                      this->Logs->Log("SETGUID", DEFAULT, "setuid() failed (wrong user?): %s", strerror(errno));
 +                      this->Logs->Log("STARTUP", LOG_DEFAULT, "setuid() failed (wrong user?): %s", strerror(errno));
                        this->QuickExit(0);
                }
        }
@@@ -586,17 -755,15 +588,17 @@@ void InspIRCd::UpdateTime(
  #endif
  }
  
 -int InspIRCd::Run()
 +void InspIRCd::Run()
  {
 +#ifdef INSPIRCD_ENABLE_TESTSUITE
        /* See if we're supposed to be running the test suite rather than entering the mainloop */
 -      if (Config->cmdline.TestSuite)
 +      if (do_testsuite)
        {
                TestSuite* ts = new TestSuite;
                delete ts;
 -              Exit(0);
 +              return;
        }
 +#endif
  
        UpdateTime();
        time_t OLDTIME = TIME.tv_sec;
                if (this->ConfigThread && this->ConfigThread->IsDone())
                {
                        /* Rehash has completed */
 -                      this->Logs->Log("CONFIG",DEBUG,"Detected ConfigThread exiting, tidying up...");
 +                      this->Logs->Log("CONFIG", LOG_DEBUG, "Detected ConfigThread exiting, tidying up...");
  
                        this->ConfigThread->Finish();
  
                {
  #ifndef _WIN32
                        getrusage(RUSAGE_SELF, &ru);
 -                      stats->LastSampled = TIME;
 -                      stats->LastCPU = ru.ru_utime;
 +                      stats.LastSampled = TIME;
 +                      stats.LastCPU = ru.ru_utime;
  #else
 -                      if(QueryPerformanceCounter(&stats->LastSampled))
 +                      if(QueryPerformanceCounter(&stats.LastSampled))
                        {
                                FILETIME CreationTime;
                                FILETIME ExitTime;
                                FILETIME KernelTime;
                                FILETIME UserTime;
                                GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime);
 -                              stats->LastCPU.dwHighDateTime = KernelTime.dwHighDateTime + UserTime.dwHighDateTime;
 -                              stats->LastCPU.dwLowDateTime = KernelTime.dwLowDateTime + UserTime.dwLowDateTime;
 +                              stats.LastCPU.dwHighDateTime = KernelTime.dwHighDateTime + UserTime.dwHighDateTime;
 +                              stats.LastCPU.dwLowDateTime = KernelTime.dwLowDateTime + UserTime.dwLowDateTime;
                        }
  #endif
  
                        {
                                SNO->WriteToSnoMask('d', "\002EH?!\002 -- Time is jumping FORWARDS! Clock skipped %lu secs.", (unsigned long)(TIME.tv_sec - OLDTIME));
                        }
 -\r
 +
                        OLDTIME = TIME.tv_sec;
  
                        if ((TIME.tv_sec % 3600) == 0)
 -                      {
 -                              this->RehashUsersAndChans();
 -                              FOREACH_MOD(I_OnGarbageCollect, OnGarbageCollect());
 -                      }
 +                              FOREACH_MOD(OnGarbageCollect, ());
  
 -                      Timers->TickTimers(TIME.tv_sec);
 -                      this->DoBackgroundUserStuff();
 +                      Timers.TickTimers(TIME.tv_sec);
 +                      Users->DoBackgroundUserStuff();
  
                        if ((TIME.tv_sec % 5) == 0)
                        {
 -                              FOREACH_MOD(I_OnBackgroundTimer,OnBackgroundTimer(TIME.tv_sec));
 +                              FOREACH_MOD(OnBackgroundTimer, (TIME.tv_sec));
                                SNO->FlushSnotices();
                        }
                }
                 * This will cause any read or write events to be
                 * dispatched to their handlers.
                 */
 -              this->SE->DispatchTrialWrites();
 -              this->SE->DispatchEvents();
 +              SocketEngine::DispatchTrialWrites();
 +              SocketEngine::DispatchEvents();
  
                /* if any users were quit, take them out */
                GlobalCulls.Apply();
                        s_signal = 0;
                }
        }
 -
 -      return 0;
 -}
 -
 -/**********************************************************************************/
 -
 -/**
 - * An ircd in five lines! bwahahaha. ahahahahaha. ahahah *cough*.
 - */
 -
 -/* 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 InspIRCd::AllModulesReportReady(LocalUser* user)
 -{
 -      ModResult res;
 -      FIRST_MOD_RESULT(OnCheckReady, res, (user));
 -      return (res == MOD_RES_PASSTHRU);
  }
  
  sig_atomic_t InspIRCd::s_signal = 0;
diff --combined src/mode.cpp
index b29eda828e197a01f5f9bb2a0a65f8c9c7c647ac,0f5d457834ba33538a28abb713c193751b9f6d71..3762dc52e14d9a25b537114532d1c58c43402dfe
  
  
  #include "inspircd.h"
 +#include "builtinmodes.h"
  
 -/* +s (secret) */
 -/* +p (private) */
 -/* +m (moderated) */
 -/* +t (only (half) ops can change topic) */
 -/* +n (no external messages) */
 -/* +i (invite only) */
 -/* +w (see wallops) */
 -/* +i (invisible) */
 -#include "modes/simplemodes.h"
 -/* +b (bans) */
 -#include "modes/cmode_b.h"
 -/* +k (keyed channel) */
 -#include "modes/cmode_k.h"
 -/* +l (channel user limit) */
 -#include "modes/cmode_l.h"
 -/* +o (channel op) */
 -#include "modes/cmode_o.h"
 -/* +v (channel voice) */
 -#include "modes/cmode_v.h"
 -/* +o (operator) */
 -#include "modes/umode_o.h"
 -/* +s (server notice masks) */
 -#include "modes/umode_s.h"
 -
 -ModeHandler::ModeHandler(Module* Creator, const std::string& Name, char modeletter, ParamSpec Params, ModeType type)
 -      : ServiceProvider(Creator, Name, SERVICE_MODE), m_paramtype(TR_TEXT),
 -      parameters_taken(Params), mode(modeletter), prefix(0), oper(false),
 -      list(false), m_type(type), levelrequired(HALFOP_VALUE)
 +ModeHandler::ModeHandler(Module* Creator, const std::string& Name, char modeletter, ParamSpec Params, ModeType type, Class mclass)
 +      : ServiceProvider(Creator, Name, SERVICE_MODE), modeid(ModeParser::MODEID_MAX),
 +      parameters_taken(Params), mode(modeletter), oper(false),
 +      list(false), m_type(type), type_id(mclass), levelrequired(HALFOP_VALUE)
  {
  }
  
  CullResult ModeHandler::cull()
  {
 -      if (ServerInstance->Modes)
 +      if (ServerInstance)
                ServerInstance->Modes->DelMode(this);
        return classbase::cull();
  }
@@@ -44,6 -67,16 +44,6 @@@ ModeHandler::~ModeHandler(
  {
  }
  
 -bool ModeHandler::IsListMode()
 -{
 -      return list;
 -}
 -
 -unsigned int ModeHandler::GetPrefixRank()
 -{
 -      return 0;
 -}
 -
  int ModeHandler::GetNumParams(bool adding)
  {
        switch (parameters_taken)
@@@ -90,17 -123,11 +90,17 @@@ bool ModeHandler::ResolveModeConflict(s
        return (theirs < ours);
  }
  
 +void ModeHandler::RegisterService()
 +{
 +      ServerInstance->Modes.AddMode(this);
 +      ServerInstance->Modules.AddReferent((GetModeType() == MODETYPE_CHANNEL ? "mode/" : "umode/") + name, this);
 +}
 +
  ModeAction SimpleUserModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
  {
        /* We're either trying to add a mode we already have or
                remove a mode we don't have, deny. */
 -      if (dest->IsModeSet(this->GetModeChar()) == adding)
 +      if (dest->IsModeSet(this) == adding)
                return MODEACTION_DENY;
  
        /* adding will be either true or false, depending on if we
                aren't removing a mode we don't have, we don't have to do any
                other checks here to see if it's true or false, just add or
                remove the mode */
 -      dest->SetMode(this->GetModeChar(), adding);
 +      dest->SetMode(this, adding);
  
        return MODEACTION_ALLOW;
  }
@@@ -119,7 -146,7 +119,7 @@@ ModeAction SimpleChannelModeHandler::On
  {
        /* We're either trying to add a mode we already have or
                remove a mode we don't have, deny. */
 -      if (channel->IsModeSet(this->GetModeChar()) == adding)
 +      if (channel->IsModeSet(this) == adding)
                return MODEACTION_DENY;
  
        /* adding will be either true or false, depending on if we
                aren't removing a mode we don't have, we don't have to do any
                other checks here to see if it's true or false, just add or
                remove the mode */
 -      channel->SetMode(this->GetModeChar(), adding);
 +      channel->SetMode(this, adding);
  
        return MODEACTION_ALLOW;
  }
  
 -ModeAction ParamChannelModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
 -{
 -      if (adding && !ParamValidate(parameter))
 -              return MODEACTION_DENY;
 -      std::string now = channel->GetModeParameter(this);
 -      if (parameter == now)
 -              return MODEACTION_DENY;
 -      if (adding)
 -              channel->SetModeParam(this, parameter);
 -      else
 -              channel->SetModeParam(this, "");
 -      return MODEACTION_ALLOW;
 -}
 -
 -bool ParamChannelModeHandler::ParamValidate(std::string& parameter)
 -{
 -      return true;
 -}
 -
 -ModeWatcher::ModeWatcher(Module* Creator, char modeletter, ModeType type)
 -      : mode(modeletter), m_type(type), creator(Creator)
 +ModeWatcher::ModeWatcher(Module* Creator, const std::string& modename, ModeType type)
 +      : mode(modename), m_type(type), creator(Creator)
  {
 +      ServerInstance->Modes->AddModeWatcher(this);
  }
  
  ModeWatcher::~ModeWatcher()
  {
 -}
 -
 -char ModeWatcher::GetModeChar()
 -{
 -      return mode;
 +      ServerInstance->Modes->DelModeWatcher(this);
  }
  
  ModeType ModeWatcher::GetModeType()
        return m_type;
  }
  
 -bool ModeWatcher::BeforeMode(User*, User*, Channel*, std::string&, bool, ModeType)
 +bool ModeWatcher::BeforeMode(User*, User*, Channel*, std::string&, bool)
  {
        return true;
  }
  
 -void ModeWatcher::AfterMode(User*, User*, Channel*, const std::string&, bool, ModeType)
 +void ModeWatcher::AfterMode(User*, User*, Channel*, const std::string&, bool)
  {
  }
  
 -User* ModeParser::SanityChecks(User *user, const char *dest, Channel *chan, int)
 +PrefixMode::PrefixMode(Module* Creator, const std::string& Name, char ModeLetter, unsigned int Rank, char PrefixChar)
 +      : ModeHandler(Creator, Name, ModeLetter, PARAM_ALWAYS, MODETYPE_CHANNEL, MC_PREFIX)
 +      , prefix(PrefixChar), prefixrank(Rank)
  {
 -      User *d;
 -      if ((!user) || (!dest) || (!chan) || (!*dest))
 -      {
 -              return NULL;
 -      }
 -      d = ServerInstance->FindNick(dest);
 -      if (!d)
 +      list = true;
 +}
 +
 +ModeAction PrefixMode::OnModeChange(User* source, User*, Channel* chan, std::string& parameter, bool adding)
 +{
 +      User* target;
 +      if (IS_LOCAL(source))
 +              target = ServerInstance->FindNickOnly(parameter);
 +      else
 +              target = ServerInstance->FindNick(parameter);
 +
 +      if (!target)
        {
 -              user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel",user->nick.c_str(), dest);
 -              return NULL;
 +              source->WriteNumeric(Numerics::NoSuchNick(parameter));
 +              return MODEACTION_DENY;
        }
 -      return d;
 +
 +      Membership* memb = chan->GetUser(target);
 +      if (!memb)
 +              return MODEACTION_DENY;
 +
 +      parameter = target->nick;
 +      return (memb->SetPrefix(this, adding) ? MODEACTION_ALLOW : MODEACTION_DENY);
  }
  
 -void ModeParser::DisplayCurrentModes(User *user, User* targetuser, Channel* targetchannel, const char* text)
 +ModeAction ParamModeBase::OnModeChange(User* source, User*, Channel* chan, std::string& parameter, bool adding)
  {
 -      if (targetchannel)
 +      if (adding)
        {
 -              /* Display channel's current mode string */
 -              user->WriteNumeric(RPL_CHANNELMODEIS, "%s %s +%s",user->nick.c_str(), targetchannel->name.c_str(), targetchannel->ChanModes(targetchannel->HasUser(user)));
 -              user->WriteNumeric(RPL_CHANNELCREATED, "%s %s %lu", user->nick.c_str(), targetchannel->name.c_str(), (unsigned long)targetchannel->age);
 -              return;
 +              if (chan->GetModeParameter(this) == parameter)
 +                      return MODEACTION_DENY;
 +
 +              if (OnSet(source, chan, parameter) != MODEACTION_ALLOW)
 +                      return MODEACTION_DENY;
 +
 +              chan->SetMode(this, true);
 +
 +              // Handler might have changed the parameter internally
 +              parameter.clear();
 +              this->GetParameter(chan, parameter);
        }
        else
        {
 -              if (targetuser == user || user->HasPrivPermission("users/auspex"))
 -              {
 -                      /* Display user's current mode string */
 -                      user->WriteNumeric(RPL_UMODEIS, "%s :+%s",targetuser->nick.c_str(),targetuser->FormatModes());
 -                      if (IS_OPER(targetuser))
 -                              user->WriteNumeric(RPL_SNOMASKIS, "%s +%s :Server notice mask", targetuser->nick.c_str(), targetuser->FormatNoticeMasks());
 -                      return;
 -              }
 -              else
 -              {
 -                      user->WriteNumeric(ERR_USERSDONTMATCH, "%s :Can't view modes for other users", user->nick.c_str());
 -                      return;
 -              }
 +              if (!chan->IsModeSet(this))
 +                      return MODEACTION_DENY;
 +              this->OnUnsetInternal(source, chan);
 +              chan->SetMode(this, false);
        }
 +      return MODEACTION_ALLOW;
  }
  
 -ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, bool adding, const unsigned char modechar,
 -              std::string &parameter, bool SkipACL)
 +ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, Modes::Change& mcitem, bool SkipACL)
  {
        ModeType type = chan ? MODETYPE_CHANNEL : MODETYPE_USER;
 -      unsigned char mask = chan ? MASK_CHANNEL : MASK_USER;
  
 -      ModeHandler *mh = FindMode(modechar, type);
 +      ModeHandler* mh = mcitem.mh;
 +      bool adding = mcitem.adding;
        int pcnt = mh->GetNumParams(adding);
  
 +      std::string& parameter = mcitem.param;
        // crop mode parameter size to 250 characters
        if (parameter.length() > 250 && adding)
 -              parameter = parameter.substr(0, 250);
 +              parameter.erase(250);
  
        ModResult MOD_RESULT;
 -      FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, modechar, parameter, adding, pcnt));
 +      FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mh, parameter, adding));
  
        if (IS_LOCAL(user) && (MOD_RESULT == MOD_RES_DENY))
                return MODEACTION_DENY;
  
 +      const char modechar = mh->GetModeChar();
 +
        if (chan && !SkipACL && (MOD_RESULT != MOD_RES_ALLOW))
        {
                MOD_RESULT = mh->AccessCheck(user, chan, parameter, adding);
                        unsigned int ourrank = chan->GetPrefixValue(user);
                        if (ourrank < neededrank)
                        {
 -                              ModeHandler* neededmh = NULL;
 +                              PrefixMode* neededmh = NULL;
                                for(char c='A'; c <= 'z'; c++)
                                {
 -                                      ModeHandler *privmh = FindMode(c, MODETYPE_CHANNEL);
 +                                      PrefixMode* privmh = FindPrefixMode(c);
                                        if (privmh && privmh->GetPrefixRank() >= neededrank)
                                        {
                                                // this mode is sufficient to allow this action
                                        }
                                }
                                if (neededmh)
 -                                      user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You must have channel %s access or above to %sset channel mode %c",
 -                                              user->nick.c_str(), chan->name.c_str(), neededmh->name.c_str(), adding ? "" : "un", modechar);
 +                                      user->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, InspIRCd::Format("You must have channel %s access or above to %sset channel mode %c",
 +                                              neededmh->name.c_str(), adding ? "" : "un", modechar));
                                else
 -                                      user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You cannot %sset channel mode %c",
 -                                              user->nick.c_str(), chan->name.c_str(), adding ? "" : "un", modechar);
 +                                      user->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, InspIRCd::Format("You cannot %sset channel mode %c", (adding ? "" : "un"), modechar));
                                return MODEACTION_DENY;
                        }
                }
        }
  
 -      unsigned char handler_id = (modechar - 'A') | mask;
 -
 -      for (ModeWatchIter watchers = modewatchers[handler_id].begin(); watchers != modewatchers[handler_id].end(); watchers++)
 +      // Ask mode watchers whether this mode change is OK
 +      std::pair<ModeWatcherMap::iterator, ModeWatcherMap::iterator> itpair = modewatchermap.equal_range(mh->name);
 +      for (ModeWatcherMap::iterator i = itpair.first; i != itpair.second; ++i)
        {
 -              if ((*watchers)->BeforeMode(user, targetuser, chan, parameter, adding, type) == false)
 -                      return MODEACTION_DENY;
 -              /* A module whacked the parameter completely, and there was one. abort. */
 -              if (pcnt && parameter.empty())
 -                      return MODEACTION_DENY;
 +              ModeWatcher* mw = i->second;
 +              if (mw->GetModeType() == type)
 +              {
 +                      if (!mw->BeforeMode(user, targetuser, chan, parameter, adding))
 +                              return MODEACTION_DENY;
 +
 +                      // A module whacked the parameter completely, and there was one. Abort.
 +                      if (pcnt && parameter.empty())
 +                              return MODEACTION_DENY;
 +              }
        }
  
 -      if (IS_LOCAL(user) && !IS_OPER(user))
 +      if (IS_LOCAL(user) && !user->IsOper())
        {
                char* disabled = (type == MODETYPE_CHANNEL) ? ServerInstance->Config->DisabledCModes : ServerInstance->Config->DisabledUModes;
                if (disabled[modechar - 'A'])
                {
 -                      user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - %s mode %c has been locked by the administrator",
 -                              user->nick.c_str(), type == MODETYPE_CHANNEL ? "channel" : "user", modechar);
 +                      user->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - %s mode %c has been locked by the administrator",
 +                              type == MODETYPE_CHANNEL ? "channel" : "user", modechar));
                        return MODEACTION_DENY;
                }
        }
        if (adding && IS_LOCAL(user) && mh->NeedsOper() && !user->HasModePermission(modechar, type))
        {
                /* It's an oper only mode, and they don't have access to it. */
 -              if (IS_OPER(user))
 +              if (user->IsOper())
                {
 -                      user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper type %s does not have access to set %s mode %c",
 -                                      user->nick.c_str(), user->oper->NameStr(), type == MODETYPE_CHANNEL ? "channel" : "user", modechar);
 +                      user->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - Oper type %s does not have access to set %s mode %c",
 +                                      user->oper->name.c_str(), type == MODETYPE_CHANNEL ? "channel" : "user", modechar));
                }
                else
                {
 -                      user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Only operators may set %s mode %c",
 -                                      user->nick.c_str(), type == MODETYPE_CHANNEL ? "channel" : "user", modechar);
 +                      user->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - Only operators may set %s mode %c",
 +                                      type == MODETYPE_CHANNEL ? "channel" : "user", modechar));
                }
                return MODEACTION_DENY;
        }
  
 -      if (mh->GetTranslateType() == TR_NICK)
 -      {
 -              User* prefixtarget;
 -              if (IS_LOCAL(user))
 -                      prefixtarget = ServerInstance->FindNickOnly(parameter);
 -              else
 -                      prefixtarget = ServerInstance->FindNick(parameter);
 -
 -              if (!prefixtarget)
 -              {
 -                      user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameter.c_str());
 -                      return MODEACTION_DENY;
 -              }
 -      }
 -
 -      if (mh->GetPrefixRank() && chan)
 -      {
 -              User* user_to_prefix = ServerInstance->FindNick(parameter);
 -              if (!user_to_prefix)
 -                      return MODEACTION_DENY;
 -              if (!chan->SetPrefix(user_to_prefix, modechar, adding))
 -                      return MODEACTION_DENY;
 -      }
 -
        /* Call the handler for the mode */
        ModeAction ma = mh->OnModeChange(user, targetuser, chan, parameter, adding);
  
        if (ma != MODEACTION_ALLOW)
                return ma;
  
 -      for (ModeWatchIter watchers = modewatchers[handler_id].begin(); watchers != modewatchers[handler_id].end(); watchers++)
 -              (*watchers)->AfterMode(user, targetuser, chan, parameter, adding, type);
 +      itpair = modewatchermap.equal_range(mh->name);
 +      for (ModeWatcherMap::iterator i = itpair.first; i != itpair.second; ++i)
 +      {
 +              ModeWatcher* mw = i->second;
 +              if (mw->GetModeType() == type)
 +                      mw->AfterMode(user, targetuser, chan, parameter, adding);
 +      }
  
        return MODEACTION_ALLOW;
  }
  
 -void ModeParser::Process(const std::vector<std::string>& parameters, User *user, bool merge)
 +void ModeParser::ModeParamsToChangeList(User* user, ModeType type, const std::vector<std::string>& parameters, Modes::ChangeList& changelist, unsigned int beginindex, unsigned int endindex)
  {
 -      const std::string& target = parameters[0];
 -      Channel* targetchannel = ServerInstance->FindChan(target);
 -      User* targetuser = NULL;
 -      if (!targetchannel)
 -      {
 -              if (IS_LOCAL(user))
 -                      targetuser = ServerInstance->FindNickOnly(target);
 -              else
 -                      targetuser = ServerInstance->FindNick(target);
 -      }
 -      ModeType type = targetchannel ? MODETYPE_CHANNEL : MODETYPE_USER;
 +      if (endindex > parameters.size())
 +              endindex = parameters.size();
  
 -      LastParse.clear();
 -      LastParseParams.clear();
 -      LastParseTranslate.clear();
 -
 -      if ((!targetchannel) && ((!targetuser) || (IS_SERVER(targetuser))))
 -      {
 -              user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel",user->nick.c_str(),target.c_str());
 -              return;
 -      }
 -      if (parameters.size() == 1)
 -      {
 -              this->DisplayCurrentModes(user, targetuser, targetchannel, target.c_str());
 -              return;
 -      }
 -
 -      ModResult MOD_RESULT;
 -      FIRST_MOD_RESULT(OnPreMode, MOD_RESULT, (user, targetuser, targetchannel, parameters));
 -
 -      bool SkipAccessChecks = false;
 -
 -      if (!IS_LOCAL(user) || ServerInstance->ULine(user->server) || MOD_RESULT == MOD_RES_ALLOW)
 -              SkipAccessChecks = true;
 -      else if (MOD_RESULT == MOD_RES_DENY)
 -              return;
 -
 -      if (targetuser && !SkipAccessChecks && user != targetuser)
 -      {
 -              user->WriteNumeric(ERR_USERSDONTMATCH, "%s :Can't change mode for other users", user->nick.c_str());
 -              return;
 -      }
 -
 -      std::string mode_sequence = parameters[1];
 -
 -      std::string output_mode;
 -      std::ostringstream output_parameters;
 -      LastParseParams.push_back(output_mode);
 -      LastParseTranslate.push_back(TR_TEXT);
 +      const std::string& mode_sequence = parameters[beginindex];
  
        bool adding = true;
 -      char output_pm = '\0'; // current output state, '+' or '-'
 -      unsigned int param_at = 2;
 +      unsigned int param_at = beginindex+1;
  
        for (std::string::const_iterator letter = mode_sequence.begin(); letter != mode_sequence.end(); letter++)
        {
                if (!mh)
                {
                        /* No mode handler? Unknown mode character then. */
 -                      user->WriteServ("%d %s %c :is unknown mode char to me", type == MODETYPE_CHANNEL ? 472 : 501, user->nick.c_str(), modechar);
 +                      user->WriteNumeric(type == MODETYPE_CHANNEL ? ERR_UNKNOWNMODE : ERR_UNKNOWNSNOMASK, modechar, "is unknown mode char to me");
                        continue;
                }
  
                std::string parameter;
 -              int pcnt = mh->GetNumParams(adding);
 -              if (pcnt && param_at == parameters.size())
 -              {
 -                      /* No parameter, continue to the next mode */
 -                      mh->OnParameterMissing(user, targetuser, targetchannel);
 -                      continue;
 -              }
 -              else if (pcnt)
 -              {
 +              if (mh->GetNumParams(adding) && param_at < endindex)
                        parameter = parameters[param_at++];
 -                      /* Make sure the user isn't trying to slip in an invalid parameter */
 -                      if ((parameter.empty()) || (parameter.find(':') == 0) || (parameter.rfind(' ') != std::string::npos))
 +
 +              changelist.push(mh, adding, parameter);
 +      }
 +}
 +
 +static bool IsModeParamValid(User* user, Channel* targetchannel, User* targetuser, const Modes::Change& item)
 +{
 +      // An empty parameter is never acceptable
 +      if (item.param.empty())
 +      {
 +              item.mh->OnParameterMissing(user, targetuser, targetchannel);
 +              return false;
 +      }
 +
 +      // The parameter cannot begin with a ':' character or contain a space
 +      if ((item.param[0] == ':') || (item.param.find(' ') != std::string::npos))
 +              return false;
 +
 +      return true;
 +}
 +
 +// Returns true if we should apply a merged mode, false if we should skip it
 +static bool ShouldApplyMergedMode(Channel* chan, Modes::Change& item)
 +{
 +      ModeHandler* mh = item.mh;
 +      if ((!chan) || (!chan->IsModeSet(mh)) || (mh->IsListMode()))
 +              // Mode not set here or merge is not applicable, apply the incoming mode
 +              return true;
 +
 +      // Mode handler decides
 +      std::string ours = chan->GetModeParameter(mh);
 +      return mh->ResolveModeConflict(item.param, ours, chan);
 +}
 +
 +void ModeParser::Process(User* user, Channel* targetchannel, User* targetuser, Modes::ChangeList& changelist, ModeProcessFlag flags)
 +{
 +      // Call ProcessSingle until the entire list is processed, but at least once to ensure
 +      // LastParse and LastChangeList are cleared
 +      unsigned int processed = 0;
 +      do
 +      {
 +              unsigned int n = ProcessSingle(user, targetchannel, targetuser, changelist, flags, processed);
 +              processed += n;
 +      }
 +      while (processed < changelist.size());
 +}
 +
 +unsigned int ModeParser::ProcessSingle(User* user, Channel* targetchannel, User* targetuser, Modes::ChangeList& changelist, ModeProcessFlag flags, unsigned int beginindex)
 +{
 +      LastParse.clear();
 +      LastChangeList.clear();
 +
 +      unsigned int modes_processed = 0;
 +      std::string output_mode;
 +      std::string output_parameters;
 +
 +      char output_pm = '\0'; // current output state, '+' or '-'
 +      Modes::ChangeList::List& list = changelist.getlist();
 +      for (Modes::ChangeList::List::iterator i = list.begin()+beginindex; i != list.end(); ++i)
 +      {
 +              modes_processed++;
 +
 +              Modes::Change& item = *i;
 +              ModeHandler* mh = item.mh;
 +
 +              // If the mode is supposed to have a parameter then we first take a look at item.param
 +              // and, if we were asked to, also handle mode merges now
 +              if (mh->GetNumParams(item.adding))
 +              {
 +                      // Skip the mode if the parameter does not pass basic validation
 +                      if (!IsModeParamValid(user, targetchannel, targetuser, item))
 +                              continue;
 +
 +                      // If this is a merge and we won we don't apply this mode
 +                      if ((flags & MODE_MERGE) && (!ShouldApplyMergedMode(targetchannel, item)))
                                continue;
 -                      if (merge && targetchannel && targetchannel->IsModeSet(modechar) && !mh->IsListMode())
 -                      {
 -                              std::string ours = targetchannel->GetModeParameter(modechar);
 -                              if (!mh->ResolveModeConflict(parameter, ours, targetchannel))
 -                                      /* we won the mode merge, don't apply this mode */
 -                                      continue;
 -                      }
                }
  
 -              ModeAction ma = TryMode(user, targetuser, targetchannel, adding, modechar, parameter, SkipAccessChecks);
 +              ModeAction ma = TryMode(user, targetuser, targetchannel, item, (!(flags & MODE_CHECKACCESS)));
  
                if (ma != MODEACTION_ALLOW)
                        continue;
  
 -              char needed_pm = adding ? '+' : '-';
 +              char needed_pm = item.adding ? '+' : '-';
                if (needed_pm != output_pm)
                {
                        output_pm = needed_pm;
                        output_mode.append(1, output_pm);
                }
 -              output_mode.append(1, modechar);
 +              output_mode.push_back(mh->GetModeChar());
  
 -              if (pcnt)
 +              if (!item.param.empty())
                {
 -                      TranslateType tt = mh->GetTranslateType();
 -                      if (tt == TR_NICK)
 -                      {
 -                              User* u = ServerInstance->FindNick(parameter);
 -                              if (u)
 -                                      parameter = u->nick;
 -                      }
 -                      output_parameters << " " << parameter;
 -                      LastParseParams.push_back(parameter);
 -                      LastParseTranslate.push_back(tt);
 +                      output_parameters.push_back(' ');
 +                      output_parameters.append(item.param);
                }
 +              LastChangeList.push(mh, item.adding, item.param);
  
 -              if ( (output_mode.length() + output_parameters.str().length() > 450)
 +              if ((output_mode.length() + output_parameters.length() > 450)
                                || (output_mode.length() > 100)
 -                              || (LastParseParams.size() > ServerInstance->Config->Limits.MaxModes))
 +                              || (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes))
                {
                        /* mode sequence is getting too long */
                        break;
                }
        }
  
 -      LastParseParams[0] = output_mode;
 -
        if (!output_mode.empty())
        {
                LastParse = targetchannel ? targetchannel->name : targetuser->nick;
                LastParse.append(" ");
                LastParse.append(output_mode);
 -              LastParse.append(output_parameters.str());
 +              LastParse.append(output_parameters);
  
                if (targetchannel)
 -              {
 -                      targetchannel->WriteChannel(user, "MODE %s", LastParse.c_str());
 -                      FOREACH_MOD(I_OnMode,OnMode(user, targetchannel, TYPE_CHANNEL, LastParseParams, LastParseTranslate));
 -              }
 +                      targetchannel->WriteChannel(user, "MODE " + LastParse);
                else
 -              {
 -                      targetuser->WriteFrom(user, "MODE %s", LastParse.c_str());
 -                      FOREACH_MOD(I_OnMode,OnMode(user, targetuser, TYPE_USER, LastParseParams, LastParseTranslate));
 -              }
 -      }
 -      else if (targetchannel && parameters.size() == 2)
 -      {
 -              /* Special case for displaying the list for listmodes,
 -               * e.g. MODE #chan b, or MODE #chan +b without a parameter
 -               */
 -              this->DisplayListModes(user, targetchannel, mode_sequence);
 +                      targetuser->WriteFrom(user, "MODE " + LastParse);
 +
 +              FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags, output_mode));
        }
 +
 +      return modes_processed;
  }
  
 -void ModeParser::DisplayListModes(User* user, Channel* chan, std::string &mode_sequence)
 +void ModeParser::ShowListModeList(User* user, Channel* chan, ModeHandler* mh)
  {
 -      seq++;
 -
 -      for (std::string::const_iterator letter = mode_sequence.begin(); letter != mode_sequence.end(); letter++)
        {
 -              unsigned char mletter = *letter;
 -              if (mletter == '+')
 -                      continue;
 -
 -              /* Ensure the user doesnt request the same mode twice,
 -               * so they cant flood themselves off out of idiocy.
 -               */
 -              if (sent[mletter] == seq)
 -                      continue;
 -
 -              sent[mletter] = seq;
 -
 -              ModeHandler *mh = this->FindMode(mletter, MODETYPE_CHANNEL);
 -
 -              if (!mh || !mh->IsListMode())
 -                      return;
 -
                ModResult MOD_RESULT;
 -              FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mletter, "", true, 0));
 +              FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mh, "", true));
                if (MOD_RESULT == MOD_RES_DENY)
 -                      continue;
 +                      return;
  
                bool display = true;
 -              if (!user->HasPrivPermission("channels/auspex") && ServerInstance->Config->HideModeLists[mletter] && (chan->GetPrefixValue(user) < HALFOP_VALUE))
 -              {
 -                      user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You do not have access to view the +%c list",
 -                              user->nick.c_str(), chan->name.c_str(), mletter);
 -                      display = false;
 -              }
  
 -              unsigned char handler_id = (mletter - 'A') | MASK_CHANNEL;
 -
 -              for(ModeWatchIter watchers = modewatchers[handler_id].begin(); watchers != modewatchers[handler_id].end(); watchers++)
 +              // Ask mode watchers whether it's OK to show the list
 +              std::pair<ModeWatcherMap::iterator, ModeWatcherMap::iterator> itpair = modewatchermap.equal_range(mh->name);
 +              for (ModeWatcherMap::iterator i = itpair.first; i != itpair.second; ++i)
                {
 -                      std::string dummyparam;
 +                      ModeWatcher* mw = i->second;
 +                      if (mw->GetModeType() == MODETYPE_CHANNEL)
 +                      {
 +                              std::string dummyparam;
  
 -                      if (!((*watchers)->BeforeMode(user, NULL, chan, dummyparam, true, MODETYPE_CHANNEL)))
 -                              display = false;
 +                              if (!mw->BeforeMode(user, NULL, chan, dummyparam, true))
 +                              {
 +                                      // A mode watcher doesn't want us to show the list
 +                                      display = false;
 +                                      break;
 +                              }
 +                      }
                }
 +
                if (display)
                        mh->DisplayList(user, chan);
                else
        }
  }
  
 -const std::string& ModeParser::GetLastParse()
 -{
 -      return LastParse;
 -}
 -
  void ModeParser::CleanMask(std::string &mask)
  {
        std::string::size_type pos_of_pling = mask.find_first_of('!');
        }
  }
  
 -bool ModeParser::AddMode(ModeHandler* mh)
 +ModeHandler::Id ModeParser::AllocateModeId(ModeType mt)
  {
 -      unsigned char mask = 0;
 -      unsigned char pos = 0;
 +      for (ModeHandler::Id i = 0; i != MODEID_MAX; ++i)
 +      {
 +              if (!modehandlersbyid[mt][i])
 +                      return i;
 +      }
 +
 +      throw ModuleException("Out of ModeIds");
 +}
  
 +void ModeParser::AddMode(ModeHandler* mh)
 +{
        /* Yes, i know, this might let people declare modes like '_' or '^'.
         * If they do that, thats their problem, and if i ever EVER see an
         * official InspIRCd developer do that, i'll beat them with a paddle!
         */
 -      if ((mh->GetModeChar() < 'A') || (mh->GetModeChar() > 'z') || (mh->GetPrefix() > 126))
 -              return false;
 +      if ((mh->GetModeChar() < 'A') || (mh->GetModeChar() > 'z'))
 +              throw ModuleException("Invalid letter for mode " + mh->name);
  
        /* A mode prefix of ',' is not acceptable, it would fuck up server to server.
         * A mode prefix of ':' will fuck up both server to server, and client to server.
         * A mode prefix of '#' will mess up /whois and /privmsg
         */
 -      if ((mh->GetPrefix() == ',') || (mh->GetPrefix() == ':') || (mh->GetPrefix() == '#'))
 -              return false;
 +      PrefixMode* pm = mh->IsPrefixMode();
 +      if (pm)
 +      {
 +              if ((pm->GetPrefix() > 126) || (pm->GetPrefix() == ',') || (pm->GetPrefix() == ':') || (pm->GetPrefix() == '#'))
 +                      throw ModuleException("Invalid prefix for mode " + mh->name);
  
 -      if (mh->GetPrefix() && FindPrefix(mh->GetPrefix()))
 -              return false;
 +              if (FindPrefix(pm->GetPrefix()))
 +                      throw ModuleException("Prefix already exists for mode " + mh->name);
 +      }
  
 -      mh->GetModeType() == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL;
 -      pos = (mh->GetModeChar()-65) | mask;
 +      ModeHandler*& slot = modehandlers[mh->GetModeType()][mh->GetModeChar()-65];
 +      if (slot)
 +              throw ModuleException("Letter is already in use for mode " + mh->name);
  
 -      if (modehandlers[pos])
 -              return false;
 +      // The mode needs an id if it is either a user mode, a simple mode (flag) or a parameter mode.
 +      // Otherwise (for listmodes and prefix modes) the id remains MODEID_MAX, which is invalid.
 +      ModeHandler::Id modeid = MODEID_MAX;
 +      if ((mh->GetModeType() == MODETYPE_USER) || (mh->IsParameterMode()) || (!mh->IsListMode()))
 +              modeid = AllocateModeId(mh->GetModeType());
  
 -      modehandlers[pos] = mh;
 -      return true;
 +      if (!modehandlersbyname[mh->GetModeType()].insert(std::make_pair(mh->name, mh)).second)
 +              throw ModuleException("Mode name already in use: " + mh->name);
 +
 +      // Everything is fine, add the mode
 +
 +      // If we allocated an id for this mode then save it and put the mode handler into the slot
 +      if (modeid != MODEID_MAX)
 +      {
 +              mh->modeid = modeid;
 +              modehandlersbyid[mh->GetModeType()][modeid] = mh;
 +      }
 +
 +      slot = mh;
 +      if (pm)
 +              mhlist.prefix.push_back(pm);
 +      else if (mh->IsListModeBase())
 +              mhlist.list.push_back(mh->IsListModeBase());
 +
 +      RecreateModeListFor004Numeric();
  }
  
  bool ModeParser::DelMode(ModeHandler* mh)
  {
 -      unsigned char mask = 0;
 -      unsigned char pos = 0;
 -
        if ((mh->GetModeChar() < 'A') || (mh->GetModeChar() > 'z'))
                return false;
  
 -      mh->GetModeType() == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL;
 -      pos = (mh->GetModeChar()-65) | mask;
 +      ModeHandlerMap& mhmap = modehandlersbyname[mh->GetModeType()];
 +      ModeHandlerMap::iterator mhmapit = mhmap.find(mh->name);
 +      if ((mhmapit == mhmap.end()) || (mhmapit->second != mh))
 +              return false;
  
 -      if (modehandlers[pos] != mh)
 +      ModeHandler*& slot = modehandlers[mh->GetModeType()][mh->GetModeChar()-65];
 +      if (slot != mh)
                return false;
  
        /* Note: We can't stack here, as we have modes potentially being removed across many different channels.
        switch (mh->GetModeType())
        {
                case MODETYPE_USER:
 -                      for (user_hash::iterator i = ServerInstance->Users->clientlist->begin(); i != ServerInstance->Users->clientlist->end(); )
 +              {
 +                      const user_hash& users = ServerInstance->Users->GetUsers();
 +                      for (user_hash::const_iterator i = users.begin(); i != users.end(); )
                        {
                                User* user = i->second;
                                ++i;
                                mh->RemoveMode(user);
                        }
 +              }
                break;
                case MODETYPE_CHANNEL:
 -                      for (chan_hash::iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); )
 +              {
 +                      const chan_hash& chans = ServerInstance->GetChans();
 +                      for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); )
                        {
                                // The channel may not be in the hash after RemoveMode(), see m_permchannels
                                Channel* chan = i->second;
                                ++i;
 -                              mh->RemoveMode(chan);
 +
 +                              Modes::ChangeList changelist;
 +                              mh->RemoveMode(chan, changelist);
 +                              this->Process(ServerInstance->FakeClient, chan, NULL, changelist, MODE_LOCALONLY);
                        }
 +              }
                break;
        }
  
 -      modehandlers[pos] = NULL;
 +      mhmap.erase(mhmapit);
 +      if (mh->GetId() != MODEID_MAX)
 +              modehandlersbyid[mh->GetModeType()][mh->GetId()] = NULL;
 +      slot = NULL;
 +      if (mh->IsPrefixMode())
 +              mhlist.prefix.erase(std::find(mhlist.prefix.begin(), mhlist.prefix.end(), mh->IsPrefixMode()));
 +      else if (mh->IsListModeBase())
 +              mhlist.list.erase(std::find(mhlist.list.begin(), mhlist.list.end(), mh->IsListModeBase()));
  
 +      RecreateModeListFor004Numeric();
        return true;
  }
  
 -ModeHandler* ModeParser::FindMode(unsigned const char modeletter, ModeType mt)
 +ModeHandler* ModeParser::FindMode(const std::string& modename, ModeType mt)
  {
 -      unsigned char mask = 0;
 -      unsigned char pos = 0;
 +      ModeHandlerMap& mhmap = modehandlersbyname[mt];
 +      ModeHandlerMap::const_iterator it = mhmap.find(modename);
 +      if (it != mhmap.end())
 +              return it->second;
  
 +      return NULL;
 +}
 +
 +ModeHandler* ModeParser::FindMode(unsigned const char modeletter, ModeType mt)
 +{
        if ((modeletter < 'A') || (modeletter > 'z'))
                return NULL;
  
 -      mt == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL;
 -      pos = (modeletter-65) | mask;
 -
 -      return modehandlers[pos];
 +      return modehandlers[mt][modeletter-65];
  }
  
 -std::string ModeParser::UserModeList()
 +PrefixMode* ModeParser::FindPrefixMode(unsigned char modeletter)
  {
 -      char modestr[256];
 -      int pointer = 0;
 -
 -      for (unsigned char mode = 'A'; mode <= 'z'; mode++)
 -      {
 -              unsigned char pos = (mode-65) | MASK_USER;
 -
 -              if (modehandlers[pos])
 -                      modestr[pointer++] = mode;
 -      }
 -      modestr[pointer++] = 0;
 -      return modestr;
 +      ModeHandler* mh = FindMode(modeletter, MODETYPE_CHANNEL);
 +      if (!mh)
 +              return NULL;
 +      return mh->IsPrefixMode();
  }
  
 -std::string ModeParser::ChannelModeList()
 +std::string ModeParser::CreateModeList(ModeType mt, bool needparam)
  {
 -      char modestr[256];
 -      int pointer = 0;
 +      std::string modestr;
  
        for (unsigned char mode = 'A'; mode <= 'z'; mode++)
        {
 -              unsigned char pos = (mode-65) | MASK_CHANNEL;
 -
 -              if (modehandlers[pos])
 -                      modestr[pointer++] = mode;
 +              ModeHandler* mh = modehandlers[mt][mode-65];
 +              if ((mh) && ((!needparam) || (mh->GetNumParams(true))))
 +                      modestr.push_back(mode);
        }
 -      modestr[pointer++] = 0;
 +
        return modestr;
  }
  
 -std::string ModeParser::ParaModeList()
 +void ModeParser::RecreateModeListFor004Numeric()
  {
 -      char modestr[256];
 -      int pointer = 0;
 -
 -      for (unsigned char mode = 'A'; mode <= 'z'; mode++)
 -      {
 -              unsigned char pos = (mode-65) | MASK_CHANNEL;
 -
 -              if ((modehandlers[pos]) && (modehandlers[pos]->GetNumParams(true)))
 -                      modestr[pointer++] = mode;
 -      }
 -      modestr[pointer++] = 0;
 -      return modestr;
 +      Cached004ModeList = CreateModeList(MODETYPE_USER) + " " + CreateModeList(MODETYPE_CHANNEL) + " " + CreateModeList(MODETYPE_CHANNEL, true);
  }
  
 -ModeHandler* ModeParser::FindPrefix(unsigned const char pfxletter)
 +PrefixMode* ModeParser::FindPrefix(unsigned const char pfxletter)
  {
 -      for (unsigned char mode = 'A'; mode <= 'z'; mode++)
 +      const PrefixModeList& list = GetPrefixModes();
 +      for (PrefixModeList::const_iterator i = list.begin(); i != list.end(); ++i)
        {
 -              unsigned char pos = (mode-65) | MASK_CHANNEL;
 -
 -              if ((modehandlers[pos]) && (modehandlers[pos]->GetPrefix() == pfxletter))
 -              {
 -                      return modehandlers[pos];
 -              }
 +              PrefixMode* pm = *i;
 +              if (pm->GetPrefix() == pfxletter)
 +                      return pm;
        }
        return NULL;
  }
  
 -std::string ModeParser::GiveModeList(ModeMasks m)
 +std::string ModeParser::GiveModeList(ModeType mt)
  {
        std::string type1;      /* Listmodes EXCEPT those with a prefix */
        std::string type2;      /* Modes that take a param when adding or removing */
  
        for (unsigned char mode = 'A'; mode <= 'z'; mode++)
        {
 -              unsigned char pos = (mode-65) | m;
 +              ModeHandler* mh = modehandlers[mt][mode-65];
                 /* One parameter when adding */
 -              if (modehandlers[pos])
 +              if (mh)
                {
 -                      if (modehandlers[pos]->GetNumParams(true))
 +                      if (mh->GetNumParams(true))
                        {
 -                              if ((modehandlers[pos]->IsListMode()) && (!modehandlers[pos]->GetPrefix()))
 +                              PrefixMode* pm = mh->IsPrefixMode();
 +                              if ((mh->IsListMode()) && ((!pm) || (pm->GetPrefix() == 0)))
                                {
 -                                      type1 += modehandlers[pos]->GetModeChar();
 +                                      type1 += mh->GetModeChar();
                                }
                                else
                                {
                                        /* ... and one parameter when removing */
 -                                      if (modehandlers[pos]->GetNumParams(false))
 +                                      if (mh->GetNumParams(false))
                                        {
                                                /* But not a list mode */
 -                                              if (!modehandlers[pos]->GetPrefix())
 +                                              if (!pm)
                                                {
 -                                                      type2 += modehandlers[pos]->GetModeChar();
 +                                                      type2 += mh->GetModeChar();
                                                }
                                        }
                                        else
                                        {
                                                /* No parameters when removing */
 -                                              type3 += modehandlers[pos]->GetModeChar();
 +                                              type3 += mh->GetModeChar();
                                        }
                                }
                        }
                        else
                        {
 -                              type4 += modehandlers[pos]->GetModeChar();
 +                              type4 += mh->GetModeChar();
                        }
                }
        }
        return type1 + "," + type2 + "," + type3 + "," + type4;
  }
  
 -      bool operator()(ModeHandler* lhs, ModeHandler* rhs)
+ struct PrefixModeSorter
+ {
++      bool operator()(PrefixMode* lhs, PrefixMode* rhs)
+       {
+               return lhs->GetPrefixRank() < rhs->GetPrefixRank();
+       }
+ };
  std::string ModeParser::BuildPrefixes(bool lettersAndModes)
  {
        std::string mletters;
        std::string mprefixes;
-       insp::flat_map<int, std::pair<char, char> > prefixes;
 -      std::vector<ModeHandler*> prefixes;
++      std::vector<PrefixMode*> prefixes;
  
 -      for (unsigned char mode = 'A'; mode <= 'z'; mode++)
 +      const PrefixModeList& list = GetPrefixModes();
 +      for (PrefixModeList::const_iterator i = list.begin(); i != list.end(); ++i)
        {
 -              unsigned char pos = (mode-65) | MASK_CHANNEL;
 -
 -              if ((modehandlers[pos]) && (modehandlers[pos]->GetPrefix()))
 -              {
 -                      prefixes.push_back(modehandlers[pos]);
 -              }
 +              PrefixMode* pm = *i;
 +              if (pm->GetPrefix())
-                       prefixes[pm->GetPrefixRank()] = std::make_pair(pm->GetPrefix(), pm->GetModeChar());
++                      prefixes.push_back(pm);
        }
  
-       for (insp::flat_map<int, std::pair<char, char> >::reverse_iterator n = prefixes.rbegin(); n != prefixes.rend(); ++n)
+       std::sort(prefixes.begin(), prefixes.end(), PrefixModeSorter());
 -      for (std::vector<ModeHandler*>::const_reverse_iterator n = prefixes.rbegin(); n != prefixes.rend(); ++n)
++      for (std::vector<PrefixMode*>::const_reverse_iterator n = prefixes.rbegin(); n != prefixes.rend(); ++n)
        {
-               mletters = mletters + n->second.first;
-               mprefixes = mprefixes + n->second.second;
+               mletters += (*n)->GetPrefix();
+               mprefixes += (*n)->GetModeChar();
        }
  
        return lettersAndModes ? "(" + mprefixes + ")" + mletters : mletters;
  }
  
 -bool ModeParser::AddModeWatcher(ModeWatcher* mw)
 +void ModeParser::AddModeWatcher(ModeWatcher* mw)
  {
 -      unsigned char mask = 0;
 -      unsigned char pos = 0;
 -
 -      if (!mw)
 -              return false;
 -
 -      if ((mw->GetModeChar() < 'A') || (mw->GetModeChar() > 'z'))
 -              return false;
 -
 -      mw->GetModeType() == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL;
 -      pos = (mw->GetModeChar()-65) | mask;
 -
 -      modewatchers[pos].push_back(mw);
 -
 -      return true;
 +      modewatchermap.insert(std::make_pair(mw->GetModeName(), mw));
  }
  
  bool ModeParser::DelModeWatcher(ModeWatcher* mw)
  {
 -      unsigned char mask = 0;
 -      unsigned char pos = 0;
 -
 -      if (!mw)
 -              return false;
 -
 -      if ((mw->GetModeChar() < 'A') || (mw->GetModeChar() > 'z'))
 -              return false;
 -
 -      mw->GetModeType() == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL;
 -      pos = (mw->GetModeChar()-65) | mask;
 -
 -      ModeWatchIter a = std::find(modewatchers[pos].begin(),modewatchers[pos].end(),mw);
 -
 -      if (a == modewatchers[pos].end())
 +      std::pair<ModeWatcherMap::iterator, ModeWatcherMap::iterator> itpair = modewatchermap.equal_range(mw->GetModeName());
 +      for (ModeWatcherMap::iterator i = itpair.first; i != itpair.second; ++i)
        {
 -              return false;
 +              if (i->second == mw)
 +              {
 +                      modewatchermap.erase(i);
 +                      return true;
 +              }
        }
  
 -      modewatchers[pos].erase(a);
 -
 -      return true;
 +      return false;
  }
  
 -/** This default implementation can remove simple user modes
 - */
 -void ModeHandler::RemoveMode(User* user, irc::modestacker* stack)
 +void ModeHandler::RemoveMode(User* user)
  {
 +      // Remove the mode if it's set on the user
        if (user->IsModeSet(this->GetModeChar()))
        {
 -              if (stack)
 -              {
 -                      stack->Push(this->GetModeChar());
 -              }
 -              else
 -              {
 -                      std::vector<std::string> parameters;
 -                      parameters.push_back(user->nick);
 -                      parameters.push_back("-");
 -                      parameters[1].push_back(this->GetModeChar());
 -                      ServerInstance->Modes->Process(parameters, ServerInstance->FakeClient);
 -              }
 +              Modes::ChangeList changelist;
 +              changelist.push_remove(this);
 +              ServerInstance->Modes->Process(ServerInstance->FakeClient, NULL, user, changelist, ModeParser::MODE_LOCALONLY);
        }
  }
  
 -/** This default implementation can remove simple channel modes
 - * (no parameters)
 - */
 -void ModeHandler::RemoveMode(Channel* channel, irc::modestacker* stack)
 +void ModeHandler::RemoveMode(Channel* channel, Modes::ChangeList& changelist)
  {
 -      if (channel->IsModeSet(this->GetModeChar()))
 +      if (channel->IsModeSet(this))
        {
 -              if (stack)
 -              {
 -                      stack->Push(this->GetModeChar());
 -              }
 +              if (this->GetNumParams(false))
 +                      // Removing this mode requires a parameter
 +                      changelist.push_remove(this, channel->GetModeParameter(this));
                else
 -              {
 -                      std::vector<std::string> parameters;
 -                      parameters.push_back(channel->name);
 -                      parameters.push_back("-");
 -                      parameters[1].push_back(this->GetModeChar());
 -                      ServerInstance->SendMode(parameters, ServerInstance->FakeClient);
 -              }
 +                      changelist.push_remove(this);
 +      }
 +}
 +
 +void PrefixMode::RemoveMode(Channel* chan, Modes::ChangeList& changelist)
 +{
 +      const Channel::MemberMap& userlist = chan->GetUsers();
 +      for (Channel::MemberMap::const_iterator i = userlist.begin(); i != userlist.end(); ++i)
 +      {
 +              if (i->second->hasMode(this->GetModeChar()))
 +                      changelist.push_remove(this, i->first->nick);
        }
  }
  
  struct builtin_modes
  {
 -      ModeChannelSecret s;
 -      ModeChannelPrivate p;
 -      ModeChannelModerated m;
 -      ModeChannelTopicOps t;
 +      SimpleChannelModeHandler s;
 +      SimpleChannelModeHandler p;
 +      SimpleChannelModeHandler m;
 +      SimpleChannelModeHandler t;
  
 -      ModeChannelNoExternal n;
 -      ModeChannelInviteOnly i;
 +      SimpleChannelModeHandler n;
 +      SimpleChannelModeHandler i;
        ModeChannelKey k;
        ModeChannelLimit l;
  
        ModeChannelOp o;
        ModeChannelVoice v;
  
 -      ModeUserWallops uw;
 -      ModeUserInvisible ui;
 +      SimpleUserModeHandler ui;
        ModeUserOperator uo;
        ModeUserServerNoticeMask us;
  
 -      void init(ModeParser* modes)
 -      {
 -              modes->AddMode(&s);
 -              modes->AddMode(&p);
 -              modes->AddMode(&m);
 -              modes->AddMode(&t);
 -              modes->AddMode(&n);
 -              modes->AddMode(&i);
 -              modes->AddMode(&k);
 -              modes->AddMode(&l);
 -              modes->AddMode(&b);
 -              modes->AddMode(&o);
 -              modes->AddMode(&v);
 -              modes->AddMode(&uw);
 -              modes->AddMode(&ui);
 -              modes->AddMode(&uo);
 -              modes->AddMode(&us);
 +      builtin_modes()
 +              : s(NULL, "secret", 's')
 +              , p(NULL, "private", 'p')
 +              , m(NULL, "moderated", 'm')
 +              , t(NULL, "topiclock", 't')
 +              , n(NULL, "noextmsg", 'n')
 +              , i(NULL, "inviteonly", 'i')
 +              , ui(NULL, "invisible", 'i')
 +      {
 +      }
 +
 +      void init()
 +      {
 +              ServiceProvider* modes[] = { &s, &p, &m, &t, &n, &i, &k, &l, &b, &o, &v,
 +                                                                       &ui, &uo, &us };
 +              ServerInstance->Modules->AddServices(modes, sizeof(modes)/sizeof(ServiceProvider*));
        }
  };
  
  static builtin_modes static_modes;
  
 +void ModeParser::InitBuiltinModes()
 +{
 +      static_modes.init();
 +      static_modes.b.DoRehash();
 +}
 +
  ModeParser::ModeParser()
  {
        /* Clear mode handler list */
        memset(modehandlers, 0, sizeof(modehandlers));
 -
 -      /* Last parse string */
 -      LastParse.clear();
 -
 -      seq = 0;
 -      memset(&sent, 0, sizeof(sent));
 -
 -      static_modes.init(this);
 +      memset(modehandlersbyid, 0, sizeof(modehandlersbyid));
  }
  
  ModeParser::~ModeParser()
index bda4e6a4878410b7c0ce8ee3c0c8c43b2520c4c6,2f4acf3f05edbb14454642940bf70fa37ae2759a..e5cb8ee9000ab3c1040db3d0a670ebce9f0c0035
  
  
  #include "inspircd.h"
 -#include <gnutls/gnutls.h>
 -#include <gnutls/x509.h>
 -#include "ssl.h"
 -#include "m_cap.h"
 -
 -#ifdef _WIN32
 -# pragma comment(lib, "libgnutls-30.lib")
 +#include "modules/ssl.h"
 +#include <memory>
 +
 +// Fix warnings about the use of commas at end of enumerator lists on C++03.
 +#if defined __clang__
 +# pragma clang diagnostic ignored "-Wc++11-extensions"
 +#elif defined __GNUC__
 +# if __GNUC__ < 6
 +#  pragma GCC diagnostic ignored "-pedantic"
 +# else
 +#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 +# endif
  #endif
  
 -/* $ModDesc: Provides SSL support for clients */
 -/* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") iflt("pkg-config --modversion gnutls","2.12") exec("libgcrypt-config --cflags") */
 -/* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") iflt("pkg-config --modversion gnutls","2.12") exec("libgcrypt-config --libs") */
 -/* $NoPedantic */
 +#include <gnutls/gnutls.h>
 +#include <gnutls/x509.h>
  
 -#ifndef GNUTLS_VERSION_MAJOR
 -#define GNUTLS_VERSION_MAJOR LIBGNUTLS_VERSION_MAJOR
 -#define GNUTLS_VERSION_MINOR LIBGNUTLS_VERSION_MINOR
 -#define GNUTLS_VERSION_PATCH LIBGNUTLS_VERSION_PATCH
 +#ifndef GNUTLS_VERSION_NUMBER
 +#define GNUTLS_VERSION_NUMBER LIBGNUTLS_VERSION_NUMBER
 +#define GNUTLS_VERSION LIBGNUTLS_VERSION
  #endif
  
 -// These don't exist in older GnuTLS versions
 -#if ((GNUTLS_VERSION_MAJOR > 2) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 1) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR == 1 && GNUTLS_VERSION_PATCH >= 7))
 -#define GNUTLS_NEW_PRIO_API
 -#endif
 +// Check if the GnuTLS library is at least version major.minor.patch
 +#define INSPIRCD_GNUTLS_HAS_VERSION(major, minor, patch) (GNUTLS_VERSION_NUMBER >= ((major << 16) | (minor << 8) | patch))
  
 -#if(GNUTLS_VERSION_MAJOR < 2)
 -typedef gnutls_certificate_credentials_t gnutls_certificate_credentials;
 -typedef gnutls_dh_params_t gnutls_dh_params;
 +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 9, 8)
 +#define GNUTLS_HAS_MAC_GET_ID
 +#include <gnutls/crypto.h>
  #endif
  
 -#if (GNUTLS_VERSION_MAJOR > 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR >= 12))
 +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0)
  # define GNUTLS_HAS_RND
 -# include <gnutls/crypto.h>
  #else
  # include <gcrypt.h>
  #endif
  
 -enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
 -
 -struct SSLConfig : public refcountbase
 -{
 -      gnutls_certificate_credentials_t x509_cred;
 -      std::vector<gnutls_x509_crt_t> x509_certs;
 -      gnutls_x509_privkey_t x509_key;
 -      gnutls_dh_params_t dh_params;
 -#ifdef GNUTLS_NEW_PRIO_API
 -      gnutls_priority_t priority;
 -#endif
 -
 -      SSLConfig()
 -              : x509_cred(NULL)
 -              , x509_key(NULL)
 -              , dh_params(NULL)
 -#ifdef GNUTLS_NEW_PRIO_API
 -              , priority(NULL)
 +#ifdef _WIN32
 +# pragma comment(lib, "libgnutls-30.lib")
  #endif
 -      {
 -      }
 -
 -      ~SSLConfig()
 -      {
 -              ServerInstance->Logs->Log("m_ssl_gnutls", DEBUG, "Destroying SSLConfig %p", (void*)this);
  
 -              if (x509_cred)
 -                      gnutls_certificate_free_credentials(x509_cred);
 +/* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") eval("print `libgcrypt-config --cflags | tr -d \r` if `pkg-config --modversion gnutls 2>/dev/null | tr -d \r` lt '2.12'") */
 +/* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") eval("print `libgcrypt-config --libs | tr -d \r` if `pkg-config --modversion gnutls 2>/dev/null | tr -d \r` lt '2.12'") */
  
 -              for (unsigned int i = 0; i < x509_certs.size(); i++)
 -                      gnutls_x509_crt_deinit(x509_certs[i]);
 -
 -              if (x509_key)
 -                      gnutls_x509_privkey_deinit(x509_key);
 -
 -              if (dh_params)
 -                      gnutls_dh_params_deinit(dh_params);
 +// These don't exist in older GnuTLS versions
 +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 1, 7)
 +#define GNUTLS_NEW_PRIO_API
 +#endif
  
 -#ifdef GNUTLS_NEW_PRIO_API
 -              if (priority)
 -                      gnutls_priority_deinit(priority);
 +#if (!INSPIRCD_GNUTLS_HAS_VERSION(2, 0, 0))
 +typedef gnutls_certificate_credentials_t gnutls_certificate_credentials;
 +typedef gnutls_dh_params_t gnutls_dh_params;
  #endif
 -      }
 -};
  
 -static reference<SSLConfig> currconf;
 +enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_HANDSHAKEN };
  
 -static SSLConfig* GetSessionConfig(gnutls_session_t session);
 +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0)
 +#define INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
 +#define GNUTLS_NEW_CERT_CALLBACK_API
 +typedef gnutls_retr2_st cert_cb_last_param_type;
 +#else
 +typedef gnutls_retr_st cert_cb_last_param_type;
 +#endif
  
 -#if(GNUTLS_VERSION_MAJOR < 2 || ( GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12 ) )
 -static int cert_callback (gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs,
 -      const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr_st * st) {
 +#if INSPIRCD_GNUTLS_HAS_VERSION(3, 3, 5)
 +#define INSPIRCD_GNUTLS_HAS_RECV_PACKET
 +#endif
  
 -      st->type = GNUTLS_CRT_X509;
 +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 99, 0)
 +// The second parameter of gnutls_init() has changed in 2.99.0 from gnutls_connection_end_t to unsigned int
 +// (it became a general flags parameter) and the enum has been deprecated and generates a warning on use.
 +typedef unsigned int inspircd_gnutls_session_init_flags_t;
  #else
 -static int cert_callback (gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs,
 -      const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr2_st * st) {
 -      st->cert_type = GNUTLS_CRT_X509;
 -      st->key_type = GNUTLS_PRIVKEY_X509;
 +typedef gnutls_connection_end_t inspircd_gnutls_session_init_flags_t;
  #endif
 -      SSLConfig* conf = GetSessionConfig(session);
 -      std::vector<gnutls_x509_crt_t>& x509_certs = conf->x509_certs;
 -      st->ncerts = x509_certs.size();
 -      st->cert.x509 = &x509_certs[0];
 -      st->key.x509 = conf->x509_key;
 -      st->deinit_all = 0;
  
 -      return 0;
 -}
 +#if INSPIRCD_GNUTLS_HAS_VERSION(3, 1, 9)
 +#define INSPIRCD_GNUTLS_HAS_CORK
 +#endif
 +
 +static Module* thismod;
  
  class RandGen : public HandlerBase2<void, char*, size_t>
  {
   public:
 -      RandGen() {}
        void Call(char* buffer, size_t len)
        {
  #ifdef GNUTLS_HAS_RND
        }
  };
  
 -/** Represents an SSL user's extra data
 - */
 -class issl_session
 -{
 -public:
 -      StreamSocket* socket;
 -      gnutls_session_t sess;
 -      issl_status status;
 -      reference<ssl_cert> cert;
 -      reference<SSLConfig> config;
 -
 -      issl_session() : socket(NULL), sess(NULL), status(ISSL_NONE) {}
 -};
 -
 -static SSLConfig* GetSessionConfig(gnutls_session_t sess)
 -{
 -      issl_session* session = reinterpret_cast<issl_session*>(gnutls_transport_get_ptr(sess));
 -      return session->config;
 -}
 -
 -class CommandStartTLS : public SplitCommand
 +namespace GnuTLS
  {
 - public:
 -      bool enabled;
 -      CommandStartTLS (Module* mod) : SplitCommand(mod, "STARTTLS")
 +      class Init
        {
 -              enabled = true;
 -              works_before_reg = true;
 -      }
 +       public:
 +              Init() { gnutls_global_init(); }
 +              ~Init() { gnutls_global_deinit(); }
 +      };
  
 -      CmdResult HandleLocal(const std::vector<std::string> &parameters, LocalUser *user)
 +      class Exception : public ModuleException
        {
 -              if (!enabled)
 -              {
 -                      user->WriteNumeric(691, "%s :STARTTLS is not enabled", user->nick.c_str());
 -                      return CMD_FAILURE;
 -              }
 +       public:
 +              Exception(const std::string& reason)
 +                      : ModuleException(reason) { }
 +      };
  
 -              if (user->registered == REG_ALL)
 -              {
 -                      user->WriteNumeric(691, "%s :STARTTLS is not permitted after client registration is complete", user->nick.c_str());
 -              }
 -              else
 +      void ThrowOnError(int errcode, const char* msg)
 +      {
 +              if (errcode < 0)
                {
 -                      if (!user->eh.GetIOHook())
 -                      {
 -                              user->WriteNumeric(670, "%s :STARTTLS successful, go ahead with TLS handshake", user->nick.c_str());
 -                              /* We need to flush the write buffer prior to adding the IOHook,
 -                               * otherwise we'll be sending this line inside the SSL session - which
 -                               * won't start its handshake until the client gets this line. Currently,
 -                               * we assume the write will not block here; this is usually safe, as
 -                               * STARTTLS is sent very early on in the registration phase, where the
 -                               * user hasn't built up much sendq. Handling a blocked write here would
 -                               * be very annoying.
 -                               */
 -                              user->eh.DoWrite();
 -                              user->eh.AddIOHook(creator);
 -                              creator->OnStreamSocketAccept(&user->eh, NULL, NULL);
 -                      }
 -                      else
 -                              user->WriteNumeric(691, "%s :STARTTLS failure", user->nick.c_str());
 +                      std::string reason = msg;
 +                      reason.append(" :").append(gnutls_strerror(errcode));
 +                      throw Exception(reason);
                }
 -
 -              return CMD_FAILURE;
        }
 -};
 -
 -class ModuleSSLGnuTLS : public Module
 -{
 -      issl_session* sessions;
 -
 -      gnutls_digest_algorithm_t hash;
  
 -      std::string sslports;
 -      int dh_bits;
 +      /** Used to create a gnutls_datum_t* from a std::string
 +       */
 +      class Datum
 +      {
 +              gnutls_datum_t datum;
  
 -      RandGen randhandler;
 -      CommandStartTLS starttls;
 +       public:
 +              Datum(const std::string& dat)
 +              {
 +                      datum.data = (unsigned char*)dat.data();
 +                      datum.size = static_cast<unsigned int>(dat.length());
 +              }
  
 -      GenericCap capHandler;
 -      ServiceProvider iohook;
 +              const gnutls_datum_t* get() const { return &datum; }
 +      };
  
 -      inline static const char* UnknownIfNULL(const char* str)
 +      class Hash
        {
 -              return str ? str : "UNKNOWN";
 -      }
 +              gnutls_digest_algorithm_t hash;
  
 -      static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size)
 -      {
 -              issl_session* session = reinterpret_cast<issl_session*>(session_wrap);
 -              if (session->socket->GetEventMask() & FD_READ_WILL_BLOCK)
 +       public:
 +              // Nothing to deallocate, constructor may throw freely
 +              Hash(const std::string& hashname)
                {
 -#ifdef _WIN32
 -                      gnutls_transport_set_errno(session->sess, EAGAIN);
 +                      // As older versions of gnutls can't do this, let's disable it where needed.
 +#ifdef GNUTLS_HAS_MAC_GET_ID
 +                      // As gnutls_digest_algorithm_t and gnutls_mac_algorithm_t are mapped 1:1, we can do this
 +                      // There is no gnutls_dig_get_id() at the moment, but it may come later
 +                      hash = (gnutls_digest_algorithm_t)gnutls_mac_get_id(hashname.c_str());
 +                      if (hash == GNUTLS_DIG_UNKNOWN)
 +                              throw Exception("Unknown hash type " + hashname);
 +
 +                      // Check if the user is giving us something that is a valid MAC but not digest
 +                      gnutls_hash_hd_t is_digest;
 +                      if (gnutls_hash_init(&is_digest, hash) < 0)
 +                              throw Exception("Unknown hash type " + hashname);
 +                      gnutls_hash_deinit(is_digest, NULL);
  #else
 -                      errno = EAGAIN;
 +                      if (hashname == "md5")
 +                              hash = GNUTLS_DIG_MD5;
 +                      else if (hashname == "sha1")
 +                              hash = GNUTLS_DIG_SHA1;
 +#ifdef INSPIRCD_GNUTLS_ENABLE_SHA256_FINGERPRINT
 +                      else if (hashname == "sha256")
 +                              hash = GNUTLS_DIG_SHA256;
 +#endif
 +                      else
 +                              throw Exception("Unknown hash type " + hashname);
  #endif
 -                      return -1;
                }
  
 -              int rv = ServerInstance->SE->Recv(session->socket, reinterpret_cast<char *>(buffer), size, 0);
 +              gnutls_digest_algorithm_t get() const { return hash; }
 +      };
  
 -#ifdef _WIN32
 -              if (rv < 0)
 +      class DHParams
 +      {
 +              gnutls_dh_params_t dh_params;
 +
 +              DHParams()
                {
 -                      /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
 -                       * and then set errno appropriately.
 -                       * The gnutls library may also have a different errno variable than us, see
 -                       * gnutls_transport_set_errno(3).
 -                       */
 -                      gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
 +                      ThrowOnError(gnutls_dh_params_init(&dh_params), "gnutls_dh_params_init() failed");
                }
 -#endif
 -
 -              if (rv < (int)size)
 -                      ServerInstance->SE->ChangeEventMask(session->socket, FD_READ_WILL_BLOCK);
 -              return rv;
 -      }
  
 -      static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size)
 -      {
 -              issl_session* session = reinterpret_cast<issl_session*>(session_wrap);
 -              if (session->socket->GetEventMask() & FD_WRITE_WILL_BLOCK)
 +       public:
 +              /** Import */
 +              static std::auto_ptr<DHParams> Import(const std::string& dhstr)
                {
 -#ifdef _WIN32
 -                      gnutls_transport_set_errno(session->sess, EAGAIN);
 -#else
 -                      errno = EAGAIN;
 -#endif
 -                      return -1;
 +                      std::auto_ptr<DHParams> dh(new DHParams);
 +                      int ret = gnutls_dh_params_import_pkcs3(dh->dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM);
 +                      ThrowOnError(ret, "Unable to import DH params");
 +                      return dh;
                }
  
 -              int rv = ServerInstance->SE->Send(session->socket, reinterpret_cast<const char *>(buffer), size, 0);
 -
 -#ifdef _WIN32
 -              if (rv < 0)
 +              ~DHParams()
                {
 -                      /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
 -                       * and then set errno appropriately.
 -                       * The gnutls library may also have a different errno variable than us, see
 -                       * gnutls_transport_set_errno(3).
 -                       */
 -                      gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
 +                      gnutls_dh_params_deinit(dh_params);
                }
 -#endif
 -
 -              if (rv < (int)size)
 -                      ServerInstance->SE->ChangeEventMask(session->socket, FD_WRITE_WILL_BLOCK);
 -              return rv;
 -      }
  
 - public:
 +              const gnutls_dh_params_t& get() const { return dh_params; }
 +      };
  
 -      ModuleSSLGnuTLS()
 -              : starttls(this), capHandler(this, "tls"), iohook(this, "ssl/gnutls", SERVICE_IOHOOK)
 +      class X509Key
        {
 -#ifndef GNUTLS_HAS_RND
 -              gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
 -#endif
 -
 -              sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
 -
 -              gnutls_global_init(); // This must be called once in the program
 -      }
 +              /** Ensure that the key is deinited in case the constructor of X509Key throws
 +               */
 +              class RAIIKey
 +              {
 +               public:
 +                      gnutls_x509_privkey_t key;
  
 -      void init()
 -      {
 -              currconf = new SSLConfig;
 -              InitSSLConfig(currconf);
 +                      RAIIKey()
 +                      {
 +                              ThrowOnError(gnutls_x509_privkey_init(&key), "gnutls_x509_privkey_init() failed");
 +                      }
  
 -              ServerInstance->GenRandom = &randhandler;
 +                      ~RAIIKey()
 +                      {
 +                              gnutls_x509_privkey_deinit(key);
 +                      }
 +              } key;
  
 -              Implementation eventlist[] = { I_On005Numeric, I_OnRehash, I_OnModuleRehash, I_OnUserConnect,
 -                      I_OnEvent, I_OnHookIO, I_OnCheckReady };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
 +       public:
 +              /** Import */
 +              X509Key(const std::string& keystr)
 +              {
 +                      int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM);
 +                      ThrowOnError(ret, "Unable to import private key");
 +              }
  
 -              ServerInstance->Modules->AddService(iohook);
 -              ServerInstance->Modules->AddService(starttls);
 -      }
 +              gnutls_x509_privkey_t& get() { return key.key; }
 +      };
  
 -      void OnRehash(User* user)
 +      class X509CertList
        {
 -              sslports.clear();
 +              std::vector<gnutls_x509_crt_t> certs;
  
 -              ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls");
 -              starttls.enabled = Conf->getBool("starttls", true);
 -
 -              if (Conf->getBool("showports", true))
 +       public:
 +              /** Import */
 +              X509CertList(const std::string& certstr)
                {
 -                      sslports = Conf->getString("advertisedports");
 -                      if (!sslports.empty())
 -                              return;
 +                      unsigned int certcount = 3;
 +                      certs.resize(certcount);
 +                      Datum datum(certstr);
  
 -                      for (size_t i = 0; i < ServerInstance->ports.size(); i++)
 +                      int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
 +                      if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
                        {
 -                              ListenSocket* port = ServerInstance->ports[i];
 -                              if (port->bind_tag->getString("ssl") != "gnutls")
 -                                      continue;
 -
 -                              const std::string& portid = port->bind_desc;
 -                              ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %s", portid.c_str());
 -
 -                              if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1")
 -                              {
 -                                      /*
 -                                       * Found an SSL port for clients that is not bound to 127.0.0.1 and handled by us, display
 -                                       * the IP:port in ISUPPORT.
 -                                       *
 -                                       * We used to advertise all ports seperated by a ';' char that matched the above criteria,
 -                                       * but this resulted in too long ISUPPORT lines if there were lots of ports to be displayed.
 -                                       * To solve this by default we now only display the first IP:port found and let the user
 -                                       * configure the exact value for the 005 token, if necessary.
 -                                       */
 -                                      sslports = portid;
 -                                      break;
 -                              }
 +                              // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs,
 +                              // try again with a bigger buffer
 +                              certs.resize(certcount);
 +                              ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
                        }
 -              }
 -      }
  
 -      void OnModuleRehash(User* user, const std::string &param)
 -      {
 -              if(param != "ssl")
 -                      return;
 +                      ThrowOnError(ret, "Unable to load certificates");
  
 -              reference<SSLConfig> newconf = new SSLConfig;
 -              try
 -              {
 -                      InitSSLConfig(newconf);
 +                      // Resize the vector to the actual number of certs because we rely on its size being correct
 +                      // when deallocating the certs
 +                      certs.resize(certcount);
                }
 -              catch (ModuleException& ex)
 +
 +              ~X509CertList()
                {
 -                      ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls: Not applying new config. %s", ex.GetReason());
 -                      return;
 +                      for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i)
 +                              gnutls_x509_crt_deinit(*i);
                }
  
 -              ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls: Applying new config, old config is in use by %d connection(s)", currconf->GetReferenceCount()-1);
 -              currconf = newconf;
 -      }
 +              gnutls_x509_crt_t* raw() { return &certs[0]; }
 +              unsigned int size() const { return certs.size(); }
 +      };
  
 -      void InitSSLConfig(SSLConfig* config)
 +      class X509CRL : public refcountbase
        {
 -              ServerInstance->Logs->Log("m_ssl_gnutls", DEBUG, "Initializing new SSLConfig %p", (void*)config);
 -
 -              std::string keyfile;
 -              std::string certfile;
 -              std::string cafile;
 -              std::string crlfile;
 -              OnRehash(NULL);
 -
 -              ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls");
 -
 -              cafile = Conf->getString("cafile", CONFIG_PATH "/ca.pem");
 -              crlfile = Conf->getString("crlfile", CONFIG_PATH "/crl.pem");
 -              certfile = Conf->getString("certfile", CONFIG_PATH "/cert.pem");
 -              keyfile = Conf->getString("keyfile", CONFIG_PATH "/key.pem");
 -              dh_bits = Conf->getInt("dhbits");
 -              std::string hashname = Conf->getString("hash", "md5");
 -
 -              // The GnuTLS manual states that the gnutls_set_default_priority()
 -              // call we used previously when initializing the session is the same
 -              // as setting the "NORMAL" priority string.
 -              // Thus if the setting below is not in the config we will behave exactly
 -              // the same as before, when the priority setting wasn't available.
 -              std::string priorities = Conf->getString("priority", "NORMAL");
 -
 -              if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))
 -                      dh_bits = 1024;
 -
 -              if (hashname == "md5")
 -                      hash = GNUTLS_DIG_MD5;
 -              else if (hashname == "sha1")
 -                      hash = GNUTLS_DIG_SHA1;
 -#ifdef INSPIRCD_GNUTLS_ENABLE_SHA256_FINGERPRINT
 -              else if (hashname == "sha256")
 -                      hash = GNUTLS_DIG_SHA256;
 -#endif
 -              else
 -                      throw ModuleException("Unknown hash type " + hashname);
 -
 +              class RAIICRL
 +              {
 +               public:
 +                      gnutls_x509_crl_t crl;
  
 -              int ret;
 +                      RAIICRL()
 +                      {
 +                              ThrowOnError(gnutls_x509_crl_init(&crl), "gnutls_x509_crl_init() failed");
 +                      }
  
 -              gnutls_certificate_credentials_t& x509_cred = config->x509_cred;
 +                      ~RAIICRL()
 +                      {
 +                              gnutls_x509_crl_deinit(crl);
 +                      }
 +              } crl;
  
 -              ret = gnutls_certificate_allocate_credentials(&x509_cred);
 -              if (ret < 0)
 +       public:
 +              /** Import */
 +              X509CRL(const std::string& crlstr)
                {
 -                      // Set to NULL because we can't be sure what value is in it and we must not try to
 -                      // deallocate it in case of an error
 -                      x509_cred = NULL;
 -                      throw ModuleException("Failed to allocate certificate credentials: " + std::string(gnutls_strerror(ret)));
 +                      int ret = gnutls_x509_crl_import(get(), Datum(crlstr).get(), GNUTLS_X509_FMT_PEM);
 +                      ThrowOnError(ret, "Unable to load certificate revocation list");
                }
  
 -              if((ret =gnutls_certificate_set_x509_trust_file(x509_cred, cafile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
 -                      ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to set X.509 trust file '%s': %s", cafile.c_str(), gnutls_strerror(ret));
 -
 -              if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
 -                      ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to set X.509 CRL file '%s': %s", crlfile.c_str(), gnutls_strerror(ret));
 +              gnutls_x509_crl_t& get() { return crl.crl; }
 +      };
  
 -              FileReader reader;
 -
 -              reader.LoadFile(certfile);
 -              std::string cert_string = reader.Contents();
 -              gnutls_datum_t cert_datum = { (unsigned char*)cert_string.data(), static_cast<unsigned int>(cert_string.length()) };
 +#ifdef GNUTLS_NEW_PRIO_API
 +      class Priority
 +      {
 +              gnutls_priority_t priority;
  
 -              reader.LoadFile(keyfile);
 -              std::string key_string = reader.Contents();
 -              gnutls_datum_t key_datum = { (unsigned char*)key_string.data(), static_cast<unsigned int>(key_string.length()) };
 +       public:
 +              Priority(const std::string& priorities)
 +              {
 +                      // Try to set the priorities for ciphers, kex methods etc. to the user supplied string
 +                      // If the user did not supply anything then the string is already set to "NORMAL"
 +                      const char* priocstr = priorities.c_str();
 +                      const char* prioerror;
  
 -              std::vector<gnutls_x509_crt_t>& x509_certs = config->x509_certs;
 +                      int ret = gnutls_priority_init(&priority, priocstr, &prioerror);
 +                      if (ret < 0)
 +                      {
 +                              // gnutls did not understand the user supplied string
 +                              throw Exception("Unable to initialize priorities to \"" + priorities + "\": " + gnutls_strerror(ret) + " Syntax error at position " + ConvToStr((unsigned int) (prioerror - priocstr)));
 +                      }
 +              }
  
 -              // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException
 -              unsigned int certcount = 3;
 -              x509_certs.resize(certcount);
 -              ret = gnutls_x509_crt_list_import(&x509_certs[0], &certcount, &cert_datum, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
 -              if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
 +              ~Priority()
                {
 -                      // the buffer wasn't big enough to hold all certs but gnutls updated certcount to the number of available certs, try again with a bigger buffer
 -                      x509_certs.resize(certcount);
 -                      ret = gnutls_x509_crt_list_import(&x509_certs[0], &certcount, &cert_datum, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
 +                      gnutls_priority_deinit(priority);
                }
  
 -              if (ret <= 0)
 +              void SetupSession(gnutls_session_t sess)
                {
 -                      // clear the vector so we won't call gnutls_x509_crt_deinit() on the (uninited) certs later
 -                      x509_certs.clear();
 -                      throw ModuleException("Unable to load GnuTLS server certificate (" + certfile + "): " + ((ret < 0) ? (std::string(gnutls_strerror(ret))) : "No certs could be read"));
 +                      gnutls_priority_set(sess, priority);
                }
 -              x509_certs.resize(ret);
  
 -              gnutls_x509_privkey_t& x509_key = config->x509_key;
 -              if (gnutls_x509_privkey_init(&x509_key) < 0)
 +              static const char* GetDefault()
                {
 -                      // Make sure the destructor does not try to deallocate this, see above
 -                      x509_key = NULL;
 -                      throw ModuleException("Unable to initialize private key");
 +                      return "NORMAL:%SERVER_PRECEDENCE:-VERS-SSL3.0";
                }
  
 -              if((ret = gnutls_x509_privkey_import(x509_key, &key_datum, GNUTLS_X509_FMT_PEM)) < 0)
 -                      throw ModuleException("Unable to load GnuTLS server private key (" + keyfile + "): " + std::string(gnutls_strerror(ret)));
 +              static std::string RemoveUnknownTokens(const std::string& prio)
 +              {
 +                      std::string ret;
 +                      irc::sepstream ss(prio, ':');
 +                      for (std::string token; ss.GetToken(token); )
 +                      {
 +                              // Save current position so we can revert later if needed
 +                              const std::string::size_type prevpos = ret.length();
 +                              // Append next token
 +                              if (!ret.empty())
 +                                      ret.push_back(':');
 +                              ret.append(token);
 +
 +                              gnutls_priority_t test;
 +                              if (gnutls_priority_init(&test, ret.c_str(), NULL) < 0)
 +                              {
 +                                      // The new token broke the priority string, revert to the previously working one
 +                                      ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Priority string token not recognized: \"%s\"", token.c_str());
 +                                      ret.erase(prevpos);
 +                              }
 +                              else
 +                              {
 +                                      // Worked
 +                                      gnutls_priority_deinit(test);
 +                              }
 +                      }
 +                      return ret;
 +              }
 +      };
 +#else
 +      /** Dummy class, used when gnutls_priority_set() is not available
 +       */
 +      class Priority
 +      {
 +       public:
 +              Priority(const std::string& priorities)
 +              {
 +                      if (priorities != GetDefault())
 +                              throw Exception("You've set a non-default priority string, but GnuTLS lacks support for it");
 +              }
  
 -              if((ret = gnutls_certificate_set_x509_key(x509_cred, &x509_certs[0], certcount, x509_key)) < 0)
 -                      throw ModuleException("Unable to set GnuTLS cert/key pair: " + std::string(gnutls_strerror(ret)));
 +              static void SetupSession(gnutls_session_t sess)
 +              {
 +                      // Always set the default priorities
 +                      gnutls_set_default_priority(sess);
 +              }
  
 -              #ifdef GNUTLS_NEW_PRIO_API
 -              // Try to set the priorities for ciphers, kex methods etc. to the user supplied string
 -              // If the user did not supply anything then the string is already set to "NORMAL"
 -              const char* priocstr = priorities.c_str();
 -              const char* prioerror;
 +              static const char* GetDefault()
 +              {
 +                      return "NORMAL";
 +              }
  
 -              gnutls_priority_t& priority = config->priority;
 -              if ((ret = gnutls_priority_init(&priority, priocstr, &prioerror)) < 0)
 +              static std::string RemoveUnknownTokens(const std::string& prio)
                {
 -                      // gnutls did not understand the user supplied string, log and fall back to the default priorities
 -                      ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to set priorities to \"%s\": %s Syntax error at position %u, falling back to default (NORMAL)", priorities.c_str(), gnutls_strerror(ret), (unsigned int) (prioerror - priocstr));
 -                      gnutls_priority_init(&priority, "NORMAL", NULL);
 +                      // We don't do anything here because only NORMAL is accepted
 +                      return prio;
                }
 +      };
 +#endif
  
 -              #else
 -              if (priorities != "NORMAL")
 -                      ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: You've set <gnutls:priority> to a value other than the default, but this is only supported with GnuTLS v2.1.7 or newer. Your GnuTLS version is older than that so the option will have no effect.");
 -              #endif
 +      class CertCredentials
 +      {
 +              /** DH parameters associated with these credentials
 +               */
 +              std::auto_ptr<DHParams> dh;
  
 -              #if(GNUTLS_VERSION_MAJOR < 2 || ( GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12 ) )
 -              gnutls_certificate_client_set_retrieve_function (x509_cred, cert_callback);
 -              #else
 -              gnutls_certificate_set_retrieve_function (x509_cred, cert_callback);
 -              #endif
 +       protected:
 +              gnutls_certificate_credentials_t cred;
  
 -              gnutls_dh_params_t& dh_params = config->dh_params;
 -              ret = gnutls_dh_params_init(&dh_params);
 -              if (ret < 0)
 +       public:
 +              CertCredentials()
                {
 -                      // Make sure the destructor does not try to deallocate this, see above
 -                      dh_params = NULL;
 -                      ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to initialise DH parameters: %s", gnutls_strerror(ret));
 -                      return;
 +                      ThrowOnError(gnutls_certificate_allocate_credentials(&cred), "Cannot allocate certificate credentials");
                }
  
 -              std::string dhfile = Conf->getString("dhfile");
 -              if (!dhfile.empty())
 +              ~CertCredentials()
                {
 -                      // Try to load DH params from file
 -                      reader.LoadFile(dhfile);
 -                      std::string dhstring = reader.Contents();
 -                      gnutls_datum_t dh_datum = { (unsigned char*)dhstring.data(), static_cast<unsigned int>(dhstring.length()) };
 -
 -                      if ((ret = gnutls_dh_params_import_pkcs3(dh_params, &dh_datum, GNUTLS_X509_FMT_PEM)) < 0)
 -                      {
 -                              // File unreadable or GnuTLS was unhappy with the contents, generate the DH primes now
 -                              ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls.so: Generating DH parameters because I failed to load them from file '%s': %s", dhfile.c_str(), gnutls_strerror(ret));
 -                              GenerateDHParams(dh_params);
 -                      }
 +                      gnutls_certificate_free_credentials(cred);
                }
 -              else
 +
 +              /** Associates these credentials with the session
 +               */
 +              void SetupSession(gnutls_session_t sess)
                {
 -                      GenerateDHParams(dh_params);
 +                      gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred);
                }
  
 -              gnutls_certificate_set_dh_params(x509_cred, dh_params);
 -      }
 +              /** Set the given DH parameters to be used with these credentials
 +               */
 +              void SetDH(std::auto_ptr<DHParams>& DH)
 +              {
 +                      dh = DH;
 +                      gnutls_certificate_set_dh_params(cred, dh->get());
 +              }
 +      };
  
 -      void GenerateDHParams(gnutls_dh_params_t dh_params)
 +      class X509Credentials : public CertCredentials
        {
 -              // Generate Diffie Hellman parameters - for use with DHE
 -              // kx algorithms. These should be discarded and regenerated
 -              // once a day, once a week or once a month. Depending on the
 -              // security requirements.
 +              /** Private key
 +               */
 +              X509Key key;
  
 -              int ret;
 +              /** Certificate list, presented to the peer
 +               */
 +              X509CertList certs;
  
 -              if((ret = gnutls_dh_params_generate2(dh_params, dh_bits)) < 0)
 -                      ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to generate DH parameters (%d bits): %s", dh_bits, gnutls_strerror(ret));
 -      }
 +              /** Trusted CA, may be NULL
 +               */
 +              std::auto_ptr<X509CertList> trustedca;
  
 -      ~ModuleSSLGnuTLS()
 -      {
 -              currconf = NULL;
 +              /** Certificate revocation list, may be NULL
 +               */
 +              std::auto_ptr<X509CRL> crl;
  
 -              gnutls_global_deinit();
 -              delete[] sessions;
 -              ServerInstance->GenRandom = &ServerInstance->HandleGenRandom;
 -      }
 +              static int cert_callback(gnutls_session_t session, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, cert_cb_last_param_type* st);
  
 -      void OnCleanup(int target_type, void* item)
 -      {
 -              if(target_type == TYPE_USER)
 +       public:
 +              X509Credentials(const std::string& certstr, const std::string& keystr)
 +                      : key(keystr)
 +                      , certs(certstr)
                {
 -                      LocalUser* user = IS_LOCAL(static_cast<User*>(item));
 +                      // Throwing is ok here, the destructor of Credentials is called in that case
 +                      int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get());
 +                      ThrowOnError(ret, "Unable to set cert/key pair");
  
 -                      if (user && user->eh.GetIOHook() == this)
 -                      {
 -                              // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
 -                              // Potentially there could be multiple SSL modules loaded at once on different ports.
 -                              ServerInstance->Users->QuitUser(user, "SSL module unloading");
 -                      }
 +#ifdef GNUTLS_NEW_CERT_CALLBACK_API
 +                      gnutls_certificate_set_retrieve_function(cred, cert_callback);
 +#else
 +                      gnutls_certificate_client_set_retrieve_function(cred, cert_callback);
 +#endif
                }
 -      }
  
 -      Version GetVersion()
 -      {
 -              return Version("Provides SSL support for clients", VF_VENDOR);
 -      }
 +              /** Sets the trusted CA and the certificate revocation list
 +               * to use when verifying certificates
 +               */
 +              void SetCA(std::auto_ptr<X509CertList>& certlist, std::auto_ptr<X509CRL>& CRL)
 +              {
 +                      // Do nothing if certlist is NULL
 +                      if (certlist.get())
 +                      {
 +                              int ret = gnutls_certificate_set_x509_trust(cred, certlist->raw(), certlist->size());
 +                              ThrowOnError(ret, "gnutls_certificate_set_x509_trust() failed");
 +
 +                              if (CRL.get())
 +                              {
 +                                      ret = gnutls_certificate_set_x509_crl(cred, &CRL->get(), 1);
 +                                      ThrowOnError(ret, "gnutls_certificate_set_x509_crl() failed");
 +                              }
  
 +                              trustedca = certlist;
 +                              crl = CRL;
 +                      }
 +              }
 +      };
  
 -      void On005Numeric(std::string &output)
 +      class DataReader
        {
 -              if (!sslports.empty())
 -                      output.append(" SSL=" + sslports);
 -              if (starttls.enabled)
 -                      output.append(" STARTTLS");
 -      }
 +              int retval;
 +#ifdef INSPIRCD_GNUTLS_HAS_RECV_PACKET
 +              gnutls_packet_t packet;
  
 -      void OnHookIO(StreamSocket* user, ListenSocket* lsb)
 -      {
 -              if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "gnutls")
 +       public:
 +              DataReader(gnutls_session_t sess)
                {
 -                      /* Hook the user with our module */
 -                      user->AddIOHook(this);
 +                      // Using the packet API avoids the final copy of the data which GnuTLS does if we supply
 +                      // our own buffer. Instead, we get the buffer containing the data from GnuTLS and copy it
 +                      // to the recvq directly from there in appendto().
 +                      retval = gnutls_record_recv_packet(sess, &packet);
                }
 -      }
  
 -      void OnRequest(Request& request)
 -      {
 -              if (strcmp("GET_SSL_CERT", request.id) == 0)
 +              void appendto(std::string& recvq)
                {
 -                      SocketCertificateRequest& req = static_cast<SocketCertificateRequest&>(request);
 -                      int fd = req.sock->GetFd();
 -                      issl_session* session = &sessions[fd];
 +                      // Copy data from GnuTLS buffers to recvq
 +                      gnutls_datum_t datum;
 +                      gnutls_packet_get(packet, &datum, NULL);
 +                      recvq.append(reinterpret_cast<const char*>(datum.data), datum.size);
  
 -                      req.cert = session->cert;
 +                      gnutls_packet_deinit(packet);
                }
 -              else if (!strcmp("GET_RAW_SSL_SESSION", request.id))
 +#else
 +              char* const buffer;
 +
 +       public:
 +              DataReader(gnutls_session_t sess)
 +                      : buffer(ServerInstance->GetReadBuffer())
                {
 -                      SSLRawSessionRequest& req = static_cast<SSLRawSessionRequest&>(request);
 -                      if ((req.fd >= 0) && (req.fd < ServerInstance->SE->GetMaxFds()))
 -                              req.data = reinterpret_cast<void*>(sessions[req.fd].sess);
 +                      // Read data from GnuTLS buffers into ReadBuffer
 +                      retval = gnutls_record_recv(sess, buffer, ServerInstance->Config->NetBufferSize);
                }
 -      }
 -
 -      void InitSession(StreamSocket* user, bool me_server)
 -      {
 -              issl_session* session = &sessions[user->GetFd()];
 -
 -              gnutls_init(&session->sess, me_server ? GNUTLS_SERVER : GNUTLS_CLIENT);
 -              session->socket = user;
 -              session->config = currconf;
 -
 -              #ifdef GNUTLS_NEW_PRIO_API
 -              gnutls_priority_set(session->sess, currconf->priority);
 -              #else
 -              gnutls_set_default_priority(session->sess);
 -              #endif
 -              gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, currconf->x509_cred);
 -              gnutls_dh_set_prime_bits(session->sess, dh_bits);
 -              gnutls_transport_set_ptr(session->sess, reinterpret_cast<gnutls_transport_ptr_t>(session));
 -              gnutls_transport_set_push_function(session->sess, gnutls_push_wrapper);
 -              gnutls_transport_set_pull_function(session->sess, gnutls_pull_wrapper);
  
 -              if (me_server)
 -                      gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
 +              void appendto(std::string& recvq)
 +              {
 +                      // Copy data from ReadBuffer to recvq
 +                      recvq.append(buffer, retval);
 +              }
 +#endif
  
 -              Handshake(session, user);
 -      }
 +              int ret() const { return retval; }
 +      };
  
 -      void OnStreamSocketAccept(StreamSocket* user, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
 +      class Profile : public refcountbase
        {
 -              issl_session* session = &sessions[user->GetFd()];
 +              /** Name of this profile
 +               */
 +              const std::string name;
  
 -              /* For STARTTLS: Don't try and init a session on a socket that already has a session */
 -              if (session->sess)
 -                      return;
 +              /** X509 certificate(s) and key
 +               */
 +              X509Credentials x509cred;
  
 -              InitSession(user, true);
 -      }
 +              /** The minimum length in bits for the DH prime to be accepted as a client
 +               */
 +              unsigned int min_dh_bits;
  
 -      void OnStreamSocketConnect(StreamSocket* user)
 -      {
 -              InitSession(user, false);
 -      }
 +              /** Hashing algorithm to use when generating certificate fingerprints
 +               */
 +              Hash hash;
  
 -      void OnStreamSocketClose(StreamSocket* user)
 -      {
 -              CloseSession(&sessions[user->GetFd()]);
 -      }
 +              /** Priorities for ciphers, compression methods, etc.
 +               */
 +              Priority priority;
  
 -      int OnStreamSocketRead(StreamSocket* user, std::string& recvq)
 -      {
 -              issl_session* session = &sessions[user->GetFd()];
 +              /** Rough max size of records to send
 +               */
 +              const unsigned int outrecsize;
  
 -              if (!session->sess)
 +              /** True to request a client certificate as a server
 +               */
 +              const bool requestclientcert;
 +
 +              Profile(const std::string& profilename, const std::string& certstr, const std::string& keystr,
 +                              std::auto_ptr<DHParams>& DH, unsigned int mindh, const std::string& hashstr,
 +                              const std::string& priostr, std::auto_ptr<X509CertList>& CA, std::auto_ptr<X509CRL>& CRL,
 +                              unsigned int recsize, bool Requestclientcert)
 +                      : name(profilename)
 +                      , x509cred(certstr, keystr)
 +                      , min_dh_bits(mindh)
 +                      , hash(hashstr)
 +                      , priority(priostr)
 +                      , outrecsize(recsize)
 +                      , requestclientcert(Requestclientcert)
                {
 -                      CloseSession(session);
 -                      user->SetError("No SSL session");
 -                      return -1;
 +                      x509cred.SetDH(DH);
 +                      x509cred.SetCA(CA, CRL);
                }
  
 -              if (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE)
 +              static std::string ReadFile(const std::string& filename)
                {
 -                      // The handshake isn't finished, try to finish it.
 +                      FileReader reader(filename);
 +                      std::string ret = reader.GetString();
 +                      if (ret.empty())
 +                              throw Exception("Cannot read file " + filename);
 +                      return ret;
 +              }
  
 -                      if(!Handshake(session, user))
 +              static std::string GetPrioStr(const std::string& profilename, ConfigTag* tag)
 +              {
 +                      // Use default priority string if this tag does not specify one
 +                      std::string priostr = GnuTLS::Priority::GetDefault();
 +                      bool found = tag->readString("priority", priostr);
 +                      // If the prio string isn't set in the config don't be strict about the default one because it doesn't work on all versions of GnuTLS
 +                      if (!tag->getBool("strictpriority", found))
                        {
 -                              if (session->status != ISSL_CLOSING)
 -                                      return 0;
 -                              return -1;
 +                              std::string stripped = GnuTLS::Priority::RemoveUnknownTokens(priostr);
 +                              if (stripped.empty())
 +                              {
 +                                      // Stripping failed, act as if a prio string wasn't set
 +                                      stripped = GnuTLS::Priority::RemoveUnknownTokens(GnuTLS::Priority::GetDefault());
 +                                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens and stripping it didn't yield a working one either, falling back to \"%s\"", profilename.c_str(), stripped.c_str());
 +                              }
 +                              else if ((found) && (stripped != priostr))
 +                              {
 +                                      // Prio string was set in the config and we ended up with something that works but different
 +                                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens, stripped to \"%s\"", profilename.c_str(), stripped.c_str());
 +                              }
 +                              priostr.swap(stripped);
                        }
 +                      return priostr;
                }
  
 -              // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN.
 -
 -              if (session->status == ISSL_HANDSHAKEN)
 +       public:
 +              static reference<Profile> Create(const std::string& profilename, ConfigTag* tag)
                {
 -                      char* buffer = ServerInstance->GetReadBuffer();
 -                      size_t bufsiz = ServerInstance->Config->NetBufferSize;
 -                      int ret = gnutls_record_recv(session->sess, buffer, bufsiz);
 -                      if (ret > 0)
 -                      {
 -                              recvq.append(buffer, ret);
 -                              // Schedule a read if there is still data in the GnuTLS buffer
 -                              if (gnutls_record_check_pending(session->sess) > 0)
 -                                      ServerInstance->SE->ChangeEventMask(user, FD_ADD_TRIAL_READ);
 -                              return 1;
 -                      }
 -                      else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
 -                      {
 -                              return 0;
 -                      }
 -                      else if (ret == 0)
 -                      {
 -                              user->SetError("Connection closed");
 -                              CloseSession(session);
 -                              return -1;
 -                      }
 -                      else
 +                      std::string certstr = ReadFile(tag->getString("certfile", "cert.pem"));
 +                      std::string keystr = ReadFile(tag->getString("keyfile", "key.pem"));
 +
 +                      std::auto_ptr<DHParams> dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")));
 +
 +                      std::string priostr = GetPrioStr(profilename, tag);
 +                      unsigned int mindh = tag->getInt("mindhbits", 1024);
 +                      std::string hashstr = tag->getString("hash", "md5");
 +
 +                      // Load trusted CA and revocation list, if set
 +                      std::auto_ptr<X509CertList> ca;
 +                      std::auto_ptr<X509CRL> crl;
 +                      std::string filename = tag->getString("cafile");
 +                      if (!filename.empty())
                        {
 -                              user->SetError(gnutls_strerror(ret));
 -                              CloseSession(session);
 -                              return -1;
 +                              ca.reset(new X509CertList(ReadFile(filename)));
 +
 +                              filename = tag->getString("crlfile");
 +                              if (!filename.empty())
 +                                      crl.reset(new X509CRL(ReadFile(filename)));
                        }
 -              }
 -              else if (session->status == ISSL_CLOSING)
 -                      return -1;
  
 -              return 0;
 -      }
 +#ifdef INSPIRCD_GNUTLS_HAS_CORK
 +                      // If cork support is available outrecsize represents the (rough) max amount of data we give GnuTLS while corked
 +                      unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512);
 +#else
 +                      unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512, 16384);
 +#endif
  
 -      int OnStreamSocketWrite(StreamSocket* user, std::string& sendq)
 -      {
 -              issl_session* session = &sessions[user->GetFd()];
 +                      const bool requestclientcert = tag->getBool("requestclientcert", true);
  
 -              if (!session->sess)
 -              {
 -                      CloseSession(session);
 -                      user->SetError("No SSL session");
 -                      return -1;
 +                      return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl, outrecsize, requestclientcert);
                }
  
 -              if (session->status == ISSL_HANDSHAKING_WRITE || session->status == ISSL_HANDSHAKING_READ)
 +              /** Set up the given session with the settings in this profile
 +               */
 +              void SetupSession(gnutls_session_t sess)
                {
 -                      // The handshake isn't finished, try to finish it.
 -                      Handshake(session, user);
 -                      if (session->status != ISSL_CLOSING)
 -                              return 0;
 -                      return -1;
 +                      priority.SetupSession(sess);
 +                      x509cred.SetupSession(sess);
 +                      gnutls_dh_set_prime_bits(sess, min_dh_bits);
 +
 +                      // Request client certificate if enabled and we are a server, no-op if we're a client
 +                      if (requestclientcert)
 +                              gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
                }
  
 -              int ret = 0;
 +              const std::string& GetName() const { return name; }
 +              X509Credentials& GetX509Credentials() { return x509cred; }
 +              gnutls_digest_algorithm_t GetHash() const { return hash.get(); }
 +              unsigned int GetOutgoingRecordSize() const { return outrecsize; }
 +      };
 +}
  
 -              if (session->status == ISSL_HANDSHAKEN)
 -              {
 -                      ret = gnutls_record_send(session->sess, sendq.data(), sendq.length());
 +class GnuTLSIOHook : public SSLIOHook
 +{
 + private:
 +      gnutls_session_t sess;
 +      issl_status status;
 +      reference<GnuTLS::Profile> profile;
 +#ifdef INSPIRCD_GNUTLS_HAS_CORK
 +      size_t gbuffersize;
 +#endif
  
 -                      if (ret == (int)sendq.length())
 -                      {
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_WRITE);
 -                              return 1;
 -                      }
 -                      else if (ret > 0)
 -                      {
 -                              sendq = sendq.substr(ret);
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
 -                              return 0;
 -                      }
 -                      else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
 -                      {
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
 -                              return 0;
 -                      }
 -                      else // (ret < 0)
 -                      {
 -                              user->SetError(gnutls_strerror(ret));
 -                              CloseSession(session);
 -                              return -1;
 -                      }
 +      void CloseSession()
 +      {
 +              if (this->sess)
 +              {
 +                      gnutls_bye(this->sess, GNUTLS_SHUT_WR);
 +                      gnutls_deinit(this->sess);
                }
 -
 -              return 0;
 +              sess = NULL;
 +              certificate = NULL;
 +              status = ISSL_NONE;
        }
  
 -      bool Handshake(issl_session* session, StreamSocket* user)
 +      // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed
 +      int Handshake(StreamSocket* user)
        {
 -              int ret = gnutls_handshake(session->sess);
 +              int ret = gnutls_handshake(this->sess);
  
                if (ret < 0)
                {
                        if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
                        {
                                // Handshake needs resuming later, read() or write() would have blocked.
 +                              this->status = ISSL_HANDSHAKING;
  
 -                              if(gnutls_record_get_direction(session->sess) == 0)
 +                              if (gnutls_record_get_direction(this->sess) == 0)
                                {
                                        // gnutls_handshake() wants to read() again.
 -                                      session->status = ISSL_HANDSHAKING_READ;
 -                                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 +                                      SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
                                }
                                else
                                {
                                        // gnutls_handshake() wants to write() again.
 -                                      session->status = ISSL_HANDSHAKING_WRITE;
 -                                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 +                                      SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
                                }
 +
 +                              return 0;
                        }
                        else
                        {
                                user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret)));
 -                              CloseSession(session);
 -                              session->status = ISSL_CLOSING;
 +                              CloseSession();
 +                              return -1;
                        }
 -
 -                      return false;
                }
                else
                {
                        // Change the seesion state
 -                      session->status = ISSL_HANDSHAKEN;
 +                      this->status = ISSL_HANDSHAKEN;
  
 -                      VerifyCertificate(session,user);
 +                      VerifyCertificate();
  
                        // Finish writing, if any left
 -                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
 +                      SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
  
 -                      return true;
 +                      return 1;
                }
        }
  
 -      void OnUserConnect(LocalUser* user)
 +      void VerifyCertificate()
        {
 -              if (user->eh.GetIOHook() == this)
 -              {
 -                      if (sessions[user->eh.GetFd()].sess)
 -                      {
 -                              const gnutls_session_t& sess = sessions[user->eh.GetFd()].sess;
 -                              std::string cipher = UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess)));
 -                              cipher.append("-").append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).append("-");
 -                              cipher.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess))));
 -
 -                              ssl_cert* cert = sessions[user->eh.GetFd()].cert;
 -                              if (cert->fingerprint.empty())
 -                                      user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick.c_str(), cipher.c_str());
 -                              else
 -                                      user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\""
 -                                              " and your SSL fingerprint is %s", user->nick.c_str(), cipher.c_str(), cert->fingerprint.c_str());
 -                      }
 -              }
 -      }
 -
 -      void CloseSession(issl_session* session)
 -      {
 -              if (session->sess)
 -              {
 -                      gnutls_bye(session->sess, GNUTLS_SHUT_WR);
 -                      gnutls_deinit(session->sess);
 -              }
 -              session->socket = NULL;
 -              session->sess = NULL;
 -              session->cert = NULL;
 -              session->status = ISSL_NONE;
 -              session->config = NULL;
 -      }
 -
 -      void VerifyCertificate(issl_session* session, StreamSocket* user)
 -      {
 -              if (!session->sess || !user)
 -                      return;
 -
 -              unsigned int status;
 +              unsigned int certstatus;
                const gnutls_datum_t* cert_list;
                int ret;
                unsigned int cert_list_size;
                gnutls_x509_crt_t cert;
 -              char name[MAXBUF];
 -              unsigned char digest[MAXBUF];
 +              char str[512];
 +              unsigned char digest[512];
                size_t digest_size = sizeof(digest);
 -              size_t name_size = sizeof(name);
 +              size_t name_size = sizeof(str);
                ssl_cert* certinfo = new ssl_cert;
 -              session->cert = certinfo;
 +              this->certificate = certinfo;
  
                /* This verification function uses the trusted CAs in the credentials
                 * structure. So you must have installed one or more CA certificates.
                 */
 -              ret = gnutls_certificate_verify_peers2(session->sess, &status);
 +              ret = gnutls_certificate_verify_peers2(this->sess, &certstatus);
  
                if (ret < 0)
                {
                        return;
                }
  
 -              certinfo->invalid = (status & GNUTLS_CERT_INVALID);
 -              certinfo->unknownsigner = (status & GNUTLS_CERT_SIGNER_NOT_FOUND);
 -              certinfo->revoked = (status & GNUTLS_CERT_REVOKED);
 -              certinfo->trusted = !(status & GNUTLS_CERT_SIGNER_NOT_CA);
 +              certinfo->invalid = (certstatus & GNUTLS_CERT_INVALID);
 +              certinfo->unknownsigner = (certstatus & GNUTLS_CERT_SIGNER_NOT_FOUND);
 +              certinfo->revoked = (certstatus & GNUTLS_CERT_REVOKED);
 +              certinfo->trusted = !(certstatus & GNUTLS_CERT_SIGNER_NOT_CA);
  
                /* Up to here the process is the same for X.509 certificates and
                 * OpenPGP keys. From now on X.509 certificates are assumed. This can
                 * be easily extended to work with openpgp keys as well.
                 */
 -              if (gnutls_certificate_type_get(session->sess) != GNUTLS_CRT_X509)
 +              if (gnutls_certificate_type_get(this->sess) != GNUTLS_CRT_X509)
                {
                        certinfo->error = "No X509 keys sent";
                        return;
                }
  
                cert_list_size = 0;
 -              cert_list = gnutls_certificate_get_peers(session->sess, &cert_list_size);
 +              cert_list = gnutls_certificate_get_peers(this->sess, &cert_list_size);
                if (cert_list == NULL)
                {
                        certinfo->error = "No certificate was found";
                        goto info_done_dealloc;
                }
  
 -              if (gnutls_x509_crt_get_dn(cert, name, &name_size) == 0)
 +              if (gnutls_x509_crt_get_dn(cert, str, &name_size) == 0)
                {
                        std::string& dn = certinfo->dn;
 -                      dn = name;
 +                      dn = str;
                        // Make sure there are no chars in the string that we consider invalid
                        if (dn.find_first_of("\r\n") != std::string::npos)
                                dn.clear();
                }
  
 -              name_size = sizeof(name);
 -              if (gnutls_x509_crt_get_issuer_dn(cert, name, &name_size) == 0)
 +              name_size = sizeof(str);
 +              if (gnutls_x509_crt_get_issuer_dn(cert, str, &name_size) == 0)
                {
                        std::string& issuer = certinfo->issuer;
 -                      issuer = name;
 +                      issuer = str;
                        if (issuer.find_first_of("\r\n") != std::string::npos)
                                issuer.clear();
                }
  
 -              if ((ret = gnutls_x509_crt_get_fingerprint(cert, hash, digest, &digest_size)) < 0)
 +              if ((ret = gnutls_x509_crt_get_fingerprint(cert, profile->GetHash(), digest, &digest_size)) < 0)
                {
                        certinfo->error = gnutls_strerror(ret);
                }
                else
                {
 -                      certinfo->fingerprint = irc::hex(digest, digest_size);
 +                      certinfo->fingerprint = BinToHex(digest, digest_size);
                }
  
                /* Beware here we do not check for errors.
@@@ -866,487 -972,15 +866,490 @@@ info_done_dealloc
                gnutls_x509_crt_deinit(cert);
        }
  
 -      void OnEvent(Event& ev)
 +      // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error
 +      int PrepareIO(StreamSocket* sock)
 +      {
 +              if (status == ISSL_HANDSHAKEN)
 +                      return 1;
 +              else if (status == ISSL_HANDSHAKING)
 +              {
 +                      // The handshake isn't finished, try to finish it
 +                      return Handshake(sock);
 +              }
 +
 +              CloseSession();
 +              sock->SetError("No SSL session");
 +              return -1;
 +      }
 +
 +#ifdef INSPIRCD_GNUTLS_HAS_CORK
 +      int FlushBuffer(StreamSocket* sock)
 +      {
 +              // If GnuTLS has some data buffered, write it
 +              if (gbuffersize)
 +                      return HandleWriteRet(sock, gnutls_record_uncork(this->sess, 0));
 +              return 1;
 +      }
 +#endif
 +
 +      int HandleWriteRet(StreamSocket* sock, int ret)
 +      {
 +              if (ret > 0)
 +              {
 +#ifdef INSPIRCD_GNUTLS_HAS_CORK
 +                      gbuffersize -= ret;
 +                      if (gbuffersize)
 +                      {
 +                              SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE);
 +                              return 0;
 +                      }
 +#endif
 +                      return ret;
 +              }
 +              else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
 +              {
 +                      SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE);
 +                      return 0;
 +              }
 +              else // (ret < 0)
 +              {
 +                      sock->SetError(gnutls_strerror(ret));
 +                      CloseSession();
 +                      return -1;
 +              }
 +      }
 +
 +      static const char* UnknownIfNULL(const char* str)
 +      {
 +              return str ? str : "UNKNOWN";
 +      }
 +
 +      static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size)
 +      {
 +              StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
 +#ifdef _WIN32
 +              GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
 +#endif
 +
 +              if (sock->GetEventMask() & FD_READ_WILL_BLOCK)
 +              {
 +#ifdef _WIN32
 +                      gnutls_transport_set_errno(session->sess, EAGAIN);
 +#else
 +                      errno = EAGAIN;
 +#endif
 +                      return -1;
 +              }
 +
 +              int rv = SocketEngine::Recv(sock, reinterpret_cast<char *>(buffer), size, 0);
 +
 +#ifdef _WIN32
 +              if (rv < 0)
 +              {
 +                      /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
 +                       * and then set errno appropriately.
 +                       * The gnutls library may also have a different errno variable than us, see
 +                       * gnutls_transport_set_errno(3).
 +                       */
 +                      gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
 +              }
 +#endif
 +
 +              if (rv < (int)size)
 +                      SocketEngine::ChangeEventMask(sock, FD_READ_WILL_BLOCK);
 +              return rv;
 +      }
 +
 +#ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
 +      static ssize_t VectorPush(gnutls_transport_ptr_t transportptr, const giovec_t* iov, int iovcnt)
 +      {
 +              StreamSocket* sock = reinterpret_cast<StreamSocket*>(transportptr);
 +#ifdef _WIN32
 +              GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
 +#endif
 +
 +              if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
 +              {
 +#ifdef _WIN32
 +                      gnutls_transport_set_errno(session->sess, EAGAIN);
 +#else
 +                      errno = EAGAIN;
 +#endif
 +                      return -1;
 +              }
 +
 +              // Cast the giovec_t to iovec not to IOVector so the correct function is called on Windows
 +              int ret = SocketEngine::WriteV(sock, reinterpret_cast<const iovec*>(iov), iovcnt);
 +#ifdef _WIN32
 +              // See the function above for more info about the usage of gnutls_transport_set_errno() on Windows
 +              if (ret < 0)
 +                      gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
 +#endif
 +
 +              int size = 0;
 +              for (int i = 0; i < iovcnt; i++)
 +                      size += iov[i].iov_len;
 +
 +              if (ret < size)
 +                      SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
 +              return ret;
 +      }
 +
 +#else // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
 +      static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size)
        {
 -              if (starttls.enabled)
 -                      capHandler.HandleEvent(ev);
 +              StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
 +#ifdef _WIN32
 +              GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod));
 +#endif
 +
 +              if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
 +              {
 +#ifdef _WIN32
 +                      gnutls_transport_set_errno(session->sess, EAGAIN);
 +#else
 +                      errno = EAGAIN;
 +#endif
 +                      return -1;
 +              }
 +
 +              int rv = SocketEngine::Send(sock, reinterpret_cast<const char *>(buffer), size, 0);
 +
 +#ifdef _WIN32
 +              if (rv < 0)
 +              {
 +                      /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError()
 +                       * and then set errno appropriately.
 +                       * The gnutls library may also have a different errno variable than us, see
 +                       * gnutls_transport_set_errno(3).
 +                       */
 +                      gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
 +              }
 +#endif
 +
 +              if (rv < (int)size)
 +                      SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
 +              return rv;
 +      }
 +#endif // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
 +
 + public:
 +      GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, inspircd_gnutls_session_init_flags_t flags, const reference<GnuTLS::Profile>& sslprofile)
 +              : SSLIOHook(hookprov)
 +              , sess(NULL)
 +              , status(ISSL_NONE)
 +              , profile(sslprofile)
 +#ifdef INSPIRCD_GNUTLS_HAS_CORK
 +              , gbuffersize(0)
 +#endif
 +      {
 +              gnutls_init(&sess, flags);
 +              gnutls_transport_set_ptr(sess, reinterpret_cast<gnutls_transport_ptr_t>(sock));
 +#ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
 +              gnutls_transport_set_vec_push_function(sess, VectorPush);
 +#else
 +              gnutls_transport_set_push_function(sess, gnutls_push_wrapper);
 +#endif
 +              gnutls_transport_set_pull_function(sess, gnutls_pull_wrapper);
 +              profile->SetupSession(sess);
 +
 +              sock->AddIOHook(this);
 +              Handshake(sock);
 +      }
 +
 +      void OnStreamSocketClose(StreamSocket* user) CXX11_OVERRIDE
 +      {
 +              CloseSession();
 +      }
 +
 +      int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE
 +      {
 +              // Finish handshake if needed
 +              int prepret = PrepareIO(user);
 +              if (prepret <= 0)
 +                      return prepret;
 +
 +              // If we resumed the handshake then this->status will be ISSL_HANDSHAKEN.
 +              {
 +                      GnuTLS::DataReader reader(sess);
 +                      int ret = reader.ret();
 +                      if (ret > 0)
 +                      {
 +                              reader.appendto(recvq);
++                              // Schedule a read if there is still data in the GnuTLS buffer
++                              if (gnutls_record_check_pending(sess) > 0)
++                                      SocketEngine::ChangeEventMask(user, FD_ADD_TRIAL_READ);
 +                              return 1;
 +                      }
 +                      else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
 +                      {
 +                              return 0;
 +                      }
 +                      else if (ret == 0)
 +                      {
 +                              user->SetError("Connection closed");
 +                              CloseSession();
 +                              return -1;
 +                      }
 +                      else
 +                      {
 +                              user->SetError(gnutls_strerror(ret));
 +                              CloseSession();
 +                              return -1;
 +                      }
 +              }
 +      }
 +
 +      int OnStreamSocketWrite(StreamSocket* user, StreamSocket::SendQueue& sendq) CXX11_OVERRIDE
 +      {
 +              // Finish handshake if needed
 +              int prepret = PrepareIO(user);
 +              if (prepret <= 0)
 +                      return prepret;
 +
 +              // Session is ready for transferring application data
 +
 +#ifdef INSPIRCD_GNUTLS_HAS_CORK
 +              while (true)
 +              {
 +                      // If there is something in the GnuTLS buffer try to send() it
 +                      int ret = FlushBuffer(user);
 +                      if (ret <= 0)
 +                              return ret; // Couldn't flush entire buffer, retry later (or close on error)
 +
 +                      // GnuTLS buffer is empty, if the sendq is empty as well then break to set FD_WANT_NO_WRITE
 +                      if (sendq.empty())
 +                              break;
 +
 +                      // GnuTLS buffer is empty but sendq is not, begin sending data from the sendq
 +                      gnutls_record_cork(this->sess);
 +                      while ((!sendq.empty()) && (gbuffersize < profile->GetOutgoingRecordSize()))
 +                      {
 +                              const StreamSocket::SendQueue::Element& elem = sendq.front();
 +                              gbuffersize += elem.length();
 +                              ret = gnutls_record_send(this->sess, elem.data(), elem.length());
 +                              if (ret < 0)
 +                              {
 +                                      CloseSession();
 +                                      return -1;
 +                              }
 +                              sendq.pop_front();
 +                      }
 +              }
 +#else
 +              int ret = 0;
 +
 +              while (!sendq.empty())
 +              {
 +                      FlattenSendQueue(sendq, profile->GetOutgoingRecordSize());
 +                      const StreamSocket::SendQueue::Element& buffer = sendq.front();
 +                      ret = HandleWriteRet(user, gnutls_record_send(this->sess, buffer.data(), buffer.length()));
 +
 +                      if (ret <= 0)
 +                              return ret;
 +                      else if (ret < (int)buffer.length())
 +                      {
 +                              sendq.erase_front(ret);
 +                              SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
 +                              return 0;
 +                      }
 +
 +                      // Wrote entire record, continue sending
 +                      sendq.pop_front();
 +              }
 +#endif
 +
 +              SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE);
 +              return 1;
 +      }
 +
 +      void GetCiphersuite(std::string& out) const CXX11_OVERRIDE
 +      {
 +              if (!IsHandshakeDone())
 +                      return;
 +              out.append(UnknownIfNULL(gnutls_protocol_get_name(gnutls_protocol_get_version(sess)))).push_back('-');
 +              out.append(UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess)))).push_back('-');
 +              out.append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).push_back('-');
 +              out.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess))));
 +      }
 +
 +      GnuTLS::Profile* GetProfile() { return profile; }
 +      bool IsHandshakeDone() const { return (status == ISSL_HANDSHAKEN); }
 +};
 +
 +int GnuTLS::X509Credentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, cert_cb_last_param_type* st)
 +{
 +#ifndef GNUTLS_NEW_CERT_CALLBACK_API
 +      st->type = GNUTLS_CRT_X509;
 +#else
 +      st->cert_type = GNUTLS_CRT_X509;
 +      st->key_type = GNUTLS_PRIVKEY_X509;
 +#endif
 +      StreamSocket* sock = reinterpret_cast<StreamSocket*>(gnutls_transport_get_ptr(sess));
 +      GnuTLS::X509Credentials& cred = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod))->GetProfile()->GetX509Credentials();
 +
 +      st->ncerts = cred.certs.size();
 +      st->cert.x509 = cred.certs.raw();
 +      st->key.x509 = cred.key.get();
 +      st->deinit_all = 0;
 +
 +      return 0;
 +}
 +
 +class GnuTLSIOHookProvider : public refcountbase, public IOHookProvider
 +{
 +      reference<GnuTLS::Profile> profile;
 +
 + public:
 +      GnuTLSIOHookProvider(Module* mod, reference<GnuTLS::Profile>& prof)
 +              : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL)
 +              , profile(prof)
 +      {
 +              ServerInstance->Modules->AddService(*this);
 +      }
 +
 +      ~GnuTLSIOHookProvider()
 +      {
 +              ServerInstance->Modules->DelService(*this);
 +      }
 +
 +      void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
 +      {
 +              new GnuTLSIOHook(this, sock, GNUTLS_SERVER, profile);
 +      }
 +
 +      void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
 +      {
 +              new GnuTLSIOHook(this, sock, GNUTLS_CLIENT, profile);
 +      }
 +};
 +
 +class ModuleSSLGnuTLS : public Module
 +{
 +      typedef std::vector<reference<GnuTLSIOHookProvider> > ProfileList;
 +
 +      // First member of the class, gets constructed first and destructed last
 +      GnuTLS::Init libinit;
 +      RandGen randhandler;
 +      ProfileList profiles;
 +
 +      void ReadProfiles()
 +      {
 +              // First, store all profiles in a new, temporary container. If no problems occur, swap the two
 +              // containers; this way if something goes wrong we can go back and continue using the current profiles,
 +              // avoiding unpleasant situations where no new SSL connections are possible.
 +              ProfileList newprofiles;
 +
 +              ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile");
 +              if (tags.first == tags.second)
 +              {
 +                      // No <sslprofile> tags found, create a profile named "gnutls" from settings in the <gnutls> block
 +                      const std::string defname = "gnutls";
 +                      ConfigTag* tag = ServerInstance->Config->ConfValue(defname);
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No <sslprofile> tags found; using settings from the <gnutls> tag");
 +
 +                      try
 +                      {
 +                              reference<GnuTLS::Profile> profile(GnuTLS::Profile::Create(defname, tag));
 +                              newprofiles.push_back(new GnuTLSIOHookProvider(this, profile));
 +                      }
 +                      catch (CoreException& ex)
 +                      {
 +                              throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason());
 +                      }
 +              }
 +
 +              for (ConfigIter i = tags.first; i != tags.second; ++i)
 +              {
 +                      ConfigTag* tag = i->second;
 +                      if (tag->getString("provider") != "gnutls")
 +                              continue;
 +
 +                      std::string name = tag->getString("name");
 +                      if (name.empty())
 +                      {
 +                              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation());
 +                              continue;
 +                      }
 +
 +                      reference<GnuTLS::Profile> profile;
 +                      try
 +                      {
 +                              profile = GnuTLS::Profile::Create(name, tag);
 +                      }
 +                      catch (CoreException& ex)
 +                      {
 +                              throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason());
 +                      }
 +
 +                      newprofiles.push_back(new GnuTLSIOHookProvider(this, profile));
 +              }
 +
 +              // New profiles are ok, begin using them
 +              // Old profiles are deleted when their refcount drops to zero
 +              profiles.swap(newprofiles);
 +      }
 +
 + public:
 +      ModuleSSLGnuTLS()
 +      {
 +#ifndef GNUTLS_HAS_RND
 +              gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
 +#endif
 +              thismod = this;
 +      }
 +
 +      void init() CXX11_OVERRIDE
 +      {
 +              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "GnuTLS lib version %s module was compiled for " GNUTLS_VERSION, gnutls_check_version(NULL));
 +              ReadProfiles();
 +              ServerInstance->GenRandom = &randhandler;
 +      }
 +
 +      void OnModuleRehash(User* user, const std::string &param) CXX11_OVERRIDE
 +      {
 +              if(param != "ssl")
 +                      return;
 +
 +              try
 +              {
 +                      ReadProfiles();
 +              }
 +              catch (ModuleException& ex)
 +              {
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings.");
 +              }
 +      }
 +
 +      ~ModuleSSLGnuTLS()
 +      {
 +              ServerInstance->GenRandom = &ServerInstance->HandleGenRandom;
 +      }
 +
 +      void OnCleanup(int target_type, void* item) CXX11_OVERRIDE
 +      {
 +              if(target_type == TYPE_USER)
 +              {
 +                      LocalUser* user = IS_LOCAL(static_cast<User*>(item));
 +
 +                      if ((user) && (user->eh.GetModHook(this)))
 +                      {
 +                              // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
 +                              // Potentially there could be multiple SSL modules loaded at once on different ports.
 +                              ServerInstance->Users->QuitUser(user, "SSL module unloading");
 +                      }
 +              }
 +      }
 +
 +      Version GetVersion() CXX11_OVERRIDE
 +      {
 +              return Version("Provides SSL support for clients", VF_VENDOR);
        }
  
 -      ModResult OnCheckReady(LocalUser* user)
 +      ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
        {
 -              if ((user->eh.GetIOHook() == this) && (sessions[user->eh.GetFd()].status != ISSL_HANDSHAKEN))
 +              const GnuTLSIOHook* const iohook = static_cast<GnuTLSIOHook*>(user->eh.GetModHook(this));
 +              if ((iohook) && (!iohook->IsHandshakeDone()))
                        return MOD_RES_DENY;
                return MOD_RES_PASSTHRU;
        }
index 4df0d8962e96b98367d1fc3d339bb2109eae9c5a,aee7a5e3428a86afc8e2817c21fea3566aed3365..8467cc6d416b5931efb721009aeca33611e7ea60
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
 - /* HACK: This prevents OpenSSL on OS X 10.7 and later from spewing deprecation
 -  * warnings for every single function call. As far as I (SaberUK) know, Apple
 -  * have no plans to remove OpenSSL so this warning just causes needless spam.
 -  */
 -#ifdef __APPLE__
 -# define __AVAILABILITYMACROS__
 -# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
 -#endif
 - 
 +
  #include "inspircd.h"
 +#include "iohook.h"
 +#include "modules/ssl.h"
 +
 +// Ignore OpenSSL deprecation warnings on OS X Lion and newer.
 +#if defined __APPLE__
 +# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 +#endif
 +
 +// Fix warnings about the use of `long long` on C++03.
 +#if defined __clang__
 +# pragma clang diagnostic ignored "-Wc++11-long-long"
 +#elif defined __GNUC__
 +# pragma GCC diagnostic ignored "-Wlong-long"
 +#endif
 +
  #include <openssl/ssl.h>
  #include <openssl/err.h>
 -#include "ssl.h"
  
  #ifdef _WIN32
  # pragma comment(lib, "ssleay32.lib")
  # pragma comment(lib, "libeay32.lib")
 -# undef MAX_DESCRIPTORS
 -# define MAX_DESCRIPTORS 10000
  #endif
  
 -/* $ModDesc: Provides SSL support for clients */
 -
 -/* $LinkerFlags: if("USE_FREEBSD_BASE_SSL") -lssl -lcrypto */
 -/* $CompileFlags: if(!"USE_FREEBSD_BASE_SSL") pkgconfversion("openssl","0.9.7") pkgconfincludes("openssl","/openssl/ssl.h","") */
 -/* $LinkerFlags: if(!"USE_FREEBSD_BASE_SSL") rpath("pkg-config --libs openssl") pkgconflibs("openssl","/libssl.so","-lssl -lcrypto -ldl") */
 +/* $CompileFlags: pkgconfversion("openssl","0.9.7") pkgconfincludes("openssl","/openssl/ssl.h","") */
 +/* $LinkerFlags: rpath("pkg-config --libs openssl") pkgconflibs("openssl","/libssl.so","-lssl -lcrypto") */
  
 -/* $NoPedantic */
 -
 -
 -class ModuleSSLOpenSSL;
 +#if ((OPENSSL_VERSION_NUMBER >= 0x10000000L) && (!(defined(OPENSSL_NO_ECDH))))
 +// OpenSSL 0.9.8 includes some ECC support, but it's unfinished. Enable only for 1.0.0 and later.
 +#define INSPIRCD_OPENSSL_ENABLE_ECDH
 +#endif
  
  enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_OPEN };
  
  static bool SelfSigned = false;
 -
 -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
 -static ModuleSSLOpenSSL* opensslmod = NULL;
 -#endif
 +static int exdataindex;
  
  char* get_error()
  {
        return ERR_error_string(ERR_get_error(), NULL);
  }
  
 -static int error_callback(const char *str, size_t len, void *u);
 +static int OnVerify(int preverify_ok, X509_STORE_CTX* ctx);
 +static void StaticSSLInfoCallback(const SSL* ssl, int where, int rc);
  
 -/** Represents an SSL user's extra data
 - */
 -class issl_session
 +namespace OpenSSL
  {
 -public:
 -      SSL* sess;
 -      issl_status status;
 -      reference<ssl_cert> cert;
 -
 -      bool outbound;
 -      bool data_to_write;
 -
 -      issl_session()
 -              : sess(NULL)
 -              , status(ISSL_NONE)
 +      class Exception : public ModuleException
        {
 -              outbound = false;
 -              data_to_write = false;
 -      }
 -};
 +       public:
 +              Exception(const std::string& reason)
 +                      : ModuleException(reason) { }
 +      };
  
 -static int OnVerify(int preverify_ok, X509_STORE_CTX *ctx)
 -{
 -      /* XXX: This will allow self signed certificates.
 -       * In the future if we want an option to not allow this,
 -       * we can just return preverify_ok here, and openssl
 -       * will boot off self-signed and invalid peer certs.
 -       */
 -      int ve = X509_STORE_CTX_get_error(ctx);
 -
 -      SelfSigned = (ve == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT);
 -
 -      return 1;
 -}
 +      class DHParams
 +      {
 +              DH* dh;
  
 -class ModuleSSLOpenSSL : public Module
 -{
 -      issl_session* sessions;
 +       public:
 +              DHParams(const std::string& filename)
 +              {
 +                      BIO* dhpfile = BIO_new_file(filename.c_str(), "r");
 +                      if (dhpfile == NULL)
 +                              throw Exception("Couldn't open DH file " + filename);
  
 -      SSL_CTX* ctx;
 -      SSL_CTX* clictx;
 +                      dh = PEM_read_bio_DHparams(dhpfile, NULL, NULL, NULL);
 +                      BIO_free(dhpfile);
  
 -      long ctx_options;
 -      long clictx_options;
 +                      if (!dh)
 +                              throw Exception("Couldn't read DH params from file " + filename);
 +              }
  
 -      std::string sslports;
 -      bool use_sha;
 +              ~DHParams()
 +              {
 +                      DH_free(dh);
 +              }
  
 -      ServiceProvider iohook;
 +              DH* get()
 +              {
 +                      return dh;
 +              }
 +      };
  
 -      static void SetContextOptions(SSL_CTX* ctx, long defoptions, const std::string& ctxname, ConfigTag* tag)
 +      class Context
        {
 -              long setoptions = tag->getInt(ctxname + "setoptions");
 -              // User-friendly config options for setting context options
 -#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
 -              if (tag->getBool("cipherserverpref"))
 -                      setoptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
 +              SSL_CTX* const ctx;
 +              long ctx_options;
 +
 +       public:
 +              Context(SSL_CTX* context)
 +                      : ctx(context)
 +              {
 +                      // Sane default options for OpenSSL see https://www.openssl.org/docs/ssl/SSL_CTX_set_options.html
 +                      // and when choosing a cipher, use the server's preferences instead of the client preferences.
 +                      long opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_DH_USE;
 +                      // Only turn options on if they exist
 +#ifdef SSL_OP_SINGLE_ECDH_USE
 +                      opts |= SSL_OP_SINGLE_ECDH_USE;
  #endif
 -#ifdef SSL_OP_NO_COMPRESSION
 -              if (!tag->getBool("compression", true))
 -                      setoptions |= SSL_OP_NO_COMPRESSION;
 +#ifdef SSL_OP_NO_TICKET
 +                      opts |= SSL_OP_NO_TICKET;
  #endif
 -              if (!tag->getBool("sslv3", true))
 -                      setoptions |= SSL_OP_NO_SSLv3;
 -              if (!tag->getBool("tlsv1", true))
 -                      setoptions |= SSL_OP_NO_TLSv1;
  
 -              long clearoptions = tag->getInt(ctxname + "clearoptions");
 -              ServerInstance->Logs->Log("m_ssl_openssl", DEBUG, "Setting OpenSSL %s context options, default: %ld set: %ld clear: %ld", ctxname.c_str(), defoptions, setoptions, clearoptions);
 +                      ctx_options = SSL_CTX_set_options(ctx, opts);
  
 -              // Clear everything
 -              SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx));
 +                      long mode = SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER;
 +#ifdef SSL_MODE_RELEASE_BUFFERS
 +                      mode |= SSL_MODE_RELEASE_BUFFERS;
 +#endif
 +                      SSL_CTX_set_mode(ctx, mode);
 +                      SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
 +                      SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
 +                      SSL_CTX_set_info_callback(ctx, StaticSSLInfoCallback);
 +              }
  
 -              // Set the default options and what is in the conf
 -              SSL_CTX_set_options(ctx, defoptions | setoptions);
 -              long final = SSL_CTX_clear_options(ctx, clearoptions);
 -              ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "OpenSSL %s context options: %ld", ctxname.c_str(), final);
 -      }
 +              ~Context()
 +              {
 +                      SSL_CTX_free(ctx);
 +              }
  
 -#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH
 -      void SetupECDH(ConfigTag* tag)
 -      {
 -              std::string curvename = tag->getString("ecdhcurve", "prime256v1");
 -              if (curvename.empty())
 -                      return;
 +              bool SetDH(DHParams& dh)
 +              {
 +                      ERR_clear_error();
 +                      return (SSL_CTX_set_tmp_dh(ctx, dh.get()) >= 0);
 +              }
  
 -              int nid = OBJ_sn2nid(curvename.c_str());
 -              if (nid == 0)
 +#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH
 +              void SetECDH(const std::string& curvename)
                {
 -                      ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Unknown curve: \"%s\"", curvename.c_str());
 -                      return;
 +                      int nid = OBJ_sn2nid(curvename.c_str());
 +                      if (nid == 0)
 +                              throw Exception("Unknown curve: " + curvename);
 +
 +                      EC_KEY* eckey = EC_KEY_new_by_curve_name(nid);
 +                      if (!eckey)
 +                              throw Exception("Unable to create EC key object");
 +
 +                      ERR_clear_error();
 +                      bool ret = (SSL_CTX_set_tmp_ecdh(ctx, eckey) >= 0);
 +                      EC_KEY_free(eckey);
 +                      if (!ret)
 +                              throw Exception("Couldn't set ECDH parameters");
                }
 +#endif
  
 -              EC_KEY* eckey = EC_KEY_new_by_curve_name(nid);
 -              if (!eckey)
 +              bool SetCiphers(const std::string& ciphers)
                {
 -                      ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Unable to create EC key object");
 -                      return;
 +                      ERR_clear_error();
 +                      return SSL_CTX_set_cipher_list(ctx, ciphers.c_str());
                }
  
 -              ERR_clear_error();
 -              if (SSL_CTX_set_tmp_ecdh(ctx, eckey) < 0)
 +              bool SetCerts(const std::string& filename)
                {
 -                      ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Couldn't set ECDH parameters");
 -                      ERR_print_errors_cb(error_callback, this);
 +                      ERR_clear_error();
 +                      return SSL_CTX_use_certificate_chain_file(ctx, filename.c_str());
                }
  
 -              EC_KEY_free(eckey);
 -      }
 -#endif
 +              bool SetPrivateKey(const std::string& filename)
 +              {
 +                      ERR_clear_error();
 +                      return SSL_CTX_use_PrivateKey_file(ctx, filename.c_str(), SSL_FILETYPE_PEM);
 +              }
  
 -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
 -      static void SSLInfoCallback(const SSL* ssl, int where, int rc)
 -      {
 -              int fd = SSL_get_fd(const_cast<SSL*>(ssl));
 -              issl_session& session = opensslmod->sessions[fd];
 +              bool SetCA(const std::string& filename)
 +              {
 +                      ERR_clear_error();
 +                      return SSL_CTX_load_verify_locations(ctx, filename.c_str(), 0);
 +              }
  
 -              if ((where & SSL_CB_HANDSHAKE_START) && (session.status == ISSL_OPEN))
 +              long GetDefaultContextOptions() const
                {
 -                      // The other side is trying to renegotiate, kill the connection and change status
 -                      // to ISSL_NONE so CheckRenego() closes the session
 -                      session.status = ISSL_NONE;
 -                      ServerInstance->SE->Shutdown(fd, 2);
 +                      return ctx_options;
                }
 -      }
  
 -      bool CheckRenego(StreamSocket* sock, issl_session* session)
 -      {
 -              if (session->status != ISSL_NONE)
 -                      return true;
 +              long SetRawContextOptions(long setoptions, long clearoptions)
 +              {
 +                      // Clear everything
 +                      SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx));
  
 -              ServerInstance->Logs->Log("m_ssl_openssl", DEBUG, "Session %p killed, attempted to renegotiate", (void*)session->sess);
 -              CloseSession(session);
 -              sock->SetError("Renegotiation is not allowed");
 -              return false;
 -      }
 -#endif
 +                      // Set the default options and what is in the conf
 +                      SSL_CTX_set_options(ctx, ctx_options | setoptions);
 +                      return SSL_CTX_clear_options(ctx, clearoptions);
 +              }
  
 - public:
 +              void SetVerifyCert()
 +              {
 +                      SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, OnVerify);
 +              }
  
 -      ModuleSSLOpenSSL() : iohook(this, "ssl/openssl", SERVICE_IOHOOK)
 -      {
 -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
 -              opensslmod = this;
 -#endif
 -              sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
 +              SSL* CreateServerSession()
 +              {
 +                      SSL* sess = SSL_new(ctx);
 +                      SSL_set_accept_state(sess); // Act as server
 +                      return sess;
 +              }
  
 -              /* Global SSL library initialization*/
 -              SSL_library_init();
 -              SSL_load_error_strings();
 +              SSL* CreateClientSession()
 +              {
 +                      SSL* sess = SSL_new(ctx);
 +                      SSL_set_connect_state(sess); // Act as client
 +                      return sess;
 +              }
 +      };
  
 -              /* Build our SSL contexts:
 -               * NOTE: OpenSSL makes us have two contexts, one for servers and one for clients. ICK.
 +      class Profile : public refcountbase
 +      {
 +              /** Name of this profile
                 */
 -              ctx = SSL_CTX_new( SSLv23_server_method() );
 -              clictx = SSL_CTX_new( SSLv23_client_method() );
 +              const std::string name;
  
 -              SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
 -              SSL_CTX_set_mode(clictx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
 +              /** DH parameters in use
 +               */
 +              DHParams dh;
  
 -              SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, OnVerify);
 -              SSL_CTX_set_verify(clictx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, OnVerify);
 +              /** OpenSSL makes us have two contexts, one for servers and one for clients
 +               */
 +              Context ctx;
 +              Context clictx;
  
 -              SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
 -              SSL_CTX_set_session_cache_mode(clictx, SSL_SESS_CACHE_OFF);
 +              /** Digest to use when generating fingerprints
 +               */
 +              const EVP_MD* digest;
  
 -              long opts = SSL_OP_NO_SSLv2 | SSL_OP_SINGLE_DH_USE;
 -              // Only turn options on if they exist
 -#ifdef SSL_OP_SINGLE_ECDH_USE
 -              opts |= SSL_OP_SINGLE_ECDH_USE;
 -#endif
 -#ifdef SSL_OP_NO_TICKET
 -              opts |= SSL_OP_NO_TICKET;
 -#endif
 +              /** Last error, set by error_callback()
 +               */
 +              std::string lasterr;
  
 -              ctx_options = SSL_CTX_set_options(ctx, opts);
 -              clictx_options = SSL_CTX_set_options(clictx, opts);
 -      }
 +              /** True if renegotiations are allowed, false if not
 +               */
 +              const bool allowrenego;
  
 -      void init()
 -      {
 -              // Needs the flag as it ignores a plain /rehash
 -              OnModuleRehash(NULL,"ssl");
 -              Implementation eventlist[] = { I_On005Numeric, I_OnRehash, I_OnModuleRehash, I_OnHookIO, I_OnUserConnect };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
 -              ServerInstance->Modules->AddService(iohook);
 -      }
 +              /** Rough max size of records to send
 +               */
 +              const unsigned int outrecsize;
  
 -      void OnHookIO(StreamSocket* user, ListenSocket* lsb)
 -      {
 -              if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "openssl")
 +              static int error_callback(const char* str, size_t len, void* u)
                {
 -                      /* Hook the user with our module */
 -                      user->AddIOHook(this);
 +                      Profile* profile = reinterpret_cast<Profile*>(u);
 +                      profile->lasterr = std::string(str, len - 1);
 +                      return 0;
                }
 -      }
 -
 -      void OnRehash(User* user)
 -      {
 -              sslports.clear();
  
 -              ConfigTag* Conf = ServerInstance->Config->ConfValue("openssl");
 -
 -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
 -              // Set the callback if we are not allowing renegotiations, unset it if we do
 -              if (Conf->getBool("renegotiation", true))
 -              {
 -                      SSL_CTX_set_info_callback(ctx, NULL);
 -                      SSL_CTX_set_info_callback(clictx, NULL);
 -              }
 -              else
 +              /** Set raw OpenSSL context (SSL_CTX) options from a config tag
 +               * @param ctxname Name of the context, client or server
 +               * @param tag Config tag defining this profile
 +               * @param context Context object to manipulate
 +               */
 +              void SetContextOptions(const std::string& ctxname, ConfigTag* tag, Context& context)
                {
 -                      SSL_CTX_set_info_callback(ctx, SSLInfoCallback);
 -                      SSL_CTX_set_info_callback(clictx, SSLInfoCallback);
 -              }
 +                      long setoptions = tag->getInt(ctxname + "setoptions");
 +                      long clearoptions = tag->getInt(ctxname + "clearoptions");
 +#ifdef SSL_OP_NO_COMPRESSION
 +                      if (!tag->getBool("compression", false)) // Disable compression by default
 +                              setoptions |= SSL_OP_NO_COMPRESSION;
  #endif
 -
 -              if (Conf->getBool("showports", true))
 -              {
 -                      sslports = Conf->getString("advertisedports");
 -                      if (!sslports.empty())
 -                              return;
 -
 -                      for (size_t i = 0; i < ServerInstance->ports.size(); i++)
 +                      if (!tag->getBool("sslv3", false)) // Disable SSLv3 by default
 +                              setoptions |= SSL_OP_NO_SSLv3;
 +                      if (!tag->getBool("tlsv1", true))
 +                              setoptions |= SSL_OP_NO_TLSv1;
 +
 +                      if (!setoptions && !clearoptions)
 +                              return; // Nothing to do
 +
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Setting %s %s context options, default: %ld set: %ld clear: %ld", name.c_str(), ctxname.c_str(), ctx.GetDefaultContextOptions(), setoptions, clearoptions);
 +                      long final = context.SetRawContextOptions(setoptions, clearoptions);
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "%s %s context options: %ld", name.c_str(), ctxname.c_str(), final);
 +              }
 +
 +       public:
 +              Profile(const std::string& profilename, ConfigTag* tag)
 +                      : name(profilename)
 +                      , dh(ServerInstance->Config->Paths.PrependConfig(tag->getString("dhfile", "dh.pem")))
 +                      , ctx(SSL_CTX_new(SSLv23_server_method()))
 +                      , clictx(SSL_CTX_new(SSLv23_client_method()))
 +                      , allowrenego(tag->getBool("renegotiation")) // Disallow by default
 +                      , outrecsize(tag->getInt("outrecsize", 2048, 512, 16384))
 +              {
 +                      if ((!ctx.SetDH(dh)) || (!clictx.SetDH(dh)))
 +                              throw Exception("Couldn't set DH parameters");
 +
 +                      std::string hash = tag->getString("hash", "md5");
 +                      digest = EVP_get_digestbyname(hash.c_str());
 +                      if (digest == NULL)
 +                              throw Exception("Unknown hash type " + hash);
 +
 +                      std::string ciphers = tag->getString("ciphers");
 +                      if (!ciphers.empty())
                        {
 -                              ListenSocket* port = ServerInstance->ports[i];
 -                              if (port->bind_tag->getString("ssl") != "openssl")
 -                                      continue;
 -
 -                              const std::string& portid = port->bind_desc;
 -                              ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Enabling SSL for port %s", portid.c_str());
 -
 -                              if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1")
 +                              if ((!ctx.SetCiphers(ciphers)) || (!clictx.SetCiphers(ciphers)))
                                {
 -                                      /*
 -                                       * Found an SSL port for clients that is not bound to 127.0.0.1 and handled by us, display
 -                                       * the IP:port in ISUPPORT.
 -                                       *
 -                                       * We used to advertise all ports seperated by a ';' char that matched the above criteria,
 -                                       * but this resulted in too long ISUPPORT lines if there were lots of ports to be displayed.
 -                                       * To solve this by default we now only display the first IP:port found and let the user
 -                                       * configure the exact value for the 005 token, if necessary.
 -                                       */
 -                                      sslports = portid;
 -                                      break;
 +                                      ERR_print_errors_cb(error_callback, this);
 +                                      throw Exception("Can't set cipher list to \"" + ciphers + "\" " + lasterr);
                                }
                        }
 -              }
 -      }
 -
 -      void OnModuleRehash(User* user, const std::string &param)
 -      {
 -              if (param != "ssl")
 -                      return;
  
 -              std::string keyfile;
 -              std::string certfile;
 -              std::string cafile;
 -              std::string dhfile;
 -              OnRehash(user);
 -
 -              ConfigTag* conf = ServerInstance->Config->ConfValue("openssl");
 +#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH
 +                      std::string curvename = tag->getString("ecdhcurve", "prime256v1");
 +                      if (!curvename.empty())
 +                              ctx.SetECDH(curvename);
 +#endif
  
 -              cafile   = conf->getString("cafile", CONFIG_PATH "/ca.pem");
 -              certfile = conf->getString("certfile", CONFIG_PATH "/cert.pem");
 -              keyfile  = conf->getString("keyfile", CONFIG_PATH "/key.pem");
 -              dhfile   = conf->getString("dhfile", CONFIG_PATH "/dhparams.pem");
 -              std::string hash = conf->getString("hash", "md5");
 -              if (hash != "sha1" && hash != "md5")
 -                      throw ModuleException("Unknown hash type " + hash);
 -              use_sha = (hash == "sha1");
 +                      SetContextOptions("server", tag, ctx);
 +                      SetContextOptions("client", tag, clictx);
  
 -              if (conf->getBool("customcontextoptions"))
 -              {
 -                      SetContextOptions(ctx, ctx_options, "server", conf);
 -                      SetContextOptions(clictx, clictx_options, "client", conf);
 -              }
 +                      /* Load our keys and certificates
 +                       * NOTE: OpenSSL's error logging API sucks, don't blame us for this clusterfuck.
 +                       */
 +                      std::string filename = ServerInstance->Config->Paths.PrependConfig(tag->getString("certfile", "cert.pem"));
 +                      if ((!ctx.SetCerts(filename)) || (!clictx.SetCerts(filename)))
 +                      {
 +                              ERR_print_errors_cb(error_callback, this);
 +                              throw Exception("Can't read certificate file: " + lasterr);
 +                      }
  
 -              std::string ciphers = conf->getString("ciphers", "");
 +                      filename = ServerInstance->Config->Paths.PrependConfig(tag->getString("keyfile", "key.pem"));
 +                      if ((!ctx.SetPrivateKey(filename)) || (!clictx.SetPrivateKey(filename)))
 +                      {
 +                              ERR_print_errors_cb(error_callback, this);
 +                              throw Exception("Can't read key file: " + lasterr);
 +                      }
  
 -              if (!ciphers.empty())
 -              {
 -                      ERR_clear_error();
 -                      if ((!SSL_CTX_set_cipher_list(ctx, ciphers.c_str())) || (!SSL_CTX_set_cipher_list(clictx, ciphers.c_str())))
 +                      // Load the CAs we trust
 +                      filename = ServerInstance->Config->Paths.PrependConfig(tag->getString("cafile", "ca.pem"));
 +                      if ((!ctx.SetCA(filename)) || (!clictx.SetCA(filename)))
                        {
 -                              ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so: Can't set cipher list to %s.", ciphers.c_str());
                                ERR_print_errors_cb(error_callback, this);
 +                              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Can't read CA list from %s. This is only a problem if you want to verify client certificates, otherwise it's safe to ignore this message. Error: %s", filename.c_str(), lasterr.c_str());
                        }
 +
 +                      clictx.SetVerifyCert();
 +                      if (tag->getBool("requestclientcert", true))
 +                              ctx.SetVerifyCert();
                }
  
 -              /* Load our keys and certificates
 -               * NOTE: OpenSSL's error logging API sucks, don't blame us for this clusterfuck.
 -               */
 -              ERR_clear_error();
 -              if ((!SSL_CTX_use_certificate_chain_file(ctx, certfile.c_str())) || (!SSL_CTX_use_certificate_chain_file(clictx, certfile.c_str())))
 +              const std::string& GetName() const { return name; }
 +              SSL* CreateServerSession() { return ctx.CreateServerSession(); }
 +              SSL* CreateClientSession() { return clictx.CreateClientSession(); }
 +              const EVP_MD* GetDigest() { return digest; }
 +              bool AllowRenegotiation() const { return allowrenego; }
 +              unsigned int GetOutgoingRecordSize() const { return outrecsize; }
 +      };
 +
 +      namespace BIOMethod
 +      {
 +              static int create(BIO* bio)
                {
 -                      ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so: Can't read certificate file %s. %s", certfile.c_str(), strerror(errno));
 -                      ERR_print_errors_cb(error_callback, this);
 +                      bio->init = 1;
 +                      return 1;
                }
  
 -              ERR_clear_error();
 -              if (((!SSL_CTX_use_PrivateKey_file(ctx, keyfile.c_str(), SSL_FILETYPE_PEM))) || (!SSL_CTX_use_PrivateKey_file(clictx, keyfile.c_str(), SSL_FILETYPE_PEM)))
 +              static int destroy(BIO* bio)
                {
 -                      ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so: Can't read key file %s. %s", keyfile.c_str(), strerror(errno));
 -                      ERR_print_errors_cb(error_callback, this);
 +                      // XXX: Dummy function to avoid a memory leak in OpenSSL.
 +                      // The memory leak happens in BIO_free() (bio_lib.c) when the destroy func of the BIO is NULL.
 +                      // This is fixed in OpenSSL but some distros still ship the unpatched version hence we provide this workaround.
 +                      return 1;
                }
  
 -              /* Load the CAs we trust*/
 -              ERR_clear_error();
 -              if (((!SSL_CTX_load_verify_locations(ctx, cafile.c_str(), 0))) || (!SSL_CTX_load_verify_locations(clictx, cafile.c_str(), 0)))
 +              static long ctrl(BIO* bio, int cmd, long num, void* ptr)
                {
 -                      ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so: Can't read CA list from %s. This is only a problem if you want to verify client certificates, otherwise it's safe to ignore this message. Error: %s", cafile.c_str(), strerror(errno));
 -                      ERR_print_errors_cb(error_callback, this);
 +                      if (cmd == BIO_CTRL_FLUSH)
 +                              return 1;
 +                      return 0;
                }
  
 -#ifdef _WIN32
 -              BIO* dhpfile = BIO_new_file(dhfile.c_str(), "r");
 -#else
 -              FILE* dhpfile = fopen(dhfile.c_str(), "r");
 -#endif
 -              DH* ret;
 +              static int read(BIO* bio, char* buf, int len);
 +              static int write(BIO* bio, const char* buf, int len);
 +      }
 +}
  
 -              if (dhpfile == NULL)
 -              {
 -                      ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so Couldn't open DH file %s: %s", dhfile.c_str(), strerror(errno));
 -                      throw ModuleException("Couldn't open DH file " + dhfile + ": " + strerror(errno));
 -              }
 -              else
 +static BIO_METHOD biomethods =
 +{
 +      (100 | BIO_TYPE_SOURCE_SINK),
 +      "inspircd",
 +      OpenSSL::BIOMethod::write,
 +      OpenSSL::BIOMethod::read,
 +      NULL, // puts
 +      NULL, // gets
 +      OpenSSL::BIOMethod::ctrl,
 +      OpenSSL::BIOMethod::create,
 +      OpenSSL::BIOMethod::destroy, // destroy, does nothing, see function body for more info
 +      NULL // callback_ctrl
 +};
 +
 +static int OnVerify(int preverify_ok, X509_STORE_CTX *ctx)
 +{
 +      /* XXX: This will allow self signed certificates.
 +       * In the future if we want an option to not allow this,
 +       * we can just return preverify_ok here, and openssl
 +       * will boot off self-signed and invalid peer certs.
 +       */
 +      int ve = X509_STORE_CTX_get_error(ctx);
 +
 +      SelfSigned = (ve == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT);
 +
 +      return 1;
 +}
 +
 +class OpenSSLIOHook : public SSLIOHook
 +{
 + private:
 +      SSL* sess;
 +      issl_status status;
 +      bool data_to_write;
 +      reference<OpenSSL::Profile> profile;
 +
 +      // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed
 +      int Handshake(StreamSocket* user)
 +      {
 +              ERR_clear_error();
 +              int ret = SSL_do_handshake(sess);
 +              if (ret < 0)
                {
 -#ifdef _WIN32
 -                      ret = PEM_read_bio_DHparams(dhpfile, NULL, NULL, NULL);
 -                      BIO_free(dhpfile);
 -#else
 -                      ret = PEM_read_DHparams(dhpfile, NULL, NULL, NULL);
 -#endif
 +                      int err = SSL_get_error(sess, ret);
  
 -                      ERR_clear_error();
 -                      if (ret)
 +                      if (err == SSL_ERROR_WANT_READ)
                        {
 -                              if ((SSL_CTX_set_tmp_dh(ctx, ret) < 0) || (SSL_CTX_set_tmp_dh(clictx, ret) < 0))
 -                              {
 -                                      ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Couldn't set DH parameters %s. SSL errors follow:", dhfile.c_str());
 -                                      ERR_print_errors_cb(error_callback, this);
 -                              }
 -                              DH_free(ret);
 +                              SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 +                              this->status = ISSL_HANDSHAKING;
 +                              return 0;
 +                      }
 +                      else if (err == SSL_ERROR_WANT_WRITE)
 +                      {
 +                              SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 +                              this->status = ISSL_HANDSHAKING;
 +                              return 0;
                        }
                        else
                        {
 -                              ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Couldn't set DH parameters %s.", dhfile.c_str());
 +                              CloseSession();
 +                              return -1;
                        }
                }
 +              else if (ret > 0)
 +              {
 +                      // Handshake complete.
 +                      VerifyCertificate();
  
 -#ifndef _WIN32
 -              fclose(dhpfile);
 -#endif
 -
 -#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH
 -              SetupECDH(conf);
 -#endif
 -      }
 +                      status = ISSL_OPEN;
  
 -      void On005Numeric(std::string &output)
 -      {
 -              if (!sslports.empty())
 -                      output.append(" SSL=" + sslports);
 -      }
 +                      SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
  
 -      ~ModuleSSLOpenSSL()
 -      {
 -              SSL_CTX_free(ctx);
 -              SSL_CTX_free(clictx);
 -              delete[] sessions;
 -      }
 -
 -      void OnUserConnect(LocalUser* user)
 -      {
 -              if (user->eh.GetIOHook() == this)
 +                      return 1;
 +              }
 +              else if (ret == 0)
                {
 -                      if (sessions[user->eh.GetFd()].sess)
 -                      {
 -                              if (!sessions[user->eh.GetFd()].cert->fingerprint.empty())
 -                                      user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\""
 -                                              " and your SSL fingerprint is %s", user->nick.c_str(), SSL_get_cipher(sessions[user->eh.GetFd()].sess), sessions[user->eh.GetFd()].cert->fingerprint.c_str());
 -                              else
 -                                      user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick.c_str(), SSL_get_cipher(sessions[user->eh.GetFd()].sess));
 -                      }
 +                      CloseSession();
                }
 +              return -1;
        }
  
 -      void OnCleanup(int target_type, void* item)
 +      void CloseSession()
        {
 -              if (target_type == TYPE_USER)
 +              if (sess)
                {
 -                      LocalUser* user = IS_LOCAL((User*)item);
 -
 -                      if (user && user->eh.GetIOHook() == this)
 -                      {
 -                              // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
 -                              // Potentially there could be multiple SSL modules loaded at once on different ports.
 -                              ServerInstance->Users->QuitUser(user, "SSL module unloading");
 -                      }
 +                      SSL_shutdown(sess);
 +                      SSL_free(sess);
                }
 +              sess = NULL;
 +              certificate = NULL;
 +              status = ISSL_NONE;
        }
  
 -      Version GetVersion()
 +      void VerifyCertificate()
        {
 -              return Version("Provides SSL support for clients", VF_VENDOR);
 -      }
 +              X509* cert;
 +              ssl_cert* certinfo = new ssl_cert;
 +              this->certificate = certinfo;
 +              unsigned int n;
 +              unsigned char md[EVP_MAX_MD_SIZE];
  
 -      void OnRequest(Request& request)
 -      {
 -              if (strcmp("GET_SSL_CERT", request.id) == 0)
 +              cert = SSL_get_peer_certificate(sess);
 +
 +              if (!cert)
                {
 -                      SocketCertificateRequest& req = static_cast<SocketCertificateRequest&>(request);
 -                      int fd = req.sock->GetFd();
 -                      issl_session* session = &sessions[fd];
 +                      certinfo->error = "Could not get peer certificate: "+std::string(get_error());
 +                      return;
 +              }
 +
 +              certinfo->invalid = (SSL_get_verify_result(sess) != X509_V_OK);
  
 -                      req.cert = session->cert;
 +              if (!SelfSigned)
 +              {
 +                      certinfo->unknownsigner = false;
 +                      certinfo->trusted = true;
                }
 -              else if (!strcmp("GET_RAW_SSL_SESSION", request.id))
 +              else
                {
 -                      SSLRawSessionRequest& req = static_cast<SSLRawSessionRequest&>(request);
 -                      if ((req.fd >= 0) && (req.fd < ServerInstance->SE->GetMaxFds()))
 -                              req.data = reinterpret_cast<void*>(sessions[req.fd].sess);
 +                      certinfo->unknownsigner = true;
 +                      certinfo->trusted = false;
                }
 -      }
 -
 -      void OnStreamSocketAccept(StreamSocket* user, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
 -      {
 -              int fd = user->GetFd();
  
 -              issl_session* session = &sessions[fd];
 +              char buf[512];
 +              X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
 +              certinfo->dn = buf;
 +              // Make sure there are no chars in the string that we consider invalid
 +              if (certinfo->dn.find_first_of("\r\n") != std::string::npos)
 +                      certinfo->dn.clear();
  
 -              session->sess = SSL_new(ctx);
 -              session->status = ISSL_NONE;
 -              session->outbound = false;
 -              session->data_to_write = false;
 +              X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
 +              certinfo->issuer = buf;
 +              if (certinfo->issuer.find_first_of("\r\n") != std::string::npos)
 +                      certinfo->issuer.clear();
  
 -              if (session->sess == NULL)
 -                      return;
 +              if (!X509_digest(cert, profile->GetDigest(), md, &n))
 +              {
 +                      certinfo->error = "Out of memory generating fingerprint";
 +              }
 +              else
 +              {
 +                      certinfo->fingerprint = BinToHex(md, n);
 +              }
  
 -              if (SSL_set_fd(session->sess, fd) == 0)
 +              if ((ASN1_UTCTIME_cmp_time_t(X509_get_notAfter(cert), ServerInstance->Time()) == -1) || (ASN1_UTCTIME_cmp_time_t(X509_get_notBefore(cert), ServerInstance->Time()) == 0))
                {
 -                      ServerInstance->Logs->Log("m_ssl_openssl",DEBUG,"BUG: Can't set fd with SSL_set_fd: %d", fd);
 -                      return;
 +                      certinfo->error = "Not activated, or expired certificate";
                }
  
 -              Handshake(user, session);
 +              X509_free(cert);
        }
  
 -      void OnStreamSocketConnect(StreamSocket* user)
 +      void SSLInfoCallback(int where, int rc)
        {
 -              int fd = user->GetFd();
 -              /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
 -              if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() -1))
 -                      return;
 +              if ((where & SSL_CB_HANDSHAKE_START) && (status == ISSL_OPEN))
 +              {
 +                      if (profile->AllowRenegotiation())
 +                              return;
  
 -              issl_session* session = &sessions[fd];
 +                      // The other side is trying to renegotiate, kill the connection and change status
 +                      // to ISSL_NONE so CheckRenego() closes the session
 +                      status = ISSL_NONE;
 +                      BIO* bio = SSL_get_rbio(sess);
 +                      EventHandler* eh = static_cast<StreamSocket*>(bio->ptr);
 +                      SocketEngine::Shutdown(eh, 2);
 +              }
 +      }
  
 -              session->sess = SSL_new(clictx);
 -              session->status = ISSL_NONE;
 -              session->outbound = true;
 -              session->data_to_write = false;
 +      bool CheckRenego(StreamSocket* sock)
 +      {
 +              if (status != ISSL_NONE)
 +                      return true;
  
 -              if (session->sess == NULL)
 -                      return;
 +              ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Session %p killed, attempted to renegotiate", (void*)sess);
 +              CloseSession();
 +              sock->SetError("Renegotiation is not allowed");
 +              return false;
 +      }
  
 -              if (SSL_set_fd(session->sess, fd) == 0)
 +      // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error
 +      int PrepareIO(StreamSocket* sock)
 +      {
 +              if (status == ISSL_OPEN)
 +                      return 1;
 +              else if (status == ISSL_HANDSHAKING)
                {
 -                      ServerInstance->Logs->Log("m_ssl_openssl",DEBUG,"BUG: Can't set fd with SSL_set_fd: %d", fd);
 -                      return;
 +                      // The handshake isn't finished, try to finish it
 +                      return Handshake(sock);
                }
  
 -              Handshake(user, session);
 +              CloseSession();
 +              return -1;
        }
  
 -      void OnStreamSocketClose(StreamSocket* user)
 -      {
 -              int fd = user->GetFd();
 -              /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
 -              if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
 -                      return;
 +      // Calls our private SSLInfoCallback()
 +      friend void StaticSSLInfoCallback(const SSL* ssl, int where, int rc);
  
 -              CloseSession(&sessions[fd]);
 + public:
 +      OpenSSLIOHook(IOHookProvider* hookprov, StreamSocket* sock, SSL* session, const reference<OpenSSL::Profile>& sslprofile)
 +              : SSLIOHook(hookprov)
 +              , sess(session)
 +              , status(ISSL_NONE)
 +              , data_to_write(false)
 +              , profile(sslprofile)
 +      {
 +              // Create BIO instance and store a pointer to the socket in it which will be used by the read and write functions
 +              BIO* bio = BIO_new(&biomethods);
 +              bio->ptr = sock;
 +              SSL_set_bio(sess, bio, bio);
 +
 +              SSL_set_ex_data(sess, exdataindex, this);
 +              sock->AddIOHook(this);
 +              Handshake(sock);
        }
  
 -      int OnStreamSocketRead(StreamSocket* user, std::string& recvq)
 +      void OnStreamSocketClose(StreamSocket* user) CXX11_OVERRIDE
        {
 -              int fd = user->GetFd();
 -              /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
 -              if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1))
 -                      return -1;
 -
 -              issl_session* session = &sessions[fd];
 -
 -              if (!session->sess)
 -              {
 -                      CloseSession(session);
 -                      return -1;
 -              }
 -
 -              if (session->status == ISSL_HANDSHAKING)
 -              {
 -                      // The handshake isn't finished and it wants to read, try to finish it.
 -                      if (!Handshake(user, session))
 -                      {
 -                              // Couldn't resume handshake.
 -                              if (session->status == ISSL_NONE)
 -                                      return -1;
 -                              return 0;
 -                      }
 -              }
 +              CloseSession();
 +      }
  
 -              // If we resumed the handshake then session->status will be ISSL_OPEN
 +      int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE
 +      {
 +              // Finish handshake if needed
 +              int prepret = PrepareIO(user);
 +              if (prepret <= 0)
 +                      return prepret;
  
 -              if (session->status == ISSL_OPEN)
 +              // If we resumed the handshake then this->status will be ISSL_OPEN
                {
                        ERR_clear_error();
                        char* buffer = ServerInstance->GetReadBuffer();
                        size_t bufsiz = ServerInstance->Config->NetBufferSize;
 -                      int ret = SSL_read(session->sess, buffer, bufsiz);
 +                      int ret = SSL_read(sess, buffer, bufsiz);
  
 -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
 -                      if (!CheckRenego(user, session))
 +                      if (!CheckRenego(user))
                                return -1;
 -#endif
  
                        if (ret > 0)
                        {
                                recvq.append(buffer, ret);
 -
+                               int mask = 0;
+                               // Schedule a read if there is still data in the OpenSSL buffer
 -                              if (SSL_pending(session->sess) > 0)
++                              if (SSL_pending(sess) > 0)
+                                       mask |= FD_ADD_TRIAL_READ;
 -                              if (session->data_to_write)
 +                              if (data_to_write)
-                                       SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_SINGLE_WRITE);
+                                       mask |= FD_WANT_POLL_READ | FD_WANT_SINGLE_WRITE;
+                               if (mask != 0)
 -                                      ServerInstance->SE->ChangeEventMask(user, mask);
++                                      SocketEngine::ChangeEventMask(user, mask);
                                return 1;
                        }
                        else if (ret == 0)
                        {
                                // Client closed connection.
 -                              CloseSession(session);
 +                              CloseSession();
                                user->SetError("Connection closed");
                                return -1;
                        }
 -                      else if (ret < 0)
 +                      else // if (ret < 0)
                        {
 -                              int err = SSL_get_error(session->sess, ret);
 +                              int err = SSL_get_error(sess, ret);
  
                                if (err == SSL_ERROR_WANT_READ)
                                {
 -                                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ);
 +                                      SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ);
                                        return 0;
                                }
                                else if (err == SSL_ERROR_WANT_WRITE)
                                {
 -                                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 +                                      SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
                                        return 0;
                                }
                                else
                                {
 -                                      CloseSession(session);
 +                                      CloseSession();
                                        return -1;
                                }
                        }
                }
 -
 -              return 0;
        }
  
 -      int OnStreamSocketWrite(StreamSocket* user, std::string& buffer)
 +      int OnStreamSocketWrite(StreamSocket* user, StreamSocket::SendQueue& sendq) CXX11_OVERRIDE
        {
 -              int fd = user->GetFd();
 -
 -              issl_session* session = &sessions[fd];
 -
 -              if (!session->sess)
 -              {
 -                      CloseSession(session);
 -                      return -1;
 -              }
 -
 -              session->data_to_write = true;
 +              // Finish handshake if needed
 +              int prepret = PrepareIO(user);
 +              if (prepret <= 0)
 +                      return prepret;
  
 -              if (session->status == ISSL_HANDSHAKING)
 -              {
 -                      if (!Handshake(user, session))
 -                      {
 -                              // Couldn't resume handshake.
 -                              if (session->status == ISSL_NONE)
 -                                      return -1;
 -                              return 0;
 -                      }
 -              }
 +              data_to_write = true;
  
 -              if (session->status == ISSL_OPEN)
 +              // Session is ready for transferring application data
 +              while (!sendq.empty())
                {
                        ERR_clear_error();
 -                      int ret = SSL_write(session->sess, buffer.data(), buffer.size());
 +                      FlattenSendQueue(sendq, profile->GetOutgoingRecordSize());
 +                      const StreamSocket::SendQueue::Element& buffer = sendq.front();
 +                      int ret = SSL_write(sess, buffer.data(), buffer.size());
  
 -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
 -                      if (!CheckRenego(user, session))
 +                      if (!CheckRenego(user))
                                return -1;
 -#endif
  
                        if (ret == (int)buffer.length())
                        {
 -                              session->data_to_write = false;
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 -                              return 1;
 +                              // Wrote entire record, continue sending
 +                              sendq.pop_front();
                        }
                        else if (ret > 0)
                        {
 -                              buffer = buffer.substr(ret);
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
 +                              sendq.erase_front(ret);
 +                              SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
                                return 0;
                        }
                        else if (ret == 0)
                        {
 -                              CloseSession(session);
 +                              CloseSession();
                                return -1;
                        }
 -                      else if (ret < 0)
 +                      else // if (ret < 0)
                        {
 -                              int err = SSL_get_error(session->sess, ret);
 +                              int err = SSL_get_error(sess, ret);
  
                                if (err == SSL_ERROR_WANT_WRITE)
                                {
 -                                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
 +                                      SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
                                        return 0;
                                }
                                else if (err == SSL_ERROR_WANT_READ)
                                {
 -                                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ);
 +                                      SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ);
                                        return 0;
                                }
                                else
                                {
 -                                      CloseSession(session);
 +                                      CloseSession();
                                        return -1;
                                }
                        }
                }
 -              return 0;
 +
 +              data_to_write = false;
 +              SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 +              return 1;
        }
  
 -      bool Handshake(StreamSocket* user, issl_session* session)
 +      void GetCiphersuite(std::string& out) const CXX11_OVERRIDE
        {
 -              int ret;
 +              if (!IsHandshakeDone())
 +                      return;
 +              out.append(SSL_get_version(sess)).push_back('-');
 +              out.append(SSL_get_cipher(sess));
 +      }
  
 -              ERR_clear_error();
 -              if (session->outbound)
 -                      ret = SSL_connect(session->sess);
 -              else
 -                      ret = SSL_accept(session->sess);
 +      bool IsHandshakeDone() const { return (status == ISSL_OPEN); }
 +};
  
 -              if (ret < 0)
 -              {
 -                      int err = SSL_get_error(session->sess, ret);
 +static void StaticSSLInfoCallback(const SSL* ssl, int where, int rc)
 +{
 +      OpenSSLIOHook* hook = static_cast<OpenSSLIOHook*>(SSL_get_ex_data(ssl, exdataindex));
 +      hook->SSLInfoCallback(where, rc);
 +}
  
 -                      if (err == SSL_ERROR_WANT_READ)
 -                      {
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 -                              session->status = ISSL_HANDSHAKING;
 -                              return true;
 -                      }
 -                      else if (err == SSL_ERROR_WANT_WRITE)
 -                      {
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 -                              session->status = ISSL_HANDSHAKING;
 -                              return true;
 -                      }
 -                      else
 -                      {
 -                              CloseSession(session);
 -                      }
 +static int OpenSSL::BIOMethod::write(BIO* bio, const char* buffer, int size)
 +{
 +      BIO_clear_retry_flags(bio);
  
 -                      return false;
 -              }
 -              else if (ret > 0)
 -              {
 -                      // Handshake complete.
 -                      VerifyCertificate(session, user);
 +      StreamSocket* sock = static_cast<StreamSocket*>(bio->ptr);
 +      if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
 +      {
 +              // Writes blocked earlier, don't retry syscall
 +              BIO_set_retry_write(bio);
 +              return -1;
 +      }
  
 -                      session->status = ISSL_OPEN;
 +      int ret = SocketEngine::Send(sock, buffer, size, 0);
 +      if ((ret < size) && ((ret > 0) || (SocketEngine::IgnoreError())))
 +      {
 +              // Blocked, set retry flag for OpenSSL
 +              SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
 +              BIO_set_retry_write(bio);
 +      }
  
 -                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
 +      return ret;
 +}
  
 -                      return true;
 -              }
 -              else if (ret == 0)
 -              {
 -                      CloseSession(session);
 -              }
 -              return false;
 +static int OpenSSL::BIOMethod::read(BIO* bio, char* buffer, int size)
 +{
 +      BIO_clear_retry_flags(bio);
 +
 +      StreamSocket* sock = static_cast<StreamSocket*>(bio->ptr);
 +      if (sock->GetEventMask() & FD_READ_WILL_BLOCK)
 +      {
 +              // Reads blocked earlier, don't retry syscall
 +              BIO_set_retry_read(bio);
 +              return -1;
        }
  
 -      void CloseSession(issl_session* session)
 +      int ret = SocketEngine::Recv(sock, buffer, size, 0);
 +      if ((ret < size) && ((ret > 0) || (SocketEngine::IgnoreError())))
        {
 -              if (session->sess)
 -              {
 -                      SSL_shutdown(session->sess);
 -                      SSL_free(session->sess);
 -              }
 +              // Blocked, set retry flag for OpenSSL
 +              SocketEngine::ChangeEventMask(sock, FD_READ_WILL_BLOCK);
 +              BIO_set_retry_read(bio);
 +      }
 +
 +      return ret;
 +}
 +
 +class OpenSSLIOHookProvider : public refcountbase, public IOHookProvider
 +{
 +      reference<OpenSSL::Profile> profile;
  
 -              session->sess = NULL;
 -              session->status = ISSL_NONE;
 -              session->cert = NULL;
 + public:
 +      OpenSSLIOHookProvider(Module* mod, reference<OpenSSL::Profile>& prof)
 +              : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL)
 +              , profile(prof)
 +      {
 +              ServerInstance->Modules->AddService(*this);
        }
  
 -      void VerifyCertificate(issl_session* session, StreamSocket* user)
 +      ~OpenSSLIOHookProvider()
        {
 -              if (!session->sess || !user)
 -                      return;
 +              ServerInstance->Modules->DelService(*this);
 +      }
  
 -              X509* cert;
 -              ssl_cert* certinfo = new ssl_cert;
 -              session->cert = certinfo;
 -              unsigned int n;
 -              unsigned char md[EVP_MAX_MD_SIZE];
 -              const EVP_MD *digest = use_sha ? EVP_sha1() : EVP_md5();
 +      void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
 +      {
 +              new OpenSSLIOHook(this, sock, profile->CreateServerSession(), profile);
 +      }
  
 -              cert = SSL_get_peer_certificate((SSL*)session->sess);
 +      void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
 +      {
 +              new OpenSSLIOHook(this, sock, profile->CreateClientSession(), profile);
 +      }
 +};
  
 -              if (!cert)
 -              {
 -                      certinfo->error = "Could not get peer certificate: "+std::string(get_error());
 -                      return;
 -              }
 +class ModuleSSLOpenSSL : public Module
 +{
 +      typedef std::vector<reference<OpenSSLIOHookProvider> > ProfileList;
  
 -              certinfo->invalid = (SSL_get_verify_result(session->sess) != X509_V_OK);
 +      ProfileList profiles;
  
 -              if (!SelfSigned)
 +      void ReadProfiles()
 +      {
 +              ProfileList newprofiles;
 +              ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile");
 +              if (tags.first == tags.second)
                {
 -                      certinfo->unknownsigner = false;
 -                      certinfo->trusted = true;
 +                      // Create a default profile named "openssl"
 +                      const std::string defname = "openssl";
 +                      ConfigTag* tag = ServerInstance->Config->ConfValue(defname);
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No <sslprofile> tags found, using settings from the <openssl> tag");
 +
 +                      try
 +                      {
 +                              reference<OpenSSL::Profile> profile(new OpenSSL::Profile(defname, tag));
 +                              newprofiles.push_back(new OpenSSLIOHookProvider(this, profile));
 +                      }
 +                      catch (OpenSSL::Exception& ex)
 +                      {
 +                              throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason());
 +                      }
                }
 -              else
 +
 +              for (ConfigIter i = tags.first; i != tags.second; ++i)
                {
 -                      certinfo->unknownsigner = true;
 -                      certinfo->trusted = false;
 +                      ConfigTag* tag = i->second;
 +                      if (tag->getString("provider") != "openssl")
 +                              continue;
 +
 +                      std::string name = tag->getString("name");
 +                      if (name.empty())
 +                      {
 +                              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation());
 +                              continue;
 +                      }
 +
 +                      reference<OpenSSL::Profile> profile;
 +                      try
 +                      {
 +                              profile = new OpenSSL::Profile(name, tag);
 +                      }
 +                      catch (CoreException& ex)
 +                      {
 +                              throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason());
 +                      }
 +
 +                      newprofiles.push_back(new OpenSSLIOHookProvider(this, profile));
                }
  
 -              char buf[512];
 -              X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
 -              certinfo->dn = buf;
 -              // Make sure there are no chars in the string that we consider invalid
 -              if (certinfo->dn.find_first_of("\r\n") != std::string::npos)
 -                      certinfo->dn.clear();
 +              profiles.swap(newprofiles);
 +      }
  
 -              X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
 -              certinfo->issuer = buf;
 -              if (certinfo->issuer.find_first_of("\r\n") != std::string::npos)
 -                      certinfo->issuer.clear();
 + public:
 +      ModuleSSLOpenSSL()
 +      {
 +              // Initialize OpenSSL
 +              SSL_library_init();
 +              SSL_load_error_strings();
 +      }
 +
 +      void init() CXX11_OVERRIDE
 +      {
 +              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "OpenSSL lib version \"%s\" module was compiled for \"" OPENSSL_VERSION_TEXT "\"", SSLeay_version(SSLEAY_VERSION));
 +
 +              // Register application specific data
 +              char exdatastr[] = "inspircd";
 +              exdataindex = SSL_get_ex_new_index(0, exdatastr, NULL, NULL, NULL);
 +              if (exdataindex < 0)
 +                      throw ModuleException("Failed to register application specific data");
 +
 +              ReadProfiles();
 +      }
 +
 +      void OnModuleRehash(User* user, const std::string &param) CXX11_OVERRIDE
 +      {
 +              if (param != "ssl")
 +                      return;
  
 -              if (!X509_digest(cert, digest, md, &n))
 +              try
                {
 -                      certinfo->error = "Out of memory generating fingerprint";
 +                      ReadProfiles();
                }
 -              else
 +              catch (ModuleException& ex)
                {
 -                      certinfo->fingerprint = irc::hex(md, n);
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings.");
                }
 +      }
  
 -              if ((ASN1_UTCTIME_cmp_time_t(X509_get_notAfter(cert), ServerInstance->Time()) == -1) || (ASN1_UTCTIME_cmp_time_t(X509_get_notBefore(cert), ServerInstance->Time()) == 0))
 +      void OnCleanup(int target_type, void* item) CXX11_OVERRIDE
 +      {
 +              if (target_type == TYPE_USER)
                {
 -                      certinfo->error = "Not activated, or expired certificate";
 +                      LocalUser* user = IS_LOCAL((User*)item);
 +
 +                      if ((user) && (user->eh.GetModHook(this)))
 +                      {
 +                              // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
 +                              // Potentially there could be multiple SSL modules loaded at once on different ports.
 +                              ServerInstance->Users->QuitUser(user, "SSL module unloading");
 +                      }
                }
 +      }
  
 -              X509_free(cert);
 +      ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
 +      {
 +              const OpenSSLIOHook* const iohook = static_cast<OpenSSLIOHook*>(user->eh.GetModHook(this));
 +              if ((iohook) && (!iohook->IsHandshakeDone()))
 +                      return MOD_RES_DENY;
 +              return MOD_RES_PASSTHRU;
        }
 -};
  
 -static int error_callback(const char *str, size_t len, void *u)
 -{
 -      ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "SSL error: " + std::string(str, len - 1));
 -
 -      //
 -      // XXX: Remove this line, it causes valgrind warnings...
 -      //
 -      // MD_update(&m, buf, j);
 -      //
 -      //
 -      // ... ONLY JOKING! :-)
 -      //
 -
 -      return 0;
 -}
 +      Version GetVersion() CXX11_OVERRIDE
 +      {
 +              return Version("Provides SSL support for clients", VF_VENDOR);
 +      }
 +};
  
  MODULE_INIT(ModuleSSLOpenSSL)
diff --combined src/modules/m_censor.cpp
index 7d8c74024a8f0b5a5a877d6dcdabdbef0b0bb871,65b965df288f8acac65d152f05cded53d3a9eb86..c8b6b73a8836cda49cd90107e3bc31c995e46457
   */
  
  
 -/* $ModDesc: Provides user and channel +G mode */
 -
 -#define _CRT_SECURE_NO_DEPRECATE
 -#define _SCL_SECURE_NO_DEPRECATE
 -
  #include "inspircd.h"
 -#include <iostream>
  
 -typedef std::map<irc::string,irc::string> censor_t;
 +typedef insp::flat_map<irc::string, irc::string> censor_t;
  
  /** Handles usermode +G
   */
@@@ -49,8 -55,24 +49,8 @@@ class ModuleCensor : public Modul
   public:
        ModuleCensor() : cu(this), cc(this) { }
  
 -      void init()
 -      {
 -              /* Read the configuration file on startup.
 -               */
 -              OnRehash(NULL);
 -              ServerInstance->Modules->AddService(cu);
 -              ServerInstance->Modules->AddService(cc);
 -              Implementation eventlist[] = { I_OnRehash, I_OnUserPreMessage, I_OnUserPreNotice };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
 -      }
 -
 -
 -      virtual ~ModuleCensor()
 -      {
 -      }
 -
        // format of a config entry is <badword text="shit" replace="poo">
 -      virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
 +      ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE
        {
                if (!IS_LOCAL(user))
                        return MOD_RES_PASSTHRU;
                bool active = false;
  
                if (target_type == TYPE_USER)
 -                      active = ((User*)dest)->IsModeSet('G');
 +                      active = ((User*)dest)->IsModeSet(cu);
                else if (target_type == TYPE_CHANNEL)
                {
 -                      active = ((Channel*)dest)->IsModeSet('G');
                        Channel* c = (Channel*)dest;
 +                      active = c->IsModeSet(cc);
                        ModResult res = ServerInstance->OnCheckExemption(user,c,"censor");
  
                        if (res == MOD_RES_ALLOW)
                        {
                                if (index->second.empty())
                                {
-                                       user->WriteNumeric(ERR_WORDFILTERED, ((Channel*)dest)->name, index->first, "Your message contained a censored word, and was blocked");
 -                                      user->WriteNumeric(ERR_WORDFILTERED, "%s %s %s :Your message contained a censored word, and was blocked", user->nick.c_str(), ((target_type == TYPE_CHANNEL) ? ((Channel*)dest)->name.c_str() : ((User*)dest)->nick.c_str()), index->first.c_str());
++                                      user->WriteNumeric(ERR_WORDFILTERED, ((target_type == TYPE_CHANNEL) ? ((Channel*)dest)->name : ((User*)dest)->nick), index->first, "Your message contained a censored word, and was blocked");
                                        return MOD_RES_DENY;
                                }
  
                return MOD_RES_PASSTHRU;
        }
  
 -      virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
 -      {
 -              return OnUserPreMessage(user,dest,target_type,text,status,exempt_list);
 -      }
 -
 -      virtual void OnRehash(User* user)
 +      void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
        {
                /*
                 * reload our config file on rehash - we must destroy and re-allocate the classes
                }
        }
  
 -      virtual Version GetVersion()
 +      Version GetVersion() CXX11_OVERRIDE
        {
                return Version("Provides user and channel +G mode",VF_VENDOR);
        }
index 21cc97aa5156198e1073d8c2b828405b87ffb67c,05fff89377f5c09f6d2faf1a097eaf70c0c2040c..d8fbef69afa4d3d6666e0d244f7167c4d4a19b7d
  
  #include "inspircd.h"
  
 -/* $ModDesc: Provides support for the /DCCALLOW command */
 +static const char* const helptext[] =
 +{
 +      "DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP]",
 +      "You may allow DCCs from specific users by specifying a",
 +      "DCC allow for the user you want to receive DCCs from.",
 +      "For example, to allow the user Brain to send you inspircd.exe",
 +      "you would type:",
 +      "/DCCALLOW +Brain",
 +      "Brain would then be able to send you files. They would have to",
 +      "resend the file again if the server gave them an error message",
 +      "before you added them to your DCCALLOW list.",
 +      "DCCALLOW entries will be temporary by default, if you want to add",
 +      "them to your DCCALLOW list until you leave IRC, type:",
 +      "/DCCALLOW +Brain 0",
 +      "To remove the user from your DCCALLOW list, type:",
 +      "/DCCALLOW -Brain",
 +      "To see the users in your DCCALLOW list, type:",
 +      "/DCCALLOW LIST",
 +      "NOTE: If the user leaves IRC or changes their nickname",
 +      "  they will be removed from your DCCALLOW list.",
 +      "  your DCCALLOW list will be deleted when you leave IRC."
 +};
  
  class BannedFileList
  {
@@@ -74,17 -53,13 +74,17 @@@ typedef std::vector<DCCAllow> dccallowl
  dccallowlist* dl;
  typedef std::vector<BannedFileList> bannedfilelist;
  bannedfilelist bfl;
 -SimpleExtItem<dccallowlist>* ext;
 +typedef SimpleExtItem<dccallowlist> DCCAllowExt;
  
  class CommandDccallow : public Command
  {
 +      DCCAllowExt& ext;
 +
   public:
        unsigned int maxentries;
 -      CommandDccallow(Module* parent) : Command(parent, "DCCALLOW", 0)
 +      CommandDccallow(Module* parent, DCCAllowExt& Ext)
 +              : Command(parent, "DCCALLOW", 0)
 +              , ext(Ext)
        {
                syntax = "[(+|-)<nick> [<time>]]|[LIST|HELP]";
                /* XXX we need to fix this so it can work with translation stuff (i.e. move +- into a seperate param */
                                }
                                else
                                {
 -                                      user->WriteNumeric(998, "%s :DCCALLOW command not understood. For help on DCCALLOW, type /DCCALLOW HELP", user->nick.c_str());
 +                                      user->WriteNumeric(998, "DCCALLOW command not understood. For help on DCCALLOW, type /DCCALLOW HELP");
                                        return CMD_FAILURE;
                                }
                        }
  
 -                      std::string nick = parameters[0].substr(1);
 +                      std::string nick(parameters[0], 1);
                        User *target = ServerInstance->FindNickOnly(nick);
  
 -                      if ((target) && (!IS_SERVER(target)) && (!target->quitting) && (target->registered == REG_ALL))
 +                      if ((target) && (!target->quitting) && (target->registered == REG_ALL))
                        {
  
                                if (action == '-')
                                {
                                        // check if it contains any entries
 -                                      dl = ext->get(user);
 +                                      dl = ext.get(user);
                                        if (dl)
                                        {
                                                for (dccallowlist::iterator i = dl->begin(); i != dl->end(); ++i)
                                                        if (i->nickname == target->nick)
                                                        {
                                                                dl->erase(i);
 -                                                              user->WriteNumeric(995, "%s %s :Removed %s from your DCCALLOW list", user->nick.c_str(), user->nick.c_str(), target->nick.c_str());
 +                                                              user->WriteNumeric(995, user->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", target->nick.c_str()));
                                                                break;
                                                        }
                                                }
                                {
                                        if (target == user)
                                        {
 -                                              user->WriteNumeric(996, "%s %s :You cannot add yourself to your own DCCALLOW list!", user->nick.c_str(), user->nick.c_str());
 +                                              user->WriteNumeric(996, user->nick, "You cannot add yourself to your own DCCALLOW list!");
                                                return CMD_FAILURE;
                                        }
  
 -                                      dl = ext->get(user);
 +                                      dl = ext.get(user);
                                        if (!dl)
                                        {
                                                dl = new dccallowlist;
 -                                              ext->set(user, dl);
 +                                              ext.set(user, dl);
                                                // add this user to the userlist
                                                ul.push_back(user);
                                        }
  
                                        if (dl->size() >= maxentries)
                                        {
 -                                              user->WriteNumeric(996, "%s %s :Too many nicks on DCCALLOW list", user->nick.c_str(), user->nick.c_str());
 +                                              user->WriteNumeric(996, user->nick, "Too many nicks on DCCALLOW list");
                                                return CMD_FAILURE;
                                        }
  
                                        {
                                                if (k->nickname == target->nick)
                                                {
 -                                                      user->WriteNumeric(996, "%s %s :%s is already on your DCCALLOW list", user->nick.c_str(), user->nick.c_str(), target->nick.c_str());
 +                                                      user->WriteNumeric(996, user->nick, InspIRCd::Format("%s is already on your DCCALLOW list", target->nick.c_str()));
                                                        return CMD_FAILURE;
                                                }
                                        }
                                        std::string mask = target->nick+"!"+target->ident+"@"+target->dhost;
                                        std::string default_length = ServerInstance->Config->ConfValue("dccallow")->getString("length");
  
 -                                      long length;
 +                                      unsigned long length;
                                        if (parameters.size() < 2)
                                        {
 -                                              length = ServerInstance->Duration(default_length);
 +                                              length = InspIRCd::Duration(default_length);
                                        }
                                        else if (!atoi(parameters[1].c_str()))
                                        {
                                        }
                                        else
                                        {
 -                                              length = ServerInstance->Duration(parameters[1]);
 +                                              length = InspIRCd::Duration(parameters[1]);
                                        }
  
 -                                      if (!ServerInstance->IsValidMask(mask))
 +                                      if (!InspIRCd::IsValidMask(mask))
                                        {
                                                return CMD_FAILURE;
                                        }
  
                                        if (length > 0)
                                        {
 -                                              user->WriteNumeric(993, "%s %s :Added %s to DCCALLOW list for %ld seconds", user->nick.c_str(), user->nick.c_str(), target->nick.c_str(), length);
 +                                              user->WriteNumeric(993, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for %ld seconds", target->nick.c_str(), length));
                                        }
                                        else
                                        {
 -                                              user->WriteNumeric(994, "%s %s :Added %s to DCCALLOW list for this session", user->nick.c_str(), user->nick.c_str(), target->nick.c_str());
 +                                              user->WriteNumeric(994, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for this session", target->nick.c_str()));
                                        }
  
                                        /* route it. */
                        else
                        {
                                // nick doesn't exist
 -                              user->WriteNumeric(401, "%s %s :No such nick/channel", user->nick.c_str(), nick.c_str());
 +                              user->WriteNumeric(Numerics::NoSuchNick(nick));
                                return CMD_FAILURE;
                        }
                }
  
        void DisplayHelp(User* user)
        {
 -              user->WriteNumeric(998, "%s :DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP]", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :You may allow DCCs from specific users by specifying a", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :DCC allow for the user you want to receive DCCs from.", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :For example, to allow the user Brain to send you inspircd.exe", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :you would type:", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :/DCCALLOW +Brain", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :Brain would then be able to send you files. They would have to", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :resend the file again if the server gave them an error message", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :before you added them to your DCCALLOW list.", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :DCCALLOW entries will be temporary by default, if you want to add", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :them to your DCCALLOW list until you leave IRC, type:", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :/DCCALLOW +Brain 0", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :To remove the user from your DCCALLOW list, type:", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :/DCCALLOW -Brain", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :To see the users in your DCCALLOW list, type:", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :/DCCALLOW LIST", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :NOTE: If the user leaves IRC or changes their nickname", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :  they will be removed from your DCCALLOW list.", user->nick.c_str());
 -              user->WriteNumeric(998, "%s :  your DCCALLOW list will be deleted when you leave IRC.", user->nick.c_str());
 -              user->WriteNumeric(999, "%s :End of DCCALLOW HELP", user->nick.c_str());
 +              for (size_t i = 0; i < sizeof(helptext)/sizeof(helptext[0]); i++)
 +                      user->WriteNumeric(998, helptext[i]);
 +              user->WriteNumeric(999, "End of DCCALLOW HELP");
  
                LocalUser* localuser = IS_LOCAL(user);
                if (localuser)
        void DisplayDCCAllowList(User* user)
        {
                 // display current DCCALLOW list
 -              user->WriteNumeric(990, "%s :Users on your DCCALLOW list:", user->nick.c_str());
 +              user->WriteNumeric(990, "Users on your DCCALLOW list:");
  
 -              dl = ext->get(user);
 +              dl = ext.get(user);
                if (dl)
                {
                        for (dccallowlist::const_iterator c = dl->begin(); c != dl->end(); ++c)
                        {
 -                              user->WriteNumeric(991, "%s %s :%s (%s)", user->nick.c_str(), user->nick.c_str(), c->nickname.c_str(), c->hostmask.c_str());
 +                              user->WriteNumeric(991, user->nick, InspIRCd::Format("%s (%s)", c->nickname.c_str(), c->hostmask.c_str()));
                        }
                }
  
 -              user->WriteNumeric(992, "%s :End of DCCALLOW list", user->nick.c_str());
 +              user->WriteNumeric(992, "End of DCCALLOW list");
        }
  
  };
  
  class ModuleDCCAllow : public Module
  {
 +      DCCAllowExt ext;
        CommandDccallow cmd;
 - public:
  
 + public:
        ModuleDCCAllow()
 -              : cmd(this)
 -      {
 -              ext = NULL;
 -      }
 -
 -      void init()
 -      {
 -              ext = new SimpleExtItem<dccallowlist>("dccallow", this);
 -              ServerInstance->Modules->AddService(*ext);
 -              ServerInstance->Modules->AddService(cmd);
 -              OnRehash(NULL);
 -              Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_OnUserQuit, I_OnUserPostNick, I_OnRehash };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
 -      }
 -
 -      virtual void OnRehash(User* user)
 +              : ext("dccallow", ExtensionItem::EXT_USER, this)
 +              , cmd(this, ext)
        {
 -              ReadFileConf();
 -              ConfigTag* tag = ServerInstance->Config->ConfValue("dccallow");
 -              cmd.maxentries = tag->getInt("maxentries", 20);
        }
  
 -      virtual void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message)
 +      void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) CXX11_OVERRIDE
        {
 -              dccallowlist* udl = ext->get(user);
 +              dccallowlist* udl = ext.get(user);
  
                // remove their DCCALLOW list if they have one
                if (udl)
 -              {
 -                      userlist::iterator it = std::find(ul.begin(), ul.end(), user);
 -                      if (it != ul.end())
 -                              ul.erase(it);
 -              }
 +                      stdalgo::erase(ul, user);
  
                // remove them from any DCCALLOW lists
                // they are currently on
                RemoveNick(user);
        }
  
 -      virtual void OnUserPostNick(User* user, const std::string &oldnick)
 +      void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE
        {
                RemoveNick(user);
        }
  
 -      virtual ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list)
 -      {
 -              return OnUserPreNotice(user, dest, target_type, text, status, exempt_list);
 -      }
 -
 -      virtual ModResult OnUserPreNotice(User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list)
 +      ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE
        {
                if (!IS_LOCAL(user))
                        return MOD_RES_PASSTHRU;
  
                                if (strncmp(text.c_str(), "\1DCC ", 5) == 0)
                                {
 -                                      dl = ext->get(u);
 +                                      dl = ext.get(u);
                                        if (dl && dl->size())
                                        {
                                                for (dccallowlist::const_iterator iter = dl->begin(); iter != dl->end(); ++iter)
                                                                return MOD_RES_PASSTHRU;
                                        }
  
-                                       // tokenize
-                                       std::stringstream ss(text);
-                                       std::string buf;
-                                       std::vector<std::string> tokens;
-                                       while (ss >> buf)
-                                               tokens.push_back(buf);
-                                       if (tokens.size() < 2)
+                                       std::string buf = text.substr(5);
+                                       size_t s = buf.find(' ');
+                                       if (s == std::string::npos)
                                                return MOD_RES_PASSTHRU;
  
-                                       irc::string type = tokens[1].c_str();
+                                       irc::string type = assign(buf.substr(0, s));
  
                                        ConfigTag* conftag = ServerInstance->Config->ConfValue("dccallow");
                                        bool blockchat = conftag->getBool("blockchat");
  
                                        if (type == "SEND")
                                        {
-                                               if (tokens.size() < 3)
+                                               size_t first;
+                                               buf = buf.substr(s + 1);
+                                               if (!buf.empty() && buf[0] == '"')
+                                               {
+                                                       s = buf.find('"', 1);
+                                                       if (s == std::string::npos || s <= 1)
+                                                               return MOD_RES_PASSTHRU;
+                                                       --s;
+                                                       first = 1;
+                                               }
+                                               else
+                                               {
+                                                       s = buf.find(' ');
+                                                       first = 0;
+                                               }
+                                               if (s == std::string::npos)
                                                        return MOD_RES_PASSTHRU;
  
                                                std::string defaultaction = conftag->getString("action");
-                                               std::string filename = tokens[2];
+                                               std::string filename = buf.substr(first, s);
  
                                                bool found = false;
                                                for (unsigned int i = 0; i < bfl.size(); i++)
                                                if ((!found) && (defaultaction == "allow"))
                                                        return MOD_RES_PASSTHRU;
  
 -                                              user->WriteServ("NOTICE %s :The user %s is not accepting DCC SENDs from you. Your file %s was not sent.", user->nick.c_str(), u->nick.c_str(), filename.c_str());
 -                                              u->WriteServ("NOTICE %s :%s (%s@%s) attempted to send you a file named %s, which was blocked.", u->nick.c_str(), user->nick.c_str(), user->ident.c_str(), user->dhost.c_str(), filename.c_str());
 -                                              u->WriteServ("NOTICE %s :If you trust %s and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.", u->nick.c_str(), user->nick.c_str());
 +                                              user->WriteNotice("The user " + u->nick + " is not accepting DCC SENDs from you. Your file " + filename + " was not sent.");
 +                                              u->WriteNotice(user->nick + " (" + user->ident + "@" + user->dhost + ") attempted to send you a file named " + filename + ", which was blocked.");
 +                                              u->WriteNotice("If you trust " + user->nick + " and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.");
                                                return MOD_RES_DENY;
                                        }
                                        else if ((type == "CHAT") && (blockchat))
                                        {
 -                                              user->WriteServ("NOTICE %s :The user %s is not accepting DCC CHAT requests from you.", user->nick.c_str(), u->nick.c_str());
 -                                              u->WriteServ("NOTICE %s :%s (%s@%s) attempted to initiate a DCC CHAT session, which was blocked.", u->nick.c_str(), user->nick.c_str(), user->ident.c_str(), user->dhost.c_str());
 -                                              u->WriteServ("NOTICE %s :If you trust %s and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.", u->nick.c_str(), user->nick.c_str());
 +                                              user->WriteNotice("The user " + u->nick + " is not accepting DCC CHAT requests from you.");
 +                                              u->WriteNotice(user->nick + " (" + user->ident + "@" + user->dhost + ") attempted to initiate a DCC CHAT session, which was blocked.");
 +                                              u->WriteNotice("If you trust " + user->nick + " and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.");
                                                return MOD_RES_DENY;
                                        }
                                }
                for (userlist::iterator iter = ul.begin(); iter != ul.end();)
                {
                        User* u = (User*)(*iter);
 -                      dl = ext->get(u);
 +                      dl = ext.get(u);
                        if (dl)
                        {
                                if (dl->size())
                                        {
                                                if (iter2->length != 0 && (iter2->set_on + iter2->length) <= ServerInstance->Time())
                                                {
 -                                                      u->WriteNumeric(997, "%s %s :DCCALLOW entry for %s has expired", u->nick.c_str(), u->nick.c_str(), iter2->nickname.c_str());
 +                                                      u->WriteNumeric(997, u->nick, InspIRCd::Format("DCCALLOW entry for %s has expired", iter2->nickname.c_str()));
                                                        iter2 = dl->erase(iter2);
                                                }
                                                else
                for (userlist::iterator iter = ul.begin(); iter != ul.end();)
                {
                        User *u = (User*)(*iter);
 -                      dl = ext->get(u);
 +                      dl = ext.get(u);
                        if (dl)
                        {
                                if (dl->size())
                                                if (i->nickname == user->nick)
                                                {
  
 -                                                      u->WriteServ("NOTICE %s :%s left the network or changed their nickname and has been removed from your DCCALLOW list", u->nick.c_str(), i->nickname.c_str());
 -                                                      u->WriteNumeric(995, "%s %s :Removed %s from your DCCALLOW list", u->nick.c_str(), u->nick.c_str(), i->nickname.c_str());
 +                                                      u->WriteNotice(i->nickname + " left the network or changed their nickname and has been removed from your DCCALLOW list");
 +                                                      u->WriteNumeric(995, u->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", i->nickname.c_str()));
                                                        dl->erase(i);
                                                        break;
                                                }
                }
        }
  
 -      void ReadFileConf()
 +      void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
        {
 +              ConfigTag* tag = ServerInstance->Config->ConfValue("dccallow");
 +              cmd.maxentries = tag->getInt("maxentries", 20);
 +
                bfl.clear();
                ConfigTagList tags = ServerInstance->Config->ConfTags("banfile");
                for (ConfigIter i = tags.first; i != tags.second; ++i)
                }
        }
  
 -      virtual ~ModuleDCCAllow()
 -      {
 -              delete ext;
 -      }
 -
 -      virtual Version GetVersion()
 +      Version GetVersion() CXX11_OVERRIDE
        {
                return Version("Provides support for the /DCCALLOW command", VF_COMMON | VF_VENDOR);
        }
diff --combined src/modules/m_sasl.cpp
index 02a302c11cacd46d1d660a792745fcffc9efdf04,9cb5592d1f8d1e2c1af817739d02c9d7552107a3..2b247a198a138ebc6e7cd9943db59002a58b0599
  
  
  #include "inspircd.h"
 -#include "m_cap.h"
 -#include "account.h"
 -#include "sasl.h"
 -#include "ssl.h"
 +#include "modules/cap.h"
 +#include "modules/account.h"
 +#include "modules/sasl.h"
 +#include "modules/ssl.h"
 +#include "modules/spanningtree.h"
  
 -/* $ModDesc: Provides support for IRC Authentication Layer (aka: atheme SASL) via AUTHENTICATE. */
 +static std::string sasl_target;
 +
 +class ServerTracker : public SpanningTreeEventListener
 +{
 +      bool online;
 +
 +      void Update(const Server* server, bool linked)
 +      {
 +              if (sasl_target == "*")
 +                      return;
 +
 +              if (InspIRCd::Match(server->GetName(), sasl_target))
 +              {
 +                      ServerInstance->Logs->Log(MODNAME, LOG_VERBOSE, "SASL target server \"%s\" %s", sasl_target.c_str(), (linked ? "came online" : "went offline"));
 +                      online = linked;
 +              }
 +      }
 +
 +      void OnServerLink(const Server* server) CXX11_OVERRIDE
 +      {
 +              Update(server, true);
 +      }
 +
 +      void OnServerSplit(const Server* server) CXX11_OVERRIDE
 +      {
 +              Update(server, false);
 +      }
 +
 + public:
 +      ServerTracker(Module* mod)
 +              : SpanningTreeEventListener(mod)
 +      {
 +              Reset();
 +      }
 +
 +      void Reset()
 +      {
 +              if (sasl_target == "*")
 +              {
 +                      online = true;
 +                      return;
 +              }
 +
 +              online = false;
 +
 +              ProtocolInterface::ServerList servers;
 +              ServerInstance->PI->GetServerList(servers);
 +              for (ProtocolInterface::ServerList::const_iterator i = servers.begin(); i != servers.end(); ++i)
 +              {
 +                      const ProtocolInterface::ServerInfo& server = *i;
 +                      if (InspIRCd::Match(server.servername, sasl_target))
 +                      {
 +                              online = true;
 +                              break;
 +                      }
 +              }
 +      }
 +
 +      bool IsOnline() const { return online; }
 +};
 +
 +class SASLCap : public Cap::Capability
 +{
 +      std::string mechlist;
 +      const ServerTracker& servertracker;
 +
 +      bool OnRequest(LocalUser* user, bool adding) CXX11_OVERRIDE
 +      {
 +              // Requesting this cap is allowed anytime
 +              if (adding)
 +                      return true;
 +
 +              // But removing it can only be done when unregistered
 +              return (user->registered != REG_ALL);
 +      }
 +
 +      bool OnList(LocalUser* user) CXX11_OVERRIDE
 +      {
 +              return servertracker.IsOnline();
 +      }
 +
 +      const std::string* GetValue(LocalUser* user) const CXX11_OVERRIDE
 +      {
 +              return &mechlist;
 +      }
 +
 + public:
 +      SASLCap(Module* mod, const ServerTracker& tracker)
 +              : Cap::Capability(mod, "sasl")
 +              , servertracker(tracker)
 +      {
 +      }
 +
 +      void SetMechlist(const std::string& newmechlist)
 +      {
 +              if (mechlist == newmechlist)
 +                      return;
 +
 +              mechlist = newmechlist;
 +              NotifyValueChange();
 +      }
 +};
  
  enum SaslState { SASL_INIT, SASL_COMM, SASL_DONE };
  enum SaslResult { SASL_OK, SASL_FAIL, SASL_ABORT };
  
 -static std::string sasl_target = "*";
 +static Events::ModuleEventProvider* saslevprov;
  
  static void SendSASL(const parameterlist& params)
  {
 -      if (!ServerInstance->PI->SendEncapsulatedData(params))
 +      if (!ServerInstance->PI->SendEncapsulatedData(sasl_target, "SASL", params))
        {
 -              SASLFallback(NULL, params);
 +              FOREACH_MOD_CUSTOM(*saslevprov, SASLEventListener, OnSASLAuth, (params));
        }
  }
  
@@@ -158,15 -56,17 +158,15 @@@ class SaslAuthenticato
                : user(user_), state(SASL_INIT), state_announced(false)
        {
                parameterlist params;
 -              params.push_back(sasl_target);
 -              params.push_back("SASL");
                params.push_back(user->uuid);
                params.push_back("*");
                params.push_back("S");
                params.push_back(method);
  
 -              if (method == "EXTERNAL" && IS_LOCAL(user_))
 +              LocalUser* localuser = IS_LOCAL(user);
 +              if (method == "EXTERNAL" && localuser)
                {
 -                      SocketCertificateRequest req(&((LocalUser*)user_)->eh, ServerInstance->Modules->Find("m_sasl.so"));
 -                      std::string fp = req.GetFingerprint();
 +                      std::string fp = SSLClientCert::GetFingerprint(&localuser->eh);
  
                        if (fp.size())
                                params.push_back(fp);
                                this->result = this->GetSaslResult(msg[3]);
                        }
                        else if (msg[2] == "M")
 -                              this->user->WriteNumeric(908, "%s %s :are available SASL mechanisms", this->user->nick.c_str(), msg[3].c_str());
 +                              this->user->WriteNumeric(908, msg[3], "are available SASL mechanisms");
                        else
 -                              ServerInstance->Logs->Log("m_sasl", DEFAULT, "Services sent an unknown SASL message \"%s\" \"%s\"", msg[2].c_str(), msg[3].c_str());
 +                              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Services sent an unknown SASL message \"%s\" \"%s\"", msg[2].c_str(), msg[3].c_str());
  
                        break;
                 case SASL_DONE:
                        break;
                 default:
 -                      ServerInstance->Logs->Log("m_sasl", DEFAULT, "WTF: SaslState is not a known state (%d)", this->state);
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WTF: SaslState is not a known state (%d)", this->state);
                        break;
                }
  
                        return true;
  
                parameterlist params;
 -              params.push_back(sasl_target);
 -              params.push_back("SASL");
                params.push_back(this->user->uuid);
                params.push_back(this->agent);
                params.push_back("C");
  
                SendSASL(params);
  
-               if (parameters[0][0] == '*')
+               if (parameters[0].c_str()[0] == '*')
                {
                        this->Abort();
                        return false;
                switch (this->result)
                {
                 case SASL_OK:
 -                      this->user->WriteNumeric(903, "%s :SASL authentication successful", this->user->nick.c_str());
 +                      this->user->WriteNumeric(903, "SASL authentication successful");
                        break;
                 case SASL_ABORT:
 -                      this->user->WriteNumeric(906, "%s :SASL authentication aborted", this->user->nick.c_str());
 +                      this->user->WriteNumeric(906, "SASL authentication aborted");
                        break;
                 case SASL_FAIL:
 -                      this->user->WriteNumeric(904, "%s :SASL authentication failed", this->user->nick.c_str());
 +                      this->user->WriteNumeric(904, "SASL authentication failed");
                        break;
                 default:
                        break;
@@@ -282,8 -184,8 +282,8 @@@ class CommandAuthenticate : public Comm
  {
   public:
        SimpleExtItem<SaslAuthenticator>& authExt;
 -      GenericCap& cap;
 -      CommandAuthenticate(Module* Creator, SimpleExtItem<SaslAuthenticator>& ext, GenericCap& Cap)
 +      Cap::Capability& cap;
 +      CommandAuthenticate(Module* Creator, SimpleExtItem<SaslAuthenticator>& ext, Cap::Capability& Cap)
                : Command(Creator, "AUTHENTICATE", 1), authExt(ext), cap(Cap)
        {
                works_before_reg = true;
  
        CmdResult Handle (const std::vector<std::string>& parameters, User *user)
        {
 -              /* Only allow AUTHENTICATE on unregistered clients */
 -              if (user->registered != REG_ALL)
                {
 -                      if (!cap.ext.get(user))
 +                      if (!cap.get(user))
                                return CMD_FAILURE;
  
                        SaslAuthenticator *sasl = authExt.get(user);
@@@ -319,10 -223,10 +319,10 @@@ class CommandSASL : public Comman
  
        CmdResult Handle(const std::vector<std::string>& parameters, User *user)
        {
 -              User* target = ServerInstance->FindNick(parameters[1]);
 -              if ((!target) || (IS_SERVER(target)))
 +              User* target = ServerInstance->FindUUID(parameters[1]);
 +              if (!target)
                {
 -                      ServerInstance->Logs->Log("m_sasl", DEBUG,"User not found in sasl ENCAP event: %s", parameters[1].c_str());
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User not found in sasl ENCAP event: %s", parameters[1].c_str());
                        return CMD_FAILURE;
                }
  
  class ModuleSASL : public Module
  {
        SimpleExtItem<SaslAuthenticator> authExt;
 -      GenericCap cap;
 +      ServerTracker servertracker;
 +      SASLCap cap;
        CommandAuthenticate auth;
        CommandSASL sasl;
 +      Events::ModuleEventProvider sasleventprov;
 +
   public:
        ModuleSASL()
 -              : authExt("sasl_auth", this), cap(this, "sasl"), auth(this, authExt, cap), sasl(this, authExt)
 +              : authExt("sasl_auth", ExtensionItem::EXT_USER, this)
 +              , servertracker(this)
 +              , cap(this, servertracker)
 +              , auth(this, authExt, cap)
 +              , sasl(this, authExt)
 +              , sasleventprov(this, "event/sasl")
        {
 +              saslevprov = &sasleventprov;
        }
  
 -      void init()
 +      void init() CXX11_OVERRIDE
        {
 -              OnRehash(NULL);
 -              Implementation eventlist[] = { I_OnEvent, I_OnUserRegister, I_OnRehash };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
 -
 -              ServiceProvider* providelist[] = { &auth, &sasl, &authExt };
 -              ServerInstance->Modules->AddServices(providelist, 3);
 -
                if (!ServerInstance->Modules->Find("m_services_account.so") || !ServerInstance->Modules->Find("m_cap.so"))
 -                      ServerInstance->Logs->Log("m_sasl", DEFAULT, "WARNING: m_services_account.so and m_cap.so are not loaded! m_sasl.so will NOT function correctly until these two modules are loaded!");
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: m_services_account.so and m_cap.so are not loaded! m_sasl.so will NOT function correctly until these two modules are loaded!");
        }
  
 -      void OnRehash(User*)
 +      void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
        {
                sasl_target = ServerInstance->Config->ConfValue("sasl")->getString("target", "*");
 +              servertracker.Reset();
        }
  
 -      ModResult OnUserRegister(LocalUser *user)
 +      ModResult OnUserRegister(LocalUser *user) CXX11_OVERRIDE
        {
                SaslAuthenticator *sasl_ = authExt.get(user);
                if (sasl_)
                return MOD_RES_PASSTHRU;
        }
  
 -      Version GetVersion()
 +      void OnDecodeMetaData(Extensible* target, const std::string& extname, const std::string& extdata) CXX11_OVERRIDE
        {
 -              return Version("Provides support for IRC Authentication Layer (aka: SASL) via AUTHENTICATE.", VF_VENDOR);
 +              if ((target == NULL) && (extname == "saslmechlist"))
 +                      cap.SetMechlist(extdata);
        }
  
 -      void OnEvent(Event &ev)
 +      Version GetVersion() CXX11_OVERRIDE
        {
 -              cap.HandleEvent(ev);
 +              return Version("Provides support for IRC Authentication Layer (aka: SASL) via AUTHENTICATE.", VF_VENDOR);
        }
  };
  
index b6c6ce174f046570fd34e634b6ea3bf105f0b63d,5d11d27f7b0fa16b72a2e568d232745e279e0f42..b925c3f3733cea83d744a25677c253ffc2794855
  
  #include "inspircd.h"
  
 -/* $ModDesc: Disallows /LIST for recently connected clients to hinder spam bots */
 -
  class ModuleSecureList : public Module
  {
 - private:
        std::vector<std::string> allowlist;
        time_t WaitTime;
 - public:
 -      void init()
 -      {
 -              OnRehash(NULL);
 -              Implementation eventlist[] = { I_OnRehash, I_OnPreCommand, I_On005Numeric };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
 -      }
  
 -      virtual ~ModuleSecureList()
 -      {
 -      }
 -
 -      virtual Version GetVersion()
 + public:
 +      Version GetVersion() CXX11_OVERRIDE
        {
                return Version("Disallows /LIST for recently connected clients to hinder spam bots", VF_VENDOR);
        }
  
 -      void OnRehash(User* user)
 +      void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
        {
                allowlist.clear();
  
         * OnPreCommand()
         *   Intercept the LIST command.
         */
 -      virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> &parameters, LocalUser *user, bool validated, const std::string &original_line)
 +      ModResult OnPreCommand(std::string &command, std::vector<std::string> &parameters, LocalUser *user, bool validated, const std::string &original_line) CXX11_OVERRIDE
        {
                /* If the command doesnt appear to be valid, we dont want to mess with it. */
                if (!validated)
                        return MOD_RES_PASSTHRU;
  
 -              if ((command == "LIST") && (ServerInstance->Time() < (user->signon+WaitTime)) && (!IS_OPER(user)))
 +              if ((command == "LIST") && (ServerInstance->Time() < (user->signon+WaitTime)) && (!user->IsOper()))
                {
                        /* Normally wouldnt be allowed here, are they exempt? */
                        for (std::vector<std::string>::iterator x = allowlist.begin(); x != allowlist.end(); x++)
                                        return MOD_RES_PASSTHRU;
  
                        /* Not exempt, BOOK EM DANNO! */
 -                      user->WriteServ("NOTICE %s :*** You cannot list within the first %lu seconds of connecting. Please try again later.",user->nick.c_str(), (unsigned long) WaitTime);
 +                      user->WriteNotice("*** You cannot list within the first " + ConvToStr(WaitTime) + " seconds of connecting. Please try again later.");
-                       /* Some crap clients (read: mIRC, various java chat applets) muck up if they don't
+                       /* Some clients (e.g. mIRC, various java chat applets) muck up if they don't
                         * receive these numerics whenever they send LIST, so give them an empty LIST to mull over.
                         */
 -                      user->WriteNumeric(321, "%s Channel :Users Name",user->nick.c_str());
 -                      user->WriteNumeric(323, "%s :End of channel list.",user->nick.c_str());
 +                      user->WriteNumeric(RPL_LISTSTART, "Channel", "Users Name");
 +                      user->WriteNumeric(RPL_LISTEND, "End of channel list.");
                        return MOD_RES_DENY;
                }
                return MOD_RES_PASSTHRU;
        }
  
 -      virtual void On005Numeric(std::string &output)
 +      void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
        {
 -              output.append(" SECURELIST");
 +              tokens["SECURELIST"];
        }
  };
  
index 7b0478229990cf2dc592bf61398eac22d5bbc1cd,3f5d427e169a7ca90b68e82ae0648217f191c825..64ca729779249cd2a44e8bb8fc80db9119719d71
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
 -
 -/* $ModDesc: Provides a spanning tree server link protocol */
 -
  #include "inspircd.h"
 -#include "socket.h"
 -#include "xline.h"
  
  #include "main.h"
  #include "utils.h"
  #include "treeserver.h"
 -#include "treesocket.h"
 +#include "commandbuilder.h"
  
 -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */
 -
 -void ModuleSpanningTree::OnPostCommand(const std::string &command, const std::vector<std::string>& parameters, LocalUser *user, CmdResult result, const std::string &original_line)
 +void ModuleSpanningTree::OnPostCommand(Command* command, const std::vector<std::string>& parameters, LocalUser* user, CmdResult result, const std::string& original_line)
  {
        if (result == CMD_SUCCESS)
                Utils->RouteCommand(NULL, command, parameters, user);
  }
  
 -void SpanningTreeUtilities::RouteCommand(TreeServer* origin, const std::string &command, const parameterlist& parameters, User *user)
 +void SpanningTreeUtilities::RouteCommand(TreeServer* origin, CommandBase* thiscmd, const parameterlist& parameters, User* user)
  {
 -      if (!ServerInstance->Parser->IsValidCommand(command, parameters.size(), user))
 -              return;
 -
 -      /* We know it's non-null because IsValidCommand returned true */
 -      Command* thiscmd = ServerInstance->Parser->GetHandler(command);
 -
 +      const std::string& command = thiscmd->name;
        RouteDescriptor routing = thiscmd->GetRouting(user, parameters);
 -
 -      std::string sent_cmd = command;
 -      parameterlist params;
 -
        if (routing.type == ROUTE_TYPE_LOCALONLY)
 -      {
 -              /* Broadcast when it's a core command with the default route descriptor and the source is a
 -               * remote user or a remote server
 -               */
 +              return;
  
 -              Version ver = thiscmd->creator->GetVersion();
 -              if ((!(ver.Flags & VF_CORE)) || (IS_LOCAL(user)) || (IS_SERVER(user) == ServerInstance->FakeClient))
 -                      return;
 +      const bool encap = ((routing.type == ROUTE_TYPE_OPT_BCAST) || (routing.type == ROUTE_TYPE_OPT_UCAST));
 +      CmdBuilder params(user, encap ? "ENCAP" : command.c_str());
 +      TreeServer* sdest = NULL;
  
 -              routing = ROUTE_BROADCAST;
 -      }
 -      else if (routing.type == ROUTE_TYPE_OPT_BCAST)
 +      if (routing.type == ROUTE_TYPE_OPT_BCAST)
        {
 -              params.push_back("*");
 +              params.push('*');
                params.push_back(command);
 -              sent_cmd = "ENCAP";
        }
 -      else if (routing.type == ROUTE_TYPE_OPT_UCAST)
 +      else if (routing.type == ROUTE_TYPE_UNICAST || routing.type == ROUTE_TYPE_OPT_UCAST)
        {
 -              TreeServer* sdest = FindServer(routing.serverdest);
 +              sdest = static_cast<TreeServer*>(routing.server);
                if (!sdest)
                {
 -                      ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Trying to route ENCAP to nonexistent server %s",
 -                              routing.serverdest.c_str());
 -                      return;
 +                      // Assume the command handler already validated routing.serverdest and have only returned success if the target is something that the
 +                      // user executing the command is allowed to look up e.g. target is not an uuid if user is local.
 +                      sdest = FindRouteTarget(routing.serverdest);
 +                      if (!sdest)
 +                      {
-                               ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Trying to route %s%s to nonexistant server %s", (encap ? "ENCAP " : ""), command.c_str(), routing.serverdest.c_str());
++                              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Trying to route %s%s to nonexistent server %s", (encap ? "ENCAP " : ""), command.c_str(), routing.serverdest.c_str());
 +                              return;
 +                      }
 +              }
 +
 +              if (encap)
 +              {
 +                      params.push_back(sdest->GetID());
 +                      params.push_back(command);
                }
 -              params.push_back(sdest->GetID());
 -              params.push_back(command);
 -              sent_cmd = "ENCAP";
        }
        else
        {
  
                if (!(ver.Flags & (VF_COMMON | VF_CORE)) && srcmodule != Creator)
                {
 -                      ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Routed command %s from non-VF_COMMON module %s",
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Routed command %s from non-VF_COMMON module %s",
                                command.c_str(), srcmodule->ModuleSourceFile.c_str());
                        return;
                }
        }
  
 -      std::string output_text;
 -      ServerInstance->Parser->TranslateUIDs(thiscmd->translation, parameters, output_text, true, thiscmd);
 +      std::string output_text = CommandParser::TranslateUIDs(thiscmd->translation, parameters, true, thiscmd);
  
        params.push_back(output_text);
  
                if (ServerInstance->Modes->FindPrefix(dest[0]))
                {
                        pfx = dest[0];
 -                      dest = dest.substr(1);
 +                      dest.erase(dest.begin());
                }
                if (dest[0] == '#')
                {
                        Channel* c = ServerInstance->FindChan(dest);
                        if (!c)
                                return;
 -                      TreeServerList list;
                        // TODO OnBuildExemptList hook was here
 -                      GetListOfServersForChannel(c,list,pfx, CUList());
 -                      std::string data = ":" + user->uuid + " " + sent_cmd;
 -                      for (unsigned int x = 0; x < params.size(); x++)
 -                              data += " " + params[x];
 -                      for (TreeServerList::iterator i = list.begin(); i != list.end(); i++)
 -                      {
 -                              TreeSocket* Sock = i->second->GetSocket();
 -                              if (origin && origin->GetSocket() == Sock)
 -                                      continue;
 -                              if (Sock)
 -                                      Sock->WriteLine(data);
 -                      }
 +                      CUList exempts;
 +                      SendChannelMessage(user->uuid, c, parameters[1], pfx, exempts, command.c_str(), origin ? origin->GetSocket() : NULL);
                }
                else if (dest[0] == '$')
                {
 -                      if (origin)
 -                              DoOneToAllButSender(user->uuid, sent_cmd, params, origin->GetName());
 -                      else
 -                              DoOneToMany(user->uuid, sent_cmd, params);
 +                      params.Forward(origin);
                }
                else
                {
                        // user target?
                        User* d = ServerInstance->FindNick(dest);
 -                      if (!d)
 +                      if (!d || IS_LOCAL(d))
                                return;
 -                      TreeServer* tsd = BestRouteTo(d->server);
 +                      TreeServer* tsd = TreeServer::Get(d)->GetRoute();
                        if (tsd == origin)
                                // huh? no routing stuff around in a circle, please.
                                return;
 -                      DoOneToOne(user->uuid, sent_cmd, params, d->server);
 +                      params.Unicast(d);
                }
        }
        else if (routing.type == ROUTE_TYPE_BROADCAST || routing.type == ROUTE_TYPE_OPT_BCAST)
        {
 -              if (origin)
 -                      DoOneToAllButSender(user->uuid, sent_cmd, params, origin->GetName());
 -              else
 -                      DoOneToMany(user->uuid, sent_cmd, params);
 +              params.Forward(origin);
        }
        else if (routing.type == ROUTE_TYPE_UNICAST || routing.type == ROUTE_TYPE_OPT_UCAST)
        {
 -              if (origin && routing.serverdest == origin->GetName())
 -                      return;
 -              DoOneToOne(user->uuid, sent_cmd, params, routing.serverdest);
 +              params.Unicast(sdest->ServerUser);
        }
  }
diff --combined src/usermanager.cpp
index 7b4bbe281cbd3a2396ffa58c668e594f64c10f35,2cb7ad51117a4743eb8835f6cd5c79dba9456d6c..5891b42f014f61cf3db0b70b8ab5889bb59e63d5
  
  #include "inspircd.h"
  #include "xline.h"
 -#include "bancache.h"
 +#include "iohook.h"
 +
 +namespace
 +{
 +      class WriteCommonQuit : public User::ForEachNeighborHandler
 +      {
 +              std::string line;
 +              std::string operline;
 +
 +              void Execute(LocalUser* user) CXX11_OVERRIDE
 +              {
 +                      user->Write(user->IsOper() ? operline : line);
 +              }
 +
 +       public:
 +              WriteCommonQuit(User* user, const std::string& msg, const std::string& opermsg)
 +                      : line(":" + user->GetFullHost() + " QUIT :")
 +                      , operline(line)
 +              {
 +                      line += msg;
 +                      operline += opermsg;
 +                      user->ForEachNeighbor(*this, false);
 +              }
 +      };
 +}
  
  UserManager::UserManager()
 -      : unregistered_count(0), local_count(0)
 +      : already_sent_id(0)
 +      , unregistered_count(0)
  {
  }
  
 +UserManager::~UserManager()
 +{
 +      for (user_hash::iterator i = clientlist.begin(); i != clientlist.end(); ++i)
 +      {
 +              delete i->second;
 +      }
 +}
 +
  /* add a client connection to the sockets list */
  void UserManager::AddUser(int socket, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
  {
        /* NOTE: Calling this one parameter constructor for User automatically
         * allocates a new UUID and places it in the hash_map.
         */
 -      LocalUser* New = NULL;
 -      try
 -      {
 -              New = new LocalUser(socket, client, server);
 -      }
 -      catch (...)
 -      {
 -              ServerInstance->Logs->Log("USERS", DEFAULT,"*** WTF *** Duplicated UUID! -- Crack smoking monkeys have been unleashed.");
 -              ServerInstance->SNO->WriteToSnoMask('a', "WARNING *** Duplicate UUID allocated!");
 -              return;
 -      }
 +      LocalUser* const New = new LocalUser(socket, client, server);
        UserIOHandler* eh = &New->eh;
  
 -      /* Give each of the modules an attempt to hook the user for I/O */
 -      FOREACH_MOD(I_OnHookIO, OnHookIO(eh, via));
 -
 -      if (eh->GetIOHook())
 +      // If this listener has an IO hook provider set then tell it about the connection
 +      for (ListenSocket::IOHookProvList::iterator i = via->iohookprovs.begin(); i != via->iohookprovs.end(); ++i)
        {
 -              try
 -              {
 -                      eh->GetIOHook()->OnStreamSocketAccept(eh, client, server);
 -              }
 -              catch (CoreException& modexcept)
 -              {
 -                      ServerInstance->Logs->Log("SOCKET", DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
 -              }
 +              ListenSocket::IOHookProvRef& iohookprovref = *i;
 +              if (iohookprovref)
 +                      iohookprovref->OnAccept(eh, client, server);
        }
  
 -      ServerInstance->Logs->Log("USERS", DEBUG,"New user fd: %d", socket);
 +      ServerInstance->Logs->Log("USERS", LOG_DEBUG, "New user fd: %d", socket);
  
        this->unregistered_count++;
  
 -      /* The users default nick is their UUID */
 -      New->nick = New->uuid;
 -      (*(this->clientlist))[New->nick] = New;
 -
 -      New->registered = REG_NONE;
 -      New->signon = ServerInstance->Time();
 -      New->lastping = 1;
 +      this->clientlist[New->nick] = New;
 +      this->AddClone(New);
  
 -      ServerInstance->Users->AddLocalClone(New);
 -      ServerInstance->Users->AddGlobalClone(New);
 +      this->local_users.push_front(New);
  
 -      New->localuseriter = this->local_users.insert(local_users.end(), New);
 -      local_count++;
 -
 -      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");
         * Check connect class settings and initialise settings into User.
         * This will be done again after DNS resolution. -- w00t
         */
 -      New->CheckClass();
 +      New->CheckClass(ServerInstance->Config->CCOnConnect);
        if (New->quitting)
                return;
  
         */
        New->exempt = (ServerInstance->XLines->MatchesLine("E",New) != NULL);
  
 -      if (BanCacheHit *b = ServerInstance->BanCache->GetHit(New->GetIPString()))
 +      BanCacheHit* const b = ServerInstance->BanCache.GetHit(New->GetIPString());
 +      if (b)
        {
                if (!b->Type.empty() && !New->exempt)
                {
                        /* user banned */
 -                      ServerInstance->Logs->Log("BANCACHE", DEBUG, std::string("BanCache: Positive hit for ") + New->GetIPString());
 -                      if (!ServerInstance->Config->MoronBanner.empty())
 -                              New->WriteServ("NOTICE %s :*** %s", New->nick.c_str(), ServerInstance->Config->MoronBanner.c_str());
 +                      ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCache: Positive hit for " + New->GetIPString());
 +                      if (!ServerInstance->Config->XLineMessage.empty())
 +                              New->WriteNumeric(ERR_YOUREBANNEDCREEP, ServerInstance->Config->XLineMessage);
                        this->QuitUser(New, b->Reason);
                        return;
                }
                else
                {
 -                      ServerInstance->Logs->Log("BANCACHE", DEBUG, std::string("BanCache: Negative hit for ") + New->GetIPString());
 +                      ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCache: Negative hit for " + New->GetIPString());
                }
        }
        else
                }
        }
  
 -      if (!ServerInstance->SE->AddFd(eh, FD_WANT_FAST_READ | FD_WANT_EDGE_WRITE))
 +      if (!SocketEngine::AddFd(eh, FD_WANT_FAST_READ | FD_WANT_EDGE_WRITE))
        {
 -              ServerInstance->Logs->Log("USERS", DEBUG,"Internal error on new connection");
 +              ServerInstance->Logs->Log("USERS", LOG_DEBUG, "Internal error on new connection");
                this->QuitUser(New, "Internal error handling connection");
+               return;
        }
  
 -      /* NOTE: even if dns lookups are *off*, we still need to display this.
 -       * BOPM and other stuff requires it.
 -       */
 -      New->WriteServ("NOTICE Auth :*** Looking up your hostname...");
        if (ServerInstance->Config->RawLog)
 -              New->WriteServ("NOTICE Auth :*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.");
 +              New->WriteNotice("*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.");
  
 -      FOREACH_MOD(I_OnSetUserIP,OnSetUserIP(New));
 +      FOREACH_MOD(OnSetUserIP, (New));
        if (New->quitting)
                return;
  
 -      FOREACH_MOD(I_OnUserInit,OnUserInit(New));
 -
 -      if (ServerInstance->Config->NoUserDns)
 -      {
 -              New->WriteServ("NOTICE %s :*** Skipping host resolution (disabled by server administrator)", New->nick.c_str());
 -              New->dns_done = true;
 -      }
 -      else
 -      {
 -              New->StartDNSLookup();
 -      }
 +      FOREACH_MOD(OnUserInit, (New));
  }
  
 -void UserManager::QuitUser(User *user, const std::string &quitreason, const char* operreason)
 +void UserManager::QuitUser(User* user, const std::string& quitreason, const std::string* operreason)
  {
        if (user->quitting)
        {
 -              ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: Tried to quit quitting user: " + user->nick);
 +              ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Tried to quit quitting user: " + user->nick);
                return;
        }
  
        if (IS_SERVER(user))
        {
 -              ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: Tried to quit server user: " + user->nick);
 +              ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Tried to quit server user: " + user->nick);
                return;
        }
  
        user->quitting = true;
  
 -      ServerInstance->Logs->Log("USERS", DEBUG, "QuitUser: %s=%s '%s'", user->uuid.c_str(), user->nick.c_str(), quitreason.c_str());
 -      user->Write("ERROR :Closing link: (%s@%s) [%s]", user->ident.c_str(), user->host.c_str(), *operreason ? operreason : quitreason.c_str());
 +      ServerInstance->Logs->Log("USERS", LOG_DEBUG, "QuitUser: %s=%s '%s'", user->uuid.c_str(), user->nick.c_str(), quitreason.c_str());
 +      user->Write("ERROR :Closing link: (%s@%s) [%s]", user->ident.c_str(), user->host.c_str(), operreason ? operreason->c_str() : quitreason.c_str());
  
        std::string reason;
 -      std::string oper_reason;
        reason.assign(quitreason, 0, ServerInstance->Config->Limits.MaxQuit);
 -      if (operreason && *operreason)
 -              oper_reason.assign(operreason, 0, ServerInstance->Config->Limits.MaxQuit);
 -      else
 -              oper_reason = quitreason;
 +      if (!operreason)
 +              operreason = &reason;
  
        ServerInstance->GlobalCulls.AddItem(user);
  
        if (user->registered == REG_ALL)
        {
 -              FOREACH_MOD(I_OnUserQuit,OnUserQuit(user, reason, oper_reason));
 -              user->WriteCommonQuit(reason, oper_reason);
 +              FOREACH_MOD(OnUserQuit, (user, reason, *operreason));
 +              WriteCommonQuit(user, reason, *operreason);
        }
 -
 -      if (user->registered != REG_ALL)
 -              if (ServerInstance->Users->unregistered_count)
 -                      ServerInstance->Users->unregistered_count--;
 +      else
 +              unregistered_count--;
  
        if (IS_LOCAL(user))
        {
                LocalUser* lu = IS_LOCAL(user);
 -              FOREACH_MOD(I_OnUserDisconnect,OnUserDisconnect(lu));
 +              FOREACH_MOD(OnUserDisconnect, (lu));
                lu->eh.Close();
 -      }
  
 -      /*
 -       * this must come before the ServerInstance->SNO->WriteToSnoMaskso that it doesnt try to fill their buffer with anything
 -       * if they were an oper with +s +qQ.
 -       */
 -      if (user->registered == REG_ALL)
 -      {
 -              if (IS_LOCAL(user))
 -              {
 -                      if (!user->quietquit)
 -                      {
 -                              ServerInstance->SNO->WriteToSnoMask('q',"Client exiting: %s (%s) [%s]",
 -                                      user->GetFullRealHost().c_str(), user->GetIPString(), oper_reason.c_str());
 -                      }
 -              }
 -              else
 -              {
 -                      if ((!ServerInstance->SilentULine(user->server)) && (!user->quietquit))
 -                      {
 -                              ServerInstance->SNO->WriteToSnoMask('Q',"Client exiting on server %s: %s (%s) [%s]",
 -                                      user->server.c_str(), user->GetFullRealHost().c_str(), user->GetIPString(), oper_reason.c_str());
 -                      }
 -              }
 -              user->AddToWhoWas();
 +              if (lu->registered == REG_ALL)
 +                      ServerInstance->SNO->WriteToSnoMask('q',"Client exiting: %s (%s) [%s]", user->GetFullRealHost().c_str(), user->GetIPString().c_str(), operreason->c_str());
 +              local_users.erase(lu);
        }
  
 -      user_hash::iterator iter = this->clientlist->find(user->nick);
 -
 -      if (iter != this->clientlist->end())
 -              this->clientlist->erase(iter);
 -      else
 -              ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: Nick not found in clientlist, cannot remove: " + user->nick);
 -
 -      ServerInstance->Users->uuidlist->erase(user->uuid);
 -}
 -
 +      if (!clientlist.erase(user->nick))
 +              ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Nick not found in clientlist, cannot remove: " + user->nick);
  
 -void UserManager::AddLocalClone(User *user)
 -{
 -      local_clones[user->GetCIDRMask()]++;
 +      uuidlist.erase(user->uuid);
 +      user->PurgeEmptyChannels();
  }
  
 -void UserManager::AddGlobalClone(User *user)
 +void UserManager::AddClone(User* user)
  {
 -      global_clones[user->GetCIDRMask()]++;
 +      CloneCounts& counts = clonemap[user->GetCIDRMask()];
 +      counts.global++;
 +      if (IS_LOCAL(user))
 +              counts.local++;
  }
  
  void UserManager::RemoveCloneCounts(User *user)
  {
 -      if (IS_LOCAL(user))
 +      CloneMap::iterator it = clonemap.find(user->GetCIDRMask());
 +      if (it != clonemap.end())
        {
 -              clonemap::iterator x = local_clones.find(user->GetCIDRMask());
 -              if (x != local_clones.end())
 +              CloneCounts& counts = it->second;
 +              counts.global--;
 +              if (counts.global == 0)
                {
 -                      x->second--;
 -                      if (!x->second)
 -                      {
 -                              local_clones.erase(x);
 -                      }
 +                      // No more users from this IP, remove entry from the map
 +                      clonemap.erase(it);
 +                      return;
                }
 -      }
  
 -      clonemap::iterator y = global_clones.find(user->GetCIDRMask());
 -      if (y != global_clones.end())
 -      {
 -              y->second--;
 -              if (!y->second)
 -              {
 -                      global_clones.erase(y);
 -              }
 +              if (IS_LOCAL(user))
 +                      counts.local--;
        }
  }
  
  void UserManager::RehashCloneCounts()
  {
 -      local_clones.clear();
 -      global_clones.clear();
 +      clonemap.clear();
  
 -      const user_hash& hash = *ServerInstance->Users->clientlist;
 +      const user_hash& hash = ServerInstance->Users.GetUsers();
        for (user_hash::const_iterator i = hash.begin(); i != hash.end(); ++i)
        {
                User* u = i->second;
 -
 -              if (IS_LOCAL(u))
 -                      AddLocalClone(u);
 -              AddGlobalClone(u);
 +              AddClone(u);
        }
  }
  
 -unsigned long UserManager::GlobalCloneCount(User *user)
 +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, ...)
  {
 -      if (!text)
 -              return;
 -
 -      char textbuffer[MAXBUF];
 -      char formatbuffer[MAXBUF];
 -      va_list argsPtr;
 -      va_start (argsPtr, text);
 -      vsnprintf(textbuffer, MAXBUF, text, argsPtr);
 -      va_end(argsPtr);
 -
 -      snprintf(formatbuffer,MAXBUF,"NOTICE $%s :%s", ServerInstance->Config->ServerName.c_str(), textbuffer);
 +      std::string message;
 +      VAFORMAT(message, text, text);
 +      message = "NOTICE $" + ServerInstance->Config->ServerName + " :" + message;
  
 -      for (LocalUserList::const_iterator i = local_users.begin(); i != local_users.end(); i++)
 +      for (LocalList::const_iterator i = local_users.begin(); i != local_users.end(); ++i)
        {
                User* t = *i;
 -              t->WriteServ(std::string(formatbuffer));
 +              t->WriteServ(message);
        }
  }
  
 -void UserManager::ServerPrivmsgAll(const char* text, ...)
 +/* 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)
  {
 -      if (!text)
 -              return;
 +      ModResult res;
 +      FIRST_MOD_RESULT(OnCheckReady, res, (user));
 +      return (res == MOD_RES_PASSTHRU);
 +}
 +
 +/**
 + * 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()
 +{
 +      /*
 +       * loop over all local users..
 +       */
 +      for (LocalList::iterator i = local_users.begin(); i != local_users.end(); )
 +      {
 +              // It's possible that we quit the user below due to ping timeout etc. and QuitUser() removes it from the list
 +              LocalUser* curr = *i;
 +              ++i;
  
 -      char textbuffer[MAXBUF];
 -      char formatbuffer[MAXBUF];
 -      va_list argsPtr;
 -      va_start (argsPtr, text);
 -      vsnprintf(textbuffer, MAXBUF, text, argsPtr);
 -      va_end(argsPtr);
 +              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();
 +              }
  
 -      snprintf(formatbuffer,MAXBUF,"PRIVMSG $%s :%s", ServerInstance->Config->ServerName.c_str(), textbuffer);
 +              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;
 +              }
  
 -      for (LocalUserList::const_iterator i = local_users.begin(); i != local_users.end(); i++)
 -      {
 -              User* t = *i;
 -              t->WriteServ(std::string(formatbuffer));
 +              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 how many users have a given mode e.g. 'a' */
 -int UserManager::ModeCount(const char mode)
 +already_sent_t UserManager::NextAlreadySentId()
  {
 -      int c = 0;
 -      for(user_hash::iterator i = clientlist->begin(); i != clientlist->end(); ++i)
 +      if (++already_sent_id == 0)
        {
 -              User* u = i->second;
 -              if (u->modes[mode-65])
 -                      c++;
 +              // Wrapped around, reset the already_sent ids of all users
 +              already_sent_id = 1;
 +              for (LocalList::iterator i = local_users.begin(); i != local_users.end(); ++i)
 +              {
 +                      LocalUser* user = *i;
 +                      user->already_sent = 0;
 +              }
        }
 -      return c;
 +      return already_sent_id;
  }
diff --combined tools/travis-ci.sh
index 0000000000000000000000000000000000000000,6dbc823008449e4581d91818804ff2d2027b68f4..8828532b083c35264a36c959a4e49b7ae654c93d
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,20 +1,19 @@@
 -./configure --enable-extras=m_geoip.cpp,m_ldapauth.cpp,m_ldapoper.cpp,m_mysql.cpp,m_pgsql.cpp,m_regex_pcre.cpp,m_regex_posix.cpp,m_regex_tre.cpp,m_sqlite3.cpp,m_ssl_gnutls.cpp,m_ssl_openssl.cpp
 -./configure --with-cc=$CXX
 -make -j4 install
+ #!/bin/bash
+ set -v
+ if [ "$TRAVIS_OS_NAME" = "linux" ]
+ then
+       sudo apt-get update --assume-yes
+       sudo apt-get install --assume-yes libgeoip-dev libgnutls-dev libldap2-dev libmysqlclient-dev libpcre3-dev libpq-dev libsqlite3-dev libssl-dev libtre-dev
+ elif [ "$TRAVIS_OS_NAME" = "osx" ]
+ then
+       brew update
+       brew install geoip gnutls mysql-connector-c openssl pcre postgresql sqlite3 tre
+       brew link sqlite3 --force
+ else
+       >&2 echo "'$TRAVIS_OS_NAME' is an unknown Travis CI environment!"
+       exit 1
+ fi
+ set -e
++export TEST_BUILD_MODULES="m_geoip.cpp,m_ldap.cpp,m_mysql.cpp,m_pgsql.cpp,m_regex_pcre.cpp,m_regex_posix.cpp,m_regex_tre.cpp,m_sqlite3.cpp,m_ssl_gnutls.cpp,m_ssl_openssl.cpp"
++./tools/test-build $CXX
+ ./run/bin/inspircd --version