X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fextra%2Fm_ssl_gnutls.cpp;h=56b60de260cab0d87830e4fb471f0a5783582f3f;hb=96cc8bf4813877724d011531da1923aa457d8542;hp=f8dc85659b1a82c927faef5d3f691c74ab968047;hpb=2972f1ec3fbecb70f7ad7f4f605fb5b9264e8816;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 f8dc85659..56b60de26 100644 --- a/src/modules/extra/m_ssl_gnutls.cpp +++ b/src/modules/extra/m_ssl_gnutls.cpp @@ -20,6 +20,18 @@ * along with this program. If not, see . */ +/// $CompilerFlags: find_compiler_flags("gnutls") +/// $CompilerFlags: require_version("gnutls" "1.0" "2.12") execute("libgcrypt-config --cflags" "LIBGCRYPT_CXXFLAGS") + +/// $LinkerFlags: find_linker_flags("gnutls" "-lgnutls") +/// $LinkerFlags: require_version("gnutls" "1.0" "2.12") execute("libgcrypt-config --libs" "LIBGCRYPT_LDFLAGS") + +/// $PackageInfo: require_system("centos") gnutls-devel pkgconfig +/// $PackageInfo: require_system("darwin") gnutls pkg-config +/// $PackageInfo: require_system("debian" "1.0" "7.99") libgcrypt11-dev +/// $PackageInfo: require_system("debian") gnutls-bin libgnutls28-dev pkg-config +/// $PackageInfo: require_system("ubuntu" "1.0" "13.10") libgcrypt11-dev +/// $PackageInfo: require_system("ubuntu") gnutls-bin libgnutls-dev pkg-config #include "inspircd.h" #include "modules/ssl.h" @@ -29,7 +41,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 +53,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,25 +71,18 @@ #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'") */ - // These don't exist in older GnuTLS versions #if INSPIRCD_GNUTLS_HAS_VERSION(2, 1, 7) #define GNUTLS_NEW_PRIO_API #endif -#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, 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 @@ -83,10 +93,24 @@ typedef gnutls_retr_st cert_cb_last_param_type; #define INSPIRCD_GNUTLS_HAS_RECV_PACKET #endif -class RandGen : public HandlerBase2 +#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 + +static Module* thismod; + +class RandGen { public: - void Call(char* buffer, size_t len) + static void Call(char* buffer, size_t len) { #ifdef GNUTLS_HAS_RND gnutls_rnd(GNUTLS_RND_RANDOM, buffer, len); @@ -195,14 +219,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); @@ -339,6 +355,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 @@ -348,7 +398,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"); } @@ -357,6 +407,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 @@ -500,7 +561,7 @@ namespace GnuTLS int ret() const { return retval; } }; - class Profile : public refcountbase + class Profile { /** Name of this profile */ @@ -522,18 +583,13 @@ namespace GnuTLS */ Priority priority; - 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) - : name(profilename) - , x509cred(certstr, keystr) - , min_dh_bits(mindh) - , hash(hashstr) - , priority(priostr) - { - x509cred.SetDH(DH); - x509cred.SetCA(CA, CRL); - } + /** Rough max size of records to send + */ + const unsigned int outrecsize; + + /** True to request a client certificate as a server + */ + const bool requestclientcert; static std::string ReadFile(const std::string& filename) { @@ -544,43 +600,92 @@ namespace GnuTLS return ret; } - public: - static reference Create(const std::string& profilename, ConfigTag* tag) + static std::string GetPrioStr(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) + // 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)) { - gendh = (gendh < 1024 ? 1024 : gendh); - dh = DHParams::Generate(gendh); + 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); } - else - dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem"))); + return priostr; + } - // Use default priority string if this tag does not specify one - std::string priostr = tag->getString("priority", "NORMAL"); - unsigned int mindh = tag->getInt("mindhbits", 1024); - std::string hashstr = tag->getString("hash", "md5"); + public: + struct Config + { + std::string name; - // Load trusted CA and revocation list, if set std::auto_ptr ca; std::auto_ptr crl; - std::string filename = tag->getString("cafile"); - if (!filename.empty()) - { - ca.reset(new X509CertList(ReadFile(filename))); - filename = tag->getString("crlfile"); + std::string certstr; + std::string keystr; + std::auto_ptr dh; + + std::string priostr; + unsigned int mindh; + std::string hashstr; + + unsigned int outrecsize; + bool requestclientcert; + + Config(const std::string& profilename, ConfigTag* tag) + : name(profilename) + , certstr(ReadFile(tag->getString("certfile", "cert.pem"))) + , keystr(ReadFile(tag->getString("keyfile", "key.pem"))) + , dh(DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")))) + , priostr(GetPrioStr(profilename, tag)) + , mindh(tag->getInt("mindhbits", 1024)) + , hashstr(tag->getString("hash", "md5")) + , requestclientcert(tag->getBool("requestclientcert", true)) + { + // Load trusted CA and revocation list, if set + std::string filename = tag->getString("cafile"); if (!filename.empty()) - crl.reset(new X509CRL(ReadFile(filename))); - } + { + ca.reset(new X509CertList(ReadFile(filename))); - return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl); - } + filename = tag->getString("crlfile"); + if (!filename.empty()) + 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 + outrecsize = tag->getInt("outrecsize", 2048, 512); +#else + outrecsize = tag->getInt("outrecsize", 2048, 512, 16384); +#endif + } + }; + + Profile(Config& config) + : name(config.name) + , x509cred(config.certstr, config.keystr) + , min_dh_bits(config.mindh) + , hash(config.hashstr) + , priority(config.priostr) + , outrecsize(config.outrecsize) + , requestclientcert(config.requestclientcert) + { + x509cred.SetDH(config.dh); + x509cred.SetCA(config.ca, config.crl); + } /** Set up the given session with the settings in this profile */ void SetupSession(gnutls_session_t sess) @@ -588,11 +693,16 @@ namespace GnuTLS priority.SetupSession(sess); x509cred.SetupSession(sess); gnutls_dh_set_prime_bits(sess, min_dh_bits); + + // Request client certificate if enabled and we are a server, no-op if we're a client + if (requestclientcert) + 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; } }; } @@ -601,20 +711,9 @@ class GnuTLSIOHook : public SSLIOHook private: 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() { @@ -758,7 +857,7 @@ class GnuTLSIOHook : public SSLIOHook issuer.clear(); } - if ((ret = gnutls_x509_crt_get_fingerprint(cert, profile->GetHash(), digest, &digest_size)) < 0) + if ((ret = gnutls_x509_crt_get_fingerprint(cert, GetProfile().GetHash(), digest, &digest_size)) < 0) { certinfo->error = gnutls_strerror(ret); } @@ -794,6 +893,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"; @@ -803,7 +939,7 @@ info_done_dealloc: { StreamSocket* sock = reinterpret_cast(session_wrap); #ifdef _WIN32 - GnuTLSIOHook* session = static_cast(sock->GetIOHook()); + GnuTLSIOHook* session = static_cast(sock->GetModHook(thismod)); #endif if (sock->GetEventMask() & FD_READ_WILL_BLOCK) @@ -835,11 +971,47 @@ 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->GetModHook(thismod)); +#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); #ifdef _WIN32 - GnuTLSIOHook* session = static_cast(sock->GetIOHook()); + GnuTLSIOHook* session = static_cast(sock->GetModHook(thismod)); #endif if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK) @@ -870,15 +1042,27 @@ 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) : 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); + GetProfile().SetupSession(sess); + sock->AddIOHook(this); Handshake(sock); } @@ -902,6 +1086,9 @@ info_done_dealloc: if (ret > 0) { reader.appendto(recvq); + // Schedule a read if there is still data in the GnuTLS buffer + if (gnutls_record_check_pending(sess) > 0) + SocketEngine::ChangeEventMask(user, FD_ADD_TRIAL_READ); return 1; } else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) @@ -923,7 +1110,7 @@ info_done_dealloc: } } - int OnStreamSocketWrite(StreamSocket* user, std::string& sendq) CXX11_OVERRIDE + int OnStreamSocketWrite(StreamSocket* user, StreamSocket::SendQueue& sendq) CXX11_OVERRIDE { // Finish handshake if needed int prepret = PrepareIO(user); @@ -931,59 +1118,92 @@ info_done_dealloc: return prepret; // Session is ready for transferring application data - int ret = 0; +#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 < GetProfile().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, GetProfile().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; - } - } - } - 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('-'); out.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess)))); } - GnuTLS::Profile* GetProfile() { return profile; } + bool GetServerName(std::string& out) const CXX11_OVERRIDE + { + std::vector nameBuffer; + size_t nameLength = 0; + unsigned int nameType = GNUTLS_NAME_DNS; + + // First, determine the size of the hostname. + if (gnutls_server_name_get(sess, &nameBuffer[0], &nameLength, &nameType, 0) != GNUTLS_E_SHORT_MEMORY_BUFFER) + return false; + + // Then retrieve the hostname. + nameBuffer.resize(nameLength); + if (gnutls_server_name_get(sess, &nameBuffer[0], &nameLength, &nameType, 0) != GNUTLS_E_SUCCESS) + return false; + + out.append(&nameBuffer[0]); + return true; + } + + GnuTLS::Profile& GetProfile(); + 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) @@ -995,7 +1215,7 @@ int GnuTLS::X509Credentials::cert_callback(gnutls_session_t sess, const gnutls_d st->key_type = GNUTLS_PRIVKEY_X509; #endif StreamSocket* sock = reinterpret_cast(gnutls_transport_get_ptr(sess)); - GnuTLS::X509Credentials& cred = static_cast(sock->GetIOHook())->GetProfile()->GetX509Credentials(); + GnuTLS::X509Credentials& cred = static_cast(sock->GetModHook(thismod))->GetProfile().GetX509Credentials(); st->ncerts = cred.certs.size(); st->cert.x509 = cred.certs.raw(); @@ -1005,14 +1225,14 @@ int GnuTLS::X509Credentials::cert_callback(gnutls_session_t sess, const gnutls_d return 0; } -class GnuTLSIOHookProvider : public refcountbase, public IOHookProvider +class GnuTLSIOHookProvider : public IOHookProvider { - reference profile; + GnuTLS::Profile profile; public: - GnuTLSIOHookProvider(Module* mod, reference& prof) - : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL) - , profile(prof) + GnuTLSIOHookProvider(Module* mod, GnuTLS::Profile::Config& config) + : IOHookProvider(mod, "ssl/" + config.name, IOHookProvider::IOH_SSL) + , profile(config) { ServerInstance->Modules->AddService(*this); } @@ -1024,22 +1244,29 @@ 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); } void OnConnect(StreamSocket* sock) CXX11_OVERRIDE { - new GnuTLSIOHook(this, sock, false, profile); + new GnuTLSIOHook(this, sock, GNUTLS_CLIENT); } + + GnuTLS::Profile& GetProfile() { return profile; } }; +GnuTLS::Profile& GnuTLSIOHook::GetProfile() +{ + IOHookProvider* hookprov = prov; + return static_cast(hookprov)->GetProfile(); +} + class ModuleSSLGnuTLS : public Module { typedef std::vector > ProfileList; // First member of the class, gets constructed first and destructed last GnuTLS::Init libinit; - RandGen randhandler; ProfileList profiles; void ReadProfiles() @@ -1059,8 +1286,8 @@ class ModuleSSLGnuTLS : public Module try { - reference profile(GnuTLS::Profile::Create(defname, tag)); - newprofiles.push_back(new GnuTLSIOHookProvider(this, profile)); + GnuTLS::Profile::Config profileconfig(defname, tag); + newprofiles.push_back(new GnuTLSIOHookProvider(this, profileconfig)); } catch (CoreException& ex) { @@ -1081,21 +1308,28 @@ class ModuleSSLGnuTLS : public Module continue; } - reference profile; + reference prov; try { - profile = GnuTLS::Profile::Create(name, tag); + GnuTLS::Profile::Config profileconfig(name, tag); + prov = new GnuTLSIOHookProvider(this, profileconfig); } catch (CoreException& ex) { throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason()); } - newprofiles.push_back(new GnuTLSIOHookProvider(this, profile)); + newprofiles.push_back(prov); } // New profiles are ok, begin using them // Old profiles are deleted when their refcount drops to zero + for (ProfileList::iterator i = profiles.begin(); i != profiles.end(); ++i) + { + GnuTLSIOHookProvider& prov = **i; + ServerInstance->Modules.DelService(prov); + } + profiles.swap(newprofiles); } @@ -1105,12 +1339,14 @@ class ModuleSSLGnuTLS : public Module #ifndef GNUTLS_HAS_RND gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); #endif + thismod = this; } 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; + ServerInstance->GenRandom = RandGen::Call; } void OnModuleRehash(User* user, const std::string ¶m) CXX11_OVERRIDE @@ -1130,16 +1366,16 @@ class ModuleSSLGnuTLS : public Module ~ModuleSSLGnuTLS() { - ServerInstance->GenRandom = &ServerInstance->HandleGenRandom; + ServerInstance->GenRandom = &InspIRCd::DefaultGenRandom; } - void OnCleanup(int target_type, void* item) CXX11_OVERRIDE + void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE { - if(target_type == TYPE_USER) + if (type == ExtensionItem::EXT_USER) { LocalUser* user = IS_LOCAL(static_cast(item)); - if (user && user->eh.GetIOHook() && user->eh.GetIOHook()->prov->creator == this) + if ((user) && (user->eh.GetModHook(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. @@ -1153,11 +1389,12 @@ 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); + const GnuTLSIOHook* const iohook = static_cast(user->eh.GetModHook(this)); + if ((iohook) && (!iohook->IsHandshakeDone())) + return MOD_RES_DENY; + return MOD_RES_PASSTHRU; } };