]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/extra/m_ssl_gnutls.cpp
Merge pull request #109 from Justasic/insp20
[user/henk/code/inspircd.git] / src / modules / extra / m_ssl_gnutls.cpp
index 037d2cf72b0360b2698d47407366a814dab8c6e4..cc934ff77288fe76b6b1d739bf36630bec82e61d 100644 (file)
@@ -1 +1,724 @@
-/*       +------------------------------------+\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
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ *   Copyright (C) 2008 John Brooks <john.brooks@dereferenced.net>
+ *   Copyright (C) 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
+ *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ *   Copyright (C) 2006 Oliver Lupton <oliverlupton@gmail.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <gcrypt.h>
+#include "ssl.h"
+#include "m_cap.h"
+
+/* $ModDesc: Provides SSL support for clients */
+/* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") */
+/* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") -lgcrypt */
+
+enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
+
+static std::vector<gnutls_x509_crt_t> x509_certs;
+static gnutls_x509_privkey_t x509_key;
+static int cert_callback (gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs,
+       const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr_st * st) {
+
+       st->type = GNUTLS_CRT_X509;
+       st->ncerts = x509_certs.size();
+       st->cert.x509 = &x509_certs[0];
+       st->key.x509 = x509_key;
+       st->deinit_all = 0;
+
+       return 0;
+}
+
+static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t user_wrap, void* buffer, size_t size)
+{
+       StreamSocket* user = reinterpret_cast<StreamSocket*>(user_wrap);
+       if (user->GetEventMask() & FD_READ_WILL_BLOCK)
+       {
+               errno = EAGAIN;
+               return -1;
+       }
+       int rv = recv(user->GetFd(), reinterpret_cast<char *>(buffer), size, 0);
+       if (rv < (int)size)
+               ServerInstance->SE->ChangeEventMask(user, FD_READ_WILL_BLOCK);
+       return rv;
+}
+
+static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t user_wrap, const void* buffer, size_t size)
+{
+       StreamSocket* user = reinterpret_cast<StreamSocket*>(user_wrap);
+       if (user->GetEventMask() & FD_WRITE_WILL_BLOCK)
+       {
+               errno = EAGAIN;
+               return -1;
+       }
+       int rv = send(user->GetFd(), reinterpret_cast<const char *>(buffer), size, 0);
+       if (rv < (int)size)
+               ServerInstance->SE->ChangeEventMask(user, FD_WRITE_WILL_BLOCK);
+       return rv;
+}
+
+class RandGen : public HandlerBase2<void, char*, size_t>
+{
+ public:
+       RandGen() {}
+       void Call(char* buffer, size_t len)
+       {
+               gcry_randomize(buffer, len, GCRY_STRONG_RANDOM);
+       }
+};
+
+/** Represents an SSL user's extra data
+ */
+class issl_session
+{
+public:
+       gnutls_session_t sess;
+       issl_status status;
+       reference<ssl_cert> cert;
+       issl_session() : sess(NULL) {}
+};
+
+class CommandStartTLS : public SplitCommand
+{
+ public:
+       bool enabled;
+       CommandStartTLS (Module* mod) : SplitCommand(mod, "STARTTLS")
+       {
+               enabled = true;
+               works_before_reg = true;
+       }
+
+       CmdResult HandleLocal(const std::vector<std::string> &parameters, LocalUser *user)
+       {
+               if (!enabled)
+               {
+                       user->WriteNumeric(691, "%s :STARTTLS is not enabled", user->nick.c_str());
+                       return CMD_FAILURE;
+               }
+
+               if (user->registered == REG_ALL)
+               {
+                       user->WriteNumeric(691, "%s :STARTTLS is not permitted after client registration is complete", user->nick.c_str());
+               }
+               else
+               {
+                       if (!user->eh.GetIOHook())
+                       {
+                               user->WriteNumeric(670, "%s :STARTTLS successful, go ahead with TLS handshake", user->nick.c_str());
+                               /* We need to flush the write buffer prior to adding the IOHook,
+                                * otherwise we'll be sending this line inside the SSL session - which
+                                * won't start its handshake until the client gets this line. Currently,
+                                * we assume the write will not block here; this is usually safe, as
+                                * STARTTLS is sent very early on in the registration phase, where the
+                                * user hasn't built up much sendq. Handling a blocked write here would
+                                * be very annoying.
+                                */
+                               user->eh.DoWrite();
+                               user->eh.AddIOHook(creator);
+                               creator->OnStreamSocketAccept(&user->eh, NULL, NULL);
+                       }
+                       else
+                               user->WriteNumeric(691, "%s :STARTTLS failure", user->nick.c_str());
+               }
+
+               return CMD_FAILURE;
+       }
+};
+
+class ModuleSSLGnuTLS : public Module
+{
+       issl_session* sessions;
+
+       gnutls_certificate_credentials x509_cred;
+       gnutls_dh_params dh_params;
+       gnutls_digest_algorithm_t hash;
+
+       std::string sslports;
+       int dh_bits;
+
+       bool cred_alloc;
+
+       RandGen randhandler;
+       CommandStartTLS starttls;
+
+       GenericCap capHandler;
+       ServiceProvider iohook;
+ public:
+
+       ModuleSSLGnuTLS()
+               : starttls(this), capHandler(this, "tls"), iohook(this, "ssl/gnutls", SERVICE_IOHOOK)
+       {
+               sessions = new issl_session[ServerInstance->SE->GetMaxFds()];
+
+               gnutls_global_init(); // This must be called once in the program
+               gnutls_x509_privkey_init(&x509_key);
+
+               cred_alloc = false;
+       }
+
+       void init()
+       {
+               // Needs the flag as it ignores a plain /rehash
+               OnModuleRehash(NULL,"ssl");
+
+               ServerInstance->GenRandom = &randhandler;
+
+               // Void return, guess we assume success
+               gnutls_certificate_set_dh_params(x509_cred, dh_params);
+               Implementation eventlist[] = { I_On005Numeric, I_OnRehash, I_OnModuleRehash, I_OnUserConnect,
+                       I_OnEvent, I_OnHookIO };
+               ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
+
+               ServerInstance->Modules->AddService(iohook);
+               ServerInstance->AddCommand(&starttls);
+       }
+
+       void OnRehash(User* user)
+       {
+               sslports.clear();
+
+               ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls");
+               starttls.enabled = Conf->getBool("starttls", true);
+
+               if (Conf->getBool("showports", true))
+               {
+                       for (size_t i = 0; i < ServerInstance->ports.size(); i++)
+                       {
+                               ListenSocket* port = ServerInstance->ports[i];
+                               if (port->bind_tag->getString("ssl") != "gnutls")
+                                       continue;
+
+                               const std::string& portid = port->bind_desc;
+                               ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %s", portid.c_str());
+
+                               if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1")
+                                       sslports.append(portid).append(";");
+                       }
+
+                       if (!sslports.empty())
+                               sslports.erase(sslports.end() - 1);
+               }
+       }
+
+       void OnModuleRehash(User* user, const std::string &param)
+       {
+               if(param != "ssl")
+                       return;
+
+               std::string keyfile;
+               std::string certfile;
+               std::string cafile;
+               std::string crlfile;
+               OnRehash(user);
+
+               ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls");
+
+               cafile = Conf->getString("cafile", "conf/ca.pem");
+               crlfile = Conf->getString("crlfile", "conf/crl.pem");
+               certfile = Conf->getString("certfile", "conf/cert.pem");
+               keyfile = Conf->getString("keyfile", "conf/key.pem");
+               dh_bits = Conf->getInt("dhbits");
+               std::string hashname = Conf->getString("hash", "md5");
+
+               if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096))
+                       dh_bits = 1024;
+
+               if (hashname == "md5")
+                       hash = GNUTLS_DIG_MD5;
+               else if (hashname == "sha1")
+                       hash = GNUTLS_DIG_SHA1;
+               else
+                       throw ModuleException("Unknown hash type " + hashname);
+
+
+               int ret;
+
+               if (cred_alloc)
+               {
+                       // Deallocate the old credentials
+                       gnutls_dh_params_deinit(dh_params);
+                       gnutls_certificate_free_credentials(x509_cred);
+
+                       for(unsigned int i=0; i < x509_certs.size(); i++)
+                               gnutls_x509_crt_deinit(x509_certs[i]);
+                       x509_certs.clear();
+               }
+               else
+                       cred_alloc = true;
+
+               if((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0)
+                       ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to allocate certificate credentials: %s", gnutls_strerror(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",DEBUG, "m_ssl_gnutls.so: Failed to set X.509 trust file '%s': %s", cafile.c_str(), gnutls_strerror(ret));
+
+               if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0)
+                       ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to set X.509 CRL file '%s': %s", crlfile.c_str(), gnutls_strerror(ret));
+
+               FileReader reader;
+
+               reader.LoadFile(certfile);
+               std::string cert_string = reader.Contents();
+               gnutls_datum_t cert_datum = { (unsigned char*)cert_string.data(), cert_string.length() };
+
+               reader.LoadFile(keyfile);
+               std::string key_string = reader.Contents();
+               gnutls_datum_t key_datum = { (unsigned char*)key_string.data(), key_string.length() };
+
+               // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException
+               unsigned int certcount = Conf->getInt("certcount", 3);
+               x509_certs.resize(certcount);
+               ret = gnutls_x509_crt_list_import(&x509_certs[0], &certcount, &cert_datum, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
+               if (ret < 0)
+                       throw ModuleException("Unable to load GnuTLS server certificate (" + certfile + "): " + std::string(gnutls_strerror(ret)));
+               x509_certs.resize(certcount);
+
+               if((ret = gnutls_x509_privkey_import(x509_key, &key_datum, GNUTLS_X509_FMT_PEM)) < 0)
+                       throw ModuleException("Unable to load GnuTLS server private key (" + keyfile + "): " + std::string(gnutls_strerror(ret)));
+
+               if((ret = gnutls_certificate_set_x509_key(x509_cred, &x509_certs[0], certcount, x509_key)) < 0)
+                       throw ModuleException("Unable to set GnuTLS cert/key pair: " + std::string(gnutls_strerror(ret)));
+
+               gnutls_certificate_client_set_retrieve_function (x509_cred, cert_callback);
+
+               if((ret = gnutls_dh_params_init(&dh_params)) < 0)
+                       ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to initialise DH parameters: %s", gnutls_strerror(ret));
+
+               // This may be on a large (once a day or week) timer eventually.
+               GenerateDHParams();
+       }
+
+       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));
+       }
+
+       ~ModuleSSLGnuTLS()
+       {
+               for(unsigned int i=0; i < x509_certs.size(); i++)
+                       gnutls_x509_crt_deinit(x509_certs[i]);
+               x509_certs.clear();
+               gnutls_x509_privkey_deinit(x509_key);
+               if (cred_alloc)
+               {
+                       gnutls_dh_params_deinit(dh_params);
+                       gnutls_certificate_free_credentials(x509_cred);
+               }
+               gnutls_global_deinit();
+               delete[] sessions;
+               ServerInstance->GenRandom = &ServerInstance->HandleGenRandom;
+       }
+
+       void OnCleanup(int target_type, void* item)
+       {
+               if(target_type == TYPE_USER)
+               {
+                       LocalUser* user = IS_LOCAL(static_cast<User*>(item));
+
+                       if (user && user->eh.GetIOHook() == this)
+                       {
+                               // User is using SSL, they're a local user, and they're using one of *our* SSL ports.
+                               // Potentially there could be multiple SSL modules loaded at once on different ports.
+                               ServerInstance->Users->QuitUser(user, "SSL module unloading");
+                       }
+               }
+       }
+
+       Version GetVersion()
+       {
+               return Version("Provides SSL support for clients", VF_VENDOR);
+       }
+
+
+       void On005Numeric(std::string &output)
+       {
+               if (!sslports.empty())
+                       output.append(" SSL=" + sslports);
+               if (starttls.enabled)
+                       output.append(" STARTTLS");
+       }
+
+       void OnHookIO(StreamSocket* user, ListenSocket* lsb)
+       {
+               if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "gnutls")
+               {
+                       /* Hook the user with our module */
+                       user->AddIOHook(this);
+               }
+       }
+
+       void OnRequest(Request& request)
+       {
+               if (strcmp("GET_SSL_CERT", request.id) == 0)
+               {
+                       SocketCertificateRequest& req = static_cast<SocketCertificateRequest&>(request);
+                       int fd = req.sock->GetFd();
+                       issl_session* session = &sessions[fd];
+
+                       req.cert = session->cert;
+               }
+       }
+
+       void OnStreamSocketAccept(StreamSocket* user, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
+       {
+               int fd = user->GetFd();
+               issl_session* session = &sessions[fd];
+
+               /* For STARTTLS: Don't try and init a session on a socket that already has a session */
+               if (session->sess)
+                       return;
+
+               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);
+
+               gnutls_transport_set_ptr(session->sess, reinterpret_cast<gnutls_transport_ptr_t>(user));
+               gnutls_transport_set_push_function(session->sess, gnutls_push_wrapper);
+               gnutls_transport_set_pull_function(session->sess, gnutls_pull_wrapper);
+
+               gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
+
+               Handshake(session, user);
+       }
+
+       void OnStreamSocketConnect(StreamSocket* user)
+       {
+               issl_session* session = &sessions[user->GetFd()];
+
+               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, reinterpret_cast<gnutls_transport_ptr_t>(user));
+               gnutls_transport_set_push_function(session->sess, gnutls_push_wrapper);
+               gnutls_transport_set_pull_function(session->sess, gnutls_pull_wrapper);
+
+               Handshake(session, user);
+       }
+
+       void OnStreamSocketClose(StreamSocket* user)
+       {
+               CloseSession(&sessions[user->GetFd()]);
+       }
+
+       int OnStreamSocketRead(StreamSocket* user, std::string& recvq)
+       {
+               issl_session* session = &sessions[user->GetFd()];
+
+               if (!session->sess)
+               {
+                       CloseSession(session);
+                       user->SetError("No SSL session");
+                       return -1;
+               }
+
+               if (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE)
+               {
+                       // The handshake isn't finished, try to finish it.
+
+                       if(!Handshake(session, user))
+                       {
+                               if (session->status != ISSL_CLOSING)
+                                       return 0;
+                               return -1;
+                       }
+               }
+
+               // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN.
+
+               if (session->status == ISSL_HANDSHAKEN)
+               {
+                       char* buffer = ServerInstance->GetReadBuffer();
+                       size_t bufsiz = ServerInstance->Config->NetBufferSize;
+                       int ret = gnutls_record_recv(session->sess, buffer, bufsiz);
+                       if (ret > 0)
+                       {
+                               recvq.append(buffer, ret);
+                               return 1;
+                       }
+                       else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
+                       {
+                               return 0;
+                       }
+                       else if (ret == 0)
+                       {
+                               user->SetError("SSL Connection closed");
+                               CloseSession(session);
+                               return -1;
+                       }
+                       else
+                       {
+                               user->SetError(gnutls_strerror(ret));
+                               CloseSession(session);
+                               return -1;
+                       }
+               }
+               else if (session->status == ISSL_CLOSING)
+                       return -1;
+
+               return 0;
+       }
+
+       int OnStreamSocketWrite(StreamSocket* user, std::string& sendq)
+       {
+               issl_session* session = &sessions[user->GetFd()];
+
+               if (!session->sess)
+               {
+                       CloseSession(session);
+                       user->SetError("No SSL session");
+                       return -1;
+               }
+
+               if (session->status == ISSL_HANDSHAKING_WRITE || session->status == ISSL_HANDSHAKING_READ)
+               {
+                       // The handshake isn't finished, try to finish it.
+                       Handshake(session, user);
+                       if (session->status != ISSL_CLOSING)
+                               return 0;
+                       return -1;
+               }
+
+               int ret = 0;
+
+               if (session->status == ISSL_HANDSHAKEN)
+               {
+                       ret = gnutls_record_send(session->sess, sendq.data(), sendq.length());
+
+                       if (ret == (int)sendq.length())
+                       {
+                               ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_WRITE);
+                               return 1;
+                       }
+                       else if (ret > 0)
+                       {
+                               sendq = sendq.substr(ret);
+                               ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
+                               return 0;
+                       }
+                       else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
+                       {
+                               ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
+                               return 0;
+                       }
+                       else // (ret < 0)
+                       {
+                               user->SetError(gnutls_strerror(ret));
+                               CloseSession(session);
+                               return -1;
+                       }
+               }
+
+               return 0;
+       }
+
+       bool Handshake(issl_session* session, StreamSocket* user)
+       {
+               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;
+                                       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->SetError(std::string("Handshake Failed - ") + gnutls_strerror(ret));
+                               CloseSession(session);
+                               session->status = ISSL_CLOSING;
+                       }
+
+                       return false;
+               }
+               else
+               {
+                       // Change the seesion state
+                       session->status = ISSL_HANDSHAKEN;
+
+                       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;
+               }
+       }
+
+       void OnUserConnect(LocalUser* user)
+       {
+               if (user->eh.GetIOHook() == this)
+               {
+                       if (sessions[user->eh.GetFd()].sess)
+                       {
+                               ssl_cert* cert = sessions[user->eh.GetFd()].cert;
+                               std::string cipher = gnutls_kx_get_name(gnutls_kx_get(sessions[user->eh.GetFd()].sess));
+                               cipher.append("-").append(gnutls_cipher_get_name(gnutls_cipher_get(sessions[user->eh.GetFd()].sess))).append("-");
+                               cipher.append(gnutls_mac_get_name(gnutls_mac_get(sessions[user->eh.GetFd()].sess)));
+                               if (cert->fingerprint.empty())
+                                       user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick.c_str(), cipher.c_str());
+                               else
+                                       user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\""
+                                               " and your SSL fingerprint is %s", user->nick.c_str(), cipher.c_str(), cert->fingerprint.c_str());
+                       }
+               }
+       }
+
+       void CloseSession(issl_session* session)
+       {
+               if (session->sess)
+               {
+                       gnutls_bye(session->sess, GNUTLS_SHUT_WR);
+                       gnutls_deinit(session->sess);
+               }
+               session->sess = NULL;
+               session->cert = NULL;
+               session->status = ISSL_NONE;
+       }
+
+       void VerifyCertificate(issl_session* session, StreamSocket* 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;
+               session->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->error = std::string(gnutls_strerror(ret));
+                       return;
+               }
+
+               certinfo->invalid = (status & GNUTLS_CERT_INVALID);
+               certinfo->unknownsigner = (status & GNUTLS_CERT_SIGNER_NOT_FOUND);
+               certinfo->revoked = (status & GNUTLS_CERT_REVOKED);
+               certinfo->trusted = !(status & GNUTLS_CERT_SIGNER_NOT_CA);
+
+               /* 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, name, &name_size);
+               certinfo->dn = name;
+
+               gnutls_x509_crt_get_issuer_dn(cert, name, &name_size);
+               certinfo->issuer = name;
+
+               if ((ret = gnutls_x509_crt_get_fingerprint(cert, hash, digest, &digest_size)) < 0)
+               {
+                       certinfo->error = gnutls_strerror(ret);
+               }
+               else
+               {
+                       certinfo->fingerprint = irc::hex(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);
+       }
+
+       void OnEvent(Event& ev)
+       {
+               if (starttls.enabled)
+                       capHandler.HandleEvent(ev);
+       }
+};
+
+MODULE_INIT(ModuleSSLGnuTLS)