diff options
author | Ashod Nakashian <ashod.nakashian@collabora.co.uk> | 2017-02-21 20:54:13 -0500 |
---|---|---|
committer | Jan Holesovsky <kendy@collabora.com> | 2017-03-10 10:47:39 +0100 |
commit | 9c595755ff4784ce78fba988e60ea785dde4c35c (patch) | |
tree | cb31ce7537a0994dc036fa9d6c99d3269133c5bc /net/SslSocket.hpp | |
parent | 18b131f30d8c1661347e5c9678c14d5493a71947 (diff) |
nb: move SslStreamSocket to own file
Change-Id: I4b6f2b0b4be3fc595dfeafc8e2b6d3e73694bd49
Diffstat (limited to 'net/SslSocket.hpp')
-rw-r--r-- | net/SslSocket.hpp | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/net/SslSocket.hpp b/net/SslSocket.hpp new file mode 100644 index 000000000..270b2ebf2 --- /dev/null +++ b/net/SslSocket.hpp @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_SSLSOCKET_HPP +#define INCLUDED_SSLSOCKET_HPP + +#include "config.h" + +#include <cerrno> + +#include "Ssl.hpp" +#include "Socket.hpp" + +/// An SSL/TSL, non-blocking, data streaming socket. +class SslStreamSocket : public BufferingSocket +{ +public: + ~SslStreamSocket() + { + shutdown(); + SSL_free(_ssl); + } + + /// Shutdown the TLS/SSL connection properly. + void shutdown() + { + if (SSL_shutdown(_ssl) == 0) + { + // Complete the bidirectional shutdown. + SSL_shutdown(_ssl); + } + } + + bool readIncomingData() override + { + const int rc = doHandshake(); + if (rc <= 0) + { + return (rc != 0); + } + + // Default implementation. + return BufferingSocket::readIncomingData(); + } + + void writeOutgoingData() override + { + const int rc = doHandshake(); + if (rc <= 0) + { + return; + } + + // Default implementation. + BufferingSocket::writeOutgoingData(); + } + + virtual int readData(char* buf, int len) + { + return handleSslState(SSL_read(_ssl, buf, len)); + } + + virtual int writeData(const char* buf, const int len) + { + assert (len > 0); // Never write 0 bytes. + return handleSslState(SSL_write(_ssl, buf, len)); + } + + int getPollEvents() override + { + if (_sslWantsTo == SslWantsTo::Read) + { + // Must read next before attempting to write. + return POLLIN; + } + else if (_sslWantsTo == SslWantsTo::Write) + { + // Must write next before attempting to read. + return POLLOUT; + } + + // Do the default. + return BufferingSocket::getPollEvents(); + } + +protected: + SslStreamSocket(const int fd) : + BufferingSocket(fd), + _ssl(nullptr), + _sslWantsTo(SslWantsTo::ReadOrWrite), + _doHandshake(true) + { + BIO* bio = BIO_new(BIO_s_socket()); + if (bio == nullptr) + { + throw std::runtime_error("Failed to create SSL BIO."); + } + + BIO_set_fd(bio, fd, BIO_NOCLOSE); + + _ssl = SslContext::newSsl(); + if (!_ssl) + { + BIO_free(bio); + throw std::runtime_error("Failed to create SSL."); + } + + SSL_set_bio(_ssl, bio, bio); + + // We are a server-side socket. + SSL_set_accept_state(_ssl); + } + + // Will construct us upon accept. + template<class T> friend class ServerSocket; + +private: + + /// The possible next I/O operation that SSL want to do. + enum class SslWantsTo + { + ReadOrWrite, + Read, + Write + }; + + int doHandshake() + { + if (_doHandshake) + { + int rc; + do + { + rc = SSL_do_handshake(_ssl); + } + while (rc < 0 && errno == EINTR); + + if (rc <= 0) + { + rc = handleSslState(rc); + if (rc <= 0) + { + return (rc != 0); + } + } + + _doHandshake = false; + } + + // Handshake complete. + return 1; + } + + /// Handles the state of SSL after read or write. + int handleSslState(const int rc) + { + if (rc > 0) + { + // Success: Reset so we can do either. + _sslWantsTo = SslWantsTo::ReadOrWrite; + return rc; + } + + // Last operation failed. Find out if SSL was trying + // to do something different that failed, or not. + const int sslError = SSL_get_error(_ssl, rc); + switch (sslError) + { + case SSL_ERROR_ZERO_RETURN: + // Shutdown complete, we're disconnected. + return 0; + + case SSL_ERROR_WANT_READ: + _sslWantsTo = SslWantsTo::Read; + return rc; + + case SSL_ERROR_WANT_WRITE: + _sslWantsTo = SslWantsTo::Write; + return rc; + + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + case SSL_ERROR_WANT_X509_LOOKUP: + // Unexpected. + return rc; + + case SSL_ERROR_SYSCALL: + if (errno != 0) + { + // Posix API error, let the caller handle. + return rc; + } + + // Fallthrough... + default: + { + // The error is comming from BIO. Find out what happened. + const long bioError = ERR_get_error(); + if (bioError == 0) + { + if (rc == 0) + { + // Socket closed. + return 0; + } + else if (rc == -1) + { + throw std::runtime_error("SSL Socket closed unexpectedly."); + } + else + { + throw std::runtime_error("SSL BIO reported error [" + std::to_string(rc) + "]."); + } + } + else + { + char buf[512]; + ERR_error_string_n(bioError, buf, sizeof(buf)); + throw std::runtime_error(buf); + } + } + break; + } + + return rc; + } + +private: + SSL* _ssl; + /// During handshake SSL might want to read + /// on write, or write on read. + SslWantsTo _sslWantsTo; + /// We must do the handshake during the first + /// read or write in non-blocking. + bool _doHandshake; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |