Merge pull request #407 from CopernicaMarketingSoftware/40373
Print a better error message
This commit is contained in:
commit
29dd838478
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
}
|
||||
|
|
@ -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
|
||||
*/
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue