#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 <gnutls/gnutls.h>
#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
#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'") */
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:
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);
{
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
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");
}
// 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
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");
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);
}
gnutls_session_t sess;
issl_status status;
reference<GnuTLS::Profile> profile;
+#ifdef INSPIRCD_GNUTLS_HAS_CORK
+ size_t gbuffersize;
+#endif
void CloseSession()
{
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";
, 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));
// 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('-');
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;
}
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<GnuTLSIOHook*>(hook)->TellCiphersAndFingerprint(user);
- }
-
ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
{
if ((user->eh.GetIOHook()) && (user->eh.GetIOHook()->prov->creator == this))