253 lines
6.9 KiB
C++
253 lines
6.9 KiB
C++
/**
|
|
* SslHandshake.h
|
|
*
|
|
* Implementation of the TCP state that is responsible for setting
|
|
* up the STARTTLS handshake.
|
|
*
|
|
* @copyright 2018 Copernica BV
|
|
*/
|
|
|
|
/**
|
|
* Include guard
|
|
*/
|
|
#pragma once
|
|
|
|
/**
|
|
* Dependencies
|
|
*/
|
|
#include "tcpoutbuffer.h"
|
|
#include "sslconnected.h"
|
|
#include "wait.h"
|
|
#include "sslwrapper.h"
|
|
#include "sslcontext.h"
|
|
|
|
/**
|
|
* Set up namespace
|
|
*/
|
|
namespace AMQP {
|
|
|
|
/**
|
|
* Class definition
|
|
*/
|
|
class SslHandshake : public TcpState, private Watchable
|
|
{
|
|
private:
|
|
/**
|
|
* SSL structure
|
|
* @var SslWrapper
|
|
*/
|
|
SslWrapper _ssl;
|
|
|
|
/**
|
|
* The socket file descriptor
|
|
* @var int
|
|
*/
|
|
int _socket;
|
|
|
|
/**
|
|
* The outgoing buffer
|
|
* @var TcpOutBuffer
|
|
*/
|
|
TcpOutBuffer _out;
|
|
|
|
|
|
/**
|
|
* Report a new state
|
|
* @param state
|
|
* @return TcpState
|
|
*/
|
|
TcpState *nextstate(TcpState *state)
|
|
{
|
|
// forget the socket to prevent that it is closed by the destructor
|
|
_socket = -1;
|
|
|
|
// done
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Helper method to report an error
|
|
* @return TcpState*
|
|
*/
|
|
TcpState *reportError()
|
|
{
|
|
// we are no longer interested in any events for this socket
|
|
_handler->monitor(_connection, _socket, 0);
|
|
|
|
// we have an error - report this to the user
|
|
_handler->onError(_connection, "failed to setup ssl connection");
|
|
|
|
// done, go to the closed state
|
|
return new TcpClosed(_connection, _handler);
|
|
}
|
|
|
|
/**
|
|
* Proceed with the handshake
|
|
* @param events the events to wait for on the socket
|
|
* @return TcpState
|
|
*/
|
|
TcpState *proceed(int events)
|
|
{
|
|
// tell the handler that we want to listen for certain events
|
|
_handler->monitor(_connection, _socket, events);
|
|
|
|
// allow chaining
|
|
return this;
|
|
}
|
|
|
|
public:
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @todo catch the exception!
|
|
*
|
|
* @param connection Parent TCP connection object
|
|
* @param socket The socket filedescriptor
|
|
* @param hostname The hostname to connect to
|
|
* @param context SSL context
|
|
* @param buffer The buffer that was already built
|
|
* @param handler User-supplied handler object
|
|
* @throws std::runtime_error
|
|
*/
|
|
SslHandshake(TcpConnection *connection, int socket, const std::string &hostname, TcpOutBuffer &&buffer, TcpHandler *handler) :
|
|
TcpState(connection, handler),
|
|
_ssl(SslContext(SSLv23_client_method())),
|
|
_socket(socket),
|
|
_out(std::move(buffer))
|
|
{
|
|
// we will be using the ssl context as a client
|
|
SSL_set_connect_state(_ssl);
|
|
|
|
// associate domain name with the connection
|
|
SSL_set_tlsext_host_name(_ssl, hostname.data());
|
|
|
|
// associate the ssl context with the socket filedescriptor
|
|
if (SSL_set_fd(_ssl, socket) == 0) throw std::runtime_error("failed to associate filedescriptor with ssl socket");
|
|
|
|
// we are going to wait until the socket becomes writable before we start the handshake
|
|
_handler->monitor(_connection, _socket, writable);
|
|
}
|
|
|
|
/**
|
|
* Destructor
|
|
*/
|
|
virtual ~SslHandshake() noexcept
|
|
{
|
|
// leap out if socket is invalidated
|
|
if (_socket < 0) return;
|
|
|
|
// close the socket
|
|
close(_socket);
|
|
}
|
|
|
|
/**
|
|
* The filedescriptor of this connection
|
|
* @return int
|
|
*/
|
|
virtual int fileno() const override { return _socket; }
|
|
|
|
/**
|
|
* Process the filedescriptor in the object
|
|
* @param fd Filedescriptor that is active
|
|
* @param flags AMQP::readable and/or AMQP::writable
|
|
* @return New state object
|
|
*/
|
|
virtual TcpState *process(int fd, int flags) override
|
|
{
|
|
// must be the socket
|
|
if (fd != _socket) return this;
|
|
|
|
// start the ssl handshake
|
|
int result = SSL_do_handshake(_ssl);
|
|
|
|
// if the connection succeeds, we can move to the ssl-connected state
|
|
if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler));
|
|
|
|
// error was returned, so we must investigate what is going on
|
|
auto error = SSL_get_error(_ssl, result);
|
|
|
|
// check the error
|
|
switch (error) {
|
|
case SSL_ERROR_WANT_READ: return proceed(readable);
|
|
case SSL_ERROR_WANT_WRITE: return proceed(readable | writable);
|
|
default: return reportError();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send data over the connection
|
|
* @param buffer buffer to send
|
|
* @param size size of the buffer
|
|
*/
|
|
virtual void send(const char *buffer, size_t size) override
|
|
{
|
|
// the handshake is still busy, outgoing data must be cached
|
|
_out.add(buffer, size);
|
|
}
|
|
|
|
/**
|
|
* Flush the connection, sent all buffered data to the socket
|
|
* @return TcpState new tcp state
|
|
*/
|
|
virtual TcpState *flush() override
|
|
{
|
|
// create an object to wait for the filedescriptor to becomes active
|
|
Wait wait(_socket);
|
|
|
|
// keep looping
|
|
while (true)
|
|
{
|
|
// start the ssl handshake
|
|
int result = SSL_do_handshake(_ssl);
|
|
|
|
// if the connection succeeds, we can move to the ssl-connected state
|
|
if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler));
|
|
|
|
// error was returned, so we must investigate what is going on
|
|
auto error = SSL_get_error(_ssl, result);
|
|
|
|
// check the error
|
|
switch (error)
|
|
{
|
|
// if openssl reports that socket readability or writability is needed,
|
|
// we wait for that until this situation is reached
|
|
case SSL_ERROR_WANT_READ: wait.readable(); break;
|
|
case SSL_ERROR_WANT_WRITE: wait.active(); break;
|
|
|
|
// something is wrong, we proceed to the next state
|
|
default: return reportError();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Report to the handler that the connection was nicely closed
|
|
*/
|
|
virtual void reportClosed() override
|
|
{
|
|
// we no longer have to monitor the socket
|
|
_handler->monitor(_connection, _socket, 0);
|
|
|
|
// close the socket
|
|
close(_socket);
|
|
|
|
// socket is closed now
|
|
_socket = -1;
|
|
|
|
// copy the handler (if might destruct this object)
|
|
auto *handler = _handler;
|
|
|
|
// reset member before the handler can make a mess of it
|
|
_handler = nullptr;
|
|
|
|
// notify to handler
|
|
handler->onClosed(_connection);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* End of namespace
|
|
*/
|
|
}
|
|
|