]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Merge insp20
authorAttila Molnar <attilamolnar@hush.com>
Mon, 27 Oct 2014 14:26:20 +0000 (15:26 +0100)
committerAttila Molnar <attilamolnar@hush.com>
Mon, 27 Oct 2014 14:26:20 +0000 (15:26 +0100)
21 files changed:
1  2 
docs/conf/modules.conf.example
include/configreader.h
make/unit-cc.pl
src/channels.cpp
src/configparser.cpp
src/configreader.cpp
src/coremods/core_info/cmd_motd.cpp
src/coremods/core_whois.cpp
src/listmode.cpp
src/modules/extra/m_sqlite3.cpp
src/modules/extra/m_ssl_gnutls.cpp
src/modules/extra/m_ssl_openssl.cpp
src/modules/m_banredirect.cpp
src/modules/m_delayjoin.cpp
src/modules/m_delaymsg.cpp
src/modules/m_nationalchars.cpp
src/modules/m_spanningtree/treesocket.h
src/modules/m_spanningtree/treesocket2.cpp
src/modules/m_watch.cpp
src/server.cpp
win/inspircd_win32wrapper.cpp

Simple merge
index 3d99235361eec991e5fcc77067f22e543b7cab5c,b01a979a7c2703384bb17155e52688370af0d3df..342743991cdece1ba40e9081601f61582c7ffc3d
@@@ -467,9 -530,19 +470,11 @@@ class CoreExport ServerConfi
         */
        ServerConfig();
  
+       ~ServerConfig();
        /** 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
diff --cc make/unit-cc.pl
Simple merge
index 2ea8688cf51c311e7c0279501d400b1bd6279d5f,9f1eafd0cb1f1e9654a8d3890310a46ac98af26a..53a48c46970204d108f2eeba992bd6d429b296f2
@@@ -132,11 -201,13 +132,16 @@@ void Channel::SetDefaultModes(
                ModeHandler* mode = ServerInstance->Modes->FindMode(*n, MODETYPE_CHANNEL);
                if (mode)
                {
 +                      if (mode->IsPrefixMode())
 +                              continue;
 +
                        if (mode->GetNumParams(true))
+                       {
                                list.GetToken(parameter);
+                               // If the parameter begins with a ':' then it's invalid
+                               if (parameter.c_str()[0] == ':')
+                                       continue;
+                       }
                        else
                                parameter.clear();
  
Simple merge
index af8a15f43defe4618c4948b6b76d455da4f693da,b5d2fdb16f8d3e16aa3eec6e37d903593976a8ef..54c32d8464f60811c121b52f0dd54e4f26314833
@@@ -41,8 -48,70 +41,16 @@@ ServerConfig::ServerConfig(
        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;
index 4481e2d53286fbd657624d0fb7c156a23c75ec20,0000000000000000000000000000000000000000..2d396858f536dbc20280d8bd9d0d4e51dcb13733
mode 100644,000000..100644
--- /dev/null
@@@ -1,66 -1,0 +1,66 @@@
-       ConfigTag* tag = NULL;
 +/*
 + * InspIRCd -- Internet Relay Chat Daemon
 + *
 + *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
 + *   Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
 + *
 + * This file is part of InspIRCd.  InspIRCd is free software: you can
 + * redistribute it and/or modify it under the terms of the GNU General Public
 + * License as published by the Free Software Foundation, version 2.
 + *
 + * This program is distributed in the hope that it will be useful, but WITHOUT
 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 + * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 + * details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 + */
 +
 +
 +#include "inspircd.h"
 +#include "core_info.h"
 +
 +CommandMotd::CommandMotd(Module* parent)
 +      : Command(parent, "MOTD", 0, 1)
 +{
 +      syntax = "[<servername>]";
 +}
 +
 +/** Handle /MOTD
 + */
 +CmdResult CommandMotd::Handle (const std::vector<std::string>& parameters, User *user)
 +{
 +      if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName)
 +              return CMD_SUCCESS;
 +
++      ConfigTag* tag = ServerInstance->Config->EmptyTag;
 +      LocalUser* localuser = IS_LOCAL(user);
 +      if (localuser)
 +              tag = localuser->GetClass()->config;
 +      std::string motd_name = tag->getString("motd", "motd");
 +      ConfigFileCache::iterator motd = ServerInstance->Config->Files.find(motd_name);
 +      if (motd == ServerInstance->Config->Files.end())
 +      {
 +              user->SendText(":%s %03d %s :Message of the day file is missing.",
 +                      ServerInstance->Config->ServerName.c_str(), ERR_NOMOTD, user->nick.c_str());
 +              return CMD_SUCCESS;
 +      }
 +
 +      user->SendText(":%s %03d %s :%s message of the day", ServerInstance->Config->ServerName.c_str(),
 +              RPL_MOTDSTART, user->nick.c_str(), ServerInstance->Config->ServerName.c_str());
 +
 +      for (file_cache::iterator i = motd->second.begin(); i != motd->second.end(); i++)
 +              user->SendText(":%s %03d %s :- %s", ServerInstance->Config->ServerName.c_str(), RPL_MOTD, user->nick.c_str(), i->c_str());
 +
 +      user->SendText(":%s %03d %s :End of message of the day.", ServerInstance->Config->ServerName.c_str(), RPL_ENDOFMOTD, user->nick.c_str());
 +
 +      return CMD_SUCCESS;
 +}
 +
 +RouteDescriptor CommandMotd::GetRouting(User* user, const std::vector<std::string>& parameters)
 +{
 +      if (parameters.size() > 0)
 +              return ROUTE_UNICAST(parameters[0]);
 +      return ROUTE_LOCALONLY;
 +}
