X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fextra%2Fm_ssl_gnutls.cpp;h=a1c989163ff0fea98012ac1913e3a89ce0964695;hb=fb66fb5ce3410d7e32813aed85e8ad3050584740;hp=e6efb97716c821d6ec879d73faaaf870ce04634b;hpb=f62654a6859998f9d63eb22702c572d5ebcff15c;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 e6efb9771..a1c989163 100644 --- a/src/modules/extra/m_ssl_gnutls.cpp +++ b/src/modules/extra/m_ssl_gnutls.cpp @@ -29,55 +29,78 @@ #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 #include -#if ((GNUTLS_VERSION_MAJOR > 2) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 9) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR == 9 && GNUTLS_VERSION_PATCH >= 8)) +#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 +#define INSPIRCD_GNUTLS_HAS_VERSION(major, minor, patch) (GNUTLS_VERSION_NUMBER >= ((major << 16) | (minor << 8) | patch)) + +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 9, 8) #define GNUTLS_HAS_MAC_GET_ID #include #endif -#if (GNUTLS_VERSION_MAJOR > 2 || GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 12) +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0) # define GNUTLS_HAS_RND #else # include #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'") */ /* $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'") */ -#ifndef GNUTLS_VERSION_MAJOR -#define GNUTLS_VERSION_MAJOR LIBGNUTLS_VERSION_MAJOR -#define GNUTLS_VERSION_MINOR LIBGNUTLS_VERSION_MINOR -#define GNUTLS_VERSION_PATCH LIBGNUTLS_VERSION_PATCH -#endif - // These don't exist in older GnuTLS versions -#if ((GNUTLS_VERSION_MAJOR > 2) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 1) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR == 1 && GNUTLS_VERSION_PATCH >= 7)) +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 1, 7) #define GNUTLS_NEW_PRIO_API #endif -#if(GNUTLS_VERSION_MAJOR < 2) +#if (!INSPIRCD_GNUTLS_HAS_VERSION(2, 0, 0)) typedef gnutls_certificate_credentials_t gnutls_certificate_credentials; typedef gnutls_dh_params_t gnutls_dh_params; #endif -enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED }; +enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_HANDSHAKEN }; -#if (GNUTLS_VERSION_MAJOR > 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR >= 12)) +#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 typedef gnutls_retr_st cert_cb_last_param_type; #endif +#if INSPIRCD_GNUTLS_HAS_VERSION(3, 3, 5) +#define INSPIRCD_GNUTLS_HAS_RECV_PACKET +#endif + +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 99, 0) +// The second parameter of gnutls_init() has changed in 2.99.0 from gnutls_connection_end_t to unsigned int +// (it became a general flags parameter) and the enum has been deprecated and generates a warning on use. +typedef unsigned int inspircd_gnutls_session_init_flags_t; +#else +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: @@ -159,6 +182,10 @@ namespace GnuTLS hash = GNUTLS_DIG_MD5; else if (hashname == "sha1") hash = GNUTLS_DIG_SHA1; +#ifdef INSPIRCD_GNUTLS_ENABLE_SHA256_FINGERPRINT + else if (hashname == "sha256") + hash = GNUTLS_DIG_SHA256; +#endif else throw Exception("Unknown hash type " + hashname); #endif @@ -186,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); @@ -330,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 @@ -339,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"); } @@ -348,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 @@ -446,6 +510,51 @@ namespace GnuTLS } }; + class DataReader + { + int retval; +#ifdef INSPIRCD_GNUTLS_HAS_RECV_PACKET + gnutls_packet_t packet; + + public: + DataReader(gnutls_session_t sess) + { + // Using the packet API avoids the final copy of the data which GnuTLS does if we supply + // our own buffer. Instead, we get the buffer containing the data from GnuTLS and copy it + // to the recvq directly from there in appendto(). + retval = gnutls_record_recv_packet(sess, &packet); + } + + void appendto(std::string& recvq) + { + // Copy data from GnuTLS buffers to recvq + gnutls_datum_t datum; + gnutls_packet_get(packet, &datum, NULL); + recvq.append(reinterpret_cast(datum.data), datum.size); + + gnutls_packet_deinit(packet); + } +#else + char* const buffer; + + public: + DataReader(gnutls_session_t sess) + : buffer(ServerInstance->GetReadBuffer()) + { + // Read data from GnuTLS buffers into ReadBuffer + retval = gnutls_record_recv(sess, buffer, ServerInstance->Config->NetBufferSize); + } + + void appendto(std::string& recvq) + { + // Copy data from ReadBuffer to recvq + recvq.append(buffer, retval); + } +#endif + + int ret() const { return retval; } + }; + class Profile : public refcountbase { /** Name of this profile @@ -468,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); @@ -490,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"); @@ -524,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 @@ -534,11 +671,15 @@ namespace GnuTLS priority.SetupSession(sess); x509cred.SetupSession(sess); gnutls_dh_set_prime_bits(sess, min_dh_bits); + + // Request client certificate if we are a server, no-op if we're a client + gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); } 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; } }; } @@ -548,19 +689,9 @@ class GnuTLSIOHook : public SSLIOHook gnutls_session_t sess; issl_status status; reference profile; - - void InitSession(StreamSocket* user, bool me_server) - { - gnutls_init(&sess, me_server ? GNUTLS_SERVER : GNUTLS_CLIENT); - - profile->SetupSession(sess); - gnutls_transport_set_ptr(sess, reinterpret_cast(user)); - gnutls_transport_set_push_function(sess, gnutls_push_wrapper); - gnutls_transport_set_pull_function(sess, gnutls_pull_wrapper); - - if (me_server) - gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); // Request client certificate if any. - } +#ifdef INSPIRCD_GNUTLS_HAS_CORK + size_t gbuffersize; +#endif void CloseSession() { @@ -574,7 +705,8 @@ class GnuTLSIOHook : public SSLIOHook status = ISSL_NONE; } - bool Handshake(StreamSocket* user) + // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed + int Handshake(StreamSocket* user) { int ret = gnutls_handshake(this->sess); @@ -583,28 +715,27 @@ class GnuTLSIOHook : public SSLIOHook if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) { // Handshake needs resuming later, read() or write() would have blocked. + this->status = ISSL_HANDSHAKING; if (gnutls_record_get_direction(this->sess) == 0) { // gnutls_handshake() wants to read() again. - this->status = ISSL_HANDSHAKING_READ; SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); } else { // gnutls_handshake() wants to write() again. - this->status = ISSL_HANDSHAKING_WRITE; SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); } + + return 0; } else { user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret))); CloseSession(); - this->status = ISSL_CLOSING; + return -1; } - - return false; } else { @@ -616,7 +747,7 @@ class GnuTLSIOHook : public SSLIOHook // Finish writing, if any left SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE); - return true; + return 1; } } @@ -724,6 +855,59 @@ info_done_dealloc: gnutls_x509_crt_deinit(cert); } + // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error + int PrepareIO(StreamSocket* sock) + { + if (status == ISSL_HANDSHAKEN) + return 1; + else if (status == ISSL_HANDSHAKING) + { + // The handshake isn't finished, try to finish it + return Handshake(sock); + } + + CloseSession(); + sock->SetError("No SSL session"); + 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"; @@ -765,6 +949,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(transportptr); +#ifdef _WIN32 + GnuTLSIOHook* session = static_cast(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(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(session_wrap); @@ -800,15 +1020,28 @@ 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& sslprofile) + GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, inspircd_gnutls_session_init_flags_t flags, const reference& sslprofile) : SSLIOHook(hookprov) , sess(NULL) , status(ISSL_NONE) , profile(sslprofile) +#ifdef INSPIRCD_GNUTLS_HAS_CORK + , gbuffersize(0) +#endif { - InitSession(sock, outbound); + gnutls_init(&sess, flags); + gnutls_transport_set_ptr(sess, reinterpret_cast(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); + sock->AddIOHook(this); Handshake(sock); } @@ -820,35 +1053,18 @@ info_done_dealloc: int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE { - if (!this->sess) - { - CloseSession(); - user->SetError("No SSL session"); - return -1; - } - - if (this->status == ISSL_HANDSHAKING_READ || this->status == ISSL_HANDSHAKING_WRITE) - { - // The handshake isn't finished, try to finish it. - - if (!Handshake(user)) - { - if (this->status != ISSL_CLOSING) - return 0; - return -1; - } - } + // Finish handshake if needed + int prepret = PrepareIO(user); + if (prepret <= 0) + return prepret; // If we resumed the handshake then this->status will be ISSL_HANDSHAKEN. - - if (this->status == ISSL_HANDSHAKEN) { - char* buffer = ServerInstance->GetReadBuffer(); - size_t bufsiz = ServerInstance->Config->NetBufferSize; - int ret = gnutls_record_recv(this->sess, buffer, bufsiz); + GnuTLS::DataReader reader(sess); + int ret = reader.ret(); if (ret > 0) { - recvq.append(buffer, ret); + reader.appendto(recvq); return 1; } else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) @@ -868,81 +1084,84 @@ info_done_dealloc: return -1; } } - else if (this->status == ISSL_CLOSING) - return -1; - - return 0; } - int OnStreamSocketWrite(StreamSocket* user, std::string& sendq) CXX11_OVERRIDE + int OnStreamSocketWrite(StreamSocket* user) CXX11_OVERRIDE { - if (!this->sess) - { - CloseSession(); - user->SetError("No SSL session"); - return -1; - } + // Finish handshake if needed + int prepret = PrepareIO(user); + if (prepret <= 0) + return prepret; + + // Session is ready for transferring application data + StreamSocket::SendQueue& sendq = user->GetSendQ(); - if (this->status == ISSL_HANDSHAKING_WRITE || this->status == ISSL_HANDSHAKING_READ) +#ifdef INSPIRCD_GNUTLS_HAS_CORK + while (true) { - // The handshake isn't finished, try to finish it. - Handshake(user); - if (this->status != ISSL_CLOSING) - return 0; - return -1; + // 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; - if (this->status == ISSL_HANDSHAKEN) + while (!sendq.empty()) { - ret = gnutls_record_send(this->sess, sendq.data(), sendq.length()); + 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 == (int)sendq.length()) - { - SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE); - return 1; - } - else if (ret > 0) - { - sendq = sendq.substr(ret); - SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE); - return 0; - } - else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || 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 // (ret < 0) - { - user->SetError(gnutls_strerror(ret)); - CloseSession(); - return -1; - } + + // Wrote entire record, continue sending + sendq.pop_front(); } +#endif - return 0; + SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE); + return 1; } - void TellCiphersAndFingerprint(LocalUser* user) + void GetCiphersuite(std::string& out) const CXX11_OVERRIDE { - if (sess) - { - std::string text = "*** You are connected using SSL cipher '"; - - text += UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess))); - text.append("-").append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).append("-"); - text.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess)))).append("'"); - - if (!certificate->fingerprint.empty()) - text += " and your SSL certificate fingerprint is " + certificate->fingerprint; - - user->WriteNotice(text); - } + 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('-'); + out.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess)))); } 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) @@ -983,12 +1202,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); } }; @@ -1068,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; } @@ -1112,11 +1332,16 @@ class ModuleSSLGnuTLS : public Module return Version("Provides SSL support for clients", VF_VENDOR); } - void OnUserConnect(LocalUser* user) CXX11_OVERRIDE + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { - IOHook* hook = user->eh.GetIOHook(); - if (hook && hook->prov->creator == this) - static_cast(hook)->TellCiphersAndFingerprint(user); + if ((user->eh.GetIOHook()) && (user->eh.GetIOHook()->prov->creator == this)) + { + GnuTLSIOHook* iohook = static_cast(user->eh.GetIOHook()); + if (!iohook->IsHandshakeDone()) + return MOD_RES_DENY; + } + + return MOD_RES_PASSTHRU; } };