]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Merge insp20
authorattilamolnar <attilamolnar@hush.com>
Fri, 30 Aug 2013 11:01:10 +0000 (13:01 +0200)
committerattilamolnar <attilamolnar@hush.com>
Fri, 30 Aug 2013 11:01:10 +0000 (13:01 +0200)
29 files changed:
1  2 
docs/conf/helpop-full.conf.example
docs/conf/helpop.conf.example
docs/conf/inspircd.conf.example
docs/conf/links.conf.example
docs/conf/modules.conf.example
docs/conf/modules/unrealircd.conf.example
docs/conf/opers.conf.example
include/base.h
include/socketengine.h
src/commands/cmd_whowas.cpp
src/helperfuncs.cpp
src/inspircd.cpp
src/inspsocket.cpp
src/modules/extra/m_ssl_gnutls.cpp
src/modules/extra/m_ssl_openssl.cpp
src/modules/m_dnsbl.cpp
src/modules/m_ircv3.cpp
src/modules/m_permchannels.cpp
src/modules/m_spanningtree/fjoin.cpp
src/modules/m_spanningtree/main.cpp
src/modules/m_spanningtree/utils.h
src/modules/m_userip.cpp
src/socketengine.cpp
src/socketengines/socketengine_epoll.cpp
src/socketengines/socketengine_kqueue.cpp
src/socketengines/socketengine_poll.cpp
src/socketengines/socketengine_ports.cpp
src/socketengines/socketengine_select.cpp
win/inspircd_win32wrapper.cpp

Simple merge
Simple merge
Simple merge
Simple merge
index ca1340f040f4b7915f64075d4e3d84bb01e721e9,54a667f344f9aef6c39b4a39c774f84016763d3a..d329ab5220dc87247867f440e8493406c07e7b51
  #   quitmsg="Throttled" bootwait="10">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
- # Custom prefixes: allows for channel prefixes to be added.
+ # Custom prefixes: allows for channel prefixes to be added. 
 -# This replaces m_chanprotect and m_halfop.
  #<module name="m_customprefix.so">
  #
  # name       The name of the mode, must be unique from other modes
  # specify below the path to the helpop.conf file, or if you like to   #
  # make a mess, define your helpop tags in this conf.                  #
  #                                                                     #
 -#<include file="conf/examples/inspircd.helpop-full.example">
 +#<include file="examples/inspircd.helpop-full.example">
  
  #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
- # HIDECHANS module: Allows opers to hide their channels list from non-
+ # HIDECHANS module: Allows users to hide their channels list from non-
  # opers by setting user mode +I on themselves.
  #<module name="m_hidechans.so">
  #
