1
0
mirror of https://github.com/anope/anope.git synced 2026-06-24 22:06:38 +02:00
Files
anope/modules/extra/ssl_openssl.cpp
T
Sadie Powell f9911dde52 Return references instead of pointers from the config system.
We used to return NULL from these methods but now we return an empty
block so this can never actually be null now.
2025-03-02 15:27:47 +00:00

448 lines
12 KiB
C++

/*
*
* (C) 2010-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
/* RequiredLibraries: ssl,crypto */
/* RequiredWindowsLibraries: libssl,libcrypto */
#include "module.h"
#include "modules/ssl.h"
#define OPENSSL_API_COMPAT 0x10100000L
#define OPENSSL_NO_DEPRECATED
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/crypto.h>
#include <openssl/evp.h>
static SSL_CTX *server_ctx, *client_ctx;
class MySSLService final
: public SSLService
{
public:
MySSLService(Module *o, const Anope::string &n);
/** Initialize a socket to use SSL
* @param s The socket
*/
void Init(Socket *s) override;
};
class SSLSocketIO final
: public SocketIO
{
public:
/* The SSL socket for this socket */
SSL *sslsock;
/** 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
*/
ssize_t Recv(Socket *s, char *buf, size_t sz) override;
/** Write something to the socket
* @param s The socket
* @param buf The data to write
* @param size The length of the data
*/
ssize_t Send(Socket *s, const char *buf, size_t sz) override;
/** Accept a connection from a socket
* @param s The socket
* @return The new socket
*/
ClientSocket *Accept(ListenSocket *s) 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) 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) 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) override;
/** Called when the socket is destructing
*/
void Destroy() override;
};
class SSLModule;
static SSLModule *me;
class SSLModule final
: public Module
{
Anope::string certfile, keyfile;
public:
MySSLService service;
SSLModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), service(this, "ssl")
{
me = this;
this->SetPermanent(true);
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, nullptr);
client_ctx = SSL_CTX_new(TLS_client_method());
server_ctx = SSL_CTX_new(TLS_server_method());
if (!client_ctx || !server_ctx)
throw ModuleException("Error initializing SSL CTX");
long opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_CIPHER_SERVER_PREFERENCE;
SSL_CTX_set_options(client_ctx, opts);
SSL_CTX_set_options(server_ctx, opts);
SSL_CTX_set_mode(client_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_mode(server_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
Anope::string context_name = "Anope";
SSL_CTX_set_session_id_context(client_ctx, reinterpret_cast<const unsigned char *>(context_name.c_str()), context_name.length());
SSL_CTX_set_session_id_context(server_ctx, reinterpret_cast<const unsigned char *>(context_name.c_str()), context_name.length());
Log() << "Module was compiled against OpenSSL version " << OPENSSL_VERSION_STR << " and is running against version " << OpenSSL_version(OPENSSL_VERSION_STRING);
}
~SSLModule()
{
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;
}
SSL_CTX_free(client_ctx);
SSL_CTX_free(server_ctx);
}
void OnReload(Configuration::Conf &conf) override
{
Configuration::Block &config = conf.GetModule(this);
this->certfile = Anope::ExpandConfig(config.Get<const Anope::string>("cert", "fullchain.pem"));
this->keyfile = Anope::ExpandConfig(config.Get<const Anope::string>("key", "privkey.pem"));
if (Anope::IsFile(this->certfile))
{
if (!SSL_CTX_use_certificate_chain_file(client_ctx, this->certfile.c_str()) || !SSL_CTX_use_certificate_chain_file(server_ctx, this->certfile.c_str()))
throw ConfigException("Error loading certificate");
else
Log(LOG_DEBUG) << "ssl_openssl: Successfully loaded certificate " << this->certfile;
}
else
Log() << "Unable to open certificate " << this->certfile;
if (Anope::IsFile(this->keyfile))
{
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) << "ssl_openssl: Successfully loaded private key " << this->keyfile;
}
else
{
if (Anope::IsFile(this->certfile))
throw ConfigException("Error loading private key " + this->keyfile + " - file not found");
else
Log() << "Unable to open private key " << this->keyfile;
}
// Allow disabling old versions of TLS
if (config.Get<bool>("tlsv10", "false"))
{
SSL_CTX_clear_options(client_ctx, SSL_OP_NO_TLSv1);
SSL_CTX_clear_options(server_ctx, SSL_OP_NO_TLSv1);
}
else
{
SSL_CTX_set_options(client_ctx, SSL_OP_NO_TLSv1);
SSL_CTX_set_options(server_ctx, SSL_OP_NO_TLSv1);
}
if (config.Get<bool>("tlsv11", "true"))
{
SSL_CTX_clear_options(client_ctx, SSL_OP_NO_TLSv1_1);
SSL_CTX_clear_options(server_ctx, SSL_OP_NO_TLSv1_1);
}
else
{
SSL_CTX_set_options(client_ctx, SSL_OP_NO_TLSv1_1);
SSL_CTX_set_options(server_ctx, SSL_OP_NO_TLSv1_1);
}
if (config.Get<bool>("tlsv12", "true"))
{
SSL_CTX_clear_options(client_ctx, SSL_OP_NO_TLSv1_2);
SSL_CTX_clear_options(server_ctx, SSL_OP_NO_TLSv1_2);
}
else
{
SSL_CTX_set_options(client_ctx, SSL_OP_NO_TLSv1_2);
SSL_CTX_set_options(server_ctx, SSL_OP_NO_TLSv1_2);
}
}
void OnPreServerConnect() 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();
}
SSLSocketIO::SSLSocketIO()
{
this->sslsock = NULL;
}
ssize_t SSLSocketIO::Recv(Socket *s, char *buf, size_t sz)
{
int i = SSL_read(this->sslsock, buf, sz);
if (i > 0)
TotalRead += i;
else if (i < 0)
{
int err = SSL_get_error(this->sslsock, i);
switch (err)
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
SocketEngine::SetLastError(EAGAIN);
}
}
return i;
}
ssize_t SSLSocketIO::Send(Socket *s, const char *buf, size_t sz)
{
int i = SSL_write(this->sslsock, buf, sz);
if (i > 0)
TotalWritten += i;
else if (i < 0)
{
int err = SSL_get_error(this->sslsock, i);
switch (err)
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
SocketEngine::SetLastError(EAGAIN);
}
}
return i;
}
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);
io->sslsock = SSL_new(server_ctx);
if (!io->sslsock)
throw SocketException("Unable to initialize SSL socket");
SSL_set_accept_state(io->sslsock);
if (!SSL_set_fd(io->sslsock, newsocket->GetFD()))
throw SocketException("Unable to set SSL fd");
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 = SSL_accept(io->sslsock);
if (ret <= 0)
{
int error = SSL_get_error(io->sslsock, ret);
if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE))
{
SocketEngine::Change(cs, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE);
SocketEngine::Change(cs, error == SSL_ERROR_WANT_READ, SF_READABLE);
return SF_ACCEPTING;
}
else
{
cs->OnError(ERR_error_string(ERR_get_error(), NULL));
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->GetFamily(), 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->sslsock == NULL)
{
io->sslsock = SSL_new(client_ctx);
if (!io->sslsock)
throw SocketException("Unable to initialize SSL socket");
if (!SSL_set_fd(io->sslsock, s->GetFD()))
throw SocketException("Unable to set SSL fd");
}
int ret = SSL_connect(io->sslsock);
if (ret <= 0)
{
int error = SSL_get_error(io->sslsock, ret);
if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE))
{
SocketEngine::Change(s, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE);
SocketEngine::Change(s, error == SSL_ERROR_WANT_READ, SF_READABLE);
return SF_CONNECTING;
}
else
{
s->OnError(ERR_error_string(ERR_get_error(), NULL));
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->sslsock)
{
SSL_shutdown(this->sslsock);
SSL_free(this->sslsock);
}
delete this;
}
MODULE_INIT(SSLModule)