1
0
mirror of https://github.com/anope/anope.git synced 2026-07-02 04:06:39 +02:00

Merge pull request #52 from attilamolnar/2.0+gnutls

Add support for SSL connections powered by GnuTLS
This commit is contained in:
Adam
2014-02-17 22:21:08 -05:00
9 changed files with 583 additions and 33 deletions
+1 -1
View File
@@ -168,7 +168,7 @@ uplink
/*
* Enable if Services should connect using SSL.
* You must have m_ssl loaded for this to work.
* You must have an SSL module loaded for this to work.
*/
ssl = no
+64 -22
View File
@@ -201,7 +201,7 @@ module { name = "help" }
/* Time before connections to this server are timed out. */
timeout = 30
/* Listen using SSL. Requires m_ssl. */
/* Listen using SSL. Requires an SSL module. */
#ssl = yes
/* If you are using a reverse proxy that sends one of the
@@ -468,6 +468,69 @@ module { name = "help" }
*/
#module { name = "m_sasl_dh-blowfish" }
/*
* m_ssl_gnutls [EXTRA]
*
* This module provides SSL services to Anope using GnuTLS, for example to
* connect to the uplink server(s) via SSL.
*
* You may only load either m_ssl_gnutls or m_ssl_openssl, bot not both.
*/
#module
{
name = "m_ssl_gnutls"
/*
* An optional certificate and key for m_gnutls to give to the uplink.
*
* You can generate your own certificate and key pair by using:
*
* certtool --generate-privkey --bits 2048 --outfile anope.key
* certtool --generate-self-signed --load-privkey anope.key --outfile anope.crt
*
*/
cert = "data/anope.crt"
key = "data/anope.key"
/*
* Diffie-Hellman parameters to use when acting as a server. This is only
* required for TLS servers that want to use ephemeral DH cipher suites.
*
* This is NOT required for Anope to connect to the uplink server(s) via SSL.
*
* You can generate DH parameters by using:
*
* certtool --generate-dh-params --bits 2048 --outfile dhparams.pem
*
*/
# dhparams = "data/dhparams.pem"
}
/*
* m_ssl_openssl [EXTRA]
*
* This module provides SSL services to Anope using OpenSSL, for example to
* connect to the uplink server(s) via SSL.
*
* You may only load either m_ssl_openssl or m_ssl_gnutls, bot not both.
*
*/
#module
{
name = "m_ssl_openssl"
/*
* An optional certificate and key for m_openssl to give to the uplink.
*
* You can generate your own certificate and key pair by using:
*
* openssl genrsa -out anope.key 2048
* openssl req -new -x509 -key anope.key -out anope.crt -days 1095
*/
cert = "data/anope.crt"
key = "data/anope.key"
}
/*
* m_sql_authentication [EXTRA]
*
@@ -662,27 +725,6 @@ module { name = "help" }
rewrite_description = "Clears all users from a channel"
}
/*
* m_ssl [EXTRA]
*
* This module uses SSL to connect to the uplink server(s).
*/
#module
{
name = "m_ssl"
/*
* An optional certificate and key for m_ssl to give to the uplink.
*
* You can generate your own certificate and key pair by using:
*
* openssl genrsa -out anope.key 2048
* openssl req -new -x509 -key anope.key -out anope.crt -days 1095
*/
cert = "data/anope.crt"
key = "data/anope.key"
}
/*
* m_xmlrpc
*
+1 -1
View File
@@ -168,7 +168,7 @@ uplink
/*
* Enable if Services should connect using SSL.
* You must have m_ssl loaded for this to work.
* You must have an SSL module loaded for this to work.
*/
ssl = no
+1
View File
@@ -2,6 +2,7 @@ Anope Version 2.0.0
-------------------
options:passlen, enforceruser, enforcerhost, releasetimeout, and guestnickprefix moved to nickserv's module configuration
options:hideregisteredcommands added
m_ssl renamed to m_ssl_openssl
Anope Version 1.9.9
-------------------
+4 -3
View File
@@ -71,9 +71,10 @@ Anope for Windows
our IRC Support channel for assistance.
Some Anope modules require third party libraries, such as m_mysql and
m_ssl. If these libraries are installed in nonstandard locations, cmake
will probably not find them and should be told where they are by passing
additional search paths to the last question in Config, such as:
the SSL modules. If these libraries are installed in nonstandard
locations, cmake will probably not find them and should be told where
they are by passing additional search paths to the last question in
Config, such as:
-DEXTRA_INCLUDE:STRING=c:/openssl/include;c:/mysql/include
-DEXTRA_LIBS:STRING=c:/openssl/lib;c:/mysql/lib
+1 -1
View File
@@ -20,7 +20,7 @@
/** Anything that inherits from this class can be referred to
* using ServiceReference. Any interfaces provided by modules,
* such as commands, use this. This is also used for modules
* that publish a service (m_ssl, etc).
* that publish a service (m_ssl_openssl, etc).
*/
class CoreExport Service : public virtual Base
{
+509
View File
@@ -0,0 +1,509 @@
/*
* (C) 2014 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
/* RequiredLibraries: gnutls */
#include "module.h"
#include "modules/ssl.h"
#include <errno.h>
#include <gnutls/gnutls.h>
class GnuTLSModule;
static GnuTLSModule *me;
class MySSLService : public SSLService
{
public:
MySSLService(Module *o, const Anope::string &n);
/** Initialize a socket to use SSL
* @param s The socket
*/
void Init(Socket *s) anope_override;
};
class SSLSocketIO : public SocketIO
{
public:
gnutls_session_t sess;
/** Constructor
*/
SSLSocketIO() : sess(NULL) { }
/** Really receive something from the buffer
* @param s The socket
* @param buf The buf to read to
* @param sz How much to read
* @return Number of bytes received
*/
int Recv(Socket *s, char *buf, size_t sz) anope_override;
/** Write something to the socket
* @param s The socket
* @param buf The data to write
* @param size The length of the data
*/
int Send(Socket *s, const char *buf, size_t sz) anope_override;
/** Accept a connection from a socket
* @param s The socket
* @return The new socket
*/
ClientSocket *Accept(ListenSocket *s) anope_override;
/** Finished accepting a connection from a socket
* @param s The socket
* @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error
*/
SocketFlag FinishAccept(ClientSocket *cs) anope_override;
/** Connect the socket
* @param s THe socket
* @param target IP to connect to
* @param port to connect to
*/
void Connect(ConnectionSocket *s, const Anope::string &target, int port) anope_override;
/** Called to potentially finish a pending connection
* @param s The socket
* @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error.
*/
SocketFlag FinishConnect(ConnectionSocket *s) anope_override;
/** Called when the socket is destructing
*/
void Destroy() anope_override;
};
namespace GnuTLS
{
class Init
{
public:
Init() { gnutls_global_init(); }
~Init() { gnutls_global_deinit(); }
};
/** Used to create a gnutls_datum_t* from an Anope::string
*/
class Datum
{
gnutls_datum_t datum;
public:
Datum(const Anope::string &dat)
{
datum.data = reinterpret_cast<unsigned char *>(const_cast<char *>(dat.data()));
datum.size = static_cast<unsigned int>(dat.length());
}
const gnutls_datum_t *get() const { return &datum; }
};
class DHParams
{
gnutls_dh_params_t dh_params;
public:
DHParams() : dh_params(NULL) { }
void Import(const Anope::string &dhstr)
{
if (dh_params != NULL)
{
gnutls_dh_params_deinit(dh_params);
dh_params = NULL;
}
int ret = gnutls_dh_params_init(&dh_params);
if (ret < 0)
throw ConfigException("Unable to initialize DH parameters");
ret = gnutls_dh_params_import_pkcs3(dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM);
if (ret < 0)
{
gnutls_dh_params_deinit(dh_params);
dh_params = NULL;
throw ConfigException("Unable to import DH parameters");
}
}
~DHParams()
{
if (dh_params)
gnutls_dh_params_deinit(dh_params);
}
gnutls_dh_params_t get() const { return dh_params; }
};
class X509CertCredentials
{
gnutls_certificate_credentials_t cred;
DHParams dh;
public:
X509CertCredentials()
{
if (gnutls_certificate_allocate_credentials(&cred) < 0)
throw ConfigException("Cannot allocate certificate credentials");
}
~X509CertCredentials()
{
gnutls_certificate_free_credentials(cred);
}
void SetupSession(gnutls_session_t sess)
{
gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred);
gnutls_set_default_priority(sess);
}
void SetCertAndKey(const Anope::string &certfile, const Anope::string &keyfile)
{
int ret = gnutls_certificate_set_x509_key_file(cred, certfile.c_str(), keyfile.c_str(), GNUTLS_X509_FMT_PEM);
if (ret < 0)
throw ConfigException("Unable to load certificate/private key: " + Anope::string(gnutls_strerror(ret)));
}
void SetDH(const Anope::string &dhfile)
{
std::ifstream ifs(dhfile.c_str());
const Anope::string dhdata((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
dh.Import(dhdata);
gnutls_certificate_set_dh_params(cred, dh.get());
}
bool HasDH() const
{
return (dh.get() != NULL);
}
};
}
class GnuTLSModule : public Module
{
GnuTLS::Init libinit;
public:
GnuTLS::X509CertCredentials cred;
MySSLService service;
GnuTLSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), service(this, "ssl")
{
me = this;
this->SetPermanent(true);
}
~GnuTLSModule()
{
for (std::map<int, Socket *>::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;)
{
Socket *s = it->second;
++it;
if (dynamic_cast<SSLSocketIO *>(s->io))
delete s;
}
}
static void CheckFile(const Anope::string &filename)
{
if (!Anope::IsFile(filename.c_str()))
{
Log() << "File does not exist: " << filename;
throw ConfigException("Error loading certificate/private key");
}
}
void OnReload(Configuration::Conf *conf) anope_override
{
Configuration::Block *config = conf->GetModule(this);
const Anope::string certfile = config->Get<const Anope::string>("cert", "data/anope.crt");
const Anope::string keyfile = config->Get<const Anope::string>("key", "data/anope.key");
const Anope::string dhfile = config->Get<const Anope::string>("dh", "data/dhparams.pem");
CheckFile(certfile);
CheckFile(keyfile);
// DH params is not mandatory
if (Anope::IsFile(dhfile.c_str()))
{
cred.SetDH(dhfile);
Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded DH parameters from " << dhfile;
}
cred.SetCertAndKey(certfile, keyfile);
Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded certificate " << certfile << " and private key " << keyfile;
}
void OnPreServerConnect() anope_override
{
Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink);
if (config->Get<bool>("ssl"))
{
this->service.Init(UplinkSock);
}
}
};
MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n)
{
}
void MySSLService::Init(Socket *s)
{
if (s->io != &NormalSocketIO)
throw CoreException("Socket initializing SSL twice");
s->io = new SSLSocketIO();
}
int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz)
{
int ret = gnutls_record_recv(this->sess, buf, sz);
if (ret > 0)
TotalRead += ret;
else if (ret < 0)
{
switch (ret)
{
case GNUTLS_E_AGAIN:
case GNUTLS_E_INTERRUPTED:
SocketEngine::SetLastError(EAGAIN);
break;
default:
if (s == UplinkSock)
{
// Log and fake an errno because this is a fatal error on the uplink socket
Log() << "SSL error: " << gnutls_strerror(ret);
}
SocketEngine::SetLastError(ECONNRESET);
}
}
return ret;
}
int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz)
{
int ret = gnutls_record_send(this->sess, buf, sz);
if (ret > 0)
TotalWritten += ret;
else
{
switch (ret)
{
case 0:
case GNUTLS_E_AGAIN:
case GNUTLS_E_INTERRUPTED:
SocketEngine::SetLastError(EAGAIN);
break;
default:
if (s == UplinkSock)
{
// Log and fake an errno because this is a fatal error on the uplink socket
Log() << "SSL error: " << gnutls_strerror(ret);
}
SocketEngine::SetLastError(ECONNRESET);
}
}
return ret;
}
ClientSocket *SSLSocketIO::Accept(ListenSocket *s)
{
if (s->io == &NormalSocketIO)
throw SocketException("Attempting to accept on uninitialized socket with SSL");
sockaddrs conaddr;
socklen_t size = sizeof(conaddr);
int newsock = accept(s->GetFD(), &conaddr.sa, &size);
#ifndef INVALID_SOCKET
const int INVALID_SOCKET = -1;
#endif
if (newsock < 0 || newsock == INVALID_SOCKET)
throw SocketException("Unable to accept connection: " + Anope::LastError());
ClientSocket *newsocket = s->OnAccept(newsock, conaddr);
me->service.Init(newsocket);
SSLSocketIO *io = anope_dynamic_static_cast<SSLSocketIO *>(newsocket->io);
if (gnutls_init(&io->sess, GNUTLS_SERVER) != GNUTLS_E_SUCCESS)
throw SocketException("Unable to initialize SSL socket");
me->cred.SetupSession(io->sess);
gnutls_transport_set_int(io->sess, newsock);
newsocket->flags[SF_ACCEPTING] = true;
this->FinishAccept(newsocket);
return newsocket;
}
SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs)
{
if (cs->io == &NormalSocketIO)
throw SocketException("Attempting to finish connect uninitialized socket with SSL");
else if (cs->flags[SF_ACCEPTED])
return SF_ACCEPTED;
else if (!cs->flags[SF_ACCEPTING])
throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?");
SSLSocketIO *io = anope_dynamic_static_cast<SSLSocketIO *>(cs->io);
int ret = gnutls_handshake(io->sess);
if (ret < 0)
{
if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
{
// gnutls_handshake() wants to read or write again;
// if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write.
if (gnutls_record_get_direction(io->sess) == 0)
{
SocketEngine::Change(cs, false, SF_WRITABLE);
SocketEngine::Change(cs, true, SF_READABLE);
}
else
{
SocketEngine::Change(cs, true, SF_WRITABLE);
SocketEngine::Change(cs, false, SF_READABLE);
}
return SF_ACCEPTING;
}
else
{
cs->OnError(Anope::string(gnutls_strerror(ret)));
cs->flags[SF_DEAD] = true;
cs->flags[SF_ACCEPTING] = false;
return SF_DEAD;
}
}
else
{
cs->flags[SF_ACCEPTED] = true;
cs->flags[SF_ACCEPTING] = false;
SocketEngine::Change(cs, false, SF_WRITABLE);
SocketEngine::Change(cs, true, SF_READABLE);
cs->OnAccept();
return SF_ACCEPTED;
}
}
void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port)
{
if (s->io == &NormalSocketIO)
throw SocketException("Attempting to connect uninitialized socket with SSL");
s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false;
s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port);
int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size());
if (c == -1)
{
if (Anope::LastErrorCode() != EINPROGRESS)
{
s->OnError(Anope::LastError());
s->flags[SF_DEAD] = true;
return;
}
else
{
SocketEngine::Change(s, true, SF_WRITABLE);
s->flags[SF_CONNECTING] = true;
return;
}
}
else
{
s->flags[SF_CONNECTING] = true;
this->FinishConnect(s);
}
}
SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s)
{
if (s->io == &NormalSocketIO)
throw SocketException("Attempting to finish connect uninitialized socket with SSL");
else if (s->flags[SF_CONNECTED])
return SF_CONNECTED;
else if (!s->flags[SF_CONNECTING])
throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?");
SSLSocketIO *io = anope_dynamic_static_cast<SSLSocketIO *>(s->io);
if (io->sess == NULL)
{
if (gnutls_init(&io->sess, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
throw SocketException("Unable to initialize SSL socket");
me->cred.SetupSession(io->sess);
gnutls_transport_set_int(io->sess, s->GetFD());
}
int ret = gnutls_handshake(io->sess);
if (ret < 0)
{
if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
{
// gnutls_handshake() wants to read or write again;
// if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write.
if (gnutls_record_get_direction(io->sess) == 0)
{
SocketEngine::Change(s, false, SF_WRITABLE);
SocketEngine::Change(s, true, SF_READABLE);
}
else
{
SocketEngine::Change(s, true, SF_WRITABLE);
SocketEngine::Change(s, false, SF_READABLE);
}
return SF_CONNECTING;
}
else
{
s->OnError(Anope::string(gnutls_strerror(ret)));
s->flags[SF_CONNECTING] = false;
s->flags[SF_DEAD] = true;
return SF_DEAD;
}
}
else
{
s->flags[SF_CONNECTING] = false;
s->flags[SF_CONNECTED] = true;
SocketEngine::Change(s, false, SF_WRITABLE);
SocketEngine::Change(s, true, SF_READABLE);
s->OnConnect();
return SF_CONNECTED;
}
}
void SSLSocketIO::Destroy()
{
if (this->sess)
{
gnutls_bye(this->sess, GNUTLS_SHUT_WR);
gnutls_deinit(this->sess);
}
delete this;
}
MODULE_INIT(GnuTLSModule)
@@ -137,7 +137,7 @@ class SSLModule : public Module
if (!SSL_CTX_use_certificate_file(client_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_certificate_file(server_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM))
throw ConfigException("Error loading certificate");
else
Log(LOG_DEBUG) << "m_ssl: Successfully loaded certificate " << this->certfile;
Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded certificate " << this->certfile;
}
else
Log() << "Unable to open certificate " << this->certfile;
@@ -147,7 +147,7 @@ class SSLModule : public Module
if (!SSL_CTX_use_PrivateKey_file(client_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_PrivateKey_file(server_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM))
throw ConfigException("Error loading private key");
else
Log(LOG_DEBUG) << "m_ssl: Successfully loaded private key " << this->keyfile;
Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded private key " << this->keyfile;
}
else
{
-3
View File
@@ -454,9 +454,6 @@ class HTTPD : public Module
void OnModuleLoad(User *u, Module *m) anope_override
{
if (m->name != "m_ssl")
return;
for (std::map<Anope::string, MyHTTPProvider *>::iterator it = this->providers.begin(), it_end = this->providers.end(); it != it_end; ++it)
{
MyHTTPProvider *p = it->second;