*BEWARE* this commit alters the RPI_ARM cross-compiler and may break things *BEWARE*; implement support for HTTPS SSL REST API configuration; add contrib binary overlay for the RPI_ARM compiler (to add libssl-dev dependences);

pull/51/head
Bryan Biedenkapp 2 years ago
parent cda6b5965f
commit 31ca17449d

@ -51,15 +51,19 @@ set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE amd64)
if (CROSS_COMPILE_ARM)
set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++)
set(ARCH arm)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm)
set(ARCH armhf)
set(CMAKE_SYSTEM_PROCESSOR armhf)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf)
set(OPENSSL_ROOT_DIR /usr/lib/arm-linux-gnueabihf)
message(CHECK_START "Cross compiling for 32-bit ARM - ${CMAKE_C_COMPILER}")
endif (CROSS_COMPILE_ARM)
if (CROSS_COMPILE_AARCH64)
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++)
set(ARCH arm64)
set(CMAKE_SYSTEM_PROCESSOR arm64)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm64)
set(OPENSSL_ROOT_DIR /usr/lib/aarch64-linux-gnu)
message(CHECK_START "Cross compiling for 64-bit ARM - ${CMAKE_C_COMPILER}")
endif (CROSS_COMPILE_AARCH64)
@ -78,14 +82,25 @@ if (CROSS_COMPILE_RPI_ARM)
GIT_REPOSITORY https://github.com/raspberrypi/tools.git
)
FetchContent_MakeAvailable(RPiTools)
set(CMAKE_C_COMPILER ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++)
set(CMAKE_C_COMPILER ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-g++)
message(CHECK_START "Apply OpenSSL library binaries for cross compling (old RPi) 32-bit ARM - ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src")
execute_process(COMMAND tar xzf contrib/openssl_cross_patch.RPI_ARM.tar.gz -C ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
set(OPENSSL_ROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/arm-linux-gnueabihf/sysroot/usr/lib)
else()
set(CMAKE_C_COMPILER ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++)
set(CMAKE_C_COMPILER ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-g++)
message(CHECK_START "Apply OpenSSL library binaries for cross compling (old RPi) 32-bit ARM - ${RPI_ARM_TOOLS}")
execute_process(COMMAND tar xzf contrib/openssl_cross_patch.RPI_ARM.tar.gz -C ${RPI_ARM_TOOLS} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
set(OPENSSL_ROOT_DIR ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/arm-linux-gnueabihf/sysroot/usr/lib)
endif ()
set(ARCH armhf)
set(CMAKE_SYSTEM_PROCESSOR armhf)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf)
message(CHECK_START "Cross compiling for (old RPi) 32-bit ARM - ${CMAKE_C_COMPILER}")
@ -94,6 +109,13 @@ if (CROSS_COMPILE_RPI_ARM)
message(CHECK_START "Enable TUI support - no; for simplicity RPI_ARM cross-compiling does not support TUI.")
endif (CROSS_COMPILE_RPI_ARM)
# search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# Standard CMake options
set(THREADS_PREFER_PTHREAD_FLAG ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@ -163,11 +185,6 @@ if (ENABLE_TUI_SUPPORT AND NOT FC_INCLUDED)
set(FINALCUT_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/_deps/finalcut-src/src)
endif (ENABLE_TUI_SUPPORT AND NOT FC_INCLUDED)
if (ENABLE_TCP_SSL)
find_package(OpenSSL REQUIRED)
include_directories("${OPENSSL_INCLUDE_DIR}")
endif (ENABLE_TCP_SSL)
#
# Set GIT_VER compiler directive
#

@ -20,10 +20,11 @@ The DVM Host software requires the library dependancies below. Generally, the so
### Dependencies
`apt-get install libasio-dev libncurses-dev`
`apt-get install libasio-dev libncurses-dev libssl-dev`
- ASIO Library (https://think-async.com/Asio/); on Debian/Ubuntu Linux's: `apt-get install libasio-dev`
- ncurses; on Debian/Ubuntu Linux's: `apt-get install libncurses-dev`
- OpenSSL; on Debian/Ubuntu Linux's: `apt-get install libssl-dev`
Alternatively, if you download the ASIO library from the ASIO website and extract it to a location, you can specify the path to the ASIO library using: `-DWITH_ASIO=/path/to/asio`. This method is required when cross-compiling for old Raspberry Pi ARM 32 bit.
@ -31,7 +32,7 @@ If cross-compiling ensure you install the appropriate libraries, for example for
```
sudo dpkg --add-architecture arm64
sudo apt-get update
sudo apt-get install libasio-dev:arm64 libncurses-dev:arm64
sudo apt-get install libasio-dev:arm64 libncurses-dev:arm64 libssl-dev:arm64
```
### Build Instructions

@ -76,6 +76,12 @@ network:
restAddress: 127.0.0.1
# Port number for REST API to listen on.
restPort: 9990
# Flag indicating whether or not REST API is operating in SSL mode.
restSsl: false
# HTTPS/TLS certificate.
restSslCertificate: web.crt
# HTTPS/TLS key file.
restSslKey: web.key
# REST API authentication password.
restPassword: "PASSWORD"
# Flag indicating whether or not verbose REST API debug logging is enabled.
@ -359,6 +365,8 @@ system:
restPort: 0
# REST API access password for control channel.
restPassword: "PASSWORD"
# Flag indicating whether or not REST API is operating in SSL mode.
restSsl: false
# Flag indicating voice channels will notify the control channel of traffic status.
notifyEnable: true
@ -376,6 +384,8 @@ system:
restPort: 9990
# REST API access password for voice channel.
restPassword: "PASSWORD"
# Flag indicating whether or not REST API is operating in SSL mode.
restSsl: false
secure:
# AES-128 16-byte Key (used for LLA)

@ -161,6 +161,12 @@ system:
restAddress: 127.0.0.1
# Port number for REST API to listen on.
restPort: 9990
# Flag indicating whether or not REST API is operating in SSL mode.
restSsl: false
# HTTPS/TLS certificate.
restSslCertificate: web.crt
# HTTPS/TLS key file.
restSslKey: web.key
# REST API authentication password.
restPassword: "PASSWORD"
# Flag indicating whether or not verbose REST API debug logging is enabled.

@ -23,3 +23,5 @@ channels:
restPort: 9990
# REST API access password for channel.
restPassword: "PASSWORD"
# Flag indicating whether or not REST API is operating in SSL mode.
restSsl: false

@ -31,11 +31,11 @@ endif (ENABLE_SETUP_TUI)
add_executable(dvmhost ${common_INCLUDE} ${dvmhost_SRC})
if (ENABLE_SETUP_TUI)
target_link_libraries(dvmhost PRIVATE common asio::asio finalcut Threads::Threads util)
target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads util)
else()
target_link_libraries(dvmhost PRIVATE common asio::asio Threads::Threads util)
target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util)
endif (ENABLE_SETUP_TUI)
target_include_directories(dvmhost PRIVATE src src/host)
target_include_directories(dvmhost PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host)
set(CPACK_SET_DESTDIR true)
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/local")
@ -64,8 +64,8 @@ include(CPack)
#
include(src/fne/CMakeLists.txt)
add_executable(dvmfne ${common_INCLUDE} ${dvmfne_SRC})
target_link_libraries(dvmfne PRIVATE common asio::asio Threads::Threads)
target_include_directories(dvmfne PRIVATE src src/host src/fne)
target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/fne)
#
## dvmmon
@ -73,8 +73,8 @@ target_include_directories(dvmfne PRIVATE src src/host src/fne)
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
include(src/monitor/CMakeLists.txt)
add_executable(dvmmon ${common_INCLUDE} ${dvmmon_SRC})
target_link_libraries(dvmmon PRIVATE common asio::asio finalcut Threads::Threads)
target_include_directories(dvmmon PRIVATE src src/host src/monitor)
target_link_libraries(dvmmon PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads)
target_include_directories(dvmmon PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/monitor)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
#
@ -82,5 +82,5 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
#
include(src/remote/CMakeLists.txt)
add_executable(dvmcmd ${common_INCLUDE} ${dvmcmd_SRC})
target_link_libraries(dvmcmd PRIVATE common asio::asio Threads::Threads)
target_include_directories(dvmcmd PRIVATE src src/remote)
target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote)

