]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/extra/m_ssl_gnutls.cpp
Merge pull request #1130 from SaberUK/master+capnew
[user/henk/code/inspircd.git] / src / modules / extra / m_ssl_gnutls.cpp
index 935b0ee778eb85a4bced1b701cfe1920e34a4bf4..10b97b3598621c448a15b2d11b98be41aa290387 100644 (file)
@@ -28,7 +28,7 @@
 // Fix warnings about the use of commas at end of enumerator lists on C++03.
 #if defined __clang__
 # pragma clang diagnostic ignored "-Wc++11-extensions"
-#elif defined __GNUC__
+#elif defined __GNUC__ && __GNUC__ < 6
 # pragma GCC diagnostic ignored "-pedantic"
 #endif
 
@@ -37,6 +37,7 @@
 
 #ifndef GNUTLS_VERSION_NUMBER
 #define GNUTLS_VERSION_NUMBER LIBGNUTLS_VERSION_NUMBER
+#define GNUTLS_VERSION LIBGNUTLS_VERSION
 #endif
 
 // Check if the GnuTLS library is at least version major.minor.patch
@@ -57,8 +58,8 @@
 # pragma comment(lib, "libgnutls-28.lib")
 #endif
 
-/* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") eval("print `libgcrypt-config --cflags | tr -d \r` if `pkg-config --modversion gnutls 2>/dev/null | tr -d \r` lt '2.12'") */
-/* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") eval("print `libgcrypt-config --libs | tr -d \r` if `pkg-config --modversion gnutls 2>/dev/null | tr -d \r` lt '2.12'") */
+/* $CompileFlags: -std=c++03 pkgconfincludes("gnutls","/gnutls/gnutls.h","") eval("print `libgcrypt-config --cflags | tr -d \r` if `pkg-config --modversion gnutls 2>/dev/null | tr -d \r` lt '2.12'") */
+/* $LinkerFlags: -std=c++03 rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") eval("print `libgcrypt-config --libs | tr -d \r` if `pkg-config --modversion gnutls 2>/dev/null | tr -d \r` lt '2.12'") */
 
 // These don't exist in older GnuTLS versions
 #if INSPIRCD_GNUTLS_HAS_VERSION(2, 1, 7)
@@ -92,6 +93,10 @@ typedef unsigned int inspircd_gnutls_session_init_flags_t;
 typedef gnutls_connection_end_t inspircd_gnutls_session_init_flags_t;
 #endif
 
+#if INSPIRCD_GNUTLS_HAS_VERSION(3, 1, 9)
+#define INSPIRCD_GNUTLS_HAS_CORK
+#endif
+
 class RandGen : public HandlerBase2<void, char*, size_t>
 {
  public:
@@ -204,14 +209,6 @@ namespace GnuTLS
                        return dh;
                }
 