index 8fce7d33948fc0d927575a3b91c51f6f13d5746a,0000000000000000000000000000000000000000..966c4a790b23a17cb949dd191e37b534ef3361e6
mode 100644,000000..100644
--- /dev/null
@@@ -1,244 -1,0 +1,244 @@@
-                       idle = abs((long)((localuser->idle_lastmsg)-ServerInstance->Time()));
 +/*
 + * InspIRCd -- Internet Relay Chat Daemon
 + *
 + *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
 + *   Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
 + *   Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
 + *
 + * This file is part of InspIRCd.  InspIRCd is free software: you can
 + * redistribute it and/or modify it under the terms of the GNU General Public
 + * License as published by the Free Software Foundation, version 2.
 + *
 + * This program is distributed in the hope that it will be useful, but WITHOUT
 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 + * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 + * details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 + */
 +
 +
 +#include "inspircd.h"
 +
 +/** Handle /WHOIS.
 + */
 +class CommandWhois : public SplitCommand
 +{
 +      ChanModeReference secretmode;
 +      ChanModeReference privatemode;
 +      UserModeReference snomaskmode;
 +
 +      void SplitChanList(User* source, User* dest, const std::string& cl);
 +      void DoWhois(User* user, User* dest, unsigned long signon, unsigned long idle);
 +      std::string ChannelList(User* source, User* dest, bool spy);
 +
 + public:
 +      /** Constructor for whois.
 +       */
 +      CommandWhois(Module* parent)
 +              : SplitCommand(parent, "WHOIS", 1)
 +              , secretmode(parent, "secret")
 +              , privatemode(parent, "private")
 +              , snomaskmode(parent, "snomask")
 +      {
 +              Penalty = 2;
 +              syntax = "<nick>{,<nick>}";
 +      }
 +
 +      /** Handle command.
 +       * @param parameters The parameters to the command
 +       * @param user The user issuing the command
 +       * @return A value from CmdResult to indicate command success or failure.
 +       */
 +      CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user);
 +      CmdResult HandleRemote(const std::vector<std::string>& parameters, RemoteUser* target);
 +};
 +
 +std::string CommandWhois::ChannelList(User* source, User* dest, bool spy)
 +{
 +      std::string list;
 +
 +      for (User::ChanList::iterator i = dest->chans.begin(); i != dest->chans.end(); i++)
 +      {
 +              Membership* memb = *i;
 +              Channel* c = memb->chan;
 +              /* If the target is the sender, neither +p nor +s is set, or
 +               * the channel contains the user, it is not a spy channel
 +               */
 +              if (spy != (source == dest || !(c->IsModeSet(privatemode) || c->IsModeSet(secretmode)) || c->HasUser(source)))
 +              {
 +                      char prefix = memb->GetPrefixChar();
 +                      if (prefix)
 +                              list.push_back(prefix);
 +                      list.append(c->name).push_back(' ');
 +              }
 +      }
 +
 +      return list;
 +}
 +
 +void CommandWhois::SplitChanList(User* source, User* dest, const std::string& cl)
 +{
 +      std::string line;
 +      std::ostringstream prefix;
 +      std::string::size_type start, pos;
 +
 +      prefix << dest->nick << " :";
 +      line = prefix.str();
 +      int namelen = ServerInstance->Config->ServerName.length() + 6;
 +
 +      for (start = 0; (pos = cl.find(' ', start)) != std::string::npos; start = pos+1)
 +      {
 +              if (line.length() + namelen + pos - start > 510)
 +              {
 +                      ServerInstance->SendWhoisLine(source, dest, 319, line);
 +                      line = prefix.str();
 +              }
 +
 +              line.append(cl.substr(start, pos - start + 1));
 +      }
 +
 +      if (line.length() != prefix.str().length())
 +      {
 +              ServerInstance->SendWhoisLine(source, dest, 319, line);
 +      }
 +}
 +
 +void CommandWhois::DoWhois(User* user, User* dest, unsigned long signon, unsigned long idle)
 +{
 +      ServerInstance->SendWhoisLine(user, dest, 311, "%s %s %s * :%s", dest->nick.c_str(), dest->ident.c_str(), dest->dhost.c_str(), dest->fullname.c_str());
 +      if (user == dest || user->HasPrivPermission("users/auspex"))
 +      {
 +              ServerInstance->SendWhoisLine(user, dest, 378, "%s :is connecting from %s@%s %s", dest->nick.c_str(), dest->ident.c_str(), dest->host.c_str(), dest->GetIPString().c_str());
 +      }
 +
 +      std::string cl = ChannelList(user, dest, false);
 +      const ServerConfig::OperSpyWhoisState state = user->HasPrivPermission("users/auspex") ? ServerInstance->Config->OperSpyWhois : ServerConfig::SPYWHOIS_NONE;
 +
 +      if (state == ServerConfig::SPYWHOIS_SINGLEMSG)
 +              cl.append(ChannelList(user, dest, true));
 +
 +      SplitChanList(user, dest, cl);
 +
 +      if (state == ServerConfig::SPYWHOIS_SPLITMSG)
 +      {
 +              std::string scl = ChannelList(user, dest, true);
 +              if (scl.length())
 +              {
 +                      ServerInstance->SendWhoisLine(user, dest, 336, "%s :is on private/secret channels:", dest->nick.c_str());
 +                      SplitChanList(user, dest, scl);
 +              }
 +      }
 +      if (user != dest && !ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex"))
 +      {
 +              ServerInstance->SendWhoisLine(user, dest, 312, "%s %s :%s", dest->nick.c_str(), ServerInstance->Config->HideWhoisServer.c_str(), ServerInstance->Config->Network.c_str());
 +      }
 +      else
 +      {
 +              ServerInstance->SendWhoisLine(user, dest, 312, "%s %s :%s", dest->nick.c_str(), dest->server->GetName().c_str(), dest->server->GetDesc().c_str());
 +      }
 +
 +      if (dest->IsAway())
 +      {
 +              ServerInstance->SendWhoisLine(user, dest, 301, "%s :%s", dest->nick.c_str(), dest->awaymsg.c_str());
 +      }
 +
 +      if (dest->IsOper())
 +      {
 +              if (ServerInstance->Config->GenericOper)
 +                      ServerInstance->SendWhoisLine(user, dest, 313, "%s :is an IRC operator", dest->nick.c_str());
 +              else
 +                      ServerInstance->SendWhoisLine(user, dest, 313, "%s :is %s %s on %s", dest->nick.c_str(), (strchr("AEIOUaeiou",dest->oper->name[0]) ? "an" : "a"),dest->oper->name.c_str(), ServerInstance->Config->Network.c_str());
 +      }
 +
 +      if (user == dest || user->HasPrivPermission("users/auspex"))
 +      {
 +              if (dest->IsModeSet(snomaskmode))
 +              {
 +                      ServerInstance->SendWhoisLine(user, dest, 379, "%s :is using modes +%s %s", dest->nick.c_str(), dest->FormatModes(), snomaskmode->GetUserParameter(dest).c_str());
 +              }
 +              else
 +              {
 +                      ServerInstance->SendWhoisLine(user, dest, 379, "%s :is using modes +%s", dest->nick.c_str(), dest->FormatModes());
 +              }
 +      }
 +
 +      FOREACH_MOD(OnWhois, (user,dest));
 +
 +      /*
 +       * We only send these if we've been provided them. That is, if hidewhois is turned off, and user is local, or
 +       * if remote whois is queried, too. This is to keep the user hidden, and also since you can't reliably tell remote time. -- w00t
 +       */
 +      if ((idle) || (signon))
 +      {
 +              ServerInstance->SendWhoisLine(user, dest, 317, "%s %lu %lu :seconds idle, signon time", dest->nick.c_str(), idle, signon);
 +      }
 +
 +      ServerInstance->SendWhoisLine(user, dest, 318, "%s :End of /WHOIS list.", dest->nick.c_str());
 +}
 +
 +CmdResult CommandWhois::HandleRemote(const std::vector<std::string>& parameters, RemoteUser* target)
 +{
 +      if (parameters.size() < 2)
 +              return CMD_FAILURE;
 +
 +      User* user = ServerInstance->FindUUID(parameters[0]);
 +      if (!user)
 +              return CMD_FAILURE;
 +
 +      unsigned long idle = ConvToInt(parameters.back());
 +      DoWhois(user, target, target->signon, idle);
 +
 +      return CMD_SUCCESS;
 +}
 +
 +CmdResult CommandWhois::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
 +{
 +      User *dest;
 +      int userindex = 0;
 +      unsigned long idle = 0, signon = 0;
 +
 +      if (CommandParser::LoopCall(user, this, parameters, 0))
 +              return CMD_SUCCESS;
 +
 +      /*
 +       * If 2 paramters are specified (/whois nick nick), ignore the first one like spanningtree
 +       * does, and use the second one, otherwise, use the only paramter. -- djGrrr
 +       */
 +      if (parameters.size() > 1)
 +              userindex = 1;
 +
 +      dest = ServerInstance->FindNickOnly(parameters[userindex]);
 +
 +      if ((dest) && (dest->registered == REG_ALL))
 +      {
 +              /*
 +               * Okay. Umpteenth attempt at doing this, so let's re-comment...
 +               * For local users (/w localuser), we show idletime if hidewhois is disabled
 +               * For local users (/w localuser localuser), we always show idletime, hence parameters.size() > 1 check.
 +               * For remote users (/w remoteuser), we do NOT show idletime
 +               * For remote users (/w remoteuser remoteuser), spanningtree will handle calling do_whois, so we can ignore this case.
 +               * Thanks to djGrrr for not being impatient while I have a crap day coding. :p -- w00t
 +               */
 +              LocalUser* localuser = IS_LOCAL(dest);
 +              if (localuser && (ServerInstance->Config->HideWhoisServer.empty() || parameters.size() > 1))
 +              {
++                      idle = labs((long)((localuser->idle_lastmsg)-ServerInstance->Time()));
 +                      signon = dest->signon;
 +              }
 +
 +              DoWhois(user,dest,signon,idle);
 +      }
 +      else
 +      {
 +              /* no such nick/channel */
 +              user->WriteNumeric(ERR_NOSUCHNICK, "%s :No such nick/channel", !parameters[userindex].empty() ? parameters[userindex].c_str() : "*");
 +              user->WriteNumeric(RPL_ENDOFWHOIS, "%s :End of /WHOIS list.", !parameters[userindex].empty() ? parameters[userindex].c_str() : "*");
 +              return CMD_FAILURE;
 +      }
 +
 +      return CMD_SUCCESS;
 +}
 +
 +COMMAND_INIT(CommandWhois)
index 9b2a0a90f2ec58d2aa56dcfea0d5496821ed5c45,0000000000000000000000000000000000000000..19f2d6061dd00220fd8809a075da364661d86470
mode 100644,000000..100644
--- /dev/null
@@@ -1,222 -1,0 +1,223 @@@
-       if (chanlimits.empty())
-               chanlimits.push_back(ListLimit("*", 64));
 +/*
 + * InspIRCd -- Internet Relay Chat Daemon
 + *
 + *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
 + *
 + * This file is part of InspIRCd.  InspIRCd is free software: you can
 + * redistribute it and/or modify it under the terms of the GNU General Public
 + * License as published by the Free Software Foundation, version 2.
 + *
 + * This program is distributed in the hope that it will be useful, but WITHOUT
 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 + * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 + * details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 + */
 +
 +#include "inspircd.h"
 +#include "listmode.h"
 +
 +ListModeBase::ListModeBase(Module* Creator, const std::string& Name, char modechar, const std::string &eolstr, unsigned int lnum, unsigned int eolnum, bool autotidy, const std::string &ctag)
 +      : ModeHandler(Creator, Name, modechar, PARAM_ALWAYS, MODETYPE_CHANNEL, MC_LIST),
 +      listnumeric(lnum), endoflistnumeric(eolnum), endofliststring(eolstr), tidy(autotidy),
 +      configtag(ctag), extItem("listbase_mode_" + name + "_list", Creator)
 +{
 +      list = true;
 +}
 +
 +void ListModeBase::DisplayList(User* user, Channel* channel)
 +{
 +      ChanData* cd = extItem.get(channel);
 +      if (cd)
 +      {
 +              for (ModeList::reverse_iterator it = cd->list.rbegin(); it != cd->list.rend(); ++it)
 +              {
 +                      user->WriteNumeric(listnumeric, "%s %s %s %lu", channel->name.c_str(), it->mask.c_str(), (!it->setter.empty() ? it->setter.c_str() : ServerInstance->Config->ServerName.c_str()), (unsigned long) it->time);
 +              }
 +      }
 +      user->WriteNumeric(endoflistnumeric, "%s :%s", channel->name.c_str(), endofliststring.c_str());
 +}
 +
 +void ListModeBase::DisplayEmptyList(User* user, Channel* channel)
 +{
 +      user->WriteNumeric(endoflistnumeric, "%s :%s", channel->name.c_str(), endofliststring.c_str());
 +}
 +
 +void ListModeBase::RemoveMode(Channel* channel, Modes::ChangeList& changelist)
 +{
 +      ChanData* cd = extItem.get(channel);
 +      if (cd)
 +      {
 +              for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); it++)
 +              {
 +                      changelist.push_remove(this, it->mask);
 +              }
 +      }
 +}
 +
 +void ListModeBase::DoRehash()
 +{
 +      ConfigTagList tags = ServerInstance->Config->ConfTags(configtag);
 +
 +      limitlist oldlimits = chanlimits;
 +      chanlimits.clear();
 +
 +      for (ConfigIter i = tags.first; i != tags.second; i++)
 +      {
 +              // For each <banlist> tag
 +              ConfigTag* c = i->second;
 +              ListLimit limit(c->getString("chan"), c->getInt("limit"));
 +
 +              if (limit.mask.size() && limit.limit > 0)
 +                      chanlimits.push_back(limit);
 +      }
 +
++      // Add the default entry. This is inserted last so if the user specifies a
++      // wildcard record in the config it will take precedence over this entry.
++      chanlimits.push_back(ListLimit("*", 64));
 +
 +      // Most of the time our settings are unchanged, so we can avoid iterating the chanlist
 +      if (oldlimits == chanlimits)
 +              return;
 +
 +      const chan_hash& chans = ServerInstance->GetChans();
 +      for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i)
 +      {
 +              ChanData* cd = extItem.get(i->second);
 +              if (cd)
 +                      cd->maxitems = -1;
 +      }
 +}
 +
 +unsigned int ListModeBase::FindLimit(const std::string& channame)
 +{
 +      for (limitlist::iterator it = chanlimits.begin(); it != chanlimits.end(); ++it)
 +      {
 +              if (InspIRCd::Match(channame, it->mask))
 +              {
 +                      // We have a pattern matching the channel
 +                      return it->limit;
 +              }
 +      }
 +      return 64;
 +}
 +
 +unsigned int ListModeBase::GetLimitInternal(const std::string& channame, ChanData* cd)
 +{
 +      if (cd->maxitems < 0)
 +              cd->maxitems = FindLimit(channame);
 +      return cd->maxitems;
 +}
 +
 +unsigned int ListModeBase::GetLimit(Channel* channel)
 +{
 +      ChanData* cd = extItem.get(channel);
 +      if (!cd) // just find the limit
 +              return FindLimit(channel->name);
 +
 +      return GetLimitInternal(channel->name, cd);
 +}
 +
 +ModeAction ListModeBase::OnModeChange(User* source, User*, Channel* channel, std::string &parameter, bool adding)
 +{
 +      // Try and grab the list
 +      ChanData* cd = extItem.get(channel);
 +
 +      if (adding)
 +      {
 +              if (tidy)
 +                      ModeParser::CleanMask(parameter);
 +
 +              if (parameter.length() > 250)
 +                      return MODEACTION_DENY;
 +
 +              // If there was no list
 +              if (!cd)
 +              {
 +                      // Make one
 +                      cd = new ChanData;
 +                      extItem.set(channel, cd);
 +              }
 +
 +              // Check if the item already exists in the list
 +              for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); it++)
 +              {
 +                      if (parameter == it->mask)
 +                      {
 +                              /* Give a subclass a chance to error about this */
 +                              TellAlreadyOnList(source, channel, parameter);
 +
 +                              // it does, deny the change
 +                              return MODEACTION_DENY;
 +                      }
 +              }
 +
 +              if ((IS_LOCAL(source)) && (cd->list.size() >= GetLimitInternal(channel->name, cd)))
 +              {
 +                      /* List is full, give subclass a chance to send a custom message */
 +                      TellListTooLong(source, channel, parameter);
 +                      return MODEACTION_DENY;
 +              }
 +
 +              /* Ok, it *could* be allowed, now give someone subclassing us
 +               * a chance to validate the parameter.
 +               * The param is passed by reference, so they can both modify it
 +               * and tell us if we allow it or not.
 +               *
 +               * eg, the subclass could:
 +               * 1) allow
 +               * 2) 'fix' parameter and then allow
 +               * 3) deny
 +               */
 +              if (ValidateParam(source, channel, parameter))
 +              {
 +                      // And now add the mask onto the list...
 +                      cd->list.push_back(ListItem(parameter, source->nick, ServerInstance->Time()));
 +                      return MODEACTION_ALLOW;
 +              }
 +              else
 +              {
 +                      /* If they deny it they have the job of giving an error message */
 +                      return MODEACTION_DENY;
 +              }
 +      }
 +      else
 +      {
 +              // We're taking the mode off
 +              if (cd)
 +              {
 +                      for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); ++it)
 +                      {
 +                              if (parameter == it->mask)
 +                              {
 +                                      cd->list.erase(it);
 +                                      return MODEACTION_ALLOW;
 +                              }
 +                      }
 +              }
 +
 +              /* Tried to remove something that wasn't set */
 +              TellNotSet(source, channel, parameter);
 +              return MODEACTION_DENY;
 +      }
 +}
 +
 +bool ListModeBase::ValidateParam(User*, Channel*, std::string&)
 +{
 +      return true;
 +}
 +
 +void ListModeBase::TellListTooLong(User* source, Channel* channel, std::string& parameter)
 +{
 +      source->WriteNumeric(ERR_BANLISTFULL, "%s %s :Channel ban list is full", channel->name.c_str(), parameter.c_str());
 +}
 +
 +void ListModeBase::TellAlreadyOnList(User*, Channel*, std::string&)
 +{
 +}
 +
 +void ListModeBase::TellNotSet(User*, Channel*, std::string&)
 +{
 +}