@ -173,3 +173,9 @@ if (HAVE_SENDMMSG)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DHAVE_SENDMMSG=1")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DHAVE_SENDMMSG=1")
endif (HAVE_SENDMMSG)
# are we enabling SSL support?
if (ENABLE_TCP_SSL)
find_package(OpenSSL REQUIRED)
include_directories("${OPENSSL_INCLUDE_DIR}")
endif (ENABLE_TCP_SSL)

@ -7,7 +7,7 @@
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2022 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__AFFILIATION_LOOKUP_H__)
@ -37,7 +37,8 @@ namespace lookups
m_chNo(0U),
m_address(),
m_port(),
m_password()
m_password(),
m_ssl()
{
/* stub */
}
@ -47,12 +48,14 @@ namespace lookups
/// <param name="address">REST API Address.</param>
/// <param name="port">REST API Port.</param>
/// <param name="password">REST API Password.</param>
VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password) :
/// <param name="ssl">Flag indicating REST is using SSL.</param>
VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl) :
m_chId(chId),
m_chNo(chNo),
m_address(address),
m_port(port),
m_password(password)
m_password(password),
m_ssl(ssl)
{
/* stub */
}
@ -68,6 +71,7 @@ namespace lookups
m_address = data.m_address;
m_port = data.m_port;
m_password = data.m_password;
m_ssl = data.m_ssl;
}
return *this;
@ -89,6 +93,8 @@ namespace lookups
__READONLY_PROPERTY_PLAIN(uint16_t, port);
/// <summary>REST API Password.</summary>
__READONLY_PROPERTY_PLAIN(std::string, password);
/// <summary>Flag indicating REST is using SSL.</summary>
__READONLY_PROPERTY_PLAIN(bool, ssl);
};
// ---------------------------------------------------------------------------

