]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/extra/m_ssl_gnutls.cpp
m_ssl_gnutls Add GnuTLS::Priority::GetDefault()
[user/henk/code/inspircd.git] / src / modules / extra / m_ssl_gnutls.cpp
index 0bc7060b8d6c2aa913ef3627aedbed828dd33cb7..cc61ababc28d0b42cd577ed738eb13f91a95ff9b 100644 (file)
@@ -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
@@ -73,6 +74,7 @@ typedef gnutls_dh_params_t gnutls_dh_params;
 enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_HANDSHAKEN };
 
 #if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0)
+#define INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
 #define GNUTLS_NEW_CERT_CALLBACK_API
 typedef gnutls_retr2_st cert_cb_last_param_type;
 #else
@@ -91,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:
@@ -203,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);
@@ -347,6 +345,11 @@ namespace GnuTLS
                {
                        gnutls_priority_set(sess, priority);
                }
+
+               static const char* GetDefault()
+               {
+                       return "NORMAL";
+               }
        };
 #else
        /** Dummy class, used when gnutls_priority_set() is not available
@@ -356,7 +359,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");
                }
 
@@ -365,6 +368,11 @@ namespace GnuTLS
                        // Always set the default priorities
                        gnutls_set_default_priority(sess);
                }
+
+               static const char* GetDefault()
+               {
+                       return "NORMAL";
+               }
        };
 #endif
 
@@ -530,14 +538,20 @@ namespace GnuTLS
                 */
                Priority priority;
 
+               /** Rough max size of records to send
+                */
+               const unsigned int outrecsize;
+
                Profile(const std::string& profilename, const std::string& certstr, const std::string& keystr,
                                std::auto_ptr<DHParams>& DH, unsigned int mindh, const std::string& hashstr,
-                               const std::string& priostr, std::auto_ptr<X509CertList>& CA, std::auto_ptr<X509CRL>& CRL)
+                               const std::string& priostr, std::auto_ptr<X509CertList>& CA, std::auto_ptr<X509CRL>& CRL,
+                               unsigned int recsize)
                        : name(profilename)
                        , x509cred(certstr, keystr)
                        , min_dh_bits(mindh)
                        , hash(hashstr)
                        , priority(priostr)
+                       , outrecsize(recsize)
                {
                        x509cred.SetDH(DH);
                        x509cred.SetCA(CA, CRL);
@@ -558,18 +572,10 @@ namespace GnuTLS
                        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 = tag->getString("priority", GnuTLS::Priority::GetDefault());
                        unsigned int mindh = tag->getInt("mindhbits", 1024);
                        std::string hashstr = tag->getString("hash", "md5");
 
@@ -586,7 +592,13 @@ namespace GnuTLS
                                        crl.reset(new X509CRL(ReadFile(filename)));
                        }
 
