Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/bitcoin/network/channels/channel_http.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class BCT_API channel_http
uint64_t identifier, const settings_t& settings,
const options_t& options) NOEXCEPT
: channel(log, socket, identifier, settings, options),
options_(options),
response_buffer_(system::to_shared<http::flat_buffer>()),
request_buffer_(options.minimum_buffer)
{
Expand All @@ -79,6 +80,9 @@ class BCT_API channel_http
/// Read request buffer (requires strand).
virtual http::flat_buffer& request_buffer() NOEXCEPT;

/// Determine if http basic authorization is satisfied if enabled.
virtual bool unauthorized(const http::request& request) NOEXCEPT;

/// Dispatch request to subscribers by verb type.
virtual void dispatch(const http::request_cptr& request) NOEXCEPT;

Expand All @@ -93,11 +97,15 @@ class BCT_API channel_http
const result_handler& handler) NOEXCEPT;

private:
void handle_unauthorized(const code& ec) NOEXCEPT;
void log_message(const http::request& request,
size_t bytes) const NOEXCEPT;
void log_message(const http::response& response,
size_t bytes) const NOEXCEPT;

// This is thread safe.
const options_t& options_;

// These are protected by strand.
http::flat_buffer_ptr response_buffer_;
http::flat_buffer request_buffer_;
Expand Down
2 changes: 1 addition & 1 deletion include/bitcoin/network/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ enum error_t : uint8_t

// boost beast http 4xx client error
bad_request,
////unauthorized,
unauthorized,
////payment_required,
forbidden,
not_found,
Expand Down
9 changes: 9 additions & 0 deletions include/bitcoin/network/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ struct BCT_API settings
/// Opaque origins are always serialized as "null".
bool allow_opaque_origin{ false };

/// Basic authorization credential stored and passed in cleartext.
/// This is not security, just compat for bitcoind on secured LAN.
std::string username{};
std::string password{};

/// Requires basic authorization.
virtual bool authorize() const NOEXCEPT;
virtual std::string credential() const NOEXCEPT;