@ -9,7 +9,7 @@
* @license BSL-1.0 License (https://opensource.org/license/bsl1-0-html)
*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__REST_HTTP__HTTP_SERVER_H__)
@ -57,15 +57,7 @@ namespace network
{
// open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR)
asio::ip::address ipAddress = asio::ip::address::from_string(address);
asio::ip::tcp::endpoint endpoint(ipAddress, port);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true));
m_acceptor.set_option(asio::socket_base::keep_alive(true));
m_acceptor.bind(endpoint);
m_acceptor.listen();
accept();
m_endpoint = asio::ip::tcp::endpoint(ipAddress, port);
}
/// <summary>Helper to set the HTTP request handlers.</summary>
@ -75,6 +67,19 @@ namespace network
m_requestHandler = RequestHandlerType(std::forward<Handler>(handler));
}
/// <summary>Open TCP acceptor.</summary>
void open()
{
// open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR)
m_acceptor.open(m_endpoint.protocol());
m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true));
m_acceptor.set_option(asio::socket_base::keep_alive(true));
m_acceptor.bind(m_endpoint);
m_acceptor.listen();
accept();
}
/// <summary>Run the servers ASIO IO service loop.</summary>
void run()
{
@ -120,6 +125,8 @@ namespace network
asio::io_service m_ioService;
asio::ip::tcp::acceptor m_acceptor;
asio::ip::tcp::endpoint m_endpoint;
ServerConnectionManager<ConnectionTypePtr> m_connectionManager;
asio::ip::tcp::socket m_socket;

@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__REST_HTTP__SECURE_CLIENT_CONNECTION_H__)
#define __REST_HTTP__SECURE_CLIENT_CONNECTION_H__
#if defined(ENABLE_TCP_SSL)
#include "common/Defines.h"
#include "common/network/rest/http/HTTPLexer.h"
#include "common/network/rest/http/HTTPPayload.h"
#include "common/Log.h"
#include <array>
#include <memory>
#include <utility>
#include <iterator>
#include <asio.hpp>
#include <asio/ssl.hpp>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// This class represents a single connection from a client.
// ---------------------------------------------------------------------------
template <typename RequestHandlerType>
class SecureClientConnection {
public:
auto operator=(SecureClientConnection&) -> SecureClientConnection& = delete;
auto operator=(SecureClientConnection&&) -> SecureClientConnection& = delete;
SecureClientConnection(SecureClientConnection&) = delete;
/// <summary>Initializes a new instance of the SecureClientConnection class.</summary>
explicit SecureClientConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, RequestHandlerType& handler) :
m_socket(std::move(socket), context),
m_requestHandler(handler),
m_lexer(HTTPLexer(true))
{
m_socket.set_verify_mode(asio::ssl::verify_none);
m_socket.set_verify_callback(std::bind(&SecureClientConnection::verify_certificate, this, std::placeholders::_1, std::placeholders::_2));
}
/// <summary>Start the first asynchronous operation for the connection.</summary>
void start()
{
m_socket.handshake(asio::ssl::stream_base::client);
read();
}
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
void stop()
{
try
{
ensureNoLinger();
if (m_socket.lowest_layer().is_open()) {
m_socket.lowest_layer().close();
}
}
catch(const std::exception&) { /* ignore */ }
}
/// <summary>Helper to enable the SO_LINGER socket option during shutdown.</summary>
void ensureNoLinger()
{
try
{
// enable SO_LINGER timeout 0
asio::socket_base::linger linger(true, 0);
m_socket.lowest_layer().set_option(linger);
}
catch(const asio::system_error& e)
{
asio::error_code ec = e.code();
if (ec) {
::LogError(LOG_REST, "SecureClientConnection::ensureNoLinger(), %s, code = %u", ec.message().c_str(), ec.value());
}
}
}
/// <summary>Perform an synchronous write operation.</summary>
void send(HTTPPayload request)
{
request.attachHostHeader(m_socket.lowest_layer().remote_endpoint());
write(request);
}
private:
/// <summary>Perform an SSL certificate verification.</summary>
bool verify_certificate(bool preverified, asio::ssl::verify_context& context)
{
return true; // ignore always valid
}
/// <summary>Perform an asynchronous read operation.</summary>
void read()
{
m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
if (!ec) {
HTTPLexer::ResultType result;
char* content;
std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
std::string contentLength = m_request.headers.find("Content-Length");
if (contentLength != "") {
size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10);
m_request.content = std::string(content, length);
}
m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string());
if (result == HTTPLexer::GOOD) {
m_requestHandler.handleRequest(m_request, m_reply);
}
else if (result == HTTPLexer::BAD) {
return;
}
else {
read();
}
}
else if (ec != asio::error::operation_aborted) {
if (ec) {
::LogError(LOG_REST, "SecureClientConnection::read(), %s, code = %u", ec.message().c_str(), ec.value());
}
stop();
}
});
}
/// <summary>Perform an synchronous write operation.</summary>
void write(HTTPPayload request)
{
try
{
m_socket.handshake(asio::ssl::stream_base::client);
auto buffers = request.toBuffers();
asio::write(m_socket, buffers);
}
catch(const asio::system_error& e)
{
asio::error_code ec = e.code();
if (ec) {
::LogError(LOG_REST, "SecureClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
try
{
// initiate graceful connection closure
asio::error_code ignored_ec;
m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
}
catch(const std::exception& e) {
::LogError(LOG_REST, "SecureClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
}
}
}
}
asio::ssl::stream<asio::ip::tcp::socket> m_socket;
RequestHandlerType& m_requestHandler;
std::array<char, 8192> m_buffer;
HTTPPayload m_request;
HTTPLexer m_lexer;
HTTPPayload m_reply;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // ENABLE_TCP_SSL
#endif // __REST_HTTP__SECURE_CLIENT_CONNECTION_H__

@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__REST_HTTP__SECURE_HTTP_CLIENT_H__)
#define __REST_HTTP__SECURE_HTTP_CLIENT_H__
#if defined(ENABLE_TCP_SSL)
#include "common/Defines.h"
#include "common/network/rest/http/SecureClientConnection.h"
#include "common/network/rest/http/HTTPRequestHandler.h"
#include "common/Thread.h"
#include <asio.hpp>
#include <asio/ssl.hpp>
#include <thread>
#include <string>
#include <signal.h>
#include <utility>
#include <memory>
#include <mutex>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements top-level routines of the secure HTTP client.
// ---------------------------------------------------------------------------
template<typename RequestHandlerType, template<class> class ConnectionImpl = SecureClientConnection>
class SecureHTTPClient : private Thread {
public:
auto operator=(SecureHTTPClient&) -> SecureHTTPClient& = delete;
auto operator=(SecureHTTPClient&&) -> SecureHTTPClient& = delete;
SecureHTTPClient(SecureHTTPClient&) = delete;
/// <summary>Initializes a new instance of the SecureHTTPClient class.</summary>
SecureHTTPClient(const std::string& address, uint16_t port) :
m_address(address),
m_port(port),
m_connection(nullptr),
m_ioContext(),
m_context(asio::ssl::context::tlsv12),
m_socket(m_ioContext),
m_requestHandler()
{
/* stub */
}
/// <summary>Finalizes a instance of the SecureHTTPClient class.</summary>
~SecureHTTPClient() override
{
if (m_connection != nullptr) {
close();
}
}
/// <summary>Helper to set the HTTP request handlers.</summary>
template<typename Handler>
void setHandler(Handler&& handler)
{
m_requestHandler = RequestHandlerType(std::forward<Handler>(handler));
}
/// <summary>Send HTTP request to HTTP server.</summary>
bool request(HTTPPayload& request)
{
if (m_completed) {
return false;
}
asio::post(m_ioContext, [this, request]() {
std::lock_guard<std::mutex> guard(m_lock);
{
if (m_connection != nullptr) {
m_connection->send(request);
}
}
});
return true;
}
/// <summary>Opens connection to the network.</summary>
bool open()
{
if (m_completed) {
return false;
}
return run();
}
/// <summary>Closes connection to the network.</summary>
void close()
{
if (m_completed) {
return;
}
m_completed = true;
m_ioContext.stop();
wait();
}
private:
/// <summary></summary>
void entry() override
{
if (m_completed) {
return;
}
asio::ip::tcp::resolver resolver(m_ioContext);
auto endpoints = resolver.resolve(m_address, std::to_string(m_port));
try {
connect(endpoints);
// the entry() call will block until all asynchronous operations
// have finished
m_ioContext.run();
}
catch (std::exception&) { /* stub */ }
if (m_connection != nullptr) {
m_connection->stop();
}
}
/// <summary>Perform an asynchronous connect operation.</summary>
void connect(asio::ip::basic_resolver_results<asio::ip::tcp>& endpoints)
{
asio::connect(m_socket, endpoints);
m_connection = std::make_unique<ConnectionType>(std::move(m_socket), m_context, m_requestHandler);
m_connection->start();
}
std::string m_address;
uint16_t m_port;
typedef ConnectionImpl<RequestHandlerType> ConnectionType;
std::unique_ptr<ConnectionType> m_connection;
bool m_completed = false;
asio::io_context m_ioContext;
asio::ssl::context m_context;
asio::ip::tcp::socket m_socket;
RequestHandlerType m_requestHandler;
std::mutex m_lock;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // ENABLE_TCP_SSL
#endif // __REST_HTTP__SECURE_HTTP_CLIENT_H__

@ -0,0 +1,164 @@
// SPDX-License-Identifier: BSL-1.0
/**
* Digital Voice Modem - Common Library
* BSL-1.0 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Common Library
* @derivedfrom CRUD (https://github.com/venediktov/CRUD)
* @license BSL-1.0 License (https://opensource.org/license/bsl1-0-html)
*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__REST_HTTP__SECURE_HTTP_SERVER_H__)
#define __REST_HTTP__SECURE_HTTP_SERVER_H__
#if defined(ENABLE_TCP_SSL)
#include "common/Defines.h"
#include "common/network/rest/http/SecureServerConnection.h"
#include "common/network/rest/http/ServerConnectionManager.h"
#include "common/network/rest/http/HTTPRequestHandler.h"
#include <asio.hpp>
#include <asio/ssl.hpp>
#include <thread>
#include <string>
#include <signal.h>
#include <utility>
#include <memory>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements top-level routines of the secure HTTP server.
// ---------------------------------------------------------------------------
template<typename RequestHandlerType, template<class> class ConnectionImpl = SecureServerConnection>
class SecureHTTPServer {
public:
auto operator=(SecureHTTPServer&) -> SecureHTTPServer& = delete;
auto operator=(SecureHTTPServer&&) -> SecureHTTPServer& = delete;
SecureHTTPServer(SecureHTTPServer&) = delete;
/// <summary>Initializes a new instance of the SecureHTTPServer class.</summary>
explicit SecureHTTPServer(const std::string& address, uint16_t port) :
m_ioService(),
m_acceptor(m_ioService),
m_connectionManager(),
m_context(asio::ssl::context::tlsv12),
m_socket(m_ioService),
m_requestHandler()
{
asio::ip::address ipAddress = asio::ip::address::from_string(address);
m_endpoint = asio::ip::tcp::endpoint(ipAddress, port);
}
/// <summary>Helper to set the SSL certificate and private key.</summary>
bool setCertAndKey(const std::string& keyFile, const std::string& certFile)
{
try
{
m_context.use_certificate_chain_file(certFile);
m_context.use_private_key_file(keyFile, asio::ssl::context::pem);
return true;
}
catch(const std::exception& e) {
::LogError(LOG_REST, "%s", e.what());
return false;
}
}
/// <summary>Helper to set the HTTP request handlers.</summary>
template<typename Handler>
void setHandler(Handler&& handler)
{
m_requestHandler = RequestHandlerType(std::forward<Handler>(handler));
}
/// <summary>Open TCP acceptor.</summary>
void open()
{
// open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR)
m_acceptor.open(m_endpoint.protocol());
m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true));
m_acceptor.set_option(asio::socket_base::keep_alive(true));
m_acceptor.bind(m_endpoint);
m_acceptor.listen();
accept();
}
/// <summary>Run the servers ASIO IO service loop.</summary>
void run()
{
// the run() call will block until all asynchronous operations
// have finished; while the server is running, there is always at least one
// asynchronous operation outstanding: the asynchronous accept call waiting
// for new incoming connections
m_ioService.run();
}
/// <summary>Helper to stop running ASIO IO services.</summary>
void stop()
{
// the server is stopped by cancelling all outstanding asynchronous
// operations; once all operations have finished the m_ioService::run()
// call will exit
m_acceptor.close();
m_connectionManager.stopAll();
}
private:
/// <summary>Perform an asynchronous accept operation.</summary>
void accept()
{
m_acceptor.async_accept(m_socket, [this](asio::error_code ec) {
// check whether the server was stopped by a signal before this
// completion handler had a chance to run
if (!m_acceptor.is_open()) {
return;
}
if (!ec) {
m_connectionManager.start(std::make_shared<ConnectionType>(std::move(m_socket), m_context, m_connectionManager, m_requestHandler));
}
accept();
});
}
typedef ConnectionImpl<RequestHandlerType> ConnectionType;
typedef std::shared_ptr<ConnectionType> ConnectionTypePtr;
asio::io_service m_ioService;
asio::ip::tcp::acceptor m_acceptor;
asio::ip::tcp::endpoint m_endpoint;
ServerConnectionManager<ConnectionTypePtr> m_connectionManager;
asio::ssl::context m_context;
asio::ip::tcp::socket m_socket;
std::string m_certFile;
std::string m_keyFile;
RequestHandlerType m_requestHandler;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // ENABLE_TCP_SSL
#endif // __REST_HTTP__SECURE_HTTP_SERVER_H__

@ -0,0 +1,202 @@
// SPDX-License-Identifier: BSL-1.0
/**
* Digital Voice Modem - Common Library
* BSL-1.0 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Common Library
* @derivedfrom CRUD (https://github.com/venediktov/CRUD)
* @license BSL-1.0 License (https://opensource.org/license/bsl1-0-html)
*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__REST_HTTP__SECURE_SERVER_CONNECTION_H__)
#define __REST_HTTP__SECURE_SERVER_CONNECTION_H__
#if defined(ENABLE_TCP_SSL)
#include "common/Defines.h"
#include "common/network/rest/http/HTTPLexer.h"
#include "common/network/rest/http/HTTPPayload.h"
#include "common/Log.h"
#include <array>
#include <memory>
#include <utility>
#include <iterator>
#include <asio.hpp>
#include <asio/ssl.hpp>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
template<class> class ServerConnectionManager;
// ---------------------------------------------------------------------------
// Class Declaration
// This class represents a single connection from a client.
// ---------------------------------------------------------------------------
template <typename RequestHandlerType>
class SecureServerConnection : public std::enable_shared_from_this<SecureServerConnection<RequestHandlerType>> {
typedef SecureServerConnection<RequestHandlerType> selfType;
typedef std::shared_ptr<selfType> selfTypePtr;
typedef ServerConnectionManager<selfTypePtr> ConnectionManagerType;
public:
auto operator=(SecureServerConnection&) -> SecureServerConnection& = delete;
auto operator=(SecureServerConnection&&) -> SecureServerConnection& = delete;
SecureServerConnection(SecureServerConnection&) = delete;
/// <summary>Initializes a new instance of the SecureServerConnection class.</summary>
explicit SecureServerConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, ConnectionManagerType& manager, RequestHandlerType& handler,
bool persistent = false) :
m_socket(std::move(socket), context),
m_connectionManager(manager),
m_requestHandler(handler),
m_lexer(HTTPLexer(false)),
m_persistent(persistent)
{
/* stub */
}
/// <summary>Start the first asynchronous operation for the connection.</summary>
void start() { handshake(); }
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
void stop()
{
try
{
if (m_socket.lowest_layer().is_open()) {
m_socket.lowest_layer().close();
}
}
catch(const std::exception&) { /* ignore */ }
}
private:
/// <summary>Perform an asynchronous SSL handshake.</summary>
void handshake()
{
if (!m_persistent) {
auto self(this->shared_from_this());
}
m_socket.async_handshake(asio::ssl::stream_base::server, [this](asio::error_code ec) {
if (!ec) {
read();
}
});
}
/// <summary>Perform an asynchronous read operation.</summary>
void read()
{
if (!m_persistent) {
auto self(this->shared_from_this());
}
m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
if (!ec) {
HTTPLexer::ResultType result;
char* content;
std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
std::string contentLength = m_request.headers.find("Content-Length");
if (contentLength != "") {
size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10);
m_request.content = std::string(content, length);
}
m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string());
if (result == HTTPLexer::GOOD) {
m_requestHandler.handleRequest(m_request, m_reply);
write();
}
else if (result == HTTPLexer::BAD) {
m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST);
write();
}
else {
read();
}
}
else if (ec != asio::error::operation_aborted) {
if (ec) {
::LogError(LOG_REST, "SecureServerConnection::read(), %s, code = %u", ec.message().c_str(), ec.value());
}
m_connectionManager.stop(this->shared_from_this());
}
});
}
/// <summary>Perform an asynchronous write operation.</summary>
void write()
{
if (!m_persistent) {
auto self(this->shared_from_this());
} else {
m_reply.headers.add("Connection", "keep-alive");
}
auto buffers = m_reply.toBuffers();
asio::async_write(m_socket, buffers, [=](asio::error_code ec, std::size_t) {
if (m_persistent) {
m_lexer.reset();
m_reply.headers = HTTPHeaders();
m_reply.status = HTTPPayload::OK;
m_reply.content = "";
m_request = HTTPPayload();
read();
}
else {
if (!ec) {
try
{
// initiate graceful connection closure
asio::error_code ignored_ec;
m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
}
catch(const std::exception& e) { ::LogError(LOG_REST, "%s", ec.message().c_str()); }
}
if (ec != asio::error::operation_aborted) {
if (ec) {
::LogError(LOG_REST, "SecureServerConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
}
m_connectionManager.stop(this->shared_from_this());
}
}
});
}
asio::ssl::stream<asio::ip::tcp::socket> m_socket;
ConnectionManagerType& m_connectionManager;
RequestHandlerType& m_requestHandler;
std::array<char, 8192> m_buffer;
HTTPPayload m_request;
HTTPLexer m_lexer;
HTTPPayload m_reply;
bool m_persistent;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // ENABLE_TCP_SSL
#endif // __REST_HTTP__SECURE_SERVER_CONNECTION_H__

@ -20,6 +20,7 @@
#include "common/network/tcp/Socket.h"
#include <cassert>
#include <cstring>
#include <openssl/ssl.h>
#include <openssl/err.h>

@ -364,6 +364,9 @@ bool HostFNE::initializeRESTAPI()
std::string restApiAddress = systemConf["restAddress"].as<std::string>("127.0.0.1");
uint16_t restApiPort = (uint16_t)systemConf["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = systemConf["restPassword"].as<std::string>();
bool restApiEnableSSL = systemConf["restSsl"].as<bool>(false);
std::string restApiSSLCert = systemConf["restSslCertificate"].as<std::string>("web.crt");
std::string restApiSSLKey = systemConf["restSslKey"].as<std::string>("web.key");
bool restApiDebug = systemConf["restDebug"].as<bool>(false);
if (restApiPassword.length() > 64) {
@ -378,12 +381,26 @@ bool HostFNE::initializeRESTAPI()
restApiEnable = false;
}
if (restApiSSLCert.empty() && restApiEnableSSL) {
::LogWarning(LOG_HOST, "REST API SSL certificate not provided; REST API SSL disabled.");
restApiEnableSSL = false;
}
if (restApiSSLKey.empty() && restApiEnableSSL) {
::LogWarning(LOG_HOST, "REST API SSL certificate private key not provided; REST API SSL disabled.");
restApiEnableSSL = false;
}
LogInfo("REST API Parameters");
LogInfo(" REST API Enabled: %s", restApiEnable ? "yes" : "no");
if (restApiEnable) {
LogInfo(" REST API Address: %s", restApiAddress.c_str());
LogInfo(" REST API Port: %u", restApiPort);
LogInfo(" REST API SSL Enabled: %s", restApiEnableSSL ? "yes" : "no");
LogInfo(" REST API SSL Certificate: %s", restApiSSLCert.c_str());
LogInfo(" REST API SSL Private Key: %s", restApiSSLKey.c_str());
if (restApiDebug) {
LogInfo(" REST API Debug: yes");
}
@ -391,7 +408,7 @@ bool HostFNE::initializeRESTAPI()
// initialize network remote command
if (restApiEnable) {
m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, this, restApiDebug);
m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, restApiSSLKey, restApiSSLCert, restApiEnableSSL, this, restApiDebug);
m_RESTAPI->setLookups(m_ridLookup, m_tidLookup);
bool ret = m_RESTAPI->open();
if (!ret) {

@ -1118,7 +1118,7 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId)
if (i == chunkCnt - 1U) {
// this is a disgusting dirty hack...
listSize = abs((i * MAX_RID_LIST_CHUNK) - ridWhitelist.size());
listSize = ::abs((long)((i * MAX_RID_LIST_CHUNK) - ridWhitelist.size()));
}
}
@ -1187,7 +1187,7 @@ void FNENetwork::writeBlacklistRIDs(uint32_t peerId)
if (i == chunkCnt - 1U) {
// this is a disgusting dirty hack...
listSize = abs((i * MAX_RID_LIST_CHUNK) - ridBlacklist.size());
listSize = ::abs((long)((i * MAX_RID_LIST_CHUNK) - ridBlacklist.size()));
}
}

@ -380,11 +380,19 @@ TalkgroupRuleGroupVoice jsonToTG(json::object& req, HTTPPayload& reply)
/// <param name="address">Network Hostname/IP address to connect to.</param>
/// <param name="port">Network port number.</param>
/// <param name="password">Authentication password.</param>
/// <param name="host">Instance of the Host class.</param>
/// <param name="keyFile"></param>
/// <param name="certFile"></param>
/// <param name="enableSSL"></param>
/// <param name="host">Instance of the HostFNE class.</param>
/// <param name="debug"></param>
RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& password, HostFNE* host, bool debug) :
RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& password,
const std::string& keyFile, const std::string& certFile, bool enableSSL, HostFNE* host, bool debug) :
m_dispatcher(debug),
m_restServer(address, port),
#if defined(ENABLE_TCP_SSL)
m_restSecureServer(address, port),
m_enableSSL(enableSSL),
#endif // ENABLE_TCP_SSL
m_random(),
m_password(password),
m_passwordHash(nullptr),
@ -417,6 +425,15 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p
Utils::dump("REST Password Hash", m_passwordHash, 32U);
}
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
if (!m_restSecureServer.setCertAndKey(keyFile, certFile)) {
m_enableSSL = false;
::LogError(LOG_REST, "failed to initialize SSL for HTTPS, disabling SSL");
}
}
#endif // ENABLE_TCP_SSL
std::random_device rd;
std::mt19937 mt(rd());
m_random = mt;
@ -454,7 +471,17 @@ void RESTAPI::setNetwork(network::FNENetwork* network)
bool RESTAPI::open()
{
initializeEndpoints();
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
m_restSecureServer.open();
m_restSecureServer.setHandler(m_dispatcher);
} else {
#endif // ENABLE_TCP_SSL
m_restServer.open();
m_restServer.setHandler(m_dispatcher);
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
return run();
}
@ -464,7 +491,15 @@ bool RESTAPI::open()
/// </summary>
void RESTAPI::close()
{
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
m_restSecureServer.stop();
} else {
#endif // ENABLE_TCP_SSL
m_restServer.stop();
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
wait();
}
@ -477,7 +512,15 @@ void RESTAPI::close()
/// </summary>
void RESTAPI::entry()
{
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
m_restSecureServer.run();
} else {
#endif // ENABLE_TCP_SSL
m_restServer.run();
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
}
/// <summary>