Simple merge
diff --cc include/base.h
Simple merge
Simple merge
index b406781b1932f17997814e859f5daf9a55bc823e,3a6444b45b655f5346138054878f729dc1aad413..75d1ee8ff449416766f0ebdf164c267e05c79df9
@@@ -70,8 -66,11 +62,9 @@@ CmdResult CommandWhowas::Handle (const 
                                        user->WriteNumeric(379, "%s %s :was connecting from *@%s",
                                                user->nick.c_str(), parameters[0].c_str(), u->host.c_str());
  
 -                              if (!ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex"))
 -                                      user->WriteNumeric(312, "%s %s %s :%s",user->nick.c_str(),parameters[0].c_str(), ServerInstance->Config->HideWhoisServer.c_str(), signon.c_str());
 -                              else
 -                                      user->WriteNumeric(312, "%s %s %s :%s",user->nick.c_str(),parameters[0].c_str(), u->server.c_str(), signon.c_str());
+                               std::string signon = ServerInstance->TimeString(u->signon);
-                               user->WriteNumeric(312, "%s %s %s :%s",user->nick.c_str(), parameters[0].c_str(), (hide_server ? ServerInstance->Config->HideWhoisServer.c_str() : u->server.c_str()), b);
 +                              bool hide_server = (!ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex"));
++                              user->WriteNumeric(312, "%s %s %s :%s",user->nick.c_str(), parameters[0].c_str(), (hide_server ? ServerInstance->Config->HideWhoisServer.c_str() : u->server.c_str()), signon.c_str());
                        }
                }
                else
index 2626da6bbd6132518866946809e73fd181503909,439320c1e00d85334c29d3da845357b246d11cfa..cfbd53c98fbe597359cfb2851129650584ecbec5
@@@ -437,9 -495,48 +437,28 @@@ bool InspIRCd::SilentULine(const std::s
  
  std::string InspIRCd::TimeString(time_t curtime)
  {
-       return std::string(ctime(&curtime),24);
+ #ifdef _WIN32
+       if (curtime < 0)
+               curtime = 0;
+ #endif
+       struct tm* timeinfo = localtime(&curtime);
+       if (!timeinfo)
+       {
+               curtime = 0;
+               timeinfo = localtime(&curtime);
+       }
+       // If the calculated year exceeds four digits or is less than the year 1000,
+       // the behavior of asctime() is undefined
+       if (timeinfo->tm_year + 1900 > 9999)
+               timeinfo->tm_year = 9999 - 1900;
+       else if (timeinfo->tm_year + 1900 < 1000)
+               timeinfo->tm_year = 0;
+       return std::string(asctime(timeinfo),24);
  }
  
 -// You should only pass a single character to this.
 -void InspIRCd::AddExtBanChar(char c)
 -{
 -      std::string &tok = Config->data005;
 -      std::string::size_type ebpos = tok.find(" EXTBAN=,");
 -
 -      if (ebpos == std::string::npos)
 -      {
 -              tok.append(" EXTBAN=,");
 -              tok.push_back(c);
 -      }
 -      else
 -      {
 -              ebpos += 9;
 -              while (isalpha(tok[ebpos]) && tok[ebpos] < c)
 -                      ebpos++;
 -              tok.insert(ebpos, 1, c);
 -      }
 -}
 -
  std::string InspIRCd::GenRandomStr(int length, bool printable)
  {
        char* buf = new char[length];
Simple merge
Simple merge
index dab377397c66fcb2af5a7d3bf631c0085aabcccb,1f1297ef90468bc8d38d841bb71593841b647a29..53fc38ec0ba5ea2035e5fa81ac56486488b95ab7
  # pragma comment(lib, "gdi32.lib")
  #endif
  
 -/* $ModDesc: Provides SSL support for clients */
 -/* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") exec("libgcrypt-config --cflags") */
 +/* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") exec("libgcrypt-config --cflags") -Wno-pedantic */
  /* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") exec("libgcrypt-config --libs") */
 -/* $NoPedantic */
  
+ #ifndef GNUTLS_VERSION_MAJOR
+ #define GNUTLS_VERSION_MAJOR LIBGNUTLS_VERSION_MAJOR
+ #define GNUTLS_VERSION_MINOR LIBGNUTLS_VERSION_MINOR
+ #define GNUTLS_VERSION_PATCH LIBGNUTLS_VERSION_PATCH
+ #endif
  // These don't exist in older GnuTLS versions
- #if ((GNUTLS_VERSION_MAJOR > 2) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 1) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR == 1 && GNUTLS_VERSION_MICRO >= 7))
+ #if ((GNUTLS_VERSION_MAJOR > 2) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 1) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR == 1 && GNUTLS_VERSION_PATCH >= 7))
  #define GNUTLS_NEW_PRIO_API
  #endif
  
@@@ -103,186 -106,77 +109,188 @@@ public
        issl_session() : socket(NULL), sess(NULL) {}
  };
  
 -class CommandStartTLS : public SplitCommand
 +class GnuTLSIOHook : public SSLIOHook
  {
 - public:
 -      bool enabled;
 -      CommandStartTLS (Module* mod) : SplitCommand(mod, "STARTTLS")
 + private:
 +      void InitSession(StreamSocket* user, bool me_server)
        {
 -              enabled = true;
 -              works_before_reg = true;
 +              issl_session* session = &sessions[user->GetFd()];
 +
 +              gnutls_init(&session->sess, me_server ? GNUTLS_SERVER : GNUTLS_CLIENT);
 +              session->socket = user;
 +
 +              #ifdef GNUTLS_NEW_PRIO_API
 +              gnutls_priority_set(session->sess, priority);
++              #else
++              gnutls_set_default_priority(session->sess);
 +              #endif
 +              gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, 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);
        }
  
 -      CmdResult HandleLocal(const std::vector<std::string> &parameters, LocalUser *user)
 +      void CloseSession(issl_session* session)
        {
 -              if (!enabled)
 +              if (session->sess)
                {
 -                      user->WriteNumeric(691, "%s :STARTTLS is not enabled", user->nick.c_str());
 -                      return CMD_FAILURE;
 +                      gnutls_bye(session->sess, GNUTLS_SHUT_WR);
 +                      gnutls_deinit(session->sess);
                }
 +              session->socket = NULL;
 +              session->sess = NULL;
 +              session->cert = NULL;
 +              session->status = ISSL_NONE;
 +      }
  
 -              if (user->registered == REG_ALL)
 -              {
 -                      user->WriteNumeric(691, "%s :STARTTLS is not permitted after client registration is complete", user->nick.c_str());
 -              }
 -              else
 +      bool Handshake(issl_session* session, StreamSocket* user)
 +      {
 +              int ret = gnutls_handshake(session->sess);
 +
 +              if (ret < 0)
                {
 -                      if (!user->eh.GetIOHook())
 +                      if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
                        {
 -                              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);
 +                              // Handshake needs resuming later, read() or write() would have blocked.
 +
 +                              if(gnutls_record_get_direction(session->sess) == 0)
 +                              {
 +                                      // gnutls_handshake() wants to read() again.
 +                                      session->status = ISSL_HANDSHAKING_READ;
 +                                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 +                              }
 +                              else
 +                              {
 +                                      // gnutls_handshake() wants to write() again.
 +                                      session->status = ISSL_HANDSHAKING_WRITE;
 +                                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 +                              }
                        }
                        else
 -                              user->WriteNumeric(691, "%s :STARTTLS failure", user->nick.c_str());
 +                      {
 +                              user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret)));
 +                              CloseSession(session);
 +                              session->status = ISSL_CLOSING;
 +                      }
 +
 +                      return false;
                }
 +              else
 +              {
 +                      // Change the seesion state
 +                      session->status = ISSL_HANDSHAKEN;
  
 -              return CMD_FAILURE;
 +                      VerifyCertificate(session,user);
 +
 +                      // Finish writing, if any left
 +                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
 +
 +                      return true;
 +              }
        }
 -};
  
 -class ModuleSSLGnuTLS : public Module
 -{
 -      issl_session* sessions;
 +      void VerifyCertificate(issl_session* session, StreamSocket* user)
 +      {
 +              if (!session->sess || !user)
 +                      return;
  
 -      gnutls_certificate_credentials_t x509_cred;
 -      gnutls_dh_params_t dh_params;
 -      gnutls_digest_algorithm_t hash;
 -      #ifdef GNUTLS_NEW_PRIO_API
 -      gnutls_priority_t priority;
 -      #endif
 +              unsigned int status;
 +              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;
 +              session->cert = certinfo;
  
 -      std::string sslports;
 -      int dh_bits;
 +              /* This verification function uses the trusted CAs in the credentials
 +               * structure. So you must have installed one or more CA certificates.
 +               */
 +              ret = gnutls_certificate_verify_peers2(session->sess, &status);
  
 -      bool cred_alloc;
 -      bool dh_alloc;
 +              if (ret < 0)
 +              {
 +                      certinfo->error = std::string(gnutls_strerror(ret));
 +                      return;
 +              }
  
 -      RandGen randhandler;
 -      CommandStartTLS starttls;
 +              certinfo->invalid = (status & GNUTLS_CERT_INVALID);
 +              certinfo->unknownsigner = (status & GNUTLS_CERT_SIGNER_NOT_FOUND);
 +              certinfo->revoked = (status & GNUTLS_CERT_REVOKED);
 +              certinfo->trusted = !(status & GNUTLS_CERT_SIGNER_NOT_CA);
  
 -      GenericCap capHandler;
 -      ServiceProvider iohook;
 +              /* Up to here the process is the same for X.509 certificates and
 +               * OpenPGP keys. From now on X.509 certificates are assumed. This can
 +               * be easily extended to work with openpgp keys as well.
 +               */
 +              if (gnutls_certificate_type_get(session->sess) != GNUTLS_CRT_X509)
 +              {
 +                      certinfo->error = "No X509 keys sent";
 +                      return;
 +              }
 +
 +              ret = gnutls_x509_crt_init(&cert);
 +              if (ret < 0)
 +              {
 +                      certinfo->error = gnutls_strerror(ret);
 +                      return;
 +              }
 +
 +              cert_list_size = 0;
 +              cert_list = gnutls_certificate_get_peers(session->sess, &cert_list_size);
 +              if (cert_list == NULL)
 +              {
 +                      certinfo->error = "No certificate was found";
 +                      goto info_done_dealloc;
 +              }
 +
 +              /* This is not a real world example, since we only check the first
 +               * certificate in the given chain.
 +               */
 +
 +              ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
 +              if (ret < 0)
 +              {
 +                      certinfo->error = gnutls_strerror(ret);
 +                      goto info_done_dealloc;
 +              }
 +
 +              gnutls_x509_crt_get_dn(cert, str, &name_size);
 +              certinfo->dn = str;
 +
 +              gnutls_x509_crt_get_issuer_dn(cert, str, &name_size);
 +              certinfo->issuer = str;
 +
 +              if ((ret = gnutls_x509_crt_get_fingerprint(cert, hash, digest, &digest_size)) < 0)
 +              {
 +                      certinfo->error = gnutls_strerror(ret);
 +              }
 +              else
 +              {
 +                      certinfo->fingerprint = BinToHex(digest, digest_size);
 +              }
 +
 +              /* 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()))
 +              {
 +                      certinfo->error = "Not activated, or expired certificate";
 +              }
 +
 +info_done_dealloc:
 +              gnutls_x509_crt_deinit(cert);
 +      }
  
 -      inline static const char* UnknownIfNULL(const char* str)
 +      static const char* UnknownIfNULL(const char* str)
        {
                return str ? str : "UNKNOWN";
        }
index 4cb6ee07b368d9a124b88c4a021bd33b7aa3d42a,7b7de023cdfd4f983051d48ebedef05175323ddb..29c3568ef117c93c7289bbefb5aad6298c39e6bc
@@@ -95,112 -100,159 +95,112 @@@ static int OnVerify(int preverify_ok, X
        return 1;
  }
  
 -class ModuleSSLOpenSSL : public Module
 +class OpenSSLIOHook : public SSLIOHook
  {
 -      issl_session* sessions;
 -
 -      SSL_CTX* ctx;
 -      SSL_CTX* clictx;
 -
 -      std::string sslports;
 -      bool use_sha;
 -
 -      ServiceProvider iohook;
 - public:
 -
 -      ModuleSSLOpenSSL() : iohook(this, "ssl/openssl", SERVICE_IOHOOK)
 + private:
 +      bool Handshake(StreamSocket* user, issl_session* session)
        {
 -              sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
 +              int ret;
  
 -              /* Global SSL library initialization*/
 -              SSL_library_init();
 -              SSL_load_error_strings();
 +              if (session->outbound)
 +                      ret = SSL_connect(session->sess);
 +              else
 +                      ret = SSL_accept(session->sess);
  
 -              /* Build our SSL contexts:
 -               * NOTE: OpenSSL makes us have two contexts, one for servers and one for clients. ICK.
 -               */
 -              ctx = SSL_CTX_new( SSLv23_server_method() );
 -              clictx = SSL_CTX_new( SSLv23_client_method() );
 +              if (ret < 0)
 +              {
 +                      int err = SSL_get_error(session->sess, ret);
  
 -              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);
 +                      if (err == SSL_ERROR_WANT_READ)
 +                      {
 +                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
 +                              session->status = ISSL_HANDSHAKING;
 +                              return true;
 +                      }
 +                      else if (err == SSL_ERROR_WANT_WRITE)
 +                      {
 +                              ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
 +                              session->status = ISSL_HANDSHAKING;
 +                              return true;
 +                      }
 +                      else
 +                      {
 +                              CloseSession(session);
 +                      }
  
 -              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);
 -      }
 +                      return false;
 +              }
 +              else if (ret > 0)
 +              {
 +                      // Handshake complete.
 +                      VerifyCertificate(session, user);
  
 -      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);
 -      }
 +                      session->status = ISSL_OPEN;
  
 -      void OnHookIO(StreamSocket* user, ListenSocket* lsb)
 -      {
 -              if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "openssl")
 +                      ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
 +
 +                      return true;
 +              }
 +              else if (ret == 0)
                {
 -                      /* Hook the user with our module */
 -                      user->AddIOHook(this);
 +                      CloseSession(session);
 +                      return true;
                }
 +
 +              return true;
        }
  
 -      void OnRehash(User* user)
 +      void CloseSession(issl_session* session)
        {
 -              sslports.clear();
 -
 -              ConfigTag* Conf = ServerInstance->Config->ConfValue("openssl");
 -
 -              if (Conf->getBool("showports", true))
 +              if (session->sess)
                {
 -                      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;
 -
 -                              const std::string& portid = port->bind_desc;
 -                              ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Enabling SSL for port %s", portid.c_str());
 -
 -                              if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1")
 -                              {
 -                                      /*
 -                                       * 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;
 -                              }
 -                      }
 +                      SSL_shutdown(session->sess);
 +                      SSL_free(session->sess);
                }
 +
 +              session->sess = NULL;
 +              session->status = ISSL_NONE;
 +              errno = EIO;
        }
  
 -      void OnModuleRehash(User* user, const std::string &param)
 +      void VerifyCertificate(issl_session* session, StreamSocket* user)
        {
 -              if (param != "ssl")
 +              if (!session->sess || !user)
                        return;
  
 -              std::string keyfile;
 -              std::string certfile;
 -              std::string cafile;
 -              std::string dhfile;
 -              OnRehash(user);
 -
 -              ConfigTag* conf = ServerInstance->Config->ConfValue("openssl");
 -
 -              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");
 +              X509* cert;
 +              ssl_cert* certinfo = new ssl_cert;
 +              session->cert = certinfo;
 +              unsigned int n;
 +              unsigned char md[EVP_MAX_MD_SIZE];
  
 -              std::string ciphers = conf->getString("ciphers", "");
 +              cert = SSL_get_peer_certificate((SSL*)session->sess);
  
 -              if (!ciphers.empty())
 +              if (!cert)
                {
 -                      if ((!SSL_CTX_set_cipher_list(ctx, ciphers.c_str())) || (!SSL_CTX_set_cipher_list(clictx, ciphers.c_str())))
 -                      {
 -                              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);
 -                      }
 +                      certinfo->error = "Could not get peer certificate: "+std::string(get_error());
 +                      return;
                }
  
 -              /* Load our keys and certificates
 -               * NOTE: OpenSSL's error logging API sucks, don't blame us for this clusterfuck.
 -               */
 -              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);
 -              }
 +              certinfo->invalid = (SSL_get_verify_result(session->sess) != X509_V_OK);
  
-               if (SelfSigned)
 -              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)))