/// Normalized configured hosts/origins helpers.
virtual system::string_list host_names() const NOEXCEPT;
virtual system::string_list origin_names() const NOEXCEPT;
Expand Down
25 changes: 25 additions & 0 deletions src/channels/channel_http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ void channel_http::handle_receive(const code& ec, size_t bytes,
// Wrap the http request as a tagged verb request and dispatch by type.
void channel_http::dispatch(const request_cptr& request) NOEXCEPT
{
BC_ASSERT(stranded());

if (unauthorized(*request))
{
send({ status::unauthorized, request->version() },
std::bind(&channel_http::handle_unauthorized,
shared_from_base<channel_http>(), _1));
return;
}

rpc::request_t model{};
switch (request.get()->method())
{
Expand Down Expand Up @@ -185,6 +195,21 @@ void channel_http::assign_json_buffer(response& response) NOEXCEPT
}
}

// unauthorized helpers
// ----------------------------------------------------------------------------

bool channel_http::unauthorized(const http::request& request) NOEXCEPT
{
return options_.authorize() &&
(options_.credential() != request[field::authorization]);
}

void channel_http::handle_unauthorized(const code& ec) NOEXCEPT
{
BC_ASSERT(stranded());
stop(ec ? ec : error::unauthorized);
}

// log helpers
// ----------------------------------------------------------------------------

Expand Down
4 changes: 2 additions & 2 deletions src/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
{ tls_set_add_verify, "failed to set tls certificate authority" },
{ tls_stream_truncated, "tls stream truncated" },
{ tls_unspecified_system_error, "tls unspecified system error" },
{ tls_unexpected_result, "tls unexpected result" },
{ tls_unexpected_result, "tls handshake failure" },

// boost beast http 4xx client error
{ bad_request, "bad request" },
////{ unauthorized, "unauthorized" },
{ unauthorized, "unauthorized" },
////{ payment_required, "payment required" },
{ forbidden, "forbidden" },
{ not_found, "not found" },
Expand Down
19 changes: 19 additions & 0 deletions src/net/socket_connect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,25 @@ void socket::handle_handshake(const boost_code& ec,
return;
}

//// // Diagnostic block for backend-specific error retrieval.
//// // Boost maps detailed wolfssl errors to a generic error code.
//// if (ec)
//// {
////#ifdef HAVE_SSL
//// const auto handle = std::get<asio::ssl::socket>(socket_).native_handle();
//// char buffer[WOLFSSL_MAX_ERROR_SZ]{};
//// const auto error = ::wolfSSL_get_error(handle, 0);
//// ::wolfSSL_ERR_error_string_n(error, &buffer[0], sizeof(buffer));
//// LOGF("wolfSSL handshake error: code=" << error << ", desc='" << buffer << "'");
////#else
//// // OpenSSL recommends 120 bytes for strings.
//// char buffer[120]{};
//// const auto error = ::ERR_get_error();
//// ::ERR_error_string_n(error, &buffer[0], sizeof(buffer));
//// LOGF("OpenSSL handshake error: code=" << error << ", desc='" << buffer << "'");
////#endif
//// }

const auto code = error::ssl_to_error_code(ec);
if (code == error::unknown) logx("handshake", ec);
handler(code);
Expand Down
5 changes: 3 additions & 2 deletions src/sessions/session_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,14 @@ void session_server::handle_accepted(const code& ec, const socket::ptr& socket,
return;
}

// There was an error accepting the channel, so try again after delay.
// There was an error accepting the channel, so try again.
if (ec)
{
BC_ASSERT_MSG(!socket || socket->stopped(), "unexpected socket");
LOGF("Failed to accept " << (secure ? "private " : "clear ")
<< name_ << " connection, " << ec.message());
defer(BIND(start_accept, _1, acceptor, secure));
////defer(BIND(start_accept, _1, acceptor, secure));
start_accept(error::success, acceptor, secure);
return;
}

Expand Down
13 changes: 13 additions & 0 deletions src/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,19 @@ system::string_list settings::http_server::origin_names() const NOEXCEPT
return config::to_host_names(hosts, port);
}

bool settings::http_server::authorize() const NOEXCEPT
{
return !username.empty() || !password.empty();
}

std::string settings::http_server::credential() const NOEXCEPT
{
static const auto value = "Basic " +
system::encode_base64(username + ":" + password);

return value;
}

// websocket_server
// ----------------------------------------------------------------------------

Expand Down
20 changes: 10 additions & 10 deletions test/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ BOOST_AUTO_TEST_CASE(error_t__code__tls_unexpected_result__true_expected_message
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "tls unexpected result");
BOOST_REQUIRE_EQUAL(ec.message(), "tls handshake failure");
}

// http 4xx client error
Expand All @@ -769,15 +769,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__bad_request__true_expected_message)
BOOST_REQUIRE_EQUAL(ec.message(), "bad request");
}

////BOOST_AUTO_TEST_CASE(error_t__code__unauthorized__true_expected_message)
////{
//// constexpr auto value = error::unauthorized;
//// const auto ec = code(value);
//// BOOST_REQUIRE(ec);
//// BOOST_REQUIRE(ec == value);
//// BOOST_REQUIRE_EQUAL(ec.message(), "unauthorized");
////}
////
BOOST_AUTO_TEST_CASE(error_t__code__unauthorized__true_expected_message)
{
constexpr auto value = error::unauthorized;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "unauthorized");
}