Simple merge
index e6efb97716c821d6ec879d73faaaf870ce04634b,3b67a61802b51106ede6db1e20421340381bfa72..74b66d2de6bac523a23b9944b40a026d05a4b9d4
@@@ -91,738 -143,540 +91,742 @@@ class RandGen : public HandlerBase2<voi
        }
  };
  
 -/** Represents an SSL user's extra data
 - */
 -class issl_session
 +namespace GnuTLS
  {
 -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) {}
 -};
 +      class Init
 +      {
 +       public:
 +              Init() { gnutls_global_init(); }
 +              ~Init() { gnutls_global_deinit(); }
 +      };
  
 -static SSLConfig* GetSessionConfig(gnutls_session_t sess)
 -{
 -      issl_session* session = reinterpret_cast<issl_session*>(gnutls_transport_get_ptr(sess));
 -      return session->config;
 -}
 +      class Exception : public ModuleException
 +      {
 +       public:
 +              Exception(const std::string& reason)
 +                      : ModuleException(reason) { }
 +      };
  
 -class CommandStartTLS : public SplitCommand
 -{
 - public:
 -      bool enabled;
 -      CommandStartTLS (Module* mod) : SplitCommand(mod, "STARTTLS")
 +      void ThrowOnError(int errcode, const char* msg)
        {
 -              enabled = true;
 -              works_before_reg = true;
 +              if (errcode < 0)
 +              {
 +                      std::string reason = msg;
 +                      reason.append(" :").append(gnutls_strerror(errcode));
 +                      throw Exception(reason);
 +              }
        }
  
 -      CmdResult HandleLocal(const std::vector<std::string> &parameters, LocalUser *user)
 +      /** Used to create a gnutls_datum_t* from a std::string
 +       */
 +      class Datum
 +      {
 +              gnutls_datum_t datum;
 +
 +       public:
 +              Datum(const std::string& dat)
 +              {
 +                      datum.data = (unsigned char*)dat.data();
 +                      datum.size = static_cast<unsigned int>(dat.length());
 +              }
 +
 +              const gnutls_datum_t* get() const { return &datum; }
 +      };
 +
 +      class Hash
        {
 -              if (!enabled)
 +              gnutls_digest_algorithm_t hash;
 +
 +       public:
 +              // Nothing to deallocate, constructor may throw freely
 +              Hash(const std::string& hashname)
                {
 -                      user->WriteNumeric(691, "%s :STARTTLS is not enabled", user->nick.c_str());
 -                      return CMD_FAILURE;
 +                      // 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
 +                      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
                }
  
 -              if (user->registered == REG_ALL)
 +              gnutls_digest_algorithm_t get() const { return hash; }
 +      };
 +
 +      class DHParams
 +      {
 +              gnutls_dh_params_t dh_params;
 +
 +              DHParams()
                {
 -                      user->WriteNumeric(691, "%s :STARTTLS is not permitted after client registration is complete", user->nick.c_str());
 +                      ThrowOnError(gnutls_dh_params_init(&dh_params), "gnutls_dh_params_init() failed");
                }
 -              else
 +
 +       public:
 +              /** Import */
 +              static std::auto_ptr<DHParams> Import(const std::string& dhstr)
 +              {
 +                      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;
 +              }
 +
 +              /** Generate */
 +              static std::auto_ptr<DHParams> Generate(unsigned int bits)
 +              {
 +                      std::auto_ptr<DHParams> dh(new DHParams);
 +                      ThrowOnError(gnutls_dh_params_generate2(dh->dh_params, bits), "Unable to generate DH params");
 +                      return dh;
 +              }
 +
 +              ~DHParams()
                {
 -                      if (!user->eh.GetIOHook())
 +                      gnutls_dh_params_deinit(dh_params);
 +              }
 +
 +              const gnutls_dh_params_t& get() const { return dh_params; }
 +      };
 +
 +      class X509Key
 +      {
 +              /** Ensure that the key is deinited in case the constructor of X509Key throws
 +               */
 +              class RAIIKey
 +              {
 +               public:
 +                      gnutls_x509_privkey_t key;
 +
 +                      RAIIKey()
                        {
 -                              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);
 +                              ThrowOnError(gnutls_x509_privkey_init(&key), "gnutls_x509_privkey_init() failed");
                        }
 -                      else
 -                              user->WriteNumeric(691, "%s :STARTTLS failure", user->nick.c_str());
 +
 +                      ~RAIIKey()
 +                      {
 +                              gnutls_x509_privkey_deinit(key);
 +                      }
 +              } key;
 +
 +       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");
                }
  
 -              return CMD_FAILURE;
 -      }
 -};
 +              gnutls_x509_privkey_t& get() { return key.key; }
 +      };
  
 -class ModuleSSLGnuTLS : public Module
 -{
 -      issl_session* sessions;
 +      class X509CertList
 +      {
 +              std::vector<gnutls_x509_crt_t> certs;
 +
 +       public:
 +              /** Import */
 +              X509CertList(const std::string& certstr)
 +              {
 +                      unsigned int certcount = 3;
 +                      certs.resize(certcount);
 +                      Datum datum(certstr);
  
 -      gnutls_digest_algorithm_t hash;
 +                      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)
 +                      {
 +                              // 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);
 +                      }
  
 -      std::string sslports;
 -      int dh_bits;
 +                      ThrowOnError(ret, "Unable to load certificates");
  
 -      RandGen randhandler;
 -      CommandStartTLS starttls;
 +                      // Resize the vector to the actual number of certs because we rely on its size being correct
 +                      // when deallocating the certs
 +                      certs.resize(certcount);
 +              }
  
 -      GenericCap capHandler;
 -      ServiceProvider iohook;
 +              ~X509CertList()
 +              {
 +                      for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i)
 +                              gnutls_x509_crt_deinit(*i);
 +              }
  
 -      inline static const char* UnknownIfNULL(const char* str)
 -      {
 -              return str ? str : "UNKNOWN";
 -      }
 +              gnutls_x509_crt_t* raw() { return &certs[0]; }
 +              unsigned int size() const { return certs.size(); }
 +      };
  
 -      static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size)
 +      class X509CRL : public refcountbase
        {
 -              issl_session* session = reinterpret_cast<issl_session*>(session_wrap);
 -              if (session->socket->GetEventMask() & FD_READ_WILL_BLOCK)
 +              class RAIICRL
                {
 -#ifdef _WIN32
 -                      gnutls_transport_set_errno(session->sess, EAGAIN);
 -#else
 -                      errno = EAGAIN;
 -#endif
 -                      return -1;
 -              }
 +               public:
 +                      gnutls_x509_crl_t crl;
  
 -              int rv = ServerInstance->SE->Recv(session->socket, reinterpret_cast<char *>(buffer), size, 0);
 +                      RAIICRL()
 +                      {
 +                              ThrowOnError(gnutls_x509_crl_init(&crl), "gnutls_x509_crl_init() failed");
 +                      }
  
 -#ifdef _WIN32
 -              if (rv < 0)
 +                      ~RAIICRL()
 +                      {
 +                              gnutls_x509_crl_deinit(crl);
 +                      }
 +              } crl;
 +
 +       public:
 +              /** Import */
 +              X509CRL(const std::string& crlstr)
                {
 -                      /* 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);
 +                      int ret = gnutls_x509_crl_import(get(), Datum(crlstr).get(), GNUTLS_X509_FMT_PEM);
 +                      ThrowOnError(ret, "Unable to load certificate revocation list");
                }
 -#endif
  
 -              if (rv < (int)size)
 -                      ServerInstance->SE->ChangeEventMask(session->socket, FD_READ_WILL_BLOCK);
 -              return rv;
 -      }
 +              gnutls_x509_crl_t& get() { return crl.crl; }
 +      };
  
 -      static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size)
 +#ifdef GNUTLS_NEW_PRIO_API
 +      class Priority
        {
 -              issl_session* session = reinterpret_cast<issl_session*>(session_wrap);
 -              if (session->socket->GetEventMask() & FD_WRITE_WILL_BLOCK)
 +              gnutls_priority_t priority;
 +
 +       public:
 +              Priority(const std::string& priorities)
                {
 -#ifdef _WIN32
 -                      gnutls_transport_set_errno(session->sess, EAGAIN);
 -#else
 -                      errno = EAGAIN;
 -#endif
 -                      return -1;
 +                      // 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;
 +
 +                      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)));
 +                      }
                }
  
 -              int rv = ServerInstance->SE->Send(session->socket, reinterpret_cast<const char *>(buffer), size, 0);
 +              ~Priority()
 +              {
 +                      gnutls_priority_deinit(priority);
 +              }
  
 -#ifdef _WIN32
 -              if (rv < 0)
 +              void SetupSession(gnutls_session_t sess)
                {
 -                      /* 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_priority_set(sess, priority);
 +              }
 +      };
 +#else
 +      /** Dummy class, used when gnutls_priority_set() is not available
 +       */
 +      class Priority
 +      {
 +       public:
 +              Priority(const std::string& priorities)
 +              {
 +                      if (priorities != "NORMAL")
 +                              throw Exception("You've set a non-default priority string, but GnuTLS lacks support for it");
 +              }
 +
 +              static void SetupSession(gnutls_session_t sess)
 +              {
 +                      // Always set the default priorities
 +                      gnutls_set_default_priority(sess);
                }
 +      };
  #endif
  
 -              if (rv < (int)size)
 -                      ServerInstance->SE->ChangeEventMask(session->socket, FD_WRITE_WILL_BLOCK);
 -              return rv;
 -      }
 +      class CertCredentials
 +      {
 +              /** DH parameters associated with these credentials
 +               */
 +              std::auto_ptr<DHParams> dh;
  
 - public:
 +       protected:
 +              gnutls_certificate_credentials_t cred;
  
 -      ModuleSSLGnuTLS()
 -              : starttls(this), capHandler(this, "tls"), iohook(this, "ssl/gnutls", SERVICE_IOHOOK)
 -      {
 -#ifndef GNUTLS_HAS_RND
 -              gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
 -#endif
 +       public:
 +              CertCredentials()
 +              {
 +                      ThrowOnError(gnutls_certificate_allocate_credentials(&cred), "Cannot allocate certificate credentials");
 +              }
  
 -              sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
 +              ~CertCredentials()
 +              {
 +                      gnutls_certificate_free_credentials(cred);
 +              }
  
 -              gnutls_global_init(); // This must be called once in the program
 -      }
 +              /** Associates these credentials with the session
 +               */
 +              void SetupSession(gnutls_session_t sess)
 +              {
 +                      gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred);
 +              }
  
 -      void init()
 -      {
 -              currconf = new SSLConfig;
 -              InitSSLConfig(currconf);
 +              /** 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());
 +              }
 +      };
  
 -              ServerInstance->GenRandom = &randhandler;
 +      class X509Credentials : public CertCredentials
 +      {
 +              /** Private key
 +               */
 +              X509Key key;
  
 -              Implementation eventlist[] = { I_On005Numeric, I_OnRehash, I_OnModuleRehash, I_OnUserConnect,
 -                      I_OnEvent, I_OnHookIO };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
 +              /** Certificate list, presented to the peer
 +               */
 +              X509CertList certs;
  
 -              ServerInstance->Modules->AddService(iohook);
 -              ServerInstance->Modules->AddService(starttls);
 -      }
 +              /** Trusted CA, may be NULL
 +               */
 +              std::auto_ptr<X509CertList> trustedca;
  
 -      void OnRehash(User* user)
 -      {
 -              sslports.clear();
 +              /** Certificate revocation list, may be NULL
 +               */
 +              std::auto_ptr<X509CRL> crl;
  
 -              ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls");
 -              starttls.enabled = Conf->getBool("starttls", true);
 +              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);
  
 -              if (Conf->getBool("showports", true))
 +       public:
 +              X509Credentials(const std::string& certstr, const std::string& keystr)
 +                      : key(keystr)
 +                      , certs(certstr)
                {
 -                      sslports = Conf->getString("advertisedports");
 -                      if (!sslports.empty())
 -                              return;
 +                      // 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");
  
 -                      for (size_t i = 0; i < ServerInstance->ports.size(); i++)
 -                      {
 -                              ListenSocket* port = ServerInstance->ports[i];
 -                              if (port->bind_tag->getString("ssl") != "gnutls")
 -                                      continue;
 +#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
 +              }
  
 -                              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());
 +              /** 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 (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1")
 +                              if (CRL.get())
                                {
 -                                      /*
 -                                       * 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;
 +                                      ret = gnutls_certificate_set_x509_crl(cred, &CRL->get(), 1);
 +                                      ThrowOnError(ret, "gnutls_certificate_set_x509_crl() failed");
                                }
 +
 +                              trustedca = certlist;
 +                              crl = CRL;
                        }
                }
 -      }
 +      };
  
 -      void OnModuleRehash(User* user, const std::string &param)
 +      class Profile : public refcountbase
        {
 -              if(param != "ssl")
 -                      return;
 +              /** Name of this profile
 +               */
 +              const std::string name;
  
 -              reference<SSLConfig> newconf = new SSLConfig;
 -              try
 +              /** X509 certificate(s) and key
 +               */
 +              X509Credentials x509cred;
 +
 +              /** The minimum length in bits for the DH prime to be accepted as a client
 +               */
 +              unsigned int min_dh_bits;
 +
 +              /** Hashing algorithm to use when generating certificate fingerprints
 +               */
 +              Hash hash;
 +
 +              /** Priorities for ciphers, compression methods, etc.
 +               */
 +              Priority priority;
 +
 +              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)
 +                      : name(profilename)
 +                      , x509cred(certstr, keystr)
 +                      , min_dh_bits(mindh)
 +                      , hash(hashstr)
 +                      , priority(priostr)
                {
 -                      InitSSLConfig(newconf);
 +                      x509cred.SetDH(DH);
 +                      x509cred.SetCA(CA, CRL);
                }
 -              catch (ModuleException& ex)
 +
 +              static std::string ReadFile(const std::string& filename)
                {
 -                      ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls: Not applying new config. %s", ex.GetReason());
 -                      return;
 +                      FileReader reader(filename);
 +                      std::string ret = reader.GetString();
 +                      if (ret.empty())
 +                              throw Exception("Cannot read file " + filename);
 +                      return ret;
                }
  
 -              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;
 -      }
 +       public:
 +              static reference<Profile> Create(const std::string& profilename, ConfigTag* tag)
 +              {
 +                      std::string certstr = ReadFile(tag->getString("certfile", "cert.pem"));
 +                      std::string keystr = ReadFile(tag->getString("keyfile", "key.pem"));
  
 -      void InitSSLConfig(SSLConfig* config)
 -      {
 -              ServerInstance->Logs->Log("m_ssl_gnutls", DEBUG, "Initializing new SSLConfig %p", (void*)config);
 +                      std::auto_ptr<DHParams> dh;
 +                      int gendh = tag->getInt("gendh");
 +                      if (gendh)
 +                      {
 +                              gendh = (gendh < 1024 ? 1024 : gendh);
 +                              dh = DHParams::Generate(gendh);
 +                      }
 +                      else
 +                              dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")));
 +
 +                      // Use default priority string if this tag does not specify one
 +                      std::string priostr = tag->getString("priority", "NORMAL");
 +                      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())
 +                      {
 +                              ca.reset(new X509CertList(ReadFile(filename)));
 +
 +                              filename = tag->getString("crlfile");
 +                              if (!filename.empty())
 +                                      crl.reset(new X509CRL(ReadFile(filename)));
 +                      }
  
 -              std::string keyfile;
 -              std::string certfile;
 -              std::string cafile;
 -              std::string crlfile;
 -              OnRehash(NULL);
 +                      return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl);
 +              }
  
 -              ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls");
 +              /** Set up the given session with the settings in this profile
 +               */
 +              void SetupSession(gnutls_session_t sess)
 +              {
 +                      priority.SetupSession(sess);
 +                      x509cred.SetupSession(sess);
 +                      gnutls_dh_set_prime_bits(sess, min_dh_bits);
 +              }
  
 -              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");
 +              const std::string& GetName() const { return name; }
 +              X509Credentials& GetX509Credentials() { return x509cred; }
 +              gnutls_digest_algorithm_t GetHash() const { return hash.get(); }
 +      };
 +}
  
 -              // 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");
 +class GnuTLSIOHook : public SSLIOHook
 +{
 + private:
 +      gnutls_session_t sess;
 +      issl_status status;
 +      reference<GnuTLS::Profile> profile;
  
 -              if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))
 -                      dh_bits = 1024;
 +      void InitSession(StreamSocket* user, bool me_server)
 +      {
 +              gnutls_init(&sess, me_server ? GNUTLS_SERVER : GNUTLS_CLIENT);
  
 -              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);
 +              profile->SetupSession(sess);
 +              gnutls_transport_set_ptr(sess, reinterpret_cast<gnutls_transport_ptr_t>(user));
 +              gnutls_transport_set_push_function(sess, gnutls_push_wrapper);
 +              gnutls_transport_set_pull_function(sess, gnutls_pull_wrapper);
  
 +              if (me_server)
 +                      gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
 +      }
  
 -              int ret;
 +      void CloseSession()
 +      {
 +              if (this->sess)
 +              {
 +                      gnutls_bye(this->sess, GNUTLS_SHUT_WR);
 +                      gnutls_deinit(this->sess);
 +              }
 +              sess = NULL;
 +              certificate = NULL;
 +              status = ISSL_NONE;
 +      }
  
 -              gnutls_certificate_credentials_t& x509_cred = config->x509_cred;
 +      bool Handshake(StreamSocket* user)
 +      {
 +              int ret = gnutls_handshake(this->sess);
  
 -              ret = gnutls_certificate_allocate_credentials(&x509_cred);
                if (ret < 0)
                {
 -                      // 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)));
 -              }
 +                      if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
 +                      {
 +                              // Handshake needs resuming later, read() or write() would have blocked.
  
 -              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 (gnutls_record_get_direction(this->sess) == 0)
 +                              {
 +                                      // gnutls_handshake() wants to read() again.
 +                                      this->status = ISSL_HANDSHAKING_READ;
 +                                      SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 +                              }
 +                              else
 +                              {
 +                                      // gnutls_handshake() wants to write() again.
 +                                      this->status = ISSL_HANDSHAKING_WRITE;
 +                                      SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 +                              }
 +                      }
 +                      else
 +                      {
 +                              user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret)));
 +                              CloseSession();
 +                              this->status = ISSL_CLOSING;
 +                      }
 +
 +                      return false;
 +              }
 +              else
 +              {
 +                      // Change the seesion state
 +                      this->status = ISSL_HANDSHAKEN;
  
 -              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));
 +                      VerifyCertificate();
  
 -              FileReader reader;
 +                      // Finish writing, if any left
 +                      SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
  
 -              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()) };
 +                      return true;
 +              }
 +      }
  
 -              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()) };
 +      void VerifyCertificate()
 +      {
 +              unsigned int certstatus;
 +              const gnutls_datum_t* cert_list;
 +              int ret;
 +              unsigned int cert_list_size;
 +              gnutls_x509_crt_t cert;
 +              char str[512];
 +              unsigned char digest[512];
 +              size_t digest_size = sizeof(digest);
 +              size_t name_size = sizeof(str);
 +              ssl_cert* certinfo = new ssl_cert;
 +              this->certificate = certinfo;
  
 -              std::vector<gnutls_x509_crt_t>& x509_certs = config->x509_certs;
 +              /* 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(this->sess, &certstatus);
  
 -              // 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)
 +              if (ret < 0)
                {
 -                      // 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);
 +                      certinfo->error = std::string(gnutls_strerror(ret));
 +                      return;
                }
  
 -              if (ret <= 0)
 +              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(this->sess) != GNUTLS_CRT_X509)
                {
 -                      // 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"));
 +                      certinfo->error = "No X509 keys sent";
 +                      return;
                }
 -              x509_certs.resize(ret);
  
 -              gnutls_x509_privkey_t& x509_key = config->x509_key;
 -              if (gnutls_x509_privkey_init(&x509_key) < 0)
 +              ret = gnutls_x509_crt_init(&cert);
 +              if (ret < 0)
                {
 -                      // Make sure the destructor does not try to deallocate this, see above
 -                      x509_key = NULL;
 -                      throw ModuleException("Unable to initialize private key");
 +                      certinfo->error = gnutls_strerror(ret);
 +                      return;
                }
  
 -              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)));
 -
 -              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)));
 -
 -              #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;
 -
 -              gnutls_priority_t& priority = config->priority;
 -              if ((ret = gnutls_priority_init(&priority, priocstr, &prioerror)) < 0)
 +              cert_list_size = 0;
 +              cert_list = gnutls_certificate_get_peers(this->sess, &cert_list_size);
 +              if (cert_list == NULL)
                {
 -                      // 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);
 +                      certinfo->error = "No certificate was found";
 +                      goto info_done_dealloc;
                }
  
 -              #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
 -
 -              #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
 +              /* This is not a real world example, since we only check the first
 +               * certificate in the given chain.
 +               */
  
 -              gnutls_dh_params_t& dh_params = config->dh_params;
 -              ret = gnutls_dh_params_init(&dh_params);
 +              ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
                if (ret < 0)
                {
 -                      // 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;
 +                      certinfo->error = gnutls_strerror(ret);
 +                      goto info_done_dealloc;
                }
  
 -              std::string dhfile = Conf->getString("dhfile");
 -              if (!dhfile.empty())
 +              if (gnutls_x509_crt_get_dn(cert, str, &name_size) == 0)
                {
 -                      // 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()) };
 +                      std::string& dn = certinfo->dn;
 +                      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();
 +              }
  
 -                      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);
 -                      }
 +              name_size = sizeof(str);
 +              if (gnutls_x509_crt_get_issuer_dn(cert, str, &name_size) == 0)
 +              {
 +                      std::string& issuer = certinfo->issuer;
 +                      issuer = str;
 +                      if (issuer.find_first_of("\r\n") != std::string::npos)
 +                              issuer.clear();
 +              }
 +
 +              if ((ret = gnutls_x509_crt_get_fingerprint(cert, profile->GetHash(), digest, &digest_size)) < 0)
 +              {
 +                      certinfo->error = gnutls_strerror(ret);
 +              }
 +              else
 +              {
 +                      certinfo->fingerprint = BinToHex(digest, digest_size);
                }
 -              else
 +
 +              /* Beware here we do not check for errors.
 +               */
 +              if ((gnutls_x509_crt_get_expiration_time(cert) < ServerInstance->Time()) || (gnutls_x509_crt_get_activation_time(cert) > ServerInstance->Time()))
                {
 -                      GenerateDHParams(dh_params);
 +                      certinfo->error = "Not activated, or expired certificate";
                }
  
 -              gnutls_certificate_set_dh_params(x509_cred, dh_params);
 +info_done_dealloc:
 +              gnutls_x509_crt_deinit(cert);
        }
  
 -      void GenerateDHParams(gnutls_dh_params_t dh_params)
 +      static const char* UnknownIfNULL(const char* str)
        {
 -              // 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.
 -
 -              int ret;
 -
 -              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));
 +              return str ? str : "UNKNOWN";
        }
  
 -      ~ModuleSSLGnuTLS()
 +      static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size)
        {
 -              currconf = NULL;
 -
 -              gnutls_global_deinit();
 -              delete[] sessions;
 -              ServerInstance->GenRandom = &ServerInstance->HandleGenRandom;
 -      }
 +              StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
 +#ifdef _WIN32
 +              GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetIOHook());
 +#endif
  
 -      void OnCleanup(int target_type, void* item)
 -      {
 -              if(target_type == TYPE_USER)
 +              if (sock->GetEventMask() & FD_READ_WILL_BLOCK)
                {
 -                      LocalUser* user = IS_LOCAL(static_cast<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");
 -                      }
 +#ifdef _WIN32
 +                      gnutls_transport_set_errno(session->sess, EAGAIN);
 +#else
 +                      errno = EAGAIN;
 +#endif
 +                      return -1;
                }
 -      }
 -
 -      Version GetVersion()
 -      {
 -              return Version("Provides SSL support for clients", VF_VENDOR);
 -      }
 -
  
 -      void On005Numeric(std::string &output)
 -      {
 -              if (!sslports.empty())
 -                      output.append(" SSL=" + sslports);
 -              if (starttls.enabled)
 -                      output.append(" STARTTLS");
 -      }
 +              int rv = SocketEngine::Recv(sock, reinterpret_cast<char *>(buffer), size, 0);
  
 -      void OnHookIO(StreamSocket* user, ListenSocket* lsb)
 -      {
 -              if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "gnutls")
 +#ifdef _WIN32
 +              if (rv < 0)
                {
 -                      /* Hook the user with our module */
 -                      user->AddIOHook(this);
 +                      /* 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;
        }
  
 -      void OnRequest(Request& request)
 +      static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size)
        {
 -              if (strcmp("GET_SSL_CERT", request.id) == 0)
 -              {
 -                      SocketCertificateRequest& req = static_cast<SocketCertificateRequest&>(request);
 -                      int fd = req.sock->GetFd();
 -                      issl_session* session = &sessions[fd];
 +              StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
 +#ifdef _WIN32
 +              GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetIOHook());
 +#endif
  
 -                      req.cert = session->cert;
 -              }
 -              else if (!strcmp("GET_RAW_SSL_SESSION", request.id))
 +              if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
                {
 -                      SSLRawSessionRequest& req = static_cast<SSLRawSessionRequest&>(request);
 -                      if ((req.fd >= 0) && (req.fd < ServerInstance->SE->GetMaxFds()))
 -                              req.data = reinterpret_cast<void*>(sessions[req.fd].sess);
 +#ifdef _WIN32
 +                      gnutls_transport_set_errno(session->sess, EAGAIN);
 +#else
 +                      errno = EAGAIN;
 +#endif
 +                      return -1;
                }
 -      }
 -
 -      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.
 -
 -              Handshake(session, user);
 -      }
 -
 -      void OnStreamSocketAccept(StreamSocket* user, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
 -      {
 -              issl_session* session = &sessions[user->GetFd()];
 +              int rv = SocketEngine::Send(sock, reinterpret_cast<const char *>(buffer), size, 0);
  
 -              /* For STARTTLS: Don't try and init a session on a socket that already has a session */
 -              if (session->sess)
 -                      return;
 +#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
  
 -              InitSession(user, true);
 +              if (rv < (int)size)
 +                      SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
 +              return rv;
        }
  
 -      void OnStreamSocketConnect(StreamSocket* user)
 + public:
 +      GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, bool outbound, const reference<GnuTLS::Profile>& sslprofile)
 +              : SSLIOHook(hookprov)
 +              , sess(NULL)
 +              , status(ISSL_NONE)
 +              , profile(sslprofile)
        {
 -              InitSession(user, false);
 +              InitSession(sock, outbound);
 +              sock->AddIOHook(this);
 +              Handshake(sock);
        }
  
 -      void OnStreamSocketClose(StreamSocket* user)
 +      void OnStreamSocketClose(StreamSocket* user) CXX11_OVERRIDE
        {
 -              CloseSession(&sessions[user->GetFd()]);
 +              CloseSession();
        }
  
 -      int OnStreamSocketRead(StreamSocket* user, std::string& recvq)
 +      int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE
        {
 -              issl_session* session = &sessions[user->GetFd()];
 -
 -              if (!session->sess)
 +              if (!this->sess)
                {
 -                      CloseSession(session);
 +                      CloseSession();
                        user->SetError("No SSL session");
                        return -1;
                }
index b09539e50f9d34c2e2e1ac034e9afbe5c68279d3,b21091d3fddc6f2b6c5ca53bdd26ebd3a0b95c70..3dd8a85441850515e3d87fc2140c2d9a74e81168
@@@ -52,363 -66,499 +52,481 @@@ 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.
-                       SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_CIPHER_SERVER_PREFERENCE);
++                      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);
 +                      SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
 +                      SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, OnVerify);
