]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/extra/m_ssl_gnutls.cpp
Add stuff so that modules can hook users by altering a pointer in the User class...
[user/henk/code/inspircd.git] / src / modules / extra / m_ssl_gnutls.cpp
index 037d2cf72b0360b2698d47407366a814dab8c6e4..f3af386aea579689b0cc5f730c8dcaf393906beb 100644 (file)
@@ -1 +1,913 @@
-/*       +------------------------------------+\r *       | Inspire Internet Relay Chat Daemon |\r *       +------------------------------------+\r *\r *  InspIRCd: (C) 2002-2007 InspIRCd Development Team\r * See: http://www.inspircd.org/wiki/index.php/Credits\r *\r * This program is free but copyrighted software; see\r *            the file COPYING for details.\r *\r * ---------------------------------------------------\r */\r\r#include "inspircd.h"\r\r#include <gnutls/gnutls.h>\r#include <gnutls/x509.h>\r\r#include "inspircd_config.h"\r#include "configreader.h"\r#include "users.h"\r#include "channels.h"\r#include "modules.h"\r#include "socket.h"\r#include "hashcomp.h"\r#include "transport.h"\r\r#ifdef WINDOWS\r#pragma comment(lib, "libgnutls-13.lib")\r#undef MAX_DESCRIPTORS\r#define MAX_DESCRIPTORS 10000\r#endif\r\r/* $ModDesc: Provides SSL support for clients */\r/* $CompileFlags: exec("libgnutls-config --cflags") */\r/* $LinkerFlags: rpath("libgnutls-config --libs") exec("libgnutls-config --libs") */\r/* $ModDep: transport.h */\r\r\renum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };\r\rbool isin(int port, const std::vector<int> &portlist)\r{\r     for(unsigned int i = 0; i < portlist.size(); i++)\r              if(portlist[i] == port)\r                        return true;\r\r  return false;\r}\r\r/** Represents an SSL user's extra data\r */\rclass issl_session : public classbase\r{\rpublic:\r   gnutls_session_t sess;\r issl_status status;\r    std::string outbuf;\r    int inbufoffset;\r       char* inbuf;\r   int fd;\r};\r\rclass ModuleSSLGnuTLS : public Module\r{\r\r   ConfigReader* Conf;\r\r   char* dummy;\r\r  std::vector<int> listenports;\r\r int inbufsize;\r issl_session sessions[MAX_DESCRIPTORS];\r\r       gnutls_certificate_credentials x509_cred;\r      gnutls_dh_params dh_params;\r\r   std::string keyfile;\r   std::string certfile;\r  std::string cafile;\r    std::string crlfile;\r   std::string sslports;\r  int dh_bits;\r\r  int clientactive;\r\r public:\r\r   ModuleSSLGnuTLS(InspIRCd* Me)\r          : Module(Me)\r   {\r              ServerInstance->PublishInterface("InspSocketHook", this);\r\r             // Not rehashable...because I cba to reduce all the sizes of existing buffers.\r         inbufsize = ServerInstance->Config->NetBufferSize;\r\r            gnutls_global_init(); // This must be called once in the program\r\r              if(gnutls_certificate_allocate_credentials(&x509_cred) != 0)\r                   ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to allocate certificate credentials");\r\r          // Guessing return meaning\r             if(gnutls_dh_params_init(&dh_params) < 0)\r                      ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to initialise DH parameters");\r\r          // Needs the flag as it ignores a plain /rehash\r                OnRehash(NULL,"ssl");\r\r         // Void return, guess we assume success\r                gnutls_certificate_set_dh_params(x509_cred, dh_params);\r        }\r\r     virtual void OnRehash(userrec* user, const std::string &param)\r {\r              if(param != "ssl")\r                     return;\r\r               Conf = new ConfigReader(ServerInstance);\r\r              for(unsigned int i = 0; i < listenports.size(); i++)\r           {\r                      ServerInstance->Config->DelIOHook(listenports[i]);\r             }\r\r             listenports.clear();\r           clientactive = 0;\r              sslports.clear();\r\r             for(int i = 0; i < Conf->Enumerate("bind"); i++)\r               {\r                      // For each <bind> tag\r                 std::string x = Conf->ReadValue("bind", "type", i);\r                    if(((x.empty()) || (x == "clients")) && (Conf->ReadValue("bind", "ssl", i) == "gnutls"))\r                       {\r                              // Get the port we're meant to be listening on with SSL\r                                std::string port = Conf->ReadValue("bind", "port", i);\r                         irc::portparser portrange(port, false);\r                                long portno = -1;\r                              while ((portno = portrange.GetToken()))\r                                {\r                                      clientactive++;\r                                        try\r                                    {\r                                              if (ServerInstance->Config->AddIOHook(portno, this))\r                                           {\r                                                      listenports.push_back(portno);\r                                                 for (size_t i = 0; i < ServerInstance->Config->ports.size(); i++)\r                                                              if (ServerInstance->Config->ports[i]->GetPort() == portno)\r                                                                     ServerInstance->Config->ports[i]->SetDescription("ssl");\r                                                       ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %d", portno);\r                                                     sslports.append("*:").append(ConvToStr(portno)).append(";");\r                                           }\r                                              else\r                                           {\r                                                      ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: FAILED to enable SSL on port %d, maybe you have another ssl or similar module loaded?", portno);\r                                                }\r                                      }\r                                      catch (ModuleException &e)\r                                     {\r                                              ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: FAILED to enable SSL on port %d: %s. Maybe it's already hooked by the same port on a different IP, or you have an other SSL or similar module loaded?", portno, e.GetReason());\r                                 }\r                              }\r                      }\r              }\r\r             std::string confdir(ServerInstance->ConfigFileName);\r           // +1 so we the path ends with a /\r             confdir = confdir.substr(0, confdir.find_last_of('/') + 1);\r\r           cafile  = Conf->ReadValue("gnutls", "cafile", 0);\r              crlfile = Conf->ReadValue("gnutls", "crlfile", 0);\r             certfile        = Conf->ReadValue("gnutls", "certfile", 0);\r            keyfile = Conf->ReadValue("gnutls", "keyfile", 0);\r             dh_bits = Conf->ReadInteger("gnutls", "dhbits", 0, false);\r\r            // Set all the default values needed.\r          if (cafile.empty())\r                    cafile = "ca.pem";\r\r            if (crlfile.empty())\r                   crlfile = "crl.pem";\r\r          if (certfile.empty())\r                  certfile = "cert.pem";\r\r                if (keyfile.empty())\r                   keyfile = "key.pem";\r\r          if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))\r                       dh_bits = 1024;\r\r               // Prepend relative paths with the path to the config directory.\r               if(cafile[0] != '/')\r                   cafile = confdir + cafile;\r\r            if(crlfile[0] != '/')\r                  crlfile = confdir + crlfile;\r\r          if(certfile[0] != '/')\r                 certfile = confdir + certfile;\r\r                if(keyfile[0] != '/')\r                  keyfile = confdir + keyfile;\r\r          int ret;\r\r              if((ret =gnutls_certificate_set_x509_trust_file(x509_cred, cafile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)\r                  ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to set X.509 trust file '%s': %s", cafile.c_str(), gnutls_strerror(ret));\r\r               if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)\r                 ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to set X.509 CRL file '%s': %s", crlfile.c_str(), gnutls_strerror(ret));\r\r                if((ret = gnutls_certificate_set_x509_key_file (x509_cred, certfile.c_str(), keyfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)\r               {\r                      // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException\r                      throw ModuleException("Unable to load GnuTLS server certificate: " + std::string(gnutls_strerror(ret)));\r               }\r\r             // This may be on a large (once a day or week) timer eventually.\r               GenerateDHParams();\r\r           DELETE(Conf);\r  }\r\r     void GenerateDHParams()\r        {\r              // Generate Diffie Hellman parameters - for use with DHE\r               // kx algorithms. These should be discarded and regenerated\r            // once a day, once a week or once a month. Depending on the\r           // security requirements.\r\r             int ret;\r\r              if((ret = gnutls_dh_params_generate2(dh_params, dh_bits)) < 0)\r                 ServerInstance->Log(DEFAULT, "m_ssl_gnutls.so: Failed to generate DH parameters (%d bits): %s", dh_bits, gnutls_strerror(ret));\r        }\r\r     virtual ~ModuleSSLGnuTLS()\r     {\r              gnutls_dh_params_deinit(dh_params);\r            gnutls_certificate_free_credentials(x509_cred);\r                gnutls_global_deinit();\r        }\r\r     virtual void OnCleanup(int target_type, void* item)\r    {\r              if(target_type == TYPE_USER)\r           {\r                      userrec* user = (userrec*)item;\r\r                       if(user->GetExt("ssl", dummy) && isin(user->GetPort(), listenports))\r                   {\r                              // User is using SSL, they're a local user, and they're using one of *our* SSL ports.\r                          // Potentially there could be multiple SSL modules loaded at once on different ports.\r                          ServerInstance->GlobalCulls.AddItem(user, "SSL module unloading");\r                     }\r                      if (user->GetExt("ssl_cert", dummy) && isin(user->GetPort(), listenports))\r                     {\r                              ssl_cert* tofree;\r                              user->GetExt("ssl_cert", tofree);\r                              delete tofree;\r                         user->Shrink("ssl_cert");\r                      }\r              }\r      }\r\r     virtual void OnUnloadModule(Module* mod, const std::string &name)\r      {\r              if(mod == this)\r                {\r                      for(unsigned int i = 0; i < listenports.size(); i++)\r                   {\r                              ServerInstance->Config->DelIOHook(listenports[i]);\r                             for (size_t j = 0; j < ServerInstance->Config->ports.size(); j++)\r                                      if (ServerInstance->Config->ports[j]->GetPort() == listenports[i])\r                                             ServerInstance->Config->ports[j]->SetDescription("plaintext");\r                 }\r              }\r      }\r\r     virtual Version GetVersion()\r   {\r              return Version(1, 1, 0, 0, VF_VENDOR, API_VERSION);\r    }\r\r     void Implements(char* List)\r    {\r              List[I_On005Numeric] = List[I_OnRawSocketConnect] = List[I_OnRawSocketAccept] = List[I_OnRawSocketClose] = List[I_OnRawSocketRead] = List[I_OnRawSocketWrite] = List[I_OnCleanup] = 1;\r         List[I_OnRequest] = List[I_OnSyncUserMetaData] = List[I_OnDecodeMetaData] = List[I_OnUnloadModule] = List[I_OnRehash] = List[I_OnWhois] = List[I_OnPostConnect] = 1;\r   }\r\r     virtual void On005Numeric(std::string &output)\r {\r              output.append(" SSL=" + sslports);\r     }\r\r     virtual char* OnRequest(Request* request)\r      {\r              ISHRequest* ISR = (ISHRequest*)request;\r                if (strcmp("IS_NAME", request->GetId()) == 0)\r          {\r                      return "gnutls";\r               }\r              else if (strcmp("IS_HOOK", request->GetId()) == 0)\r             {\r                      char* ret = "OK";\r                      try\r                    {\r                              ret = ServerInstance->Config->AddIOHook((Module*)this, (InspSocket*)ISR->Sock) ? (char*)"OK" : NULL;\r                   }\r                      catch (ModuleException &e)\r                     {\r                              return NULL;\r                   }\r                      return ret;\r            }\r              else if (strcmp("IS_UNHOOK", request->GetId()) == 0)\r           {\r                      return ServerInstance->Config->DelIOHook((InspSocket*)ISR->Sock) ? (char*)"OK" : NULL;\r         }\r              else if (strcmp("IS_HSDONE", request->GetId()) == 0)\r           {\r                      if (ISR->Sock->GetFd() < 0)\r                            return (char*)"OK";\r\r                   issl_session* session = &sessions[ISR->Sock->GetFd()];\r                 return (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE) ? NULL : (char*)"OK";\r           }\r              else if (strcmp("IS_ATTACH", request->GetId()) == 0)\r           {\r                      if (ISR->Sock->GetFd() > -1)\r                   {\r                              issl_session* session = &sessions[ISR->Sock->GetFd()];\r                         if (session->sess)\r                             {\r                                      if ((Extensible*)ServerInstance->FindDescriptor(ISR->Sock->GetFd()) == (Extensible*)(ISR->Sock))\r                                       {\r                                              VerifyCertificate(session, (InspSocket*)ISR->Sock);\r                                            return "OK";\r                                   }\r                              }\r                      }\r              }\r              return NULL;\r   }\r\r\r    virtual void OnRawSocketAccept(int fd, const std::string &ip, int localport)\r   {\r              issl_session* session = &sessions[fd];\r\r                session->fd = fd;\r              session->inbuf = new char[inbufsize];\r          session->inbufoffset = 0;\r\r             gnutls_init(&session->sess, GNUTLS_SERVER);\r\r           gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.\r                gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);\r              gnutls_dh_set_prime_bits(session->sess, dh_bits);\r\r             /* This is an experimental change to avoid a warning on 64bit systems about casting between integer and pointer of different sizes\r              * This needs testing, but it's easy enough to rollback if need be\r              * Old: gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.\r             * New: gnutls_transport_set_ptr(session->sess, &fd); // Give gnutls the fd for the socket.\r             *\r              * With testing this seems to...not work :/\r             */\r\r           gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.\r\r           gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.\r\r              Handshake(session);\r    }\r\r     virtual void OnRawSocketConnect(int fd)\r        {\r              issl_session* session = &sessions[fd];\r\r                session->fd = fd;\r              session->inbuf = new char[inbufsize];\r          session->inbufoffset = 0;\r\r             gnutls_init(&session->sess, GNUTLS_CLIENT);\r\r           gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.\r                gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);\r              gnutls_dh_set_prime_bits(session->sess, dh_bits);\r              gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.\r\r           Handshake(session);\r    }\r\r     virtual void OnRawSocketClose(int fd)\r  {\r              CloseSession(&sessions[fd]);\r\r          EventHandler* user = ServerInstance->SE->GetRef(fd);\r\r          if ((user) && (user->GetExt("ssl_cert", dummy)))\r               {\r                      ssl_cert* tofree;\r                      user->GetExt("ssl_cert", tofree);\r                      delete tofree;\r                 user->Shrink("ssl_cert");\r              }\r      }\r\r     virtual int OnRawSocketRead(int fd, char* buffer, unsigned int count, int &readresult)\r {\r              issl_session* session = &sessions[fd];\r\r                if (!session->sess)\r            {\r                      readresult = 0;\r                        CloseSession(session);\r                 return 1;\r              }\r\r             if (session->status == ISSL_HANDSHAKING_READ)\r          {\r                      // The handshake isn't finished, try to finish it.\r\r                    if(!Handshake(session))\r                        {\r                              // Couldn't resume handshake.\r                          return -1;\r                     }\r              }\r              else if (session->status == ISSL_HANDSHAKING_WRITE)\r            {\r                      errno = EAGAIN;\r                        return -1;\r             }\r\r             // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN.\r\r          if (session->status == ISSL_HANDSHAKEN)\r                {\r                      // Is this right? Not sure if the unencrypted data is garaunteed to be the same length.\r                        // Read into the inbuffer, offset from the beginning by the amount of data we have that insp hasn't taken yet.\r                 int ret = gnutls_record_recv(session->sess, session->inbuf + session->inbufoffset, inbufsize - session->inbufoffset);\r\r                 if (ret == 0)\r                  {\r                              // Client closed connection.\r                           readresult = 0;\r                                CloseSession(session);\r                         return 1;\r                      }\r                      else if (ret < 0)\r                      {\r                              if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)\r                              {\r                                      errno = EAGAIN;\r                                        return -1;\r                             }\r                              else\r                           {\r                                      readresult = 0;\r                                        CloseSession(session);\r                         }\r                      }\r                      else\r                   {\r                              // Read successfully 'ret' bytes into inbuf + inbufoffset\r                              // There are 'ret' + 'inbufoffset' bytes of data in 'inbuf'\r                            // 'buffer' is 'count' long\r\r                           unsigned int length = ret + session->inbufoffset;\r\r                             if(count <= length)\r                            {\r                                      memcpy(buffer, session->inbuf, count);\r                                 // Move the stuff left in inbuf to the beginning of it\r                                 memcpy(session->inbuf, session->inbuf + count, (length - count));\r                                      // Now we need to set session->inbufoffset to the amount of data still waiting to be handed to insp.\r                                   session->inbufoffset = length - count;\r                                 // Insp uses readresult as the count of how much data there is in buffer, so:\r                                  readresult = count;\r                            }\r                              else\r                           {\r                                      // There's not as much in the inbuf as there is space in the buffer, so just copy the whole thing.\r                                     memcpy(buffer, session->inbuf, length);\r                                        // Zero the offset, as there's nothing there..\r                                 session->inbufoffset = 0;\r                                      // As above\r                                    readresult = length;\r                           }\r                      }\r              }\r              else if(session->status == ISSL_CLOSING)\r                       readresult = 0;\r\r               return 1;\r      }\r\r     virtual int OnRawSocketWrite(int fd, const char* buffer, int count)\r    {\r              if (!count)\r                    return 0;\r\r             issl_session* session = &sessions[fd];\r         const char* sendbuffer = buffer;\r\r              if (!session->sess)\r            {\r                      ServerInstance->Log(DEBUG,"No session");\r                       CloseSession(session);\r                 return 1;\r              }\r\r             session->outbuf.append(sendbuffer, count);\r             sendbuffer = session->outbuf.c_str();\r          count = session->outbuf.size();\r\r               if (session->status == ISSL_HANDSHAKING_WRITE)\r         {\r                      // The handshake isn't finished, try to finish it.\r                     ServerInstance->Log(DEBUG,"Finishing handshake");\r                      Handshake(session);\r                    errno = EAGAIN;\r                        return -1;\r             }\r\r             int ret = 0;\r\r          if (session->status == ISSL_HANDSHAKEN)\r                {\r                      ServerInstance->Log(DEBUG,"Send record");\r                      ret = gnutls_record_send(session->sess, sendbuffer, count);\r                    ServerInstance->Log(DEBUG,"Return: %d", ret);\r\r                 if (ret == 0)\r                  {\r                              CloseSession(session);\r                 }\r                      else if (ret < 0)\r                      {\r                              if(ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED)\r                               {\r                                      ServerInstance->Log(DEBUG,"Not egain or interrupt, close session");\r                                    CloseSession(session);\r                         }\r                              else\r                           {\r                                      ServerInstance->Log(DEBUG,"Again please");\r                                     errno = EAGAIN;\r                                        return -1;\r                             }\r                      }\r                      else\r                   {\r                              ServerInstance->Log(DEBUG,"Trim buffer");\r                              session->outbuf = session->outbuf.substr(ret);\r                 }\r              }\r\r             /* Who's smart idea was it to return 1 when we havent written anything?\r                 * This fucks the buffer up in InspSocket :p\r            */\r            return ret < 1 ? 0 : ret;\r      }\r\r     // :kenny.chatspike.net 320 Om Epy|AFK :is a Secure Connection\r virtual void OnWhois(userrec* source, userrec* dest)\r   {\r              if (!clientactive)\r                     return;\r\r               // Bugfix, only send this numeric for *our* SSL users\r          if(dest->GetExt("ssl", dummy) || (IS_LOCAL(dest) &&  isin(dest->GetPort(), listenports)))\r              {\r                      ServerInstance->SendWhoisLine(source, dest, 320, "%s %s :is using a secure connection", source->nick, dest->nick);\r             }\r      }\r\r     virtual void OnSyncUserMetaData(userrec* user, Module* proto, void* opaque, const std::string &extname, bool displayable)\r      {\r              // check if the linking module wants to know about OUR metadata\r                if(extname == "ssl")\r           {\r                      // check if this user has an swhois field to send\r                      if(user->GetExt(extname, dummy))\r                       {\r                              // call this function in the linking module, let it format the data how it\r                             // sees fit, and send it on its way. We dont need or want to know how.\r                         proto->ProtoSendMetaData(opaque, TYPE_USER, user, extname, displayable ? "Enabled" : "ON");\r                    }\r              }\r      }\r\r     virtual void OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata)\r   {\r              // check if its our metadata key, and its associated with a user\r               if ((target_type == TYPE_USER) && (extname == "ssl"))\r          {\r                      userrec* dest = (userrec*)target;\r                      // if they dont already have an ssl flag, accept the remote server's\r                   if (!dest->GetExt(extname, dummy))\r                     {\r                              dest->Extend(extname, "ON");\r                   }\r              }\r      }\r\r     bool Handshake(issl_session* session)\r  {\r              int ret = gnutls_handshake(session->sess);\r\r            if (ret < 0)\r           {\r                      if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)\r                       {\r                              // Handshake needs resuming later, read() or write() would have blocked.\r\r                              if(gnutls_record_get_direction(session->sess) == 0)\r                            {\r                                      // gnutls_handshake() wants to read() again.\r                                   session->status = ISSL_HANDSHAKING_READ;\r                               }\r                              else\r                           {\r                                      // gnutls_handshake() wants to write() again.\r                                  session->status = ISSL_HANDSHAKING_WRITE;\r                                      MakePollWrite(session);\r                                }\r                      }\r                      else\r                   {\r                              // Handshake failed.\r                           CloseSession(session);\r                         session->status = ISSL_CLOSING;\r                        }\r\r                     return false;\r          }\r              else\r           {\r                      // Handshake complete.\r                 // This will do for setting the ssl flag...it could be done earlier if it's needed. But this seems neater.\r                     userrec* extendme = ServerInstance->FindDescriptor(session->fd);\r                       if (extendme)\r                  {\r                              if (!extendme->GetExt("ssl", dummy))\r                                   extendme->Extend("ssl", "ON");\r                 }\r\r                     // Change the seesion state\r                    session->status = ISSL_HANDSHAKEN;\r\r                    // Finish writing, if any left\r                 MakePollWrite(session);\r\r                       return true;\r           }\r      }\r\r     virtual void OnPostConnect(userrec* user)\r      {\r              // This occurs AFTER OnUserConnect so we can be sure the\r               // protocol module has propogated the NICK message.\r            if ((user->GetExt("ssl", dummy)) && (IS_LOCAL(user)))\r          {\r                      // Tell whatever protocol module we're using that we need to inform other servers of this metadata NOW.\r                        std::deque<std::string>* metadata = new std::deque<std::string>;\r                       metadata->push_back(user->nick);\r                       metadata->push_back("ssl");             // The metadata id\r                     metadata->push_back("ON");              // The value to send\r                   Event* event = new Event((char*)metadata,(Module*)this,"send_metadata");\r                       event->Send(ServerInstance);            // Trigger the event. We don't care what module picks it up.\r                   DELETE(event);\r                 DELETE(metadata);\r\r                     VerifyCertificate(&sessions[user->GetFd()],user);\r                      if (sessions[user->GetFd()].sess)\r                      {\r                              std::string cipher = gnutls_kx_get_name(gnutls_kx_get(sessions[user->GetFd()].sess));\r                          cipher.append("-").append(gnutls_cipher_get_name(gnutls_cipher_get(sessions[user->GetFd()].sess))).append("-");\r                                cipher.append(gnutls_mac_get_name(gnutls_mac_get(sessions[user->GetFd()].sess)));\r                              user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick, cipher.c_str());\r                       }\r              }\r      }\r\r     void MakePollWrite(issl_session* session)\r      {\r              OnRawSocketWrite(session->fd, NULL, 0);\r        }\r\r     void CloseSession(issl_session* session)\r       {\r              if(session->sess)\r              {\r                      gnutls_bye(session->sess, GNUTLS_SHUT_WR);\r                     gnutls_deinit(session->sess);\r          }\r\r             if(session->inbuf)\r             {\r                      delete[] session->inbuf;\r               }\r\r             session->outbuf.clear();\r               session->inbuf = NULL;\r         session->sess = NULL;\r          session->status = ISSL_NONE;\r   }\r\r     void VerifyCertificate(issl_session* session, Extensible* user)\r        {\r              if (!session->sess || !user)\r                   return;\r\r               unsigned int status;\r           const gnutls_datum_t* cert_list;\r               int ret;\r               unsigned int cert_list_size;\r           gnutls_x509_crt_t cert;\r                char name[MAXBUF];\r             unsigned char digest[MAXBUF];\r          size_t digest_size = sizeof(digest);\r           size_t name_size = sizeof(name);\r               ssl_cert* certinfo = new ssl_cert;\r\r            user->Extend("ssl_cert",certinfo);\r\r            /* This verification function uses the trusted CAs in the credentials\r           * structure. So you must have installed one or more CA certificates.\r           */\r            ret = gnutls_certificate_verify_peers2(session->sess, &status);\r\r               if (ret < 0)\r           {\r                      certinfo->data.insert(std::make_pair("error",std::string(gnutls_strerror(ret))));\r                      return;\r                }\r\r             if (status & GNUTLS_CERT_INVALID)\r              {\r                      certinfo->data.insert(std::make_pair("invalid",ConvToStr(1)));\r         }\r              else\r           {\r                      certinfo->data.insert(std::make_pair("invalid",ConvToStr(0)));\r         }\r              if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)\r             {\r                      certinfo->data.insert(std::make_pair("unknownsigner",ConvToStr(1)));\r           }\r              else\r           {\r                      certinfo->data.insert(std::make_pair("unknownsigner",ConvToStr(0)));\r           }\r              if (status & GNUTLS_CERT_REVOKED)\r              {\r                      certinfo->data.insert(std::make_pair("revoked",ConvToStr(1)));\r         }\r              else\r           {\r                      certinfo->data.insert(std::make_pair("revoked",ConvToStr(0)));\r         }\r              if (status & GNUTLS_CERT_SIGNER_NOT_CA)\r                {\r                      certinfo->data.insert(std::make_pair("trusted",ConvToStr(0)));\r         }\r              else\r           {\r                      certinfo->data.insert(std::make_pair("trusted",ConvToStr(1)));\r         }\r\r             /* Up to here the process is the same for X.509 certificates and\r                * OpenPGP keys. From now on X.509 certificates are assumed. This can\r           * be easily extended to work with openpgp keys as well.\r                */\r            if (gnutls_certificate_type_get(session->sess) != GNUTLS_CRT_X509)\r             {\r                      certinfo->data.insert(std::make_pair("error","No X509 keys sent"));\r                    return;\r                }\r\r             ret = gnutls_x509_crt_init(&cert);\r             if (ret < 0)\r           {\r                      certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));\r                   return;\r                }\r\r             cert_list_size = 0;\r            cert_list = gnutls_certificate_get_peers(session->sess, &cert_list_size);\r              if (cert_list == NULL)\r         {\r                      certinfo->data.insert(std::make_pair("error","No certificate was found"));\r                     return;\r                }\r\r             /* This is not a real world example, since we only check the first\r              * certificate in the given chain.\r              */\r\r           ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);\r                if (ret < 0)\r           {\r                      certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));\r                   return;\r                }\r\r             gnutls_x509_crt_get_dn(cert, name, &name_size);\r\r               certinfo->data.insert(std::make_pair("dn",name));\r\r             gnutls_x509_crt_get_issuer_dn(cert, name, &name_size);\r\r                certinfo->data.insert(std::make_pair("issuer",name));\r\r         if ((ret = gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_MD5, digest, &digest_size)) < 0)\r           {\r                      certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));\r           }\r              else\r           {\r                      certinfo->data.insert(std::make_pair("fingerprint",irc::hex(digest, digest_size)));\r            }\r\r             /* Beware here we do not check for errors.\r              */\r            if ((gnutls_x509_crt_get_expiration_time(cert) < time(0)) || (gnutls_x509_crt_get_activation_time(cert) > time(0)))\r            {\r                      certinfo->data.insert(std::make_pair("error","Not activated, or expired certificate"));\r                }\r\r             gnutls_x509_crt_deinit(cert);\r\r         return;\r        }\r\r};\r\rMODULE_INIT(ModuleSSLGnuTLS);\r\r
\ No newline at end of file
+/*       +------------------------------------+
+ *       | Inspire Internet Relay Chat Daemon |
+ *       +------------------------------------+
+ *
+ *  InspIRCd: (C) 2002-2008 InspIRCd Development Team
+ * See: http://www.inspircd.org/wiki/index.php/Credits
+ *
+ * This program is free but copyrighted software; see
+ *         the file COPYING for details.
+ *
+ * ---------------------------------------------------
+ */
+
+#include "inspircd.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+#include "inspircd_config.h"
+#include "configreader.h"
+#include "users.h"
+#include "channels.h"
+#include "modules.h"
+#include "socket.h"
+#include "hashcomp.h"
+#include "transport.h"
+#include "m_cap.h"
+
+#ifdef WINDOWS
+#pragma comment(lib, "libgnutls-13.lib")
+#undef MAX_DESCRIPTORS
+#define MAX_DESCRIPTORS 10000
+#endif
+
+/* $ModDesc: Provides SSL support for clients */
+/* $CompileFlags: exec("libgnutls-config --cflags") */
+/* $LinkerFlags: rpath("libgnutls-config --libs") exec("libgnutls-config --libs") */
+/* $ModDep: transport.h */
+/* $CopyInstall: conf/key.pem $(CONPATH) */
+/* $CopyInstall: conf/cert.pem $(CONPATH) */
+
+enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
+
+bool isin(int port, const std::vector<int> &portlist)
+{
+       for(unsigned int i = 0; i < portlist.size(); i++)
+               if(portlist[i] == port)
+                       return true;
+
+       return false;
+}
+
+/** Represents an SSL user's extra data
+ */
+class issl_session : public classbase
+{
+public:
+       gnutls_session_t sess;
+       issl_status status;
+       std::string outbuf;
+       int inbufoffset;
+       char* inbuf;
+       int fd;
+};
+
+class CommandStartTLS : public Command
+{
+       Module* Caller;
+ public:
+       /* Command 'dalinfo', takes no parameters and needs no special modes */
+       CommandStartTLS (InspIRCd* Instance, Module* mod) : Command(Instance,"STARTTLS", 0, 0, true), Caller(mod)
+       {
+               this->source = "m_ssl_gnutls.so";
+       }
+
+       CmdResult Handle (const char* const* parameters, int pcnt, User *user)
+       {
+               if (!user->GetExt("tls"))
+                       return CMD_FAILURE;
+
+               user->io = Caller;
+               Caller->OnRawSocketAccept(user->GetFd(), user->GetIPString(), ServerInstance->Config->ports[i]->GetPort());
+
+               return CMD_FAILURE;
+       }
+};
+
+class ModuleSSLGnuTLS : public Module
+{
+
+       ConfigReader* Conf;
+
+       char* dummy;
+
+       std::vector<int> listenports;
+
+       int inbufsize;
+       issl_session sessions[MAX_DESCRIPTORS];
+
+       gnutls_certificate_credentials x509_cred;
+       gnutls_dh_params dh_params;
+
+       std::string keyfile;
+       std::string certfile;
+       std::string cafile;
+       std::string crlfile;
+       std::string sslports;
+       int dh_bits;
+
+       int clientactive;
+
+       CommandStartTLS* starttls;
+
+ public:
+
+       ModuleSSLGnuTLS(InspIRCd* Me)
+               : Module(Me)
+       {
+               ServerInstance->Modules->PublishInterface("BufferedSocketHook", this);
+
+               // Not rehashable...because I cba to reduce all the sizes of existing buffers.
+               inbufsize = ServerInstance->Config->NetBufferSize;
+
+               gnutls_global_init(); // This must be called once in the program
+
+               if(gnutls_certificate_allocate_credentials(&x509_cred) != 0)
+                       ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to allocate certificate credentials");
+
+               // Guessing return meaning
+               if(gnutls_dh_params_init(&dh_params) < 0)
+                       ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to initialise DH parameters");
+
+               // Needs the flag as it ignores a plain /rehash
+               OnRehash(NULL,"ssl");
+
+               // Void return, guess we assume success
+               gnutls_certificate_set_dh_params(x509_cred, dh_params);
+               Implementation eventlist[] = { I_On005Numeric, I_OnRawSocketConnect, I_OnRawSocketAccept, I_OnRawSocketClose, I_OnRawSocketRead, I_OnRawSocketWrite, I_OnCleanup,
+                       I_OnBufferFlushed, I_OnRequest, I_OnSyncUserMetaData, I_OnDecodeMetaData, I_OnUnloadModule, I_OnRehash, I_OnWhois, I_OnPostConnect, I_OnEvent, I_OnHookUserIO };
+               ServerInstance->Modules->Attach(eventlist, this, 17);
+
+               starttls = new CommandStartTLS(ServerInstance, this);
+               ServerInstance->AddCommand(starttls);
+       }
+
+       virtual void OnRehash(User* user, const std::string &param)
+       {
+               Conf = new ConfigReader(ServerInstance);
+
+               for(unsigned int i = 0; i < listenports.size(); i++)
+               {
+                       ServerInstance->Config->DelIOHook(listenports[i]);
+               }
+
+               listenports.clear();
+               clientactive = 0;
+               sslports.clear();
+
+               for(int index = 0; index < Conf->Enumerate("bind"); index++)
+               {
+                       // For each <bind> tag
+                       std::string x = Conf->ReadValue("bind", "type", index);
+                       if(((x.empty()) || (x == "clients")) && (Conf->ReadValue("bind", "ssl", index) == "gnutls"))
+                       {
+                               // Get the port we're meant to be listening on with SSL
+                               std::string port = Conf->ReadValue("bind", "port", index);
+                               irc::portparser portrange(port, false);
+                               long portno = -1;
+                               while ((portno = portrange.GetToken()))
+                               {
+                                       clientactive++;
+                                       try
+                                       {
+                                               if (ServerInstance->Config->AddIOHook(portno, this))
+                                               {
+                                                       listenports.push_back(portno);
+                                                       for (size_t i = 0; i < ServerInstance->Config->ports.size(); i++)
+                                                               if (ServerInstance->Config->ports[i]->GetPort() == portno)
+                                                                       ServerInstance->Config->ports[i]->SetDescription("ssl");
+                                                       ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %d", portno);
+                                                       sslports.append("*:").append(ConvToStr(portno)).append(";");
+                                               }
+                                               else
+                                               {
+                                                       ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: FAILED to enable SSL on port %d, maybe you have another ssl or similar module loaded?", portno);
+                                               }
+                                       }
+                                       catch (ModuleException &e)
+                                       {
+                                               ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: FAILED to enable SSL on port %d: %s. Maybe it's already hooked by the same port on a different IP, or you have an other SSL or similar module loaded?", portno, e.GetReason());
+                                       }
+                               }
+                       }
+               }
+
+               if(param != "ssl")
+               {
+                       delete Conf;
+                       return;
+               }
+
+               std::string confdir(ServerInstance->ConfigFileName);
+               // +1 so we the path ends with a /
+               confdir = confdir.substr(0, confdir.find_last_of('/') + 1);
+
+               cafile  = Conf->ReadValue("gnutls", "cafile", 0);
+               crlfile = Conf->ReadValue("gnutls", "crlfile", 0);
+               certfile        = Conf->ReadValue("gnutls", "certfile", 0);
+               keyfile = Conf->ReadValue("gnutls", "keyfile", 0);
+               dh_bits = Conf->ReadInteger("gnutls", "dhbits", 0, false);
+
+               // Set all the default values needed.
+               if (cafile.empty())
+                       cafile = "ca.pem";
+
+               if (crlfile.empty())
+                       crlfile = "crl.pem";
+
+               if (certfile.empty())
+                       certfile = "cert.pem";
+
+               if (keyfile.empty())
+                       keyfile = "key.pem";
+
+               if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))
+                       dh_bits = 1024;
+
+               // Prepend relative paths with the path to the config directory.
+               if(cafile[0] != '/')
+                       cafile = confdir + cafile;
+
+               if(crlfile[0] != '/')
+                       crlfile = confdir + crlfile;
+
+               if(certfile[0] != '/')
+                       certfile = confdir + certfile;
+
+               if(keyfile[0] != '/')
+                       keyfile = confdir + keyfile;
+
+               int ret;
+
+               if((ret =gnutls_certificate_set_x509_trust_file(x509_cred, cafile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
+                       ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to set X.509 trust file '%s': %s", cafile.c_str(), gnutls_strerror(ret));
+
+               if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
+                       ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to set X.509 CRL file '%s': %s", crlfile.c_str(), gnutls_strerror(ret));
+
+               if((ret = gnutls_certificate_set_x509_key_file (x509_cred, certfile.c_str(), keyfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
+               {
+                       // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException
+                       throw ModuleException("Unable to load GnuTLS server certificate: " + std::string(gnutls_strerror(ret)));
+               }
+
+               // This may be on a large (once a day or week) timer eventually.
+               GenerateDHParams();
+
+               delete Conf;
+       }
+
+       void GenerateDHParams()
+       {
+               // 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));
+       }
+
+       virtual ~ModuleSSLGnuTLS()
+       {
+               gnutls_dh_params_deinit(dh_params);
+               gnutls_certificate_free_credentials(x509_cred);
+               gnutls_global_deinit();
+       }
+
+       virtual void OnCleanup(int target_type, void* item)
+       {
+               if(target_type == TYPE_USER)
+               {
+                       User* user = (User*)item;
+
+                       if(user->GetExt("ssl", dummy) && isin(user->GetPort(), listenports))
+                       {
+                               // 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.
+                               User::QuitUser(ServerInstance, user, "SSL module unloading");
+                       }
+                       if (user->GetExt("ssl_cert", dummy) && isin(user->GetPort(), listenports))
+                       {
+                               ssl_cert* tofree;
+                               user->GetExt("ssl_cert", tofree);
+                               delete tofree;
+                               user->Shrink("ssl_cert");
+                       }
+
+                       user->io = NULL;
+               }
+       }
+
+       virtual void OnUnloadModule(Module* mod, const std::string &name)
+       {
+               if(mod == this)
+               {
+                       for(unsigned int i = 0; i < listenports.size(); i++)
+                       {
+                               ServerInstance->Config->DelIOHook(listenports[i]);
+                               for (size_t j = 0; j < ServerInstance->Config->ports.size(); j++)
+                                       if (ServerInstance->Config->ports[j]->GetPort() == listenports[i])
+                                               ServerInstance->Config->ports[j]->SetDescription("plaintext");
+                       }
+               }
+       }
+
+       virtual Version GetVersion()
+       {
+               return Version(1, 2, 0, 0, VF_VENDOR, API_VERSION);
+       }
+
+
+       virtual void On005Numeric(std::string &output)
+       {
+               output.append(" SSL=" + sslports);
+       }
+
+       virtual void OnHookUserIO(User* user)
+       {
+               if (!user->io && isin(user->GetPort(), listenports))
+               {
+                       /* Hook the user with our module */
+                       user->io = this;
+               }
+       }
+
+       virtual const char* OnRequest(Request* request)
+       {
+               ISHRequest* ISR = (ISHRequest*)request;
+               if (strcmp("IS_NAME", request->GetId()) == 0)
+               {
+                       return "gnutls";
+               }
+               else if (strcmp("IS_HOOK", request->GetId()) == 0)
+               {
+                       const char* ret = "OK";
+                       try
+                       {
+                               ret = ServerInstance->Config->AddIOHook((Module*)this, (BufferedSocket*)ISR->Sock) ? "OK" : NULL;
+                       }
+                       catch (ModuleException &e)
+                       {
+                               return NULL;
+                       }
+                       return ret;
+               }
+               else if (strcmp("IS_UNHOOK", request->GetId()) == 0)
+               {
+                       return ServerInstance->Config->DelIOHook((BufferedSocket*)ISR->Sock) ? "OK" : NULL;
+               }
+               else if (strcmp("IS_HSDONE", request->GetId()) == 0)
+               {
+                       if (ISR->Sock->GetFd() < 0)
+                               return "OK";
+
+                       issl_session* session = &sessions[ISR->Sock->GetFd()];
+                       return (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE) ? NULL : "OK";
+               }
+               else if (strcmp("IS_ATTACH", request->GetId()) == 0)
+               {
+                       if (ISR->Sock->GetFd() > -1)
+                       {
+                               issl_session* session = &sessions[ISR->Sock->GetFd()];
+                               if (session->sess)
+                               {
+                                       if ((Extensible*)ServerInstance->FindDescriptor(ISR->Sock->GetFd()) == (Extensible*)(ISR->Sock))
+                                       {
+                                               VerifyCertificate(session, (BufferedSocket*)ISR->Sock);
+                                               return "OK";
+                                       }
+                               }
+                       }
+               }
+               return NULL;
+       }
+
+
+       virtual void OnRawSocketAccept(int fd, const std::string &ip, int localport)
+       {
+               /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
+               if ((fd < 0) || (fd > MAX_DESCRIPTORS))
+                       return;
+
+               issl_session* session = &sessions[fd];
+
+               session->fd = fd;
+               session->inbuf = new char[inbufsize];
+               session->inbufoffset = 0;
+
+               gnutls_init(&session->sess, GNUTLS_SERVER);
+
+               gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
+               gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
+               gnutls_dh_set_prime_bits(session->sess, dh_bits);
+
+               /* This is an experimental change to avoid a warning on 64bit systems about casting between integer and pointer of different sizes
+                * This needs testing, but it's easy enough to rollback if need be
+                * Old: gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
+                * New: gnutls_transport_set_ptr(session->sess, &fd); // Give gnutls the fd for the socket.
+                *
+                * With testing this seems to...not work :/
+                */
+
+               gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
+
+               gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
+
+               Handshake(session);
+       }
+
+       virtual void OnRawSocketConnect(int fd)
+       {
+               /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
+               if ((fd < 0) || (fd > MAX_DESCRIPTORS))
+                       return;
+
+               issl_session* session = &sessions[fd];
+
+               session->fd = fd;
+               session->inbuf = new char[inbufsize];
+               session->inbufoffset = 0;
+
+               gnutls_init(&session->sess, GNUTLS_CLIENT);
+
+               gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
+               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, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
+
+               Handshake(session);
+       }
+
+       virtual void OnRawSocketClose(int fd)
+       {
+               /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
+               if ((fd < 0) || (fd > MAX_DESCRIPTORS))
+                       return;
+
+               CloseSession(&sessions[fd]);
+
+               EventHandler* user = ServerInstance->SE->GetRef(fd);
+
+               if ((user) && (user->GetExt("ssl_cert", dummy)))
+               {
+                       ssl_cert* tofree;
+                       user->GetExt("ssl_cert", tofree);
+                       delete tofree;
+                       user->Shrink("ssl_cert");
+               }
+       }
+
+       virtual int OnRawSocketRead(int fd, char* buffer, unsigned int count, int &readresult)
+       {
+               /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
+               if ((fd < 0) || (fd > MAX_DESCRIPTORS))
+                       return 0;
+
+               issl_session* session = &sessions[fd];
+
+               if (!session->sess)
+               {
+                       readresult = 0;
+                       CloseSession(session);
+                       return 1;
+               }
+
+               if (session->status == ISSL_HANDSHAKING_READ)
+               {
+                       // The handshake isn't finished, try to finish it.
+
+                       if(!Handshake(session))
+                       {
+                               // Couldn't resume handshake.
+                               return -1;
+                       }
+               }
+               else if (session->status == ISSL_HANDSHAKING_WRITE)
+               {
+                       errno = EAGAIN;
+                       MakePollWrite(session);
+                       return -1;
+               }
+
+               // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN.
+
+               if (session->status == ISSL_HANDSHAKEN)
+               {
+                       // Is this right? Not sure if the unencrypted data is garaunteed to be the same length.
+                       // Read into the inbuffer, offset from the beginning by the amount of data we have that insp hasn't taken yet.
+                       int ret = gnutls_record_recv(session->sess, session->inbuf + session->inbufoffset, inbufsize - session->inbufoffset);
+
+                       if (ret == 0)
+                       {
+                               // Client closed connection.
+                               readresult = 0;
+                               CloseSession(session);
+                               return 1;
+                       }
+                       else if (ret < 0)
+                       {
+                               if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
+                               {
+                                       errno = EAGAIN;
+                                       return -1;
+                               }
+                               else
+                               {
+                                       readresult = 0;
+                                       CloseSession(session);
+                               }
+                       }
+                       else
+                       {
+                               // Read successfully 'ret' bytes into inbuf + inbufoffset
+                               // There are 'ret' + 'inbufoffset' bytes of data in 'inbuf'
+                               // 'buffer' is 'count' long
+
+                               unsigned int length = ret + session->inbufoffset;
+
+                               if(count <= length)
+                               {
+                                       memcpy(buffer, session->inbuf, count);
+                                       // Move the stuff left in inbuf to the beginning of it
+                                       memcpy(session->inbuf, session->inbuf + count, (length - count));
+                                       // Now we need to set session->inbufoffset to the amount of data still waiting to be handed to insp.
+                                       session->inbufoffset = length - count;
+                                       // Insp uses readresult as the count of how much data there is in buffer, so:
+                                       readresult = count;
+                               }
+                               else
+                               {
+                                       // There's not as much in the inbuf as there is space in the buffer, so just copy the whole thing.
+                                       memcpy(buffer, session->inbuf, length);
+                                       // Zero the offset, as there's nothing there..
+                                       session->inbufoffset = 0;
+                                       // As above
+                                       readresult = length;
+                               }
+                       }
+               }
+               else if(session->status == ISSL_CLOSING)
+                       readresult = 0;
+
+               return 1;
+       }
+
+       virtual int OnRawSocketWrite(int fd, const char* buffer, int count)
+       {
+               /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */
+               if ((fd < 0) || (fd > MAX_DESCRIPTORS))
+                       return 0;
+
+               issl_session* session = &sessions[fd];
+               const char* sendbuffer = buffer;
+
+               if (!session->sess)
+               {
+                       CloseSession(session);
+                       return 1;
+               }
+
+               session->outbuf.append(sendbuffer, count);
+               sendbuffer = session->outbuf.c_str();
+               count = session->outbuf.size();
+
+               if (session->status == ISSL_HANDSHAKING_WRITE)
+               {
+                       // The handshake isn't finished, try to finish it.
+                       Handshake(session);
+                       errno = EAGAIN;
+                       return -1;
+               }
+
+               int ret = 0;
+
+               if (session->status == ISSL_HANDSHAKEN)
+               {
+                       ret = gnutls_record_send(session->sess, sendbuffer, count);
+
+                       if (ret == 0)
+                       {
+                               CloseSession(session);
+                       }
+                       else if (ret < 0)
+                       {
+                               if(ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED)
+                               {
+                                       CloseSession(session);
+                               }
+                               else
+                               {
+                                       errno = EAGAIN;
+                               }
+                       }
+                       else
+                       {
+                               session->outbuf = session->outbuf.substr(ret);
+                       }
+               }
+
+               MakePollWrite(session);
+
+               /* Who's smart idea was it to return 1 when we havent written anything?
+                * This fucks the buffer up in BufferedSocket :p
+                */
+               return ret < 1 ? 0 : ret;
+       }
+
+       // :kenny.chatspike.net 320 Om Epy|AFK :is a Secure Connection
+       virtual void OnWhois(User* source, User* dest)
+       {
+               if (!clientactive)
+                       return;
+
+               // Bugfix, only send this numeric for *our* SSL users
+               if(dest->GetExt("ssl", dummy) || (IS_LOCAL(dest) &&  isin(dest->GetPort(), listenports)))
+               {
+                       ServerInstance->SendWhoisLine(source, dest, 320, "%s %s :is using a secure connection", source->nick, dest->nick);
+               }
+       }
+
+       virtual void OnSyncUserMetaData(User* user, Module* proto, void* opaque, const std::string &extname, bool displayable)
+       {
+               // check if the linking module wants to know about OUR metadata
+               if(extname == "ssl")
+               {
+                       // check if this user has an swhois field to send
+                       if(user->GetExt(extname, dummy))
+                       {
+                               // call this function in the linking module, let it format the data how it
+                               // sees fit, and send it on its way. We dont need or want to know how.
+                               proto->ProtoSendMetaData(opaque, TYPE_USER, user, extname, displayable ? "Enabled" : "ON");
+                       }
+               }
+       }
+
+       virtual void OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata)
+       {
+               // check if its our metadata key, and its associated with a user
+               if ((target_type == TYPE_USER) && (extname == "ssl"))
+               {
+                       User* dest = (User*)target;
+                       // if they dont already have an ssl flag, accept the remote server's
+                       if (!dest->GetExt(extname, dummy))
+                       {
+                               dest->Extend(extname, "ON");
+                       }
+               }
+       }
+
+       bool Handshake(issl_session* session)
+       {
+               int ret = gnutls_handshake(session->sess);
+
+               if (ret < 0)
+               {
+                       if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
+                       {
+                               // 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;
+                               }
+                               else
+                               {
+                                       // gnutls_handshake() wants to write() again.
+                                       session->status = ISSL_HANDSHAKING_WRITE;
+                                       MakePollWrite(session);
+                               }
+                       }
+                       else
+                       {
+                               // Handshake failed.
+                               CloseSession(session);
+                               session->status = ISSL_CLOSING;
+                       }
+
+                       return false;
+               }
+               else
+               {
+                       // Handshake complete.
+                       // This will do for setting the ssl flag...it could be done earlier if it's needed. But this seems neater.
+                       User* extendme = ServerInstance->FindDescriptor(session->fd);
+                       if (extendme)
+                       {
+                               if (!extendme->GetExt("ssl", dummy))
+                                       extendme->Extend("ssl", "ON");
+                       }
+
+                       // Change the seesion state
+                       session->status = ISSL_HANDSHAKEN;
+
+                       // Finish writing, if any left
+                       MakePollWrite(session);
+
+                       return true;
+               }
+       }
+
+       virtual void OnPostConnect(User* user)
+       {
+               // This occurs AFTER OnUserConnect so we can be sure the
+               // protocol module has propagated the NICK message.
+               if ((user->GetExt("ssl", dummy)) && (IS_LOCAL(user)))
+               {
+                       // Tell whatever protocol module we're using that we need to inform other servers of this metadata NOW.
+                       std::deque<std::string>* metadata = new std::deque<std::string>;
+                       metadata->push_back(user->nick);
+                       metadata->push_back("ssl");             // The metadata id
+                       metadata->push_back("ON");              // The value to send
+                       Event* event = new Event((char*)metadata,(Module*)this,"send_metadata");
+                       event->Send(ServerInstance);            // Trigger the event. We don't care what module picks it up.
+                       delete event;
+                       delete metadata;
+
+                       VerifyCertificate(&sessions[user->GetFd()],user);
+                       if (sessions[user->GetFd()].sess)
+                       {
+                               std::string cipher = gnutls_kx_get_name(gnutls_kx_get(sessions[user->GetFd()].sess));
+                               cipher.append("-").append(gnutls_cipher_get_name(gnutls_cipher_get(sessions[user->GetFd()].sess))).append("-");
+                               cipher.append(gnutls_mac_get_name(gnutls_mac_get(sessions[user->GetFd()].sess)));
+                               user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick, cipher.c_str());
+                       }
+               }
+       }
+
+       void MakePollWrite(issl_session* session)
+       {
+               //OnRawSocketWrite(session->fd, NULL, 0);
+               EventHandler* eh = ServerInstance->FindDescriptor(session->fd);
+               if (eh)
+                       ServerInstance->SE->WantWrite(eh);
+       }
+
+       virtual void OnBufferFlushed(User* user)
+       {
+               if (user->GetExt("ssl"))
+               {
+                       issl_session* session = &sessions[user->GetFd()];
+                       if (session && session->outbuf.size())
+                               OnRawSocketWrite(user->GetFd(), NULL, 0);
+               }
+       }
+
+       void CloseSession(issl_session* session)
+       {
+               if(session->sess)
+               {
+                       gnutls_bye(session->sess, GNUTLS_SHUT_WR);
+                       gnutls_deinit(session->sess);
+               }
+
+               if(session->inbuf)
+               {
+                       delete[] session->inbuf;
+               }
+
+               session->outbuf.clear();
+               session->inbuf = NULL;
+               session->sess = NULL;
+               session->status = ISSL_NONE;
+       }
+
+       void VerifyCertificate(issl_session* session, Extensible* user)
+       {
+               if (!session->sess || !user)
+                       return;
+
+               unsigned int status;
+               const gnutls_datum_t* cert_list;
+               int ret;
+               unsigned int cert_list_size;
+               gnutls_x509_crt_t cert;
+               char name[MAXBUF];
+               unsigned char digest[MAXBUF];
+               size_t digest_size = sizeof(digest);
+               size_t name_size = sizeof(name);
+               ssl_cert* certinfo = new ssl_cert;
+
+               user->Extend("ssl_cert",certinfo);
+
+               /* This verification function uses the trusted CAs in the credentials
+                * structure. So you must have installed one or more CA certificates.
+                */
+               ret = gnutls_certificate_verify_peers2(session->sess, &status);
+
+               if (ret < 0)
+               {
+                       certinfo->data.insert(std::make_pair("error",std::string(gnutls_strerror(ret))));
+                       return;
+               }
+
+               if (status & GNUTLS_CERT_INVALID)
+               {
+                       certinfo->data.insert(std::make_pair("invalid",ConvToStr(1)));
+               }
+               else
+               {
+                       certinfo->data.insert(std::make_pair("invalid",ConvToStr(0)));
+               }
+               if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
+               {
+                       certinfo->data.insert(std::make_pair("unknownsigner",ConvToStr(1)));
+               }
+               else
+               {
+                       certinfo->data.insert(std::make_pair("unknownsigner",ConvToStr(0)));
+               }
+               if (status & GNUTLS_CERT_REVOKED)
+               {
+                       certinfo->data.insert(std::make_pair("revoked",ConvToStr(1)));
+               }
+               else
+               {
+                       certinfo->data.insert(std::make_pair("revoked",ConvToStr(0)));
+               }
+               if (status & GNUTLS_CERT_SIGNER_NOT_CA)
+               {
+                       certinfo->data.insert(std::make_pair("trusted",ConvToStr(0)));
+               }
+               else
+               {
+                       certinfo->data.insert(std::make_pair("trusted",ConvToStr(1)));
+               }
+
+               /* 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->data.insert(std::make_pair("error","No X509 keys sent"));
+                       return;
+               }
+
+               ret = gnutls_x509_crt_init(&cert);
+               if (ret < 0)
+               {
+                       certinfo->data.insert(std::make_pair("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->data.insert(std::make_pair("error","No certificate was found"));
+                       return;
+               }
+
+               /* 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->data.insert(std::make_pair("error",gnutls_strerror(ret)));
+                       return;
+               }
+
+               gnutls_x509_crt_get_dn(cert, name, &name_size);
+
+               certinfo->data.insert(std::make_pair("dn",name));
+
+               gnutls_x509_crt_get_issuer_dn(cert, name, &name_size);
+
+               certinfo->data.insert(std::make_pair("issuer",name));
+
+               if ((ret = gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_MD5, digest, &digest_size)) < 0)
+               {
+                       certinfo->data.insert(std::make_pair("error",gnutls_strerror(ret)));
+               }
+               else
+               {
+                       certinfo->data.insert(std::make_pair("fingerprint",irc::hex(digest, digest_size)));
+               }
+
+               /* Beware here we do not check for errors.
+                */
+               if ((gnutls_x509_crt_get_expiration_time(cert) < time(0)) || (gnutls_x509_crt_get_activation_time(cert) > time(0)))
+               {
+                       certinfo->data.insert(std::make_pair("error","Not activated, or expired certificate"));
+               }
+
+               gnutls_x509_crt_deinit(cert);
+
+               return;
+       }
+
+       void OnEvent(Event* ev)
+       {
+               GenericCapHandler(ev, "tls", "tls");
+       }
+
+};
+
+MODULE_INIT(ModuleSSLGnuTLS)