-               /** Generate */
-               static std::auto_ptr<DHParams> Generate(unsigned int bits)
-               {
-                       std::auto_ptr<DHParams> dh(new DHParams);
-                       ThrowOnError(gnutls_dh_params_generate2(dh->dh_params, bits), "Unable to generate DH params");
-                       return dh;
-               }
-
                ~DHParams()
                {
                        gnutls_dh_params_deinit(dh_params);
@@ -348,6 +345,40 @@ namespace GnuTLS
                {
                        gnutls_priority_set(sess, priority);
                }
+
+               static const char* GetDefault()
+               {
+                       return "NORMAL:%SERVER_PRECEDENCE:-VERS-SSL3.0";
+               }
+
+               static std::string RemoveUnknownTokens(const std::string& prio)
+               {
+                       std::string ret;
+                       irc::sepstream ss(prio, ':');
+                       for (std::string token; ss.GetToken(token); )
+                       {
+                               // Save current position so we can revert later if needed
+                               const std::string::size_type prevpos = ret.length();
+                               // Append next token
+                               if (!ret.empty())
+                                       ret.push_back(':');
+                               ret.append(token);
+
+                               gnutls_priority_t test;
+                               if (gnutls_priority_init(&test, ret.c_str(), NULL) < 0)
+                               {
+                                       // The new token broke the priority string, revert to the previously working one
+                                       ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Priority string token not recognized: \"%s\"", token.c_str());
+                                       ret.erase(prevpos);
+                               }
+                               else
+                               {
+                                       // Worked
+                                       gnutls_priority_deinit(test);
+                               }
+                       }
+                       return ret;
+               }
        };
 #else
        /** Dummy class, used when gnutls_priority_set() is not available
@@ -357,7 +388,7 @@ namespace GnuTLS
         public:
                Priority(const std::string& priorities)
                {
-                       if (priorities != "NORMAL")
+                       if (priorities != GetDefault())
                                throw Exception("You've set a non-default priority string, but GnuTLS lacks support for it");
                }
 
@@ -366,6 +397,17 @@ namespace GnuTLS
                        // Always set the default priorities
                        gnutls_set_default_priority(sess);
                }
+
+               static const char* GetDefault()
+               {
+                       return "NORMAL";
+               }
+
+               static std::string RemoveUnknownTokens(const std::string& prio)
+               {
+                       // We don't do anything here because only NORMAL is accepted
+                       return prio;
+               }
        };
 #endif
 
@@ -559,24 +601,40 @@ namespace GnuTLS
                        return ret;
                }
 
+               static std::string GetPrioStr(const std::string& profilename, ConfigTag* tag)
+               {
+                       // Use default priority string if this tag does not specify one
+                       std::string priostr = GnuTLS::Priority::GetDefault();
+                       bool found = tag->readString("priority", priostr);
+                       // If the prio string isn't set in the config don't be strict about the default one because it doesn't work on all versions of GnuTLS
+                       if (!tag->getBool("strictpriority", found))
+                       {
+                               std::string stripped = GnuTLS::Priority::RemoveUnknownTokens(priostr);
+                               if (stripped.empty())
+                               {
+                                       // Stripping failed, act as if a prio string wasn't set
+                                       stripped = GnuTLS::Priority::RemoveUnknownTokens(GnuTLS::Priority::GetDefault());
+                                       ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens and stripping it didn't yield a working one either, falling back to \"%s\"", profilename.c_str(), stripped.c_str());
+                               }
+                               else if ((found) && (stripped != priostr))
+                               {
+                                       // Prio string was set in the config and we ended up with something that works but different
+                                       ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens, stripped to \"%s\"", profilename.c_str(), stripped.c_str());
+                               }
+                               priostr.swap(stripped);
+                       }
+                       return priostr;
+               }
+
         public:
                static reference<Profile> Create(const std::string& profilename, ConfigTag* tag)
                {
                        std::string certstr = ReadFile(tag->getString("certfile", "cert.pem"));
                        std::string keystr = ReadFile(tag->getString("keyfile", "key.pem"));
 
-                       std::auto_ptr<DHParams> dh;
-                       int gendh = tag->getInt("gendh");
-                       if (gendh)
-                       {
-                               gendh = (gendh < 1024 ? 1024 : gendh);
-                               dh = DHParams::Generate(gendh);
-                       }
-                       else
-                               dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")));
+                       std::auto_ptr<DHParams> dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")));
 
-                       // Use default priority string if this tag does not specify one
-                       std::string priostr = tag->getString("priority", "NORMAL");
+                       std::string priostr = GetPrioStr(profilename, tag);
                        unsigned int mindh = tag->getInt("mindhbits", 1024);
                        std::string hashstr = tag->getString("hash", "md5");
 
@@ -593,7 +651,12 @@ namespace GnuTLS
                                        crl.reset(new X509CRL(ReadFile(filename)));
                        }
 
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+                       // If cork support is available outrecsize represents the (rough) max amount of data we give GnuTLS while corked
+                       unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512);
+#else
                        unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512, 16384);
+#endif
                        return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl, outrecsize);
                }
 
@@ -622,6 +685,9 @@ class GnuTLSIOHook : public SSLIOHook
        gnutls_session_t sess;
        issl_status status;
        reference<GnuTLS::Profile> profile;
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+       size_t gbuffersize;
+#endif
 
        void CloseSession()
        {
@@ -801,6 +867,43 @@ info_done_dealloc:
                return -1;
        }
 
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+       int FlushBuffer(StreamSocket* sock)
+       {
+               // If GnuTLS has some data buffered, write it
+               if (gbuffersize)
+                       return HandleWriteRet(sock, gnutls_record_uncork(this->sess, 0));
+               return 1;
+       }
+#endif
+
+       int HandleWriteRet(StreamSocket* sock, int ret)
+       {
+               if (ret > 0)
+               {
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+                       gbuffersize -= ret;
+                       if (gbuffersize)
+                       {
+                               SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE);
+                               return 0;
+                       }
+#endif
+                       return ret;
+               }
+               else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
+               {
+                       SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE);
+                       return 0;
+               }
+               else // (ret < 0)
+               {
+                       sock->SetError(gnutls_strerror(ret));
+                       CloseSession();
+                       return -1;
+               }
+       }
+
        static const char* UnknownIfNULL(const char* str)
        {
                return str ? str : "UNKNOWN";
@@ -921,6 +1024,9 @@ info_done_dealloc:
                , sess(NULL)
                , status(ISSL_NONE)
                , profile(sslprofile)
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+               , gbuffersize(0)
+#endif
        {
                gnutls_init(&sess, flags);
                gnutls_transport_set_ptr(sess, reinterpret_cast<gnutls_transport_ptr_t>(sock));
@@ -985,37 +1091,56 @@ info_done_dealloc:
 
                // Session is ready for transferring application data
                StreamSocket::SendQueue& sendq = user->GetSendQ();
+
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+               while (true)
+               {
+                       // If there is something in the GnuTLS buffer try to send() it
+                       int ret = FlushBuffer(user);
+                       if (ret <= 0)
+                               return ret; // Couldn't flush entire buffer, retry later (or close on error)
+
+                       // GnuTLS buffer is empty, if the sendq is empty as well then break to set FD_WANT_NO_WRITE
+                       if (sendq.empty())
+                               break;
+
+                       // GnuTLS buffer is empty but sendq is not, begin sending data from the sendq
+                       gnutls_record_cork(this->sess);
+                       while ((!sendq.empty()) && (gbuffersize < profile->GetOutgoingRecordSize()))
+                       {
+                               const StreamSocket::SendQueue::Element& elem = sendq.front();
+                               gbuffersize += elem.length();
+                               ret = gnutls_record_send(this->sess, elem.data(), elem.length());
+                               if (ret < 0)
+                               {
+                                       CloseSession();
+                                       return -1;
+                               }
+                               sendq.pop_front();
+                       }
+               }
+#else
                int ret = 0;
 
                while (!sendq.empty())
                {
                        FlattenSendQueue(sendq, profile->GetOutgoingRecordSize());
                        const StreamSocket::SendQueue::Element& buffer = sendq.front();
-                       ret = gnutls_record_send(this->sess, buffer.data(), buffer.length());
+                       ret = HandleWriteRet(user, gnutls_record_send(this->sess, buffer.data(), buffer.length()));
 
-                       if (ret == (int)buffer.length())
-                       {
-                               // Wrote entire record, continue sending
-                               sendq.pop_front();
-                       }
-                       else if (ret > 0)
+                       if (ret <= 0)
+                               return ret;
+                       else if (ret < (int)buffer.length())
                        {
                                sendq.erase_front(ret);
                                SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
                                return 0;
                        }
-                       else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
-                       {
-                               SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
-                               return 0;
-                       }
-                       else // (ret < 0)
-                       {
-                               user->SetError(gnutls_strerror(ret));
-                               CloseSession();
-                               return -1;
-                       }
+
+                       // Wrote entire record, continue sending
+                       sendq.pop_front();
                }
+#endif
 
                SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE);
                return 1;
@@ -1170,6 +1295,7 @@ class ModuleSSLGnuTLS : public Module
 
        void init() CXX11_OVERRIDE
        {
+               ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "GnuTLS lib version %s module was compiled for " GNUTLS_VERSION, gnutls_check_version(NULL));
                ReadProfiles();
                ServerInstance->GenRandom = &randhandler;
        }