++              if (!SelfSigned)
                {
 -                      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);
 +                      certinfo->unknownsigner = false;
 +                      certinfo->trusted = true;
                }
 -
 -              /* Load the CAs we trust*/
 -              if (((!SSL_CTX_load_verify_locations(ctx, cafile.c_str(), 0))) || (!SSL_CTX_load_verify_locations(clictx, cafile.c_str(), 0)))
 +              else
                {
 -                      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);
 +                      certinfo->unknownsigner = true;
 +                      certinfo->trusted = false;
                }
  
 -              FILE* dhpfile = fopen(dhfile.c_str(), "r");
 -              DH* ret;
 +              certinfo->dn = X509_NAME_oneline(X509_get_subject_name(cert),0,0);
 +              certinfo->issuer = X509_NAME_oneline(X509_get_issuer_name(cert),0,0);
  
 -              if (dhpfile == NULL)
 +              if (!X509_digest(cert, digest, md, &n))
                {
 -                      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));
 +                      certinfo->error = "Out of memory generating fingerprint";
                }
                else
                {
index becc7a6e85d59762b01886b1d0c544d8386d1d4a,3a9360380e43decfaa1bf556db0dfb0914ed959a..3c9d64d49a598ad5c7822deacdd25985465bdc48
  
  #include "inspircd.h"
  #include "xline.h"
 -
 -/* $ModDesc: Provides handling of DNS blacklists */
 +#include "modules/dns.h"
  
  /* Class holding data for a single entry */
- class DNSBLConfEntry
+ class DNSBLConfEntry : public refcountbase
  {
        public:
                enum EnumBanaction { I_UNKNOWN, I_KILL, I_ZLINE, I_KLINE, I_GLINE, I_MARK };
@@@ -53,8 -55,8 +53,8 @@@ class DNSBLResolver : public DNS::Reque
  
   public:
  
-       DNSBLResolver(DNS::Manager *mgr, Module *me, LocalStringExt& match, LocalIntExt& ctr, const std::string &hostname, LocalUser* u, DNSBLConfEntry *conf)
 -      DNSBLResolver(Module *me, LocalStringExt& match, LocalIntExt& ctr, const std::string &hostname, LocalUser* u, reference<DNSBLConfEntry> conf, bool &cached)
 -              : Resolver(hostname, DNS_QUERY_A, cached, me), theiruid(u->uuid), nameExt(match), countExt(ctr), ConfEntry(conf)
++      DNSBLResolver(DNS::Manager *mgr, Module *me, LocalStringExt& match, LocalIntExt& ctr, const std::string &hostname, LocalUser* u, reference<DNSBLConfEntry> conf)
 +              : DNS::Request(mgr, me, hostname, DNS::QUERY_A, true), theiruid(u->uuid), nameExt(match), countExt(ctr), ConfEntry(conf)
        {
        }
  
  
  class ModuleDNSBL : public Module
  {
-       std::vector<DNSBLConfEntry *> DNSBLConfEntries;
+       std::vector<reference<DNSBLConfEntry> > DNSBLConfEntries;
 +      dynamic_reference<DNS::Manager> DNS;
        LocalStringExt nameExt;
        LocalIntExt countExt;
  
                ReadConf();
                ServerInstance->Modules->AddService(nameExt);
                ServerInstance->Modules->AddService(countExt);
 -              Implementation eventlist[] = { I_OnRehash, I_OnSetUserIP, I_OnStats, I_OnSetConnectClass, I_OnCheckReady };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
        }
  
-       ~ModuleDNSBL()
-       {
-               ClearEntries();
-       }
 -      Version GetVersion()
 +      Version GetVersion() CXX11_OVERRIDE
        {
                return Version("Provides handling of DNS blacklists", VF_VENDOR);
        }
index 59a69f6691ede180a2a43afa50e1ec8056618714,da42d823d2678a63712a4a05ba3de3416a882cd8..f46ae97b4de39a516692fc0490d92a5fc92106f7
@@@ -76,15 -78,17 +76,15 @@@ class ModuleIRCv3 : public Modul
        {
        }
  
 -      void init()
 +      void init() CXX11_OVERRIDE
        {
                OnRehash(NULL);
 -              Implementation eventlist[] = { I_OnUserJoin, I_OnPostJoin, I_OnSetAway, I_OnEvent, I_OnRehash };
 -              ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
        }
  
 -      void OnRehash(User* user)
 +      void OnRehash(User* user) CXX11_OVERRIDE
        {
                ConfigTag* conf = ServerInstance->Config->ConfValue("ircv3");
-               accountnotify = conf->getBool("accoutnotify", true);
 -              accountnotify = conf->getBool("accountnotify", conf->getBool("accoutnotify", true));
++              accountnotify = conf->getBool("accountnotify", true);
                awaynotify = conf->getBool("awaynotify", true);
                extendedjoin = conf->getBool("extendedjoin", true);
        }
index 2a3dff6ee764fd58e50e0d68f7547f5ecf760c7b,0a7dc8ed96b9b2d551fa03cac6b528da4531b50a..a19a184e08bb91d4cdfd05c15581ce99e7815e91
  
  
  #include "inspircd.h"
++#include "listmode.h"
 +#include <fstream>
  
 -/* $ModDesc: Provides support for channel mode +P to provide permanent channels */
  
 -// Not in a class due to circular dependancy hell.
 -static std::string permchannelsconf;
 -static bool WriteDatabase(Module* mod, bool save_listmodes)
+ struct ListModeData
+ {
+       std::string modes;
+       std::string params;
+ };
 +/** Handles the +P channel mode
 + */
 +class PermChannel : public ModeHandler
  {
 -      FILE *f;
 -
 -      if (permchannelsconf.empty())
 + public:
 +      PermChannel(Module* Creator)
 +              : ModeHandler(Creator, "permanent", 'P', PARAM_NONE, MODETYPE_CHANNEL)
        {
 -              // Fake success.
 -              return true;
 +              oper = true;
        }
  
 -      std::string tempname = permchannelsconf + ".tmp";
 +      ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding)
 +      {
 +              if (adding == channel->IsModeSet(this))
 +                      return MODEACTION_DENY;
 +
 +              channel->SetMode(this, adding);
 +              if (!adding)
 +                      channel->CheckDestroy();
 +
 +              return MODEACTION_ALLOW;
 +      }
 +};
  
- static bool WriteDatabase(PermChannel& permchanmode)
 +// Not in a class due to circular dependancy hell.
 +static std::string permchannelsconf;
++static bool WriteDatabase(PermChannel& permchanmode, Module* mod, bool save_listmodes)
 +{
++      ChanModeReference ban(mod, "ban");
        /*
         * We need to perform an atomic write so as not to fuck things up.
 -       * So, let's write to a temporary file, flush and sync the FD, then rename the file..
 -       *              -- w00t
 +       * So, let's write to a temporary file, flush it, then rename the file..
 +       *     -- w00t
         */
 -      f = fopen(tempname.c_str(), "w");
 -      if (!f)
 +      
 +      // If the user has not specified a configuration file then we don't write one.
 +      if (permchannelsconf.empty())
 +              return true;
 +
 +      std::string permchannelsnewconf = permchannelsconf + ".tmp";
 +      std::ofstream stream(permchannelsnewconf.c_str());
 +      if (!stream.is_open())
        {
 -              ServerInstance->Logs->Log("m_permchannels",DEFAULT, "permchannels: Cannot create database! %s (%d)", strerror(errno), errno);
 +              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Cannot create database! %s (%d)", strerror(errno), errno);
                ServerInstance->SNO->WriteToSnoMask('a', "database: cannot create new db: %s (%d)", strerror(errno), errno);
                return false;
        }
        for (chan_hash::const_iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); i++)
        {
                Channel* chan = i->second;
 -              if (!chan->IsModeSet('P'))
 +              if (!chan->IsModeSet(permchanmode))
                        continue;
  
 -                      lm.modes = std::string(chan->bans.size(), 'b');
 -                      for (BanList::const_iterator j = chan->bans.begin(); j != chan->bans.end(); ++j)
 -                      {
 -                              lm.params += j->data;
 -                              lm.params += ' ';
 -                      }
+               std::string chanmodes = chan->ChanModes(true);
+               if (save_listmodes)
+               {
+                       ListModeData lm;
+                       // Bans are managed by the core, so we have to process them separately
 -                      FOREACH_MOD(I_OnSyncChannel, OnSyncChannel(chan, mod, &lm));
++                      static_cast<ListModeBase*>(*ban)->DoSyncChannel(chan, mod, &lm);
+                       // All other listmodes are managed by modules, so we need to ask them (call their
+                       // OnSyncChannel() handler) to give our ProtoSendMode() a list of modes that are
+                       // set on the channel. The ListModeData struct is passed as an opaque pointer
+                       // that will be passed back to us by the module handling the mode.
 -              std::string chants = ConvToStr(chan->age);
 -              std::string topicts = ConvToStr(chan->topicset);
 -              const char* items[] =
 -              {
 -                      "<permchannels channel=",
 -                      chan->name.c_str(),
 -                      " ts=",
 -                      chants.c_str(),
 -                      " topic=",
 -                      chan->topic.c_str(),
 -                      " topicts=",
 -                      topicts.c_str(),
 -                      " topicsetby=",
 -                      chan->setby.c_str(),
 -                      " modes=",
 -                      chanmodes.c_str(),
 -                      ">\n"
 -              };
 -
 -              line.clear();
 -              int item = 0, ipos = 0;
 -              while (item < 13)
 -              {
 -                      char c = items[item][ipos++];
 -                      if (c == 0)
 -                      {
 -                              // end of this string; hop to next string, insert a quote
 -                              item++;
 -                              ipos = 0;
 -                              c = '"';
 -                      }
 -                      else if (c == '\\' || c == '"')
 -                      {
 -                              line += '\\';
 -                      }
 -                      line += c;
 -              }
 -
 -              // Erase last '"'
 -              line.erase(line.end()-1);
 -              fputs(line.c_str(), f);
++                      FOREACH_MOD(OnSyncChannel, (chan, mod, &lm));
+                       if (!lm.modes.empty())
+                       {
+                               // Remove the last space
+                               lm.params.erase(lm.params.end()-1);
+                               // If there is at least a space in chanmodes (that is, a non-listmode has a parameter)
+                               // insert the listmode mode letters before the space. Otherwise just append them.
+                               std::string::size_type p = chanmodes.find(' ');
+                               if (p == std::string::npos)
+                                       chanmodes += lm.modes;
+                               else
+                                       chanmodes.insert(p, lm.modes);
+                               // Append the listmode parameters (the masks themselves)
+                               chanmodes += ' ';
+                               chanmodes += lm.params;
+                       }
+               }
-                       << "\" modes=\"" << ServerConfig::Escape(chan->ChanModes(true))
 +              stream << "<permchannels channel=\"" << ServerConfig::Escape(chan->name)
++                      << "\" ts=\"" << chan->age
 +                      << "\" topic=\"" << ServerConfig::Escape(chan->topic)
++                      << "\" topicts=\"" << chan->topicset
++                      << "\" topicsetby=\"" << ServerConfig::Escape(chan->setby)
++                      << "\" modes=\"" << ServerConfig::Escape(chanmodes)
 +                      << "\">" << std::endl;
        }
  
 -      int write_error = 0;
 -      write_error = ferror(f);
 -      write_error |= fclose(f);
 -      if (write_error)
 +      if (stream.fail())
        {
 -              ServerInstance->Logs->Log("m_permchannels",DEFAULT, "permchannels: Cannot write to new database! %s (%d)", strerror(errno), errno);
 +              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Cannot write to new database! %s (%d)", strerror(errno), errno);
                ServerInstance->SNO->WriteToSnoMask('a', "database: cannot write to new db: %s (%d)", strerror(errno), errno);
                return false;
        }
@@@ -154,9 -256,11 +199,11 @@@ public
                return Module::cull();
        }
  
 -      virtual void OnRehash(User *user)
 +      void OnRehash(User *user) CXX11_OVERRIDE
        {
-               permchannelsconf = ServerInstance->Config->ConfValue("permchanneldb")->getString("filename");
+               ConfigTag* tag = ServerInstance->Config->ConfValue("permchanneldb");
+               permchannelsconf = tag->getString("filename");
+               save_listmodes = tag->getBool("listmodes");
        }
  
        void LoadDatabase()
                {
                        ConfigTag* tag = i->second;
                        std::string channel = tag->getString("channel");
--                      std::string topic = tag->getString("topic");
                        std::string modes = tag->getString("modes");
  
-                       if (channel.empty())
+                       if ((channel.empty()) || (channel.length() > ServerInstance->Config->Limits.ChanMax))
                        {
-                               ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Malformed permchannels tag with empty channel name.");
 -                              ServerInstance->Logs->Log("m_permchannels", DEFAULT, "Ignoring permchannels tag with empty or too long channel name (\"" + channel + "\")");
++                              ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring permchannels tag with empty or too long channel name (\"" + channel + "\")");
                                continue;
                        }
  
  
                        if (!c)
                        {
-                               c = new Channel(channel, ServerInstance->Time());
-                               if (!topic.empty())
 -                              time_t TS = tag->getInt("ts");
 -                              c = new Channel(channel, ((TS > 0) ? TS : ServerInstance->Time()));
++                              time_t TS = tag->getInt("ts", ServerInstance->Time(), 1);
++                              c = new Channel(channel, TS);
 -                              c->SetTopic(NULL, topic, true);
 -                              c->setby = tag->getString("topicsetby");
 -                              if (c->setby.empty())
 -                                      c->setby = ServerInstance->Config->ServerName;
+                               unsigned int topicset = tag->getInt("topicts");
 -                              // SetTopic() sets the topic TS to now, if there was no topicts saved then don't overwrite that with a 0
 -                              if (topicset > 0)
++                              c->topic = tag->getString("topic");
++
++                              if ((topicset != 0) || (!c->topic.empty()))
 +                              {
-                                       c->SetTopic(ServerInstance->FakeClient, topic);
-                                       /*
-                                        * Due to the way protocol works in 1.2, we need to hack the topic TS in such a way that this
-                                        * topic will always win over others.
-                                        *
-                                        * This is scheduled for (proper) fixing in a later release, and can be removed at a later date.
-                                        */
-                                       c->topicset = 42;
++                                      if (topicset == 0)
++                                              topicset = ServerInstance->Time();
+                                       c->topicset = topicset;
++                                      c->setby = tag->getString("topicsetby");
++                                      if (c->setby.empty())
++                                              c->setby = ServerInstance->Config->ServerName;
 +                              }
-                               ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Added %s with topic %s", channel.c_str(), topic.c_str());
 -                              ServerInstance->Logs->Log("m_permchannels", DEBUG, "Added %s with topic %s", channel.c_str(), topic.c_str());
++                              ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Added %s with topic %s", channel.c_str(), c->topic.c_str());
  
                                if (modes.empty())
                                        continue;
                        dirty = true;
        }
  
 -      void OnBackgroundTimer(time_t)
 +      void OnBackgroundTimer(time_t) CXX11_OVERRIDE
        {
                if (dirty)
-                       WriteDatabase(p);
 -                      WriteDatabase(this, save_listmodes);
++                      WriteDatabase(p, this, save_listmodes);
                dirty = false;
        }
  
                }
        }
  
 -      virtual Version GetVersion()