@ -16,6 +16,7 @@
#include "fne/Defines.h"
#include "common/network/rest/RequestDispatcher.h"
#include "common/network/rest/http/HTTPServer.h"
#include "common/network/rest/http/SecureHTTPServer.h"
#include "common/lookups/RadioIdLookup.h"
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/Thread.h"
@ -40,7 +41,8 @@ namespace network { class HOST_SW_API FNENetwork; }
class HOST_SW_API RESTAPI : private Thread {
public:
/// <summary>Initializes a new instance of the RESTAPI class.</summary>
RESTAPI(const std::string& address, uint16_t port, const std::string& password, HostFNE* host, bool debug);
RESTAPI(const std::string& address, uint16_t port, const std::string& password, const std::string& keyFile, const std::string& certFile,
bool enableSSL, HostFNE* host, bool debug);
/// <summary>Finalizes a instance of the RESTAPI class.</summary>
~RESTAPI() override;
@ -60,6 +62,10 @@ private:
typedef network::rest::http::HTTPPayload HTTPPayload;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer<RESTDispatcherType> m_restServer;
#if defined(ENABLE_TCP_SSL)
network::rest::http::SecureHTTPServer<RESTDispatcherType> m_restSecureServer;
bool m_enableSSL;
#endif // ENABLE_TCP_SSL
std::mt19937 m_random;

@ -7,7 +7,7 @@
* @package DVM / Modem Host Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2017-2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
@ -187,12 +187,13 @@ bool Host::readParams()
std::string restApiAddress = controlCh["restAddress"].as<std::string>("");
uint16_t restApiPort = (uint16_t)controlCh["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = controlCh["restPassword"].as<std::string>();
bool restSsl = controlCh["restSsl"].as<bool>(false);
VoiceChData data = VoiceChData(m_channelId, m_channelNo, restApiAddress, restApiPort, restApiPassword);
VoiceChData data = VoiceChData(m_channelId, m_channelNo, restApiAddress, restApiPort, restApiPassword, restSsl);
m_controlChData = data;
if (!m_controlChData.address().empty() && m_controlChData.port() > 0) {
::LogInfoEx(LOG_HOST, "Control Channel REST API Address %s:%u", m_controlChData.address().c_str(), m_controlChData.port());
::LogInfoEx(LOG_HOST, "Control Channel REST API Address %s:%u SSL %u", m_controlChData.address().c_str(), m_controlChData.port(), restSsl);
} else {
::LogInfoEx(LOG_HOST, "No Control Channel REST API Configured, CC notify disabled");
}
@ -234,10 +235,11 @@ bool Host::readParams()
std::string restApiAddress = channel["restAddress"].as<std::string>("127.0.0.1");
uint16_t restApiPort = (uint16_t)channel["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = channel["restPassword"].as<std::string>();
bool restSsl = channel["restSsl"].as<bool>(false);
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u", chId, chNo, restApiAddress.c_str(), restApiPort);
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", chId, chNo, restApiAddress.c_str(), restApiPort, restSsl);
VoiceChData data = VoiceChData(chId, chNo, restApiAddress, restApiPort, restApiPassword);
VoiceChData data = VoiceChData(chId, chNo, restApiAddress, restApiPort, restApiPassword, restSsl);
m_voiceChData[chNo] = data;
m_voiceChNo.push_back(chNo);
}
@ -658,6 +660,9 @@ bool Host::createNetwork()
std::string restApiAddress = networkConf["restAddress"].as<std::string>("127.0.0.1");
uint16_t restApiPort = (uint16_t)networkConf["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = networkConf["restPassword"].as<std::string>();
bool restApiEnableSSL = networkConf["restSsl"].as<bool>(false);
std::string restApiSSLCert = networkConf["restSslCertificate"].as<std::string>("web.crt");
std::string restApiSSLKey = networkConf["restSslKey"].as<std::string>("web.key");
bool restApiDebug = networkConf["restDebug"].as<bool>(false);
uint32_t id = networkConf["id"].as<uint32_t>(1000U);
uint32_t jitter = networkConf["talkgroupHang"].as<uint32_t>(360U);
@ -718,6 +723,16 @@ bool Host::createNetwork()
restApiEnable = false;
}
if (restApiSSLCert.empty() && restApiEnableSSL) {
::LogWarning(LOG_HOST, "REST API SSL certificate not provided; REST API SSL disabled.");
restApiEnableSSL = false;
}
if (restApiSSLKey.empty() && restApiEnableSSL) {
::LogWarning(LOG_HOST, "REST API SSL certificate private key not provided; REST API SSL disabled.");
restApiEnableSSL = false;
}
IdenTable entry = m_idenTable->find(m_channelId);
LogInfo("Network Parameters");
@ -748,6 +763,10 @@ bool Host::createNetwork()
LogInfo(" REST API Address: %s", restApiAddress.c_str());
LogInfo(" REST API Port: %u", restApiPort);
LogInfo(" REST API SSL Enabled: %s", restApiEnableSSL ? "yes" : "no");
LogInfo(" REST API SSL Certificate: %s", restApiSSLCert.c_str());
LogInfo(" REST API SSL Private Key: %s", restApiSSLKey.c_str());
if (restApiDebug) {
LogInfo(" REST API Debug: yes");
}
@ -781,7 +800,7 @@ bool Host::createNetwork()
// initialize network remote command
if (restApiEnable) {
m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, this, restApiDebug);
m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, restApiSSLKey, restApiSSLCert, restApiEnableSSL, this, restApiDebug);
m_RESTAPI->setLookups(m_ridLookup, m_tidLookup);
bool ret = m_RESTAPI->open();
if (!ret) {

@ -876,7 +876,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
req["clear"].set<bool>(clear);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, tscc->m_debug);
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), tscc->m_debug);
}
else {
::LogError(LOG_DMR, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), failed to clear payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -893,7 +893,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
req["slot"].set<uint8_t>(slot);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, m_dmr->m_debug);
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), m_dmr->m_debug);
}
else {
::LogError(LOG_DMR, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), failed to clear TG permit, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot);
@ -1115,7 +1115,7 @@ void Slot::notifyCC_ReleaseGrant(uint32_t dstId)
req["slot"].set<uint8_t>(slot);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_RELEASE_TG, req, m_debug);
HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}
@ -1148,7 +1148,7 @@ void Slot::notifyCC_TouchGrant(uint32_t dstId)
req["slot"].set<uint8_t>(slot);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_TOUCH_TG, req, m_debug);
HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}

@ -900,7 +900,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
req["slot"].set<uint8_t>(slot);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, m_tscc->m_debug);
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), m_tscc->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", m_tscc->m_slotNo, chNo, slot);
m_tscc->m_affiliations->releaseGrant(dstId, false);
@ -951,7 +951,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
req["voice"].set<bool>(voice);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, m_tscc->m_debug);
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), m_tscc->m_debug);
}
else {
::LogError(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", m_tscc->m_slotNo, chNo, slot);
@ -978,7 +978,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
req["slot"].set<uint8_t>(slot);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, m_tscc->m_debug);
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), m_tscc->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", m_tscc->m_slotNo, chNo, slot);
m_tscc->m_affiliations->releaseGrant(dstId, false);
@ -1027,7 +1027,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
req["voice"].set<bool>(voice);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, m_tscc->m_debug);
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), m_tscc->m_debug);
}
else {
::LogError(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", m_tscc->m_slotNo, chNo, slot);
@ -1185,7 +1185,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
req["voice"].set<bool>(voice);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, m_tscc->m_debug);
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), m_tscc->m_debug);
}
else {
::LogError(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", m_tscc->m_slotNo, chNo, slot);
@ -1232,7 +1232,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
req["voice"].set<bool>(voice);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, m_tscc->m_debug);
HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), m_tscc->m_debug);
}
else {
::LogError(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", m_tscc->m_slotNo, chNo, slot);

@ -135,11 +135,19 @@ bool parseRequestBody(const HTTPPayload& request, HTTPPayload& reply, json::obje
/// <param name="address">Network Hostname/IP address to connect to.</param>
/// <param name="port">Network port number.</param>
/// <param name="password">Authentication password.</param>
/// <param name="keyFile"></param>
/// <param name="certFile"></param>
/// <param name="enableSSL"></param>
/// <param name="host">Instance of the Host class.</param>
/// <param name="debug"></param>
RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& password, Host* host, bool debug) :
RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& password,
const std::string& keyFile, const std::string& certFile, bool enableSSL, Host* host, bool debug) :
m_dispatcher(debug),
m_restServer(address, port),
#if defined(ENABLE_TCP_SSL)
m_restSecureServer(address, port),
m_enableSSL(enableSSL),
#endif // ENABLE_TCP_SSL
m_random(),
m_p25MFId(p25::P25_MFG_STANDARD),
m_password(password),
@ -175,6 +183,15 @@ RESTAPI::RESTAPI(const std::string& address, uint16_t port, const std::string& p
Utils::dump("REST Password Hash", m_passwordHash, 32U);
}
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
if (!m_restSecureServer.setCertAndKey(keyFile, certFile)) {
m_enableSSL = false;
::LogError(LOG_REST, "failed to initialize SSL for HTTPS, disabling SSL");
}
}
#endif // ENABLE_TCP_SSL
std::random_device rd;
std::mt19937 mt(rd());
m_random = mt;
@ -216,7 +233,17 @@ void RESTAPI::setProtocols(dmr::Control* dmr, p25::Control* p25, nxdn::Control*
bool RESTAPI::open()
{
initializeEndpoints();
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
m_restSecureServer.open();
m_restSecureServer.setHandler(m_dispatcher);
} else {
#endif // ENABLE_TCP_SSL
m_restServer.open();
m_restServer.setHandler(m_dispatcher);
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
return run();
}
@ -226,7 +253,15 @@ bool RESTAPI::open()
/// </summary>
void RESTAPI::close()
{
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
m_restSecureServer.stop();
} else {
#endif // ENABLE_TCP_SSL
m_restServer.stop();
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
wait();
}
@ -239,7 +274,15 @@ void RESTAPI::close()
/// </summary>
void RESTAPI::entry()
{
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
m_restSecureServer.run();
} else {
#endif // ENABLE_TCP_SSL
m_restServer.run();
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
}
/// <summary>

@ -16,6 +16,7 @@
#include "Defines.h"
#include "common/network/rest/RequestDispatcher.h"
#include "common/network/rest/http/HTTPServer.h"
#include "common/network/rest/http/SecureHTTPServer.h"
#include "common/lookups/RadioIdLookup.h"
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/Thread.h"
@ -42,7 +43,8 @@ namespace nxdn { class HOST_SW_API Control; }
class HOST_SW_API RESTAPI : private Thread {
public:
/// <summary>Initializes a new instance of the RESTAPI class.</summary>
RESTAPI(const std::string& address, uint16_t port, const std::string& password, Host* host, bool debug);
RESTAPI(const std::string& address, uint16_t port, const std::string& password, const std::string& keyFile, const std::string& certFile,
bool enableSSL, Host* host, bool debug);
/// <summary>Finalizes a instance of the RESTAPI class.</summary>
~RESTAPI() override;
@ -62,6 +64,10 @@ private:
typedef network::rest::http::HTTPPayload HTTPPayload;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer<RESTDispatcherType> m_restServer;
#if defined(ENABLE_TCP_SSL)
network::rest::http::SecureHTTPServer<RESTDispatcherType> m_restSecureServer;
bool m_enableSSL;
#endif // ENABLE_TCP_SSL
std::mt19937 m_random;

@ -297,7 +297,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
req["dstId"].set<uint32_t>(dstId);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, m_debug);
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), m_debug);
}
else {
::LogError(LOG_NXDN, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_RESP ", failed to clear TG permit, chNo = %u", chNo);
@ -1052,7 +1052,7 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId)
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_RELEASE_TG, req, m_debug);
HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}
@ -1083,7 +1083,7 @@ void Control::notifyCC_TouchGrant(uint32_t dstId)
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_TOUCH_TG, req, m_debug);
HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}

@ -607,7 +607,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, m_nxdn->m_debug);
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), m_nxdn->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcch->toString().c_str(), chNo);
m_nxdn->m_affiliations.releaseGrant(dstId, false);

@ -433,7 +433,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
req["dstId"].set<uint32_t>(dstId);
RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, m_debug);
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), m_debug);
}
else {
::LogError(LOG_P25, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Grant), failed to clear TG permit, chNo = %u", chNo);
@ -1460,7 +1460,7 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId)
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_RELEASE_TG, req, m_debug);
HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_P25, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}
@ -1491,7 +1491,7 @@ void Control::notifyCC_TouchGrant(uint32_t dstId)
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_TOUCH_TG, req, m_debug);
HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_P25, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId);
}

@ -2300,7 +2300,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, m_p25->m_debug);
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), m_p25->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Grant), failed to permit TG for use, chNo = %u", chNo);
m_p25->m_affiliations.releaseGrant(dstId, false);
@ -2354,7 +2354,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
req["dstId"].set<uint32_t>(dstId);
int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
HTTP_PUT, PUT_PERMIT_TG, req, m_p25->m_debug);
HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), m_p25->m_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Grant), failed to permit TG for use, chNo = %u", chNo);
m_p25->m_affiliations.releaseGrant(dstId, false);

@ -7,7 +7,7 @@
* @package DVM / Host Monitor Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__INHIBIT_SUBSCRIBER_WND_H__)
@ -132,7 +132,7 @@ private:
// callback REST API
int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(),
HTTP_PUT, method, req, g_debug);
HTTP_PUT, method, req, m_selectedCh.ssl(), g_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to send request %s to %s:%u", method.c_str(), m_selectedCh.address().c_str(), m_selectedCh.port());
}

@ -168,10 +168,11 @@ private:
std::string restApiAddress = channel["restAddress"].as<std::string>("127.0.0.1");
uint16_t restApiPort = (uint16_t)channel["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = channel["restPassword"].as<std::string>();
bool restSsl = channel["restSsl"].as<bool>(false);
::LogInfoEx(LOG_HOST, "Channel REST API Adddress %s:%u", restApiAddress.c_str(), restApiPort);
VoiceChData data = VoiceChData(0U, 0U, restApiAddress, restApiPort, restApiPassword);
VoiceChData data = VoiceChData(0U, 0U, restApiAddress, restApiPort, restApiPassword, restSsl);
NodeStatusWnd* wnd = new NodeStatusWnd(this);
wnd->setChData(data);

@ -7,7 +7,7 @@
* @package DVM / Host Monitor Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__NODE_STATUS_WND_H__)
@ -264,7 +264,7 @@ private:
json::object rsp = json::object();
int ret = RESTClient::send(m_chData.address(), m_chData.port(), m_chData.password(),
HTTP_GET, GET_STATUS, req, rsp, g_debug);
HTTP_GET, GET_STATUS, req, rsp, m_chData.ssl(), g_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to get status for %s:%u, chNo = %u", m_chData.address().c_str(), m_chData.port(), m_channelNo);
++m_failCnt;
@ -395,7 +395,7 @@ private:
// callback REST API to get status of the channel we represent
json::object req = json::object();
int ret = RESTClient::send(m_chData.address(), m_chData.port(), m_chData.password(),
HTTP_GET, GET_STATUS, req, g_debug);
HTTP_GET, GET_STATUS, req, m_chData.ssl(), g_debug);
if (ret == network::rest::http::HTTPPayload::StatusType::OK) {
m_failed = false;
m_failCnt = 0U;

@ -7,7 +7,7 @@
* @package DVM / Host Monitor Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__PAGE_SUBSCRIBER_WND_H__)
@ -132,7 +132,7 @@ private:
// callback REST API
int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(),
HTTP_PUT, method, req, g_debug);
HTTP_PUT, method, req, m_selectedCh.ssl(), g_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to send request %s to %s:%u", method.c_str(), m_selectedCh.address().c_str(), m_selectedCh.port());
}

@ -7,7 +7,7 @@
* @package DVM / Host Monitor Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__RADIO_CHECK_SUBSCRIBER_WND_H__)
@ -132,7 +132,7 @@ private:
// callback REST API
int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(),
HTTP_PUT, method, req, g_debug);
HTTP_PUT, method, req, m_selectedCh.ssl(), g_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to send request %s to %s:%u", method.c_str(), m_selectedCh.address().c_str(), m_selectedCh.port());
}

@ -7,7 +7,7 @@
* @package DVM / Host Monitor Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__TRANSMIT_WND_BASE_H__)
@ -100,7 +100,7 @@ protected:
json::object rsp = json::object();
int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(),
HTTP_GET, GET_STATUS, req, rsp, g_debug);
HTTP_GET, GET_STATUS, req, rsp, m_selectedCh.ssl(), g_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to get status for %s:%u", m_selectedCh.address().c_str(), m_selectedCh.port());
}

@ -7,7 +7,7 @@
* @package DVM / Host Monitor Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__UNINHIBIT_SUBSCRIBER_WND_H__)
@ -132,7 +132,7 @@ private:
// callback REST API
int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(),
HTTP_PUT, method, req, g_debug);
HTTP_PUT, method, req, m_selectedCh.ssl(), g_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to send request %s to %s:%u", method.c_str(), m_selectedCh.address().c_str(), m_selectedCh.port());
}

@ -7,13 +7,14 @@
* @package DVM / Remote Command Client
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "common/edac/SHA256.h"
#include "common/network/json/json.h"
#include "common/network/rest/http/HTTPClient.h"
#include "common/network/rest/http/SecureHTTPClient.h"
#include "common/network/rest/RequestDispatcher.h"
#include "common/Thread.h"
#include "common/Log.h"
@ -50,6 +51,7 @@ bool RESTClient::m_responseAvailable = false;
HTTPPayload RESTClient::m_response;
bool RESTClient::m_console = false;
bool RESTClient::m_enableSSL = false;
bool RESTClient::m_debug = false;
// ---------------------------------------------------------------------------
@ -95,8 +97,9 @@ bool parseResponseBody(const HTTPPayload& response, json::object& obj)
/// <param name="address">Network Hostname/IP address to connect to.</param>
/// <param name="port">Network port number.</param>
/// <param name="password">Authentication password.</param>
/// <param name="enableSSL"></param>
/// <param name="debug">Flag indicating whether debug is enabled.</param>
RESTClient::RESTClient(const std::string& address, uint32_t port, const std::string& password, bool debug) :
RESTClient::RESTClient(const std::string& address, uint32_t port, const std::string& password, bool enableSSL, bool debug) :
m_address(address),
m_port(port),
m_password(password)
@ -105,6 +108,7 @@ RESTClient::RESTClient(const std::string& address, uint32_t port, const std::str
assert(port > 0U);
m_console = true;
m_enableSSL = enableSSL;
m_debug = debug;
}
@ -136,7 +140,7 @@ int RESTClient::send(const std::string method, const std::string endpoint, json:
/// <returns>EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.</returns>
int RESTClient::send(const std::string method, const std::string endpoint, json::object payload, json::object& response)
{
return send(m_address, m_port, m_password, method, endpoint, payload, response, m_debug);
return send(m_address, m_port, m_password, method, endpoint, payload, response, m_enableSSL, m_debug);
}
/// <summary>
@ -148,13 +152,14 @@ int RESTClient::send(const std::string method, const std::string endpoint, json:
/// <param name="method">REST API method.</param>
/// <param name="endpoint">REST API endpoint.</param>
/// <param name="payload">REST API endpoint payload.</param>
/// <param name="enableSSL"></param>
/// <param name="debug">Flag indicating whether debug is enabled.</param>
/// <returns>EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.</returns>
int RESTClient::send(const std::string& address, uint32_t port, const std::string& password, const std::string method,
const std::string endpoint, json::object payload, bool debug)
const std::string endpoint, json::object payload, bool enableSSL, bool debug)
{
json::object rsp = json::object();
return send(address, port, password, method, endpoint, payload, rsp, debug);
return send(address, port, password, method, endpoint, payload, rsp, enableSSL, debug);
}
/// <summary>
@ -167,10 +172,11 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
/// <param name="endpoint">REST API endpoint.</param>
/// <param name="payload">REST API endpoint payload.</param>
/// <param name="response">REST API endpoint response.</param>
/// <param name="enableSSL"></param>
/// <param name="debug">Flag indicating whether debug is enabled.</param>
/// <returns>EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.</returns>
int RESTClient::send(const std::string& address, uint32_t port, const std::string& password, const std::string method,
const std::string endpoint, json::object payload, json::object& response, bool debug)
const std::string endpoint, json::object payload, json::object& response, bool enableSSL, bool debug)
{
if (address.empty()) {
return ERRNO_NO_ADDRESS;
@ -183,20 +189,37 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
}
int ret = EXIT_SUCCESS;
m_enableSSL = enableSSL;
m_debug = debug;
typedef network::rest::BasicRequestDispatcher<network::rest::http::HTTPPayload, network::rest::http::HTTPPayload> RESTDispatcherType;
RESTDispatcherType m_dispatcher(RESTClient::responseHandler);
HTTPClient<RESTDispatcherType>* client = nullptr;
#if defined(ENABLE_TCP_SSL)
SecureHTTPClient<RESTDispatcherType>* sslClient = nullptr;
#endif // ENABLE_TCP_SSL
try {
// setup HTTP client for authentication payload
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient = new SecureHTTPClient<RESTDispatcherType>(address, port);
if (!sslClient->open()) {
delete sslClient;
return ERRNO_SOCK_OPEN;
}
sslClient->setHandler(m_dispatcher);
} else {
#endif // ENABLE_TCP_SSL
client = new HTTPClient<RESTDispatcherType>(address, port);
if (!client->open()) {
delete client;
return ERRNO_SOCK_OPEN;
}
client->setHandler(m_dispatcher);
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
// generate password SHA hash
size_t size = password.size();
@ -227,12 +250,29 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
HTTPPayload httpPayload = HTTPPayload::requestPayload(HTTP_PUT, "/auth");
httpPayload.payload(request);
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient->request(httpPayload);
} else {
#endif // ENABLE_TCP_SSL
client->request(httpPayload);
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
// wait for response and parse
if (wait()) {
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient->close();
delete sslClient;
} else {
#endif // ENABLE_TCP_SSL
client->close();
delete client;
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
return ERRNO_API_CALL_TIMEOUT;
}
@ -247,30 +287,80 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
token = rsp["token"].get<std::string>();
}
else {
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient->close();
delete sslClient;
} else {
#endif // ENABLE_TCP_SSL
client->close();
delete client;
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
return ERRNO_BAD_AUTH_RESPONSE;
}
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient->close();
delete sslClient;
} else {
#endif // ENABLE_TCP_SSL
client->close();
delete client;
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
// reset the HTTP client and setup for actual payload request
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient = new SecureHTTPClient<RESTDispatcherType>(address, port);
if (!sslClient->open()) {
delete sslClient;
return ERRNO_SOCK_OPEN;
}
sslClient->setHandler(m_dispatcher);
} else {
#endif // ENABLE_TCP_SSL
client = new HTTPClient<RESTDispatcherType>(address, port);
if (!client->open())
if (!client->open()) {
delete client;
return ERRNO_SOCK_OPEN;
}
client->setHandler(m_dispatcher);
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
// send actual API request
httpPayload = HTTPPayload::requestPayload(method, endpoint);
httpPayload.headers.add("X-DVM-Auth-Token", token);
httpPayload.payload(payload);
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient->request(httpPayload);
} else {
#endif // ENABLE_TCP_SSL
client->request(httpPayload);
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
// wait for response and parse
if (wait()) {
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient->close();
delete sslClient;
} else {
#endif // ENABLE_TCP_SSL
client->close();
delete client;
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
return ERRNO_API_CALL_TIMEOUT;
}
@ -289,13 +379,32 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
}
}
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
sslClient->close();
delete sslClient;
} else {
#endif // ENABLE_TCP_SSL
client->close();
delete client;
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
}
catch (std::exception&) {
#if defined(ENABLE_TCP_SSL)
if (m_enableSSL) {
if (sslClient != nullptr) {
delete sslClient;
}
} else {
#endif // ENABLE_TCP_SSL
if (client != nullptr) {
delete client;
}
#if defined(ENABLE_TCP_SSL)
}
#endif // ENABLE_TCP_SSL
return ERRNO_INTERNAL_ERROR;
}

@ -7,7 +7,7 @@
* @package DVM / Remote Command Client
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__REST_CLIENT_H__)
@ -28,7 +28,7 @@ class HOST_SW_API RESTClient
{
public:
/// <summary>Initializes a new instance of the RESTClient class.</summary>
RESTClient(const std::string& address, uint32_t port, const std::string& password, bool debug);
RESTClient(const std::string& address, uint32_t port, const std::string& password, bool enableSSL, bool debug);
/// <summary>Finalizes a instance of the RESTClient class.</summary>
~RESTClient();
@ -39,10 +39,10 @@ public:
/// <summary>Sends remote control command to the specified modem.</summary>
static int send(const std::string& address, uint32_t port, const std::string& password, const std::string method,
const std::string endpoint, json::object payload, bool debug = false);
const std::string endpoint, json::object payload, bool enableSSL, bool debug = false);
/// <summary>Sends remote control command to the specified modem.</summary>
static int send(const std::string& address, uint32_t port, const std::string& password, const std::string method,
const std::string endpoint, json::object payload, json::object& response, bool debug = false);
const std::string endpoint, json::object payload, json::object& response, bool enableSSL, bool debug = false);
private:
typedef network::rest::http::HTTPPayload HTTPPayload;
@ -61,6 +61,7 @@ private:
static bool m_responseAvailable;
static HTTPPayload m_response;
static bool m_enableSSL;
static bool m_debug;
};

@ -7,7 +7,7 @@
* @package DVM / Remote Command Client
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
*
*/
#include "remote/RESTClient.h"
@ -122,6 +122,7 @@ static std::string g_progExe = std::string(__EXE_NAME__);
static std::string g_remoteAddress = std::string("127.0.0.1");
static uint32_t g_remotePort = REST_API_DEFAULT_PORT;
static std::string g_remotePassword = std::string();
static bool g_enableSSL = false;
static bool g_debug = false;
// ---------------------------------------------------------------------------
@ -155,7 +156,7 @@ void usage(const char* message, const char* arg)
}
::fprintf(stdout,
"usage: %s [-dvh]"
"usage: %s [-dvhs]"
"[-a <address>]"
"[-p <port>]"
"[-P <password>]"
@ -169,6 +170,8 @@ void usage(const char* message, const char* arg)
" -p remote modem command port\n"
" -P remote modem authentication password\n"
"\n"
" -s use HTTPS/SSL\n"
"\n"
" -- stop handling options\n",
g_progExe.c_str());
@ -299,6 +302,10 @@ int checkArgs(int argc, char* argv[])
p += 2;
}
else if (IS("-s")) {
++p;
g_enableSSL = true;
}
else if (IS("-d")) {
++p;
g_debug = true;
@ -398,7 +405,7 @@ int main(int argc, char** argv)
return 1;
}
RESTClient* client = new RESTClient(g_remoteAddress, g_remotePort, g_remotePassword, g_debug);
RESTClient* client = new RESTClient(g_remoteAddress, g_remotePort, g_remotePassword, g_enableSSL, g_debug);
int retCode = EXIT_SUCCESS;
std::vector<std::string> args = std::vector<std::string>();

Loading…
Cancel
Save

Powered by TurnKey Linux.