-                       const unsigned char session_id[] = "inspircd";
-                       SSL_CTX_set_session_id_context(ctx, session_id, sizeof(session_id) - 1);
++                      SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
++                      SSL_CTX_set_info_callback(ctx, StaticSSLInfoCallback);
 +              }
  
 -              // Clear everything
 -              SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx));
 +              ~Context()
 +              {
 +                      SSL_CTX_free(ctx);
 +              }
  
 -              // 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);
 -      }
 +              bool SetDH(DHParams& dh)
 +              {
++                      ERR_clear_error();
 +                      return (SSL_CTX_set_tmp_dh(ctx, dh.get()) >= 0);
 +              }
  
 -      void SetupECDH(ConfigTag* tag)
 -      {
 -              std::string curvename = tag->getString("ecdhcurve", "prime256v1");
 -              if (curvename.empty())
 -                      return;
 -
 -              int nid = OBJ_sn2nid(curvename.c_str());
 -              if (nid == 0)
+ #ifdef INSPIRCD_OPENSSL_ENABLE_ECDH
 -                      ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Unknown curve: \"%s\"", curvename.c_str());
 -                      return;
++              void SetECDH(const std::string& curvename)
+               {
 -              EC_KEY* eckey = EC_KEY_new_by_curve_name(nid);
 -              if (!eckey)
++                      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
 +              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:
 +              SSL* CreateSession()
 +              {
 +                      return SSL_new(ctx);
 +              }
 +      };
  
 -      ModuleSSLOpenSSL() : iohook(this, "ssl/openssl", SERVICE_IOHOOK)
 +      class Profile : public refcountbase
        {
 -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
 -              opensslmod = this;
 -#endif
 -              sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
 -
 -              /* Global SSL library initialization*/
 -              SSL_library_init();
 -              SSL_load_error_strings();
 -
 -              /* Build our SSL contexts:
 -               * NOTE: OpenSSL makes us have two contexts, one for servers and one for clients. ICK.
 +              /** Name of this profile
                 */
 -              ctx = SSL_CTX_new( SSLv23_server_method() );
 -              clictx = SSL_CTX_new( SSLv23_client_method() );
 -
 -              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);
 +              const std::string name;
  
 -              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);
 +              /** DH parameters in use
 +               */
 +              DHParams dh;
  
 -              SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
 -              SSL_CTX_set_session_cache_mode(clictx, SSL_SESS_CACHE_OFF);
 +              /** OpenSSL makes us have two contexts, one for servers and one for clients
 +               */
 +              Context ctx;
 +              Context clictx;
  
 -              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
 +              /** Digest to use when generating fingerprints
 +               */
 +              const EVP_MD* digest;
  
 -              ctx_options = SSL_CTX_set_options(ctx, opts);
 -              clictx_options = SSL_CTX_set_options(clictx, opts);
 -      }
 +              /** Last error, set by error_callback()
 +               */
 +              std::string lasterr;
  
 -      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);
 -      }