+       void ProtoSendMode(void* opaque, TargetTypeFlags type, void* target, const std::vector<std::string>& modes, const std::vector<TranslateType>& translate)
+       {
+               // We never pass an empty modelist but better be sure
+               if (modes.empty())
+                       return;
+               ListModeData* lm = static_cast<ListModeData*>(opaque);
+               // Append the mode letters without the trailing '+' (for example "IIII", "gg")
+               lm->modes.append(modes[0].begin()+1, modes[0].end());
+               // Append the parameters
+               for (std::vector<std::string>::const_iterator i = modes.begin()+1; i != modes.end(); ++i)
+               {
+                       lm->params += *i;
+                       lm->params += ' ';
+               }
+       }
 +      Version GetVersion() CXX11_OVERRIDE
        {
                return Version("Provides support for channel mode +P to provide permanent channels",VF_VENDOR);
        }
index 93320757c7e8249c4fae49dac12e37b6ff87f0e3,ee18c8e8742dd02021ce2c978a447e8fb8ef1f51..0fb4468772bb83ea62bc2d58f3e66d59c4ab1ff4
@@@ -88,139 -83,174 +88,163 @@@ CmdResult CommandFJoin::Handle(User* sr
        else
        {
                time_t ourTS = chan->age;
 -
                if (TS != ourTS)
 +              {
                        ServerInstance->SNO->WriteToSnoMask('d', "Merge FJOIN received for %s, ourTS: %lu, TS: %lu, difference: %lu",
                                chan->name.c_str(), (unsigned long)ourTS, (unsigned long)TS, (unsigned long)(ourTS - TS));
 -              /* If our TS is less than theirs, we dont accept their modes */
 -              if (ourTS < TS)
 -              {
 -                      ServerInstance->SNO->WriteToSnoMask('d', "NOT Applying modes from other side");
 -                      apply_other_sides_modes = false;
 -              }
 -              else if (ourTS > TS)
 -              {
 -                      /* Our TS greater than theirs, clear all our modes from the channel, accept theirs. */
 -                      ServerInstance->SNO->WriteToSnoMask('d', "Removing our modes, accepting remote");
 -                      parameterlist param_list;
 -                      if (Utils->AnnounceTSChange)
 -                              chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :TS for %s changed from %lu to %lu", chan->name.c_str(), channel.c_str(), (unsigned long) ourTS, (unsigned long) TS);
 -                      // while the name is equal in case-insensitive compare, it might differ in case; use the remote version
 -                      chan->name = channel;
 -                      chan->age = TS;
 -                      chan->ClearInvites();
 -                      param_list.push_back(channel);
 -                      this->RemoveStatus(ServerInstance->FakeClient, param_list);
 -
 -                      // XXX: If the channel does not exist in the chan hash at this point, create it so the remote modes can be applied on it.
 -                      // This happens to 0-user permanent channels on the losing side, because those are removed (from the chan hash, then
 -                      // deleted later) as soon as the permchan mode is removed from them.
 -                      if (ServerInstance->FindChan(channel) == NULL)
 +                      /* If our TS is less than theirs, we dont accept their modes */
 +                      if (ourTS < TS)
                        {
 -                              chan = new Channel(channel, TS);
 +                              apply_other_sides_modes = false;
 +                      }
 +                      else if (ourTS > TS)
 +                      {
 +                              /* Our TS greater than theirs, clear all our modes from the channel, accept theirs. */
 +                              if (Utils->AnnounceTSChange)
 +                                      chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :TS for %s changed from %lu to %lu", chan->name.c_str(), channel.c_str(), (unsigned long) ourTS, (unsigned long) TS);
 +
 +                              // while the name is equal in case-insensitive compare, it might differ in case; use the remote version
 +                              chan->name = channel;
 +                              chan->age = TS;
 +                              chan->ClearInvites();
 +
 +                              CommandFJoin::RemoveStatus(chan);
 +
 +                              // XXX: If the channel does not exist in the chan hash at this point, create it so the remote modes can be applied on it.
 +                              // This happens to 0-user permanent channels on the losing side, because those are removed (from the chan hash, then
 +                              // deleted later) as soon as the permchan mode is removed from them.
 +                              if (ServerInstance->FindChan(channel) == NULL)
 +                              {
 +                                      chan = new Channel(channel, TS);
 +                              }
                        }
                }
 -              // The silent case here is ourTS == TS, we don't need to remove modes here, just to merge them later on.
        }
  
 -      /* First up, apply their modes if they won the TS war */
 +      /* First up, apply their channel modes if they won the TS war */
        if (apply_other_sides_modes)
        {
+               // Need to use a modestacker here due to maxmodes
+               irc::modestacker stack(true);
+               std::vector<std::string>::const_iterator paramit = params.begin() + 3;
+               const std::vector<std::string>::const_iterator lastparamit = ((params.size() > 3) ? (params.end() - 1) : params.end());
+               for (std::string::const_iterator i = params[2].begin(); i != params[2].end(); ++i)
+               {
+                       ModeHandler* mh = ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL);
+                       if (!mh)
+                               continue;
+                       std::string modeparam;
+                       if ((paramit != lastparamit) && (mh->GetNumParams(true)))
+                       {
+                               modeparam = *paramit;
+                               ++paramit;
+                       }
+                       stack.Push(*i, modeparam);
+               }
                std::vector<std::string> modelist;
-               modelist.push_back(channel);
  
-               /* Remember, params[params.size() - 1] is userlist, and we don't want to apply *that* */
-               modelist.insert(modelist.end(), params.begin()+2, params.end()-1);
-               ServerInstance->Modes->Process(modelist, srcuser, ModeParser::MODE_LOCALONLY | ModeParser::MODE_MERGE);
+               // Mode parser needs to know what channel to act on.
+               modelist.push_back(params[0]);
+               while (stack.GetStackedLine(modelist))
+               {
 -                      ServerInstance->Modes->Process(modelist, srcuser, true);
++                      ServerInstance->Modes->Process(modelist, srcuser, ModeParser::MODE_LOCALONLY | ModeParser::MODE_MERGE);
+                       modelist.erase(modelist.begin() + 1, modelist.end());
+               }
 -
 -              ServerInstance->Modes->Process(modelist, srcuser, true);
        }
  
 -      /* Now, process every 'modes,nick' pair */
 +      irc::modestacker modestack(true);
 +      TreeSocket* src_socket = Utils->FindServer(srcuser->server)->GetSocket();
 +
 +      /* Now, process every 'modes,uuid' pair */
 +      irc::tokenstream users(*params.rbegin());
 +      std::string item;
 +      irc::modestacker* modestackptr = (apply_other_sides_modes ? &modestack : NULL);
        while (users.GetToken(item))
        {
 -              const char* usr = item.c_str();
 -              if (usr && *usr)
 -              {
 -                      const char* unparsedmodes = usr;
 -                      std::string modes;
 -
 -
 -                      /* Iterate through all modes for this user and check they are valid. */
 -                      while ((*unparsedmodes) && (*unparsedmodes != ','))
 -                      {
 -                              ModeHandler *mh = ServerInstance->Modes->FindMode(*unparsedmodes, MODETYPE_CHANNEL);
 -                              if (!mh)
 -                              {
 -                                      ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Unrecognised mode %c, dropping link", *unparsedmodes);
 -                                      return CMD_INVALID;
 -                              }
 -
 -                              modes += *unparsedmodes;
 -                              usr++;
 -                              unparsedmodes++;
 -                      }
 +              if (!ProcessModeUUIDPair(item, src_socket, chan, modestackptr))
 +                      return CMD_INVALID;
 +      }
  
 -                      /* Advance past the comma, to the nick */
 -                      usr++;
 +      /* Flush mode stacker if we lost the FJOIN or had equal TS */
 +      if (apply_other_sides_modes)
 +              CommandFJoin::ApplyModeStack(srcuser, chan, modestack);
  
 -                      /* Check the user actually exists */
 -                      who = ServerInstance->FindUUID(usr);
 -                      if (who)
 -                      {
 -                              /* Check that the user's 'direction' is correct */
 -                              TreeServer* route_back_again = Utils->BestRouteTo(who->server);
 -                              if ((!route_back_again) || (route_back_again->GetSocket() != src_socket))
 -                                      continue;
 +      return CMD_SUCCESS;
 +}
  
 -                              /* Add any modes this user had to the mode stack */
 -                              for (std::string::iterator x = modes.begin(); x != modes.end(); ++x)
 -                                      modestack.Push(*x, who->nick);
 +bool CommandFJoin::ProcessModeUUIDPair(const std::string& item, TreeSocket* src_socket, Channel* chan, irc::modestacker* modestack)
 +{
 +      std::string::size_type comma = item.find(',');
  
 -                              Channel::JoinUser(who, channel.c_str(), true, "", route_back_again->bursting, TS);
 -                      }
 -                      else
 -                      {
 -                              ServerInstance->Logs->Log("m_spanningtree",SPARSE, "Ignored nonexistant user %s in fjoin to %s (probably quit?)", usr, channel.c_str());
 -                              continue;
 -                      }
 -              }
 +      // Comma not required anymore if the user has no modes
 +      std::string uuid = ((comma == std::string::npos) ? item : item.substr(comma+1));
 +      User* who = ServerInstance->FindUUID(uuid);
 +      if (!who)
 +      {
 +              // Probably KILLed, ignore
 +              return true;
        }
  
 -      /* Flush mode stacker if we lost the FJOIN or had equal TS */
 -      if (apply_other_sides_modes)
 +      /* Check that the user's 'direction' is correct */
 +      TreeServer* route_back_again = Utils->BestRouteTo(who->server);
 +      if ((!route_back_again) || (route_back_again->GetSocket() != src_socket))
        {
 -              parameterlist stackresult;
 -              stackresult.push_back(channel);
 +              return true;
 +      }
  
 -              while (modestack.GetStackedLine(stackresult))
 +      /* Check if the user received at least one mode */
 +      if ((modestack) && (comma > 0) && (comma != std::string::npos))
 +      {
 +              /* Iterate through the modes and see if they are valid here, if so, apply */
 +              std::string::const_iterator commait = item.begin()+comma;
 +              for (std::string::const_iterator i = item.begin(); i != commait; ++i)
                {
 -                      ServerInstance->SendMode(stackresult, srcuser);
 -                      stackresult.erase(stackresult.begin() + 1, stackresult.end());
 +                      if (!ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL))
 +                      {
 +                              ServerInstance->SNO->WriteToSnoMask('d', "Unrecognised mode '%c' for a user in FJOIN, dropping link", *i);
 +                              return false;
 +                      }
 +
 +                      /* Add any modes this user had to the mode stack */
 +                      modestack->Push(*i, who->nick);
                }
        }
 -      return CMD_SUCCESS;
 +
 +      chan->ForceJoin(who, NULL, route_back_again->bursting);
 +      return true;
  }
  
 -void CommandFJoin::RemoveStatus(User* srcuser, parameterlist &params)
 +void CommandFJoin::RemoveStatus(Channel* c)
  {
 -      if (params.size() < 1)
 -              return;
 -
 -      Channel* c = ServerInstance->FindChan(params[0]);
 +      irc::modestacker stack(false);
  
 -      if (c)
 +      for (char modeletter = 'A'; modeletter <= 'z'; ++modeletter)
        {
 -              irc::modestacker stack(false);
 -              parameterlist stackresult;
 -              stackresult.push_back(c->name);
 +              ModeHandler* mh = ServerInstance->Modes->FindMode(modeletter, MODETYPE_CHANNEL);
 +
 +              /* Passing a pointer to a modestacker here causes the mode to be put onto the mode stack,
 +               * rather than applied immediately. Module unloads require this to be done immediately,
 +               * for this function we require tidyness instead. Fixes bug #493
 +               */
 +              if (mh)
 +                      mh->RemoveMode(c, stack);
 +      }
  
 -              for (char modeletter = 'A'; modeletter <= 'z'; ++modeletter)
 -              {
 -                      ModeHandler* mh = ServerInstance->Modes->FindMode(modeletter, MODETYPE_CHANNEL);
 +      ApplyModeStack(ServerInstance->FakeClient, c, stack);
 +}
  
 -                      /* Passing a pointer to a modestacker here causes the mode to be put onto the mode stack,
 -                       * rather than applied immediately. Module unloads require this to be done immediately,
 -                       * for this function we require tidyness instead. Fixes bug #493
 -                       */
 -                      if (mh)
 -                              mh->RemoveMode(c, &stack);
 -              }
 +void CommandFJoin::ApplyModeStack(User* srcuser, Channel* c, irc::modestacker& stack)
 +{
 +      parameterlist stackresult;
 +      stackresult.push_back(c->name);
  
 -              while (stack.GetStackedLine(stackresult))
 -              {
 -                      ServerInstance->SendMode(stackresult, srcuser);
 -                      stackresult.erase(stackresult.begin() + 1, stackresult.end());
 -              }
 +      while (stack.GetStackedLine(stackresult))
 +      {
 +              ServerInstance->Modes->Process(stackresult, srcuser, ModeParser::MODE_LOCALONLY);
 +              stackresult.erase(stackresult.begin() + 1, stackresult.end());
        }
  }
 -
index 98f9a304bb2e7f36f881813b545b11ceef7736cd,ce1792a02b6717a89a3939cafcb9b23fb57746f9..9ece3c03dcf4e1b90ff94b766f76a9eb8be07050
@@@ -669,6 -815,13 +669,13 @@@ void ModuleSpanningTree::OnUnloadModule
                        sock->Close();
                }
        }
 -              if (sock->GetIOHook() == mod)
+       for (SpanningTreeUtilities::TimeoutList::const_iterator i = Utils->timeoutlist.begin(); i != Utils->timeoutlist.end(); ++i)
+       {
+               TreeSocket* sock = i->first;
++              if (sock->GetIOHook() && sock->GetIOHook()->creator == mod)
+                       sock->Close();
+       }
  }
  
  // note: the protocol does not allow direct umode +o except
index 9e6f41852f2d1c4ba631551d8c95c4859a14af85,a0543b6bd30ae0d3e12b8d23b9d7b9cd2eccfa8a..164bed1ca61b46f07b7b5684cebf741242eb1675
@@@ -46,10 -49,8 +46,11 @@@ typedef TR1NS::unordered_map<std::strin
   */
  class SpanningTreeUtilities : public classbase
  {
 +      CacheRefreshTimer RefreshTimer;
 +
   public:
 +      typedef std::set<TreeSocket*> TreeSocketSet;
+       typedef std::map<TreeSocket*, std::pair<std::string, int> > TimeoutList;
  
        /** Creator module
         */
Simple merge
Simple merge
index 68f14cc3840e7a5878097514cd28c50ed60f728f,6913076a297f5019925494af4d0f9007194c2908..099f793a143c67ce75e14e940dc03d97220f1873
@@@ -61,9 -61,9 +61,9 @@@ EPollEngine::EPollEngine(
        }
        else
        {
 -              ServerInstance->Logs->Log("SOCKET", DEFAULT, "ERROR: Can't determine maximum number of open sockets!");
 +              ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Can't determine maximum number of open sockets!");
                std::cout << "ERROR: Can't determine maximum number of open sockets!" << std::endl;
-               ServerInstance->Exit(EXIT_STATUS_SOCKETENGINE);
+               ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE);
        }
  
        // This is not a maximum, just a hint at the eventual number of sockets that may be polled.
  
        if (EngineHandle == -1)
        {
 -              ServerInstance->Logs->Log("SOCKET",DEFAULT, "ERROR: Could not initialize socket engine: %s", strerror(errno));
 -              ServerInstance->Logs->Log("SOCKET",DEFAULT, "ERROR: Your kernel probably does not have the proper features. This is a fatal error, exiting now.");
 +              ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Could not initialize socket engine: %s", strerror(errno));
 +              ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Your kernel probably does not have the proper features. This is a fatal error, exiting now.");
                std::cout << "ERROR: Could not initialize epoll socket engine: " << strerror(errno) << std::endl;
                std::cout << "ERROR: Your kernel probably does not have the proper features. This is a fatal error, exiting now." << std::endl;
-               ServerInstance->Exit(EXIT_STATUS_SOCKETENGINE);
+               ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE);
        }
  
        ref = new EventHandler* [GetMaxFds()];
