summaryrefslogtreecommitdiff
path: root/modules/extra/ssl_openssl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/extra/ssl_openssl.cpp')
-rw-r--r--modules/extra/ssl_openssl.cpp435
1 files changed, 435 insertions, 0 deletions
diff --git a/modules/extra/ssl_openssl.cpp b/modules/extra/ssl_openssl.cpp
new file mode 100644
index 000000000..20673b5ef
--- /dev/null
+++ b/modules/extra/ssl_openssl.cpp
@@ -0,0 +1,435 @@
+/*
+ * Anope IRC Services
+ *
+ * Copyright (C) 2010-2016 Anope Team <team@anope.org>
+ *
+ * This file is part of Anope. Anope is free software; you can
+ * redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software
+ * Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see see <http://www.gnu.org/licenses/>.
+ */
+
+/* RequiredLibraries: ssl,crypto */
+/* RequiredWindowsLibraries: ssleay32,libeay32 */
+
+#include "module.h"
+#include "modules/ssl.h"
+
+#define OPENSSL_NO_SHA512
+#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 : 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 : 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
+ */
+ int 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
+ */
+ int 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 : public Module
+ , public EventHook<Event::PreServerConnect>
+{
+ 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);
+
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ client_ctx = SSL_CTX_new(SSLv23_client_method());
+ server_ctx = SSL_CTX_new(SSLv23_server_method());
+
+ if (!client_ctx || !server_ctx)
+ throw ModuleException("Error initializing SSL CTX");
+
+ long opts = SSL_OP_NO_SSLv2 | 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());
+ }
+
+ ~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 = config->Get<Anope::string>("cert", "data/anope.crt");
+ this->keyfile = config->Get<Anope::string>("key", "data/anope.key");
+
+ if (Anope::IsFile(this->certfile.c_str()))
+ {
+ 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(LogType::DEBUG) << "m_ssl_openssl: Successfully loaded certificate " << this->certfile;
+ }
+ else
+ Log() << "Unable to open certificate " << this->certfile;
+
+ if (Anope::IsFile(this->keyfile.c_str()))
+ {
+ 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(LogType::DEBUG) << "m_ssl_openssl: Successfully loaded private key " << this->keyfile;
+ }
+ else
+ {
+ if (Anope::IsFile(this->certfile.c_str()))
+ throw ConfigException("Error loading private key " + this->keyfile + " - file not found");
+ else
+ Log() << "Unable to open private key " << this->keyfile;
+ }
+
+ // Allow disabling SSLv3
+ if (!config->Get<Anope::string>("sslv3").empty())
+ {
+ if (config->Get<bool>("sslv3"))
+ {
+ SSL_CTX_clear_options(client_ctx, SSL_OP_NO_SSLv3);
+ SSL_CTX_clear_options(server_ctx, SSL_OP_NO_SSLv3);
+ }
+ else
+ {
+ SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv3);
+ SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv3);
+ }
+ }
+ }
+
+ 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;
+}
+
+int 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;
+}
+
+int 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->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->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)