++              /** True if renegotiations are allowed, false if not
++               */
++              const bool allowrenego;
 -      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();
++              /** 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)
++              {
++                      long setoptions = tag->getInt(ctxname + "setoptions");
++                      long clearoptions = tag->getInt(ctxname + "clearoptions");
++#ifdef SSL_OP_NO_COMPRESSION
++                      if (!tag->getBool("compression", true))
++                              setoptions |= SSL_OP_NO_COMPRESSION;
++#endif
++                      if (!tag->getBool("sslv3", true))
++                              setoptions |= SSL_OP_NO_SSLv3;
++                      if (!tag->getBool("tlsv1", true))
++                              setoptions |= SSL_OP_NO_TLSv1;
 -              ConfigTag* Conf = ServerInstance->Config->ConfValue("openssl");
++                      if (!setoptions && !clearoptions)
++                              return; // Nothing to do
 -#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);
++                      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);
+               }
 -              else
 -              {
 -                      SSL_CTX_set_info_callback(ctx, SSLInfoCallback);
 -                      SSL_CTX_set_info_callback(clictx, SSLInfoCallback);
 -              }
 -#endif
 -              if (Conf->getBool("showports", true))
 +       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", true))
                {
 -                      sslports = Conf->getString("advertisedports");
 -                      if (!sslports.empty())
 -                              return;
 -
 -                      for (size_t i = 0; i < ServerInstance->ports.size(); i++)
 -                      {
 -                              ListenSocket* port = ServerInstance->ports[i];
 -                              if (port->bind_tag->getString("ssl") != "openssl")
 -                                      continue;
 +                      if ((!ctx.SetDH(dh)) || (!clictx.SetDH(dh)))
 +                              throw Exception("Couldn't set DH parameters");
  
 -                              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());
 +                      std::string hash = tag->getString("hash", "md5");
 +                      digest = EVP_get_digestbyname(hash.c_str());
 +                      if (digest == NULL)
 +                              throw Exception("Unknown hash type " + hash);
  
 -                              if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1")
 +                      std::string ciphers = tag->getString("ciphers");
 +                      if (!ciphers.empty())
 +                      {
 +                              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());
                        }
                }
  
 -              /* 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())))
 -              {
 -                      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);
 -              }
 +              const std::string& GetName() const { return name; }
 +              SSL* CreateServerSession() { return ctx.CreateSession(); }
 +              SSL* CreateClientSession() { return clictx.CreateSession(); }
 +              const EVP_MD* GetDigest() { return digest; }
++              bool AllowRenegotiation() const { return allowrenego; }
 +      };
 +}
  
 -              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)))
 -              {
 -                      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);
 -              }
 +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);
  
 -              /* 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)))
 -              {
 -                      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);
 -              }
 +      SelfSigned = (ve == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT);
  
 -#ifdef _WIN32
 -              BIO* dhpfile = BIO_new_file(dhfile.c_str(), "r");
 -#else
 -              FILE* dhpfile = fopen(dhfile.c_str(), "r");
 -#endif
 -              DH* ret;
 +      return 1;
 +}
  
 -              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));
 -              }
 +class OpenSSLIOHook : public SSLIOHook
 +{
 + private:
 +      SSL* sess;
 +      issl_status status;
 +      const bool outbound;
 +      bool data_to_write;
 +      reference<OpenSSL::Profile> profile;
 +
 +      bool Handshake(StreamSocket* user)
 +      {
 +              int ret;
 +
++              ERR_clear_error();
 +              if (outbound)
 +                      ret = SSL_connect(sess);
                else
 +                      ret = SSL_accept(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 ((SSL_CTX_set_tmp_dh(ctx, ret) < 0) || (SSL_CTX_set_tmp_dh(clictx, ret) < 0))
 +                      if (err == SSL_ERROR_WANT_READ)
                        {
 -                              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);
 +                              SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 +                              this->status = ISSL_HANDSHAKING;
 +                              return true;
 +                      }
 +                      else if (err == SSL_ERROR_WANT_WRITE)
 +                      {
 +                              SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 +                              this->status = ISSL_HANDSHAKING;
 +                              return true;
 +                      }
 +                      else
 +                      {
 +                              CloseSession();
                        }
 -                      DH_free(ret);
 -              }
 -
 -#ifndef _WIN32
 -              fclose(dhpfile);
 -#endif
  
 -#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH
 -              SetupECDH(conf);
 -#endif
 -      }
 +                      return false;
 +              }
 +              else if (ret > 0)
 +              {
 +                      // Handshake complete.
 +                      VerifyCertificate();
  
 -      void On005Numeric(std::string &output)
 -      {
 -              if (!sslports.empty())
 -                      output.append(" SSL=" + sslports);
 -      }
 +                      status = ISSL_OPEN;
  
 -      ~ModuleSSLOpenSSL()
 -      {
 -              SSL_CTX_free(ctx);
 -              SSL_CTX_free(clictx);
 -              delete[] sessions;
 -      }
 +                      SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
  
 -      void OnUserConnect(LocalUser* user)
 -      {
 -              if (user->eh.GetIOHook() == this)
 +                      return true;
 +              }
 +              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 true;
                }
-               return true;
++              return false;
        }
  
 -      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);
                }
-               errno = EIO;
 +              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)
++#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
++      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;
 -
 -              issl_session* session = &sessions[fd];
 -
 -              session->sess = SSL_new(clictx);
 -              session->status = ISSL_NONE;
 -              session->outbound = true;
 -              session->data_to_write = false;
 -
 -              if (session->sess == NULL)
 -                      return;
 -
 -              if (SSL_set_fd(session->sess, fd) == 0)
++              if ((where & SSL_CB_HANDSHAKE_START) && (status == ISSL_OPEN))
+               {
 -                      ServerInstance->Logs->Log("m_ssl_openssl",DEBUG,"BUG: Can't set fd with SSL_set_fd: %d", fd);
 -                      return;
++                      if (profile->AllowRenegotiation())
++                              return;
++
++                      // The other side is trying to renegotiate, kill the connection and change status
++                      // to ISSL_NONE so CheckRenego() closes the session
++                      status = ISSL_NONE;
++                      SocketEngine::Shutdown(SSL_get_fd(sess), 2);
+               }
++      }
++
++      bool CheckRenego(StreamSocket* sock)
++      {
++              if (status != ISSL_NONE)
++                      return true;
 -              Handshake(user, session);
++              ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Session %p killed, attempted to renegotiate", (void*)sess);
++              CloseSession();
++              sock->SetError("Renegotiation is not allowed");
++              return false;
+       }
++#endif
++
++      // Calls our private SSLInfoCallback()
++      friend void StaticSSLInfoCallback(const SSL* ssl, int where, int rc);
 -      void OnStreamSocketClose(StreamSocket* user)
 + public:
 +      OpenSSLIOHook(IOHookProvider* hookprov, StreamSocket* sock, bool is_outbound, SSL* session, const reference<OpenSSL::Profile>& sslprofile)
 +              : SSLIOHook(hookprov)
 +              , sess(session)
 +              , status(ISSL_NONE)
 +              , outbound(is_outbound)
 +              , data_to_write(false)
 +              , profile(sslprofile)
        {
 -              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))
 +              if (sess == NULL)
                        return;
 +              if (SSL_set_fd(sess, sock->GetFd()) == 0)
 +                      throw ModuleException("Can't set fd with SSL_set_fd: " + ConvToStr(sock->GetFd()));
  
 -              CloseSession(&sessions[fd]);
 +              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];
 +              CloseSession();
 +      }
  
 -              if (!session->sess)
 +      int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE
 +      {
 +              if (!sess)
                {
 -                      CloseSession(session);
 +                      CloseSession();
                        return -1;
                }
  
                        }
                }
  
 -              // If we resumed the handshake then session->status will be ISSL_OPEN
 +              // If we resumed the handshake then this->status will be ISSL_OPEN
  
 -              if (session->status == ISSL_OPEN)
 +              if (status == 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);
  
 -                      if (!CheckRenego(user, session))
+ #ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
++                      if (!CheckRenego(user))
+                               return -1;
+ #endif
                        if (ret > 0)
                        {
                                recvq.append(buffer, ret);
                        }
                }
  
 -              if (session->status == ISSL_OPEN)
 +              if (status == ISSL_OPEN)
                {
 -                      int ret = SSL_write(session->sess, buffer.data(), buffer.size());
+                       ERR_clear_error();
 -                      if (!CheckRenego(user, session))
 +                      int ret = SSL_write(sess, buffer.data(), buffer.size());
+ #ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
++                      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);
 +                              data_to_write = false;
 +                              SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
                                return 1;
                        }
                        else if (ret > 0)
                return 0;
        }
  
 -      bool Handshake(StreamSocket* user, issl_session* session)
 +      void TellCiphersAndFingerprint(LocalUser* user)
        {
 -              int ret;
 +              if (sess)
 +              {
 +                      std::string text = "*** You are connected using SSL cipher '" + std::string(SSL_get_cipher(sess)) + "'";
 +                      const std::string& fingerprint = certificate->fingerprint;
 +                      if (!fingerprint.empty())
 +                              text += " and your SSL certificate fingerprint is " + fingerprint;
  
 -              ERR_clear_error();
 -              if (session->outbound)
 -                      ret = SSL_connect(session->sess);
 -              else
 -                      ret = SSL_accept(session->sess);
 +                      user->WriteNotice(text);
 +              }
 +      }
 +};
  
 -              if (ret < 0)
++static void StaticSSLInfoCallback(const SSL* ssl, int where, int rc)
++{
++#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION
++      OpenSSLIOHook* hook = static_cast<OpenSSLIOHook*>(SSL_get_ex_data(ssl, exdataindex));
++      hook->SSLInfoCallback(where, rc);
++#endif
++}
++
 +class OpenSSLIOHookProvider : public refcountbase, public IOHookProvider
 +{
 +      reference<OpenSSL::Profile> profile;
 +
 + public:
 +      OpenSSLIOHookProvider(Module* mod, reference<OpenSSL::Profile>& prof)
 +              : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL)
 +              , profile(prof)
 +      {
 +              ServerInstance->Modules->AddService(*this);
 +      }
 +
 +      ~OpenSSLIOHookProvider()
 +      {
 +              ServerInstance->Modules->DelService(*this);
 +      }
 +
 +      void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
 +      {
 +              new OpenSSLIOHook(this, sock, false, profile->CreateServerSession(), profile);
 +      }
 +
 +      void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
 +      {
 +              new OpenSSLIOHook(this, sock, true, profile->CreateClientSession(), profile);
 +      }
 +};
 +
 +class ModuleSSLOpenSSL : public Module
 +{
 +      typedef std::vector<reference<OpenSSLIOHookProvider> > ProfileList;
 +
 +      ProfileList profiles;
 +
 +      void ReadProfiles()
 +      {
 +              ProfileList newprofiles;
 +              ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile");
 +              if (tags.first == tags.second)
                {
 -                      int err = SSL_get_error(session->sess, ret);
 +                      // 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");
  
 -                      if (err == SSL_ERROR_WANT_READ)
 +                      try
                        {
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 -                              session->status = ISSL_HANDSHAKING;
 -                              return true;
 +                              reference<OpenSSL::Profile> profile(new OpenSSL::Profile(defname, tag));
 +                              newprofiles.push_back(new OpenSSLIOHookProvider(this, profile));
                        }
 -                      else if (err == SSL_ERROR_WANT_WRITE)
 +                      catch (OpenSSL::Exception& ex)
                        {
 -                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 -                              session->status = ISSL_HANDSHAKING;
 -                              return true;
 -                      }
 -                      else
 -                      {
 -                              CloseSession(session);
 +                              throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason());
                        }
 -
 -                      return false;
                }
 -              else if (ret > 0)
 +
 +              for (ConfigIter i = tags.first; i != tags.second; ++i)
                {
 -                      // Handshake complete.
 -                      VerifyCertificate(session, user);
 +                      ConfigTag* tag = i->second;
 +                      if (tag->getString("provider") != "openssl")
 +                              continue;
  
 -                      session->status = ISSL_OPEN;
 +                      std::string name = tag->getString("name");
 +                      if (name.empty())
 +                      {
 +                              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation());
 +                              continue;
 +                      }
  
 -                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
 +                      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());
 +                      }
  
 -                      return true;
 -              }
 -              else if (ret == 0)
 -              {
 -                      CloseSession(session);
 +                      newprofiles.push_back(new OpenSSLIOHookProvider(this, profile));
                }
 -              return false;
 +
 +              profiles.swap(newprofiles);
        }
  
 -      void CloseSession(issl_session* session)
 + public:
 +      ModuleSSLOpenSSL()
        {
 -              if (session->sess)
 -              {
 -                      SSL_shutdown(session->sess);
 -                      SSL_free(session->sess);
 -              }
 -
 -              session->sess = NULL;
 -              session->status = ISSL_NONE;
 -              session->cert = NULL;
 +              // Initialize OpenSSL
 +              SSL_library_init();
 +              SSL_load_error_strings();
        }
  
 -      void VerifyCertificate(issl_session* session, StreamSocket* user)
 +      void init() CXX11_OVERRIDE
        {
 -              if (!session->sess || !user)
 -                      return;
 +              // 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");
  
 -              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();
 -
 -              cert = SSL_get_peer_certificate((SSL*)session->sess);
 +              ReadProfiles();
 +      }
  
 -              if (!cert)
 -              {
 -                      certinfo->error = "Could not get peer certificate: "+std::string(get_error());
 +      void OnModuleRehash(User* user, const std::string &param) CXX11_OVERRIDE
 +      {
 +              if (param != "ssl")
                        return;
 -              }
 -
 -              certinfo->invalid = (SSL_get_verify_result(session->sess) != X509_V_OK);
  
 -              if (!SelfSigned)
 +              try
                {
 -                      certinfo->unknownsigner = false;
 -                      certinfo->trusted = true;
 +                      ReadProfiles();
                }
 -              else
 +              catch (ModuleException& ex)
                {
 -                      certinfo->unknownsigner = true;
 -                      certinfo->trusted = false;
 +                      ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings.");
                }
 +      }
  
 -              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();
 -
 -              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();
 +      void OnUserConnect(LocalUser* user) CXX11_OVERRIDE
 +      {
 +              IOHook* hook = user->eh.GetIOHook();
 +              if (hook && hook->prov->creator == this)
 +                      static_cast<OpenSSLIOHook*>(hook)->TellCiphersAndFingerprint(user);
 +      }
  
 -              if (!X509_digest(cert, digest, md, &n))
 -              {
 -                      certinfo->error = "Out of memory generating fingerprint";
 -              }
 -              else
 +      void OnCleanup(int target_type, void* item) CXX11_OVERRIDE
 +      {
 +              if (target_type == TYPE_USER)
                {
 -                      certinfo->fingerprint = irc::hex(md, n);
 -              }
 +                      LocalUser* user = IS_LOCAL((User*)item);
  
 -              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))
 -              {
 -                      certinfo->error = "Not activated, or expired certificate";
 +                      if (user && user->eh.GetIOHook() && user->eh.GetIOHook()->prov->creator == 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);
 +      Version GetVersion() CXX11_OVERRIDE
 +      {
 +              return Version("Provides SSL support for clients", VF_VENDOR);
        }
  };
  
index d82b8473c513e3dfc6e72d3cac9a112143232aaf,1b9e361bf408dc12fd0697f354323388941a3545..9833c720d13e78b71b45f94790db48f6a5c0eca0
@@@ -74,12 -75,12 +74,15 @@@ class BanRedirect : public ModeWatche
                        if (param.length() >= 2 && param[1] == ':')
                                return true;
  
 -                      if(adding && (channel->bans.size() > static_cast<unsigned>(maxbans)))
+                       if (param.find('#') == std::string::npos)
+                               return true;
 +                      ListModeBase* banlm = static_cast<ListModeBase*>(*ban);
 +                      unsigned int maxbans = banlm->GetLimit(channel);
 +                      ListModeBase::ModeList* list = banlm->GetList(channel);
 +                      if ((list) && (adding) && (maxbans <= list->size()))
                        {
 -                              source->WriteNumeric(478, "%s %s :Channel ban list for %s is full (maximum entries for this channel is %ld)", source->nick.c_str(), channel->name.c_str(), channel->name.c_str(), maxbans);
 +                              source->WriteNumeric(ERR_BANLISTFULL, "%s :Channel ban list for %s is full (maximum entries for this channel is %u)", channel->name.c_str(), channel->name.c_str(), maxbans);
                                return false;
                        }
  
index 55ffd08d706d649cb03686a78383cf50a2d18524,20d4c8e8faeeed905350aac88dfc2eb79c90b511..2474cd6ad21c8146ac7b060d3f3c691e555b4ae4
@@@ -168,6 -182,11 +168,10 @@@ ModResult ModuleDelayJoin::OnRawMode(Us
        if (!user || !channel || param.empty())
                return MOD_RES_PASSTHRU;
  
 -      ModeHandler* mh = ServerInstance->Modes->FindMode(mode, MODETYPE_CHANNEL);
+       // If not a prefix mode then we got nothing to do here
 -      if (!mh || !mh->GetPrefixRank())
++      if (!mh->IsPrefixMode())
+               return MOD_RES_PASSTHRU;
        User* dest;
        if (IS_LOCAL(user))
                dest = ServerInstance->FindNickOnly(param);
index 06a05db0238cc51287156e3e7d85b939953b51e2,978ab55d2191c94b31bb74862bf454f4f01ce72d..c906e2c3064d5f2566182067f7a83a001471cdc7
@@@ -46,25 -41,57 +46,27 @@@ class DelayMsgMode : public ParamMode<D
  
  class ModuleDelayMsg : public Module
  {
 - private:
        DelayMsgMode djm;
++      bool allownotice;
   public:
        ModuleDelayMsg() : djm(this)
        {
        }
  
 -      void init()
 -      {
 -              ServerInstance->Modules->AddService(djm);
 -              ServerInstance->Modules->AddService(djm.jointime);
 -              Implementation eventlist[] = { I_OnUserJoin, I_OnUserPreMessage, I_OnRehash };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
 -              OnRehash(NULL);
 -      }
 -      Version GetVersion();
 -      void OnUserJoin(Membership* memb, bool sync, bool created, CUList&);
 -      ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list);
 -      ModResult OnUserPreNotice(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list);
 -      void OnRehash(User* user);
 +      Version GetVersion() CXX11_OVERRIDE;
 +      void OnUserJoin(Membership* memb, bool sync, bool created, CUList&) CXX11_OVERRIDE;
 +      ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE;
++      void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE;
  };
  
 -ModeAction DelayMsgMode::OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
 +ModeAction DelayMsgMode::OnSet(User* source, Channel* chan, std::string& parameter)
  {
 -      if (adding)
 -      {
 -              if ((channel->IsModeSet('d')) && (channel->GetModeParameter('d') == parameter))
 -                      return MODEACTION_DENY;
 -
 -              /* Setting a new limit, sanity check */
 -              long limit = atoi(parameter.c_str());
 -
 -              /* Wrap low values at 32768 */
 -              if (limit < 0)
 -                      limit = 0x7FFF;
 +      // Setting a new limit, sanity check
 +      unsigned int limit = ConvToInt(parameter);
 +      if (limit == 0)
 +              limit = 1;
  
 -              parameter = ConvToStr(limit);
 -      }
 -      else
 -      {
 -              if (!channel->IsModeSet('d'))
 -                      return MODEACTION_DENY;
 -
 -              /*
 -               * Clean up metadata
 -               */
 -              const UserMembList* names = channel->GetUsers();
 -              for (UserMembCIter n = names->begin(); n != names->end(); ++n)
 -                      jointime.set(n->second, 0);
 -      }
 -      channel->SetModeParam('d', adding ? parameter : "");
 +      ext.set(chan, limit);
        return MODEACTION_ALLOW;
  }
  