index a6c84133a10d52c5db6b97eb768db44556d9cd00,8694a0bdd84d4cc2e5249aba5fa4d3f3ed6f5732..99ac51499c012e231771a3192136286f56d2acb0
@@@ -72,9 -72,9 +72,9 @@@ KQueueEngine::KQueueEngine(
        sysctl(mib, 2, &MAX_DESCRIPTORS, &len, NULL, 0);
        if (MAX_DESCRIPTORS <= 0)
        {
 -              ServerInstance->Logs->Log("SOCKET", DEFAULT, "ERROR: Can't determine maximum number of open sockets!");
 +              ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Can't determine maximum number of open sockets!");
                std::cout << "ERROR: Can't determine maximum number of open sockets!" << std::endl;
-               ServerInstance->Exit(EXIT_STATUS_SOCKETENGINE);
+               ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE);
        }
  
        this->RecoverFromFork();
@@@ -93,11 -93,11 +93,11 @@@ void KQueueEngine::RecoverFromFork(
        EngineHandle = kqueue();
        if (EngineHandle == -1)
        {
 -              ServerInstance->Logs->Log("SOCKET",DEFAULT, "ERROR: Could not initialize socket engine. Your kernel probably does not have the proper features.");
 -              ServerInstance->Logs->Log("SOCKET",DEFAULT, "ERROR: this is a fatal error, exiting now.");
 +              ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Could not initialize socket engine. Your kernel probably does not have the proper features.");
 +              ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: this is a fatal error, exiting now.");
                std::cout << "ERROR: Could not initialize socket engine. Your kernel probably does not have the proper features." << std::endl;
                std::cout << "ERROR: this is a fatal error, exiting now." << std::endl;
-               ServerInstance->Exit(EXIT_STATUS_SOCKETENGINE);
+               ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE);
        }
        CurrentSetSize = 0;
  }
