mirror of
git://git.acid.vegas/anope.git
synced 2024-11-15 04:06:42 +00:00
657 lines
16 KiB
C++
657 lines
16 KiB
C++
|
/*
|
||
|
*
|
||
|
* (C) 2014 Attila Molnar <attilamolnar@hush.com>
|
||
|
* (C) 2014-2022 Anope Team
|
||
|
* Contact us at team@anope.org
|
||
|
*
|
||
|
* Please read COPYING and README for further details.
|
||
|
*/
|
||
|
|
||
|
/* RequiredLibraries: gnutls */
|
||
|
/* RequiredWindowsLibraries: libgnutls-30 */
|
||
|
|
||
|
#include "module.h"
|
||
|
#include "modules/ssl.h"
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <gnutls/gnutls.h>
|
||
|
#include <gnutls/x509.h>
|
||
|
|
||
|
class GnuTLSModule;
|
||
|
static GnuTLSModule *me;
|
||
|
|
||
|
namespace GnuTLS { class X509CertCredentials; }
|
||
|
|
||
|
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;
|
||
|
GnuTLS::X509CertCredentials* mycreds;
|
||
|
|
||
|
/** Constructor
|
||
|
*/
|
||
|
SSLSocketIO();
|
||
|
|
||
|
/** 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 X509Key
|
||
|
{
|
||
|
/** Ensure that the key is deinited in case the constructor of X509Key throws
|
||
|
*/
|
||
|
class RAIIKey
|
||
|
{
|
||
|
public:
|
||
|
gnutls_x509_privkey_t key;
|
||
|
|
||
|
RAIIKey()
|
||
|
{
|
||
|
int ret = gnutls_x509_privkey_init(&key);
|
||
|
if (ret < 0)
|
||
|
throw ConfigException("gnutls_x509_privkey_init() failed");
|
||
|
}
|
||
|
|
||
|
~RAIIKey()
|
||
|
{
|
||
|
gnutls_x509_privkey_deinit(key);
|
||
|
}
|
||
|
} key;
|
||
|
|
||
|
public:
|
||
|
/** Import */
|
||
|
X509Key(const Anope::string &keystr)
|
||
|
{
|
||
|
int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM);
|
||
|
if (ret < 0)
|
||
|
throw ConfigException("Error loading private key: " + Anope::string(gnutls_strerror(ret)));
|
||
|
}
|
||
|
|
||
|
gnutls_x509_privkey_t& get() { return key.key; }
|
||
|
};
|
||
|
|
||
|
class X509CertList
|
||
|
{
|
||
|
std::vector<gnutls_x509_crt_t> certs;
|
||
|
|
||
|
public:
|
||
|
/** Import */
|
||
|
X509CertList(const Anope::string &certstr)
|
||
|
{
|
||
|
unsigned int certcount = 3;
|
||
|
certs.resize(certcount);
|
||
|
Datum datum(certstr);
|
||
|
|
||
|
int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
|
||
|
if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
|
||
|
{
|
||
|
// the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs,
|
||
|
// try again with a bigger buffer
|
||
|
certs.resize(certcount);
|
||
|
ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
|
||
|
}
|
||
|
|
||
|
if (ret < 0)
|
||
|
throw ConfigException("Unable to load certificates" + Anope::string(gnutls_strerror(ret)));
|
||
|
|
||
|
// Resize the vector to the actual number of certs because we rely on its size being correct
|
||
|
// when deallocating the certs
|
||
|
certs.resize(certcount);
|
||
|
}
|
||
|
|
||
|
~X509CertList()
|
||
|
{
|
||
|
for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i)
|
||
|
gnutls_x509_crt_deinit(*i);
|
||
|
}
|
||
|
|
||
|
gnutls_x509_crt_t* raw() { return &certs[0]; }
|
||
|
unsigned int size() const { return certs.size(); }
|
||
|
};
|
||
|
|
||
|
class X509CertCredentials
|
||
|
{
|
||
|
unsigned int refcount;
|
||
|
gnutls_certificate_credentials_t cred;
|
||
|
DHParams dh;
|
||
|
|
||
|
static Anope::string LoadFile(const Anope::string &filename)
|
||
|
{
|
||
|
std::ifstream ifs(filename.c_str());
|
||
|
const Anope::string ret((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
|
||
|
static int 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, gnutls_retr_st* st);
|
||
|
#else
|
||
|
static int 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, gnutls_retr2_st* st);
|
||
|
#endif
|
||
|
|
||
|
public:
|
||
|
X509CertList certs;
|
||
|
X509Key key;
|
||
|
|
||
|
X509CertCredentials(const Anope::string &certfile, const Anope::string &keyfile)
|
||
|
: refcount(0), certs(LoadFile(certfile)), key(LoadFile(keyfile))
|
||
|
{
|
||
|
if (gnutls_certificate_allocate_credentials(&cred) < 0)
|
||
|
throw ConfigException("Cannot allocate certificate credentials");
|
||
|
|
||
|
int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get());
|
||
|
if (ret < 0)
|
||
|
{
|
||
|
gnutls_certificate_free_credentials(cred);
|
||
|
throw ConfigException("Unable to set cert/key pair");
|
||
|
}
|
||
|
|
||
|
#if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
|
||
|
gnutls_certificate_client_set_retrieve_function(cred, cert_callback);
|
||
|
#else
|
||
|
gnutls_certificate_set_retrieve_function(cred, cert_callback);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
~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 SetDH(const Anope::string &dhfile)
|
||
|
{
|
||
|
const Anope::string dhdata = LoadFile(dhfile);
|
||
|
dh.Import(dhdata);
|
||
|
gnutls_certificate_set_dh_params(cred, dh.get());
|
||
|
}
|
||
|
|
||
|
bool HasDH() const
|
||
|
{
|
||
|
return (dh.get() != NULL);
|
||
|
}
|
||
|
|
||
|
void incrref() { refcount++; }
|
||
|
void decrref() { if (!--refcount) delete this; }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
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), cred(NULL), 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;
|
||
|
}
|
||
|
|
||
|
if (cred)
|
||
|
cred->decrref();
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
GnuTLS::X509CertCredentials *newcred = new GnuTLS::X509CertCredentials(certfile, keyfile);
|
||
|
|
||
|
// DH params is not mandatory
|
||
|
if (Anope::IsFile(dhfile.c_str()))
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
newcred->SetDH(dhfile);
|
||
|
}
|
||
|
catch (...)
|
||
|
{
|
||
|
delete newcred;
|
||
|
throw;
|
||
|
}
|
||
|
Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded DH parameters from " << dhfile;
|
||
|
}
|
||
|
|
||
|
if (cred)
|
||
|
cred->decrref();
|
||
|
cred = newcred;
|
||
|
cred->incrref();
|
||
|
|
||
|
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_ptr(io->sess, reinterpret_cast<gnutls_transport_ptr_t>(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_ptr(io->sess, reinterpret_cast<gnutls_transport_ptr_t>(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);
|
||
|
}
|
||
|
|
||
|
mycreds->decrref();
|
||
|
|
||
|
delete this;
|
||
|
}
|
||
|
|
||
|
SSLSocketIO::SSLSocketIO() : sess(NULL), mycreds(me->cred)
|
||
|
{
|
||
|
mycreds->incrref();
|
||
|
}
|
||
|
|
||
|
#if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
|
||
|
int GnuTLS::X509CertCredentials::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, gnutls_retr_st* st)
|
||
|
{
|
||
|
st->type = GNUTLS_CRT_X509;
|
||
|
#else
|
||
|
int GnuTLS::X509CertCredentials::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, gnutls_retr2_st* st)
|
||
|
{
|
||
|
st->cert_type = GNUTLS_CRT_X509;
|
||
|
st->key_type = GNUTLS_PRIVKEY_X509;
|
||
|
#endif
|
||
|
st->ncerts = me->cred->certs.size();
|
||
|
st->cert.x509 = me->cred->certs.raw();
|
||
|
st->key.x509 = me->cred->key.get();
|
||
|
st->deinit_all = 0;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
MODULE_INIT(GnuTLSModule)
|