-                       return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl);
+#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);
                }
 
                /** Set up the given session with the settings in this profile
@@ -604,6 +616,7 @@ namespace GnuTLS
                const std::string& GetName() const { return name; }
                X509Credentials& GetX509Credentials() { return x509cred; }
                gnutls_digest_algorithm_t GetHash() const { return hash.get(); }
+               unsigned int GetOutgoingRecordSize() const { return outrecsize; }
        };
 }
 
@@ -613,6 +626,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()
        {
@@ -792,6 +808,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";
@@ -833,6 +886,42 @@ info_done_dealloc:
                return rv;
        }
 
+#ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
+       static ssize_t VectorPush(gnutls_transport_ptr_t transportptr, const giovec_t* iov, int iovcnt)
+       {
+               StreamSocket* sock = reinterpret_cast<StreamSocket*>(transportptr);
+#ifdef _WIN32
+               GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetIOHook());
+#endif
+
+               if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK)
+               {
+#ifdef _WIN32
+                       gnutls_transport_set_errno(session->sess, EAGAIN);
+#else
+                       errno = EAGAIN;
+#endif
+                       return -1;
+               }
+
+               // Cast the giovec_t to iovec not to IOVector so the correct function is called on Windows
+               int ret = SocketEngine::WriteV(sock, reinterpret_cast<const iovec*>(iov), iovcnt);
+#ifdef _WIN32
+               // See the function above for more info about the usage of gnutls_transport_set_errno() on Windows
+               if (ret < 0)
+                       gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno);
+#endif
+
+               int size = 0;
+               for (int i = 0; i < iovcnt; i++)
+                       size += iov[i].iov_len;
+
+               if (ret < size)
+                       SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
+               return ret;
+       }
+
+#else // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
        static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size)
        {
                StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap);
@@ -868,17 +957,25 @@ info_done_dealloc:
                        SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK);
                return rv;
        }
+#endif // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
 
  public:
-       GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, bool outbound, const reference<GnuTLS::Profile>& sslprofile)
+       GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, inspircd_gnutls_session_init_flags_t flags, const reference<GnuTLS::Profile>& sslprofile)
                : SSLIOHook(hookprov)
                , sess(NULL)
                , status(ISSL_NONE)
                , profile(sslprofile)
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+               , gbuffersize(0)
+#endif
        {
-               gnutls_init(&sess, outbound ? GNUTLS_SERVER : GNUTLS_CLIENT);
+               gnutls_init(&sess, flags);
                gnutls_transport_set_ptr(sess, reinterpret_cast<gnutls_transport_ptr_t>(sock));
+#ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH
+               gnutls_transport_set_vec_push_function(sess, VectorPush);
+#else
                gnutls_transport_set_push_function(sess, gnutls_push_wrapper);
+#endif
                gnutls_transport_set_pull_function(sess, gnutls_pull_wrapper);
                profile->SetupSession(sess);
 
@@ -926,7 +1023,7 @@ info_done_dealloc:
                }
        }
 
-       int OnStreamSocketWrite(StreamSocket* user, std::string& sendq) CXX11_OVERRIDE
+       int OnStreamSocketWrite(StreamSocket* user) CXX11_OVERRIDE
        {
                // Finish handshake if needed
                int prepret = PrepareIO(user);
@@ -934,34 +1031,60 @@ info_done_dealloc:
                        return prepret;
 
                // Session is ready for transferring application data
-               int ret = 0;
+               StreamSocket::SendQueue& sendq = user->GetSendQ();
 
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+               while (true)
                {
-                       ret = gnutls_record_send(this->sess, sendq.data(), sendq.length());
-
-                       if (ret == (int)sendq.length())
-                       {
-                               SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE);
-                               return 1;
-                       }
-                       else if (ret > 0)
+                       // 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()))
                        {
-                               sendq.erase(0, ret);
-                               SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE);
-                               return 0;
+                               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 if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0)
+               }
+#else
+               int ret = 0;
+
+               while (!sendq.empty())
+               {
+                       FlattenSendQueue(sendq, profile->GetOutgoingRecordSize());
+                       const StreamSocket::SendQueue::Element& buffer = sendq.front();
+                       ret = HandleWriteRet(user, gnutls_record_send(this->sess, buffer.data(), buffer.length()));
+
+                       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 // (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;
        }
 
        void TellCiphersAndFingerprint(LocalUser* user)
@@ -987,6 +1110,7 @@ info_done_dealloc:
        }
 
        GnuTLS::Profile* GetProfile() { return profile; }
+       bool IsHandshakeDone() const { return (status == ISSL_HANDSHAKEN); }
 };
 
 int GnuTLS::X509Credentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, cert_cb_last_param_type* st)
@@ -1027,12 +1151,12 @@ class GnuTLSIOHookProvider : public refcountbase, public IOHookProvider
 
        void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
        {
-               new GnuTLSIOHook(this, sock, true, profile);
+               new GnuTLSIOHook(this, sock, GNUTLS_SERVER, profile);
        }
 
        void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
        {
-               new GnuTLSIOHook(this, sock, false, profile);
+               new GnuTLSIOHook(this, sock, GNUTLS_CLIENT, profile);
        }
 };
 
@@ -1112,6 +1236,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;
        }
@@ -1162,6 +1287,18 @@ class ModuleSSLGnuTLS : public Module
                if (hook && hook->prov->creator == this)
                        static_cast<GnuTLSIOHook*>(hook)->TellCiphersAndFingerprint(user);
        }
+
+       ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
+       {
+               if ((user->eh.GetIOHook()) && (user->eh.GetIOHook()->prov->creator == this))
+               {
+                       GnuTLSIOHook* iohook = static_cast<GnuTLSIOHook*>(user->eh.GetIOHook());
+                       if (!iohook->IsHandshakeDone())
+                               return MOD_RES_DENY;
+               }
+
+               return MOD_RES_PASSTHRU;
+       }
 };
 
 MODULE_INIT(ModuleSSLGnuTLS)