index 37f4b6836a09038d3b3abd3199c187a4608c9716,ea7780686572405a54f5f92baab1a17df225bf63..493b22630e406d4dbbf362efaa742d614ed90d30
@@@ -102,11 -86,10 +83,10 @@@ PollEngine::PollEngine(
        }
        else
        {
 -              ServerInstance->Logs->Log("SOCKET", DEFAULT, "ERROR: Can't determine maximum number of open sockets: %s", strerror(errno));
 +              ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Can't determine maximum number of open sockets: %s", strerror(errno));
                std::cout << "ERROR: Can't determine maximum number of open sockets: " << strerror(errno) << std::endl;
-               ServerInstance->Exit(EXIT_STATUS_SOCKETENGINE);
+               ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE);
        }
- #endif
  
        ref = new EventHandler* [GetMaxFds()];
        events = new struct pollfd[GetMaxFds()];
index 8a2fb87f409ced96fd8ffa7b178f2f5901cb14e5,f7c547d4510d79367297db443d3868db868dc579..ba4e8f2d7330ec580c7975d5d8909d17a7f0f82c
@@@ -74,19 -75,19 +74,19 @@@ PortsEngine::PortsEngine(
        }
        else
        {
 -              ServerInstance->Logs->Log("SOCKET", DEFAULT, "ERROR: Can't determine maximum number of open sockets!");
 +              ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Can't determine maximum number of open sockets!");
                std::cout << "ERROR: Can't determine maximum number of open sockets!" << std::endl;
-               ServerInstance->Exit(EXIT_STATUS_SOCKETENGINE);
+               ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE);
        }
        EngineHandle = port_create();
  
        if (EngineHandle == -1)
        {
 -              ServerInstance->Logs->Log("SOCKET",SPARSE,"ERROR: Could not initialize socket engine: %s", strerror(errno));
 -              ServerInstance->Logs->Log("SOCKET",SPARSE,"ERROR: This is a fatal error, exiting now.");
 +              ServerInstance->Logs->Log("SOCKET", LOG_SPARSE, "ERROR: Could not initialize socket engine: %s", strerror(errno));
 +              ServerInstance->Logs->Log("SOCKET", LOG_SPARSE, "ERROR: This is a fatal error, exiting now.");
                std::cout << "ERROR: Could not initialize socket engine: " << strerror(errno) << std::endl;
                std::cout << "ERROR: This is a fatal error, exiting now." << std::endl;
-               ServerInstance->Exit(EXIT_STATUS_SOCKETENGINE);
+               ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE);
        }
        CurrentSetSize = 0;
  
Simple merge