////BOOST_AUTO_TEST_CASE(error_t__code__payment_required__true_expected_message)
////{
//// constexpr auto value = error::payment_required;
Expand Down
25 changes: 25 additions & 0 deletions test/net/connector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,15 @@ BOOST_AUTO_TEST_CASE(connector__connect__stop__resolve_failed_race_operation_can
connector::parameters params{ .connect_timeout = seconds(1000), .maximum_request = 42 };
auto instance = std::make_shared<accessor>(log, strand, pool.service(), suspended, std::move(params));
auto result = true;
code ec1{};

boost::asio::post(strand, [&, instance]()NOEXCEPT
{
// DNS resolve failure (race), cancel may include a socket.
instance->connect(config::endpoint{ "bogus.xxx", 42 },
[&](const code& ec, const socket::ptr& socket) NOEXCEPT
{
ec1 = ec;
result &= (((ec == error::resolve_failed) && !socket) || (ec == error::operation_canceled));
});

Expand All @@ -218,6 +220,15 @@ BOOST_AUTO_TEST_CASE(connector__connect__stop__resolve_failed_race_operation_can
pool.stop();
BOOST_REQUIRE(pool.join());
BOOST_REQUIRE(instance->get_stopped());

// BUGBUG: macOS:
// Error codes have changed and the resulting race failure test is not matching expected code.
if (!result)
{
BOOST_CHECK_EQUAL(ec1, error::resolve_failed);
BOOST_CHECK_EQUAL(ec1, error::operation_canceled);
}

BOOST_REQUIRE(result);
}

Expand All @@ -231,20 +242,24 @@ BOOST_AUTO_TEST_CASE(connector__connect__started_start__operation_failed)
connector::parameters params{ .connect_timeout = seconds(1000), .maximum_request = 42 };
auto instance = std::make_shared<accessor>(log, strand, pool.service(), suspended, std::move(params));
auto result = true;
code ec1{};
code ec2{};

boost::asio::post(strand, [&, instance]() NOEXCEPT
{
// DNS resolve failure (race), cancel may include a socket.
instance->connect(config::endpoint{ "bogus.xxx", 42 },
[&](const code& ec, const socket::ptr& socket) NOEXCEPT
{
ec1 = ec;
result &= (((ec == error::resolve_failed) && !socket) || (ec == error::operation_canceled));
});

// Connector is busy.
instance->connect(config::endpoint{ "bogus.yyy", 24 },
[&](const code& ec, const socket::ptr& socket) NOEXCEPT
{
ec2 = ec;
result &= (ec == error::operation_failed);
result &= is_null(socket);
});
Expand All @@ -256,6 +271,16 @@ BOOST_AUTO_TEST_CASE(connector__connect__started_start__operation_failed)
pool.stop();
BOOST_REQUIRE(pool.join());
BOOST_REQUIRE(instance->get_stopped());

// BUGBUG: macOS:
// Error codes have changed and the resulting race failure test is not matching expected code.
if (!result)
{
BOOST_CHECK_EQUAL(ec1, error::resolve_failed);
BOOST_CHECK_EQUAL(ec1, error::operation_canceled);
BOOST_CHECK_EQUAL(ec2, error::operation_failed);
}

BOOST_REQUIRE(result);
}

Expand Down
8 changes: 8 additions & 0 deletions test/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ BOOST_AUTO_TEST_CASE(settings__http_server__defaults__expected)
BOOST_REQUIRE(instance.host_names().empty());
BOOST_REQUIRE(instance.origin_names().empty());
BOOST_REQUIRE(!instance.allow_opaque_origin);
BOOST_REQUIRE(!instance.authorize());
BOOST_REQUIRE(instance.username.empty());
BOOST_REQUIRE(instance.password.empty());
BOOST_REQUIRE_EQUAL(instance.credential(), "Basic Og==");
}

BOOST_AUTO_TEST_CASE(settings__websocket_server__defaults__expected)
Expand Down Expand Up @@ -437,6 +441,10 @@ BOOST_AUTO_TEST_CASE(settings__websocket_server__defaults__expected)
BOOST_REQUIRE(instance.host_names().empty());
BOOST_REQUIRE(instance.origin_names().empty());
BOOST_REQUIRE(!instance.allow_opaque_origin);
BOOST_REQUIRE(!instance.authorize());
BOOST_REQUIRE(instance.username.empty());
BOOST_REQUIRE(instance.password.empty());
BOOST_REQUIRE_EQUAL(instance.credential(), "Basic Og==");

// websocket_server (no unique settings yet)
}
Expand Down
Loading