@@@ -97,7 -114,7 +99,7 @@@ ModResult ModuleDelayMsg::OnUserPreMess
        if ((!user) || (!IS_LOCAL(user)))
                return MOD_RES_PASSTHRU;
  
-       if ((target_type != TYPE_CHANNEL) || (msgtype != MSG_PRIVMSG))
 -      if (target_type != TYPE_CHANNEL)
++      if ((target_type != TYPE_CHANNEL) || ((!allownotice) && (msgtype == MSG_NOTICE)))
                return MOD_RES_PASSTHRU;
  
        Channel* channel = (Channel*) dest;
        return MOD_RES_PASSTHRU;
  }
  
 -ModResult ModuleDelayMsg::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);
 -}
 -
 -void ModuleDelayMsg::OnRehash(User* user)
++void ModuleDelayMsg::ReadConfig(ConfigStatus& status)
+ {
+       ConfigTag* tag = ServerInstance->Config->ConfValue("delaymsg");
 -      if (tag->getBool("allownotice", true))
 -              ServerInstance->Modules->Detach(I_OnUserPreNotice, this);
 -      else
 -              ServerInstance->Modules->Attach(I_OnUserPreNotice, this);
++      allownotice = tag->getBool("allownotice", true);
+ }
  MODULE_INIT(ModuleDelayMsg)
 -
