Merge pull request #407 from CopernicaMarketingSoftware/40373

Print a better error message
This commit is contained in:
Emiel Bruijntjes 2021-07-14 08:23:12 +02:00 committed by GitHub
commit 29dd838478
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 209 additions and 12 deletions

View File

@ -354,6 +354,20 @@ void ERR_clear_error()
return func();
}
/**
* Print errors via a callback
* @param cb
* @param u
*/
void ERR_print_errors_cb(int (*cb)(const char *str, size_t len, void *u), void *u)
{
// the actual function
static Function<decltype(::ERR_print_errors_cb)> func(handle, "ERR_print_errors_cb");
// call the openssl function
func(cb, u);
}
/**
* End of namespace
*/

View File

@ -53,6 +53,7 @@ long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg);
long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg);
int SSL_CTX_set_default_verify_paths(SSL_CTX *ctx);
void ERR_clear_error(void);
void ERR_print_errors_cb(int (*cb)(const char *str, size_t len, void *u), void *u);
/**
* End of namespace

View File

@ -19,6 +19,7 @@
#include "poll.h"
#include "sslwrapper.h"
#include "sslshutdown.h"
#include "sslerrorprinter.h"
/**
* Set up namespace
@ -133,17 +134,17 @@ private:
case SSL_ERROR_WANT_READ:
// remember state
_state = state;
// the operation must be repeated when readable
_parent->onIdle(this, _socket, readable);
// allow chaining
return true;
case SSL_ERROR_WANT_WRITE:
// remember state
_state = state;
// wait until socket becomes writable again
_parent->onIdle(this, _socket, readable | writable);
@ -155,19 +156,24 @@ private:
case SSL_ERROR_NONE:
// we're ready for the next instruction from userspace
_state = state_idle;
// if we still have an outgoing buffer we want to send out data, otherwise we just read
_parent->onIdle(this, _socket, _out ? readable | writable : readable);
// nothing is wrong, we are done
return true;
default:
// we are now in an error state
_state = state_error;
// report an error to user-space
_parent->onError(this, "ssl protocol error");
{
// get a human-readable error string
const SslErrorPrinter message{error};
// report an error to user-space
_parent->onError(this, message.data());
}
// ssl level error, we have to tear down the tcp connection
return false;
@ -304,7 +310,6 @@ private:
// proceed with the write operation or the event loop
return _out && isWritable() ? write(monitor) : proceed();
}
public:
/**

View File

@ -0,0 +1,100 @@
/**
* SslErrorPrinter.cpp
*
* Implementation of SslErrorPrinter
*
* @copyright 2021 copernica BV
*/
/**
* Dependencies
*/
#include "sslerrorprinter.h"
#include <cstring>
/**
* Begin namespace
*/
namespace AMQP {
/**
* Callback used for ERR_print_errors_cb
* @param str The string
* @param len The length
* @param ctx The context (this ptr)
* @return always 0 to signal to OpenSSL to continue
*/
int sslerrorprintercallback(const char *str, size_t len, void *ctx)
{
// Cast to ourselves and store the error line. OpenSSL adds a newline to every error line.
static_cast<SslErrorPrinter*>(ctx)->_message.append(str, len);
// continue with the next message
return 0;
}
/**
* Constructor
* @param retval return value of SSL_get_error (must be a proper error)
*/
SslErrorPrinter::SslErrorPrinter(int retval)
{
// check the return value of the SSL_get_error function, which has a very unfortunate name.
switch (retval)
{
// It can be a syscall error.
case SSL_ERROR_SYSCALL:
// The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected
// EOF from the peer. This will be properly reported as SSL_ERROR_SSL
// with reason code SSL_R_UNEXPECTED_EOF_WHILE_READING in the
// OpenSSL 3.0 release because it is truly a TLS protocol error to
// terminate the connection without a SSL_shutdown().
if (errno == 0) _message = "SSL_R_UNEXPECTED_EOF_WHILE_READING";
// Otherwise we ask the OS for a description of the error.
else _message = ::strerror(errno);
// done
break;
// It can be an error in OpenSSL. In that case the error stack contains
// more information. The documentation notes: if this error occurs then
// no further I/O operations should be performed on the connection and
// SSL_shutdown() must not be called.
case SSL_ERROR_SSL:
// collect all error lines
OpenSSL::ERR_print_errors_cb(&sslerrorprintercallback, this);
// remove the last newline
if (!_message.empty() && _message.back() == '\n') _message.pop_back();
// done
break;
default:
// we don't know what kind of error this is
_message = "unknown ssl error";
// done
break;
}
}
/**
* data ptr (guaranteed null-terminated)
* @return const char *
*/
const char *SslErrorPrinter::data() const noexcept { return _message.data(); }
/**
* length of the string
* @return size_t
*/
std::size_t SslErrorPrinter::size() const noexcept { return _message.size(); }
/**
* End of namespace AMQP
*/
}

View File

@ -0,0 +1,72 @@
/**
* SslErrorPrinter.h
*
* Flushes the SSL error stack to a string.
* You can get at the string content via the data() and size() methods.
* After constructing an instance of this class, the SSL error stack
* is empty.
*
* @copyright 2021 copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "openssl.h"
#include <memory>
/**
* Begin namespace
*/
namespace AMQP {
/**
* Class declaration
*/
class SslErrorPrinter final
{
public:
/**
* Constructor
* @param retval return value of SSL_get_error (must be a proper error)
*/
SslErrorPrinter(int retval);
/**
* data ptr (guaranteed null-terminated)
* @return const char *
*/
const char *data() const noexcept;
/**
* length of the string
* @return size_t
*/
std::size_t size() const noexcept;
private:
/**
* The error message
* @var std::string
*/
std::string _message;
/**
* Callback used for ERR_print_errors_cb
* @param str The string
* @param len The length
* @param ctx The context (this ptr)
* @return always 0 to signal to OpenSSL to continue
*/
friend int sslerrorprintercallback(const char *str, size_t len, void *ctx);
};
/**
* End of namespace AMQP
*/
}

View File

@ -20,6 +20,7 @@
#include "poll.h"
#include "sslwrapper.h"
#include "sslcontext.h"
#include "sslerrorprinter.h"
/**
* Set up namespace
@ -80,12 +81,16 @@ private:
/**
* Helper method to report an error
* @param monitor
* @param retval return value of SSL_get_error
* @return TcpState*
*/
TcpState *reportError(const Monitor &monitor)
TcpState *reportError(const Monitor &monitor, int retval)
{
// extract a human-readable error string
const SslErrorPrinter message{retval};
// we have an error - report this to the user
_parent->onError(this, "failed to setup ssl connection");
_parent->onError(this, message.data());
// stop if connection is gone
if (!monitor.valid()) return nullptr;
@ -182,7 +187,7 @@ public:
switch (error) {
case SSL_ERROR_WANT_READ: return proceed(readable);
case SSL_ERROR_WANT_WRITE: return proceed(readable | writable);
default: return reportError(monitor);
default: return reportError(monitor, error);
}
}