X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fextra%2Fm_ssl_gnutls.cpp;h=a1c989163ff0fea98012ac1913e3a89ce0964695;hb=fb66fb5ce3410d7e32813aed85e8ad3050584740;hp=e142ead1188ae39be7b72081ba0c858f9bdb59f2;hpb=1f0485039a276ad1c2fa3d53d284e3a87940ec77;p=user%2Fhenk%2Fcode%2Finspircd.git diff --git a/src/modules/extra/m_ssl_gnutls.cpp b/src/modules/extra/m_ssl_gnutls.cpp index e142ead11..a1c989163 100644 --- a/src/modules/extra/m_ssl_gnutls.cpp +++ b/src/modules/extra/m_ssl_gnutls.cpp @@ -29,7 +29,11 @@ #if defined __clang__ # pragma clang diagnostic ignored "-Wc++11-extensions" #elif defined __GNUC__ -# pragma GCC diagnostic ignored "-pedantic" +# if __GNUC__ < 6 +# pragma GCC diagnostic ignored "-pedantic" +# else +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +# endif #endif #include @@ -37,6 +41,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 @@ -54,7 +59,7 @@ #endif #ifdef _WIN32 -# pragma comment(lib, "libgnutls-28.lib") +# pragma comment(lib, "libgnutls-30.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'") */ @@ -92,6 +97,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 { public: @@ -204,14 +213,6 @@ namespace GnuTLS return dh; } - /** Generate */ - static std::auto_ptr Generate(unsigned int bits) - { - std::auto_ptr 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 +349,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 +392,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 +401,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 @@ -531,14 +577,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& DH, unsigned int mindh, const std::string& hashstr, - const std::string& priostr, std::auto_ptr& CA, std::auto_ptr& CRL) + const std::string& priostr, std::auto_ptr& CA, std::auto_ptr& 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); @@ -553,24 +605,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 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 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 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"); @@ -587,7 +655,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 @@ -605,6 +679,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; } }; } @@ -614,6 +689,9 @@ class GnuTLSIOHook : public SSLIOHook gnutls_session_t sess; issl_status status; reference profile; +#ifdef INSPIRCD_GNUTLS_HAS_CORK + size_t gbuffersize; +#endif void CloseSession() { @@ -793,6 +871,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"; @@ -913,6 +1028,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(sock)); @@ -977,53 +1095,65 @@ 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()) - { - SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE); - return 1; - } - 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; - } - } - } - - void TellCiphersAndFingerprint(LocalUser* user) - { - if (sess) - { - std::string text = "*** You are connected using SSL cipher '"; - GetCiphersuite(text); - text += '\''; - if (!certificate->fingerprint.empty()) - text += " and your SSL certificate fingerprint is " + certificate->fingerprint; - user->WriteNotice(text); + // Wrote entire record, continue sending + sendq.pop_front(); } +#endif + + SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE); + return 1; } - void GetCiphersuite(std::string& out) const + void GetCiphersuite(std::string& out) const CXX11_OVERRIDE { + if (!IsHandshakeDone()) + return; out.append(UnknownIfNULL(gnutls_protocol_get_name(gnutls_protocol_get_version(sess)))).push_back('-'); out.append(UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess)))).push_back('-'); out.append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).push_back('-'); @@ -1157,6 +1287,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; } @@ -1201,13 +1332,6 @@ class ModuleSSLGnuTLS : public Module return Version("Provides SSL support for clients", VF_VENDOR); } - void OnUserConnect(LocalUser* user) CXX11_OVERRIDE - { - IOHook* hook = user->eh.GetIOHook(); - if (hook && hook->prov->creator == this) - static_cast(hook)->TellCiphersAndFingerprint(user); - } - ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { if ((user->eh.GetIOHook()) && (user->eh.GetIOHook()->prov->creator == this))