index 0650cb3d000b004d704f15325f61b912cea5290a,bf95f0f9f3992b70de15db10c427eb0ef2f1996e..f77899ad4f8105edc521e732b148720d8fbb5f97
@@@ -220,17 -224,44 +220,46 @@@ class ModuleNationalChars : public Modu
        lwbNickHandler myhandler;
        std::string charset, casemapping;
        unsigned char m_additional[256], m_additionalUp[256], m_lower[256], m_upper[256];
 -      caller2<bool, const char*, size_t> rememberer;
 +      caller1<bool, const std::string&> rememberer;
        bool forcequit;
        const unsigned char * lowermap_rememberer;
 -              ServerInstance->RehashUsersAndChans();
+       unsigned char prev_map[256];
++      template <typename T>
++      void RehashHashmap(T& hashmap)
++      {
++              T newhash(hashmap.bucket_count());
++              for (typename T::const_iterator i = hashmap.begin(); i != hashmap.end(); ++i)
++                      newhash.insert(std::make_pair(i->first, i->second));
++              hashmap.swap(newhash);
++      }
++
+       void CheckRehash()
+       {
+               // See if anything changed
+               if (!memcmp(prev_map, national_case_insensitive_map, sizeof(prev_map)))
+                       return;
+               memcpy(prev_map, national_case_insensitive_map, sizeof(prev_map));
 -
 -              // Send a Request to m_spanningtree asking it to rebuild its hashmaps
 -              mod = ServerInstance->Modules->Find("m_spanningtree.so");
 -              if (mod)
 -              {
 -                      Request req(this, mod, "rehash");
 -                      req.Send();
 -              }
++              RehashHashmap(ServerInstance->Users.clientlist);
++              RehashHashmap(ServerInstance->Users.uuidlist);
++              RehashHashmap(ServerInstance->chanlist);
+               // The OnGarbageCollect() method in m_watch rebuilds the hashmap used by it
+               Module* mod = ServerInstance->Modules->Find("m_watch.so");
+               if (mod)
+                       mod->OnGarbageCollect();
+       }
  
   public:
        ModuleNationalChars()
                : rememberer(ServerInstance->IsNick), lowermap_rememberer(national_case_insensitive_map)
        {
+               memcpy(prev_map, national_case_insensitive_map, sizeof(prev_map));
        }
  
 -      void init()
 +      void init() CXX11_OVERRIDE
        {
                memcpy(m_lower, rfc_case_insensitive_map, 256);
                national_case_insensitive_map = m_lower;
                ServerInstance->IsNick = rememberer;
                national_case_insensitive_map = lowermap_rememberer;
                CheckForceQuit("National characters module unloaded");
+               CheckRehash();
        }
  
 -      virtual Version GetVersion()
 +      Version GetVersion() CXX11_OVERRIDE
        {
                return Version("Provides an ability to have non-RFC1459 nicks & support for national CASEMAPPING", VF_VENDOR | VF_COMMON, charset);
        }
index a775d7c243d27ac9180343b185f4068d5637d2af,abda283353def3e2fab90d2ea4cdf04593f80f76..6dc584537cc97669145d16aabe3afce8f42cea77
@@@ -95,15 -97,8 +95,13 @@@ class TreeSocket : public BufferedSocke
        ServerState LinkState;                  /* Link state */
        CapabData* capab;                       /* Link setup data (held until burst is sent) */
        TreeServer* MyRoot;                     /* The server we are talking to */
-       time_t NextPing;                        /* Time when we are due to ping this server */
-       bool LastPingWasGood;                   /* Responded to last ping we sent? */
        int proto_version;                      /* Remote protocol version */
 -      bool ConnectionFailureShown; /* Set to true if a connection failure message was shown */
 +
 +      /** True if we've sent our burst.
 +       * This only changes the behavior of message translation for 1202 protocol servers and it can be
 +       * removed once 1202 support is dropped.
 +       */
 +      bool burstsent;
  
        /** Checks if the given servername and sid are both free
         */
Simple merge
diff --cc src/server.cpp
index 256ccfc4c0540a1e2f06c3d914f5b68ce8757a14,d05ece8a45ac2c7f86bce4308dbb3fcc598687c9..42dce137230ad892c743cb456d94c7a702aa132a
@@@ -46,13 -46,10 +46,10 @@@ void InspIRCd::Exit(int status
  #ifdef _WIN32
        SetServiceStopped(status);
  #endif
-       if (this)
-       {
-               this->SendError("Exiting with status " + ConvToStr(status) + " (" + std::string(ExitCodes[status]) + ")");
-               this->Cleanup();
-               ServerInstance = NULL;
-               delete this;
-       }
+       this->SendError("Exiting with status " + ConvToStr(status) + " (" + std::string(ExitCodes[status]) + ")");
+       this->Cleanup();
 -      delete this;
+       ServerInstance = NULL;
++      delete this;
        exit (status);
  }
  
Simple merge