diff --git a/CMakeLists.txt b/CMakeLists.txt
index 86046f84..ad8df43c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
#
diff --git a/README.md b/README.md
index 30764ac3..28fc5fa4 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/configs/config.example.yml b/configs/config.example.yml
index 8a2adecb..fe838c43 100644
--- a/configs/config.example.yml
+++ b/configs/config.example.yml
@@ -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)
diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml
index 9549b4c5..69693952 100644
--- a/configs/fne-config.example.yml
+++ b/configs/fne-config.example.yml
@@ -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.
diff --git a/configs/monitor-config.example.yml b/configs/monitor-config.example.yml
index 78452b5f..3dbe63fd 100644
--- a/configs/monitor-config.example.yml
+++ b/configs/monitor-config.example.yml
@@ -22,4 +22,6 @@ channels:
# REST API Port number for channel.
restPort: 9990
# REST API access password for channel.
- restPassword: "PASSWORD"
\ No newline at end of file
+ restPassword: "PASSWORD"
+ # Flag indicating whether or not REST API is operating in SSL mode.
+ restSsl: false
diff --git a/contrib/openssl_cross_patch.RPI_ARM.tar.gz b/contrib/openssl_cross_patch.RPI_ARM.tar.gz
new file mode 100644
index 00000000..29c5aac5
Binary files /dev/null and b/contrib/openssl_cross_patch.RPI_ARM.tar.gz differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 257f59cb..fb50d73c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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)
diff --git a/src/CompilerOptions.cmake b/src/CompilerOptions.cmake
index dadd9c42..dde00d88 100644
--- a/src/CompilerOptions.cmake
+++ b/src/CompilerOptions.cmake
@@ -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)
diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h
index f52da52f..bb547443 100644
--- a/src/common/lookups/AffiliationLookup.h
+++ b/src/common/lookups/AffiliationLookup.h
@@ -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
/// REST API Address.
/// REST API Port.
/// REST API Password.
- VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password) :
+ /// Flag indicating REST is using SSL.
+ 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);
/// REST API Password.
__READONLY_PROPERTY_PLAIN(std::string, password);
+ /// Flag indicating REST is using SSL.
+ __READONLY_PROPERTY_PLAIN(bool, ssl);
};
// ---------------------------------------------------------------------------
diff --git a/src/common/network/rest/http/HTTPServer.h b/src/common/network/rest/http/HTTPServer.h
index fccd1bba..825c05ee 100644
--- a/src/common/network/rest/http/HTTPServer.h
+++ b/src/common/network/rest/http/HTTPServer.h
@@ -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);
}
/// Helper to set the HTTP request handlers.
@@ -75,6 +67,19 @@ namespace network
m_requestHandler = RequestHandlerType(std::forward(handler));
}
+ /// Open TCP acceptor.
+ 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();
+ }
+
/// Run the servers ASIO IO service loop.
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 m_connectionManager;
asio::ip::tcp::socket m_socket;
diff --git a/src/common/network/rest/http/SecureClientConnection.h b/src/common/network/rest/http/SecureClientConnection.h
new file mode 100644
index 00000000..f930e597
--- /dev/null
+++ b/src/common/network/rest/http/SecureClientConnection.h
@@ -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
+#include
+#include
+#include
+#include
+#include
+
+namespace network
+{
+ namespace rest
+ {
+ namespace http
+ {
+ // ---------------------------------------------------------------------------
+ // Class Declaration
+ // This class represents a single connection from a client.
+ // ---------------------------------------------------------------------------
+
+ template
+ class SecureClientConnection {
+ public:
+ auto operator=(SecureClientConnection&) -> SecureClientConnection& = delete;
+ auto operator=(SecureClientConnection&&) -> SecureClientConnection& = delete;
+ SecureClientConnection(SecureClientConnection&) = delete;
+
+ /// Initializes a new instance of the SecureClientConnection class.
+ 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));
+ }
+
+ /// Start the first asynchronous operation for the connection.
+ void start()
+ {
+ m_socket.handshake(asio::ssl::stream_base::client);
+ read();
+ }
+ /// Stop all asynchronous operations associated with the connection.
+ void stop()
+ {
+ try
+ {
+ ensureNoLinger();
+ if (m_socket.lowest_layer().is_open()) {
+ m_socket.lowest_layer().close();
+ }
+ }
+ catch(const std::exception&) { /* ignore */ }
+ }
+
+ /// Helper to enable the SO_LINGER socket option during shutdown.
+ 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());
+ }
+ }
+ }
+
+ /// Perform an synchronous write operation.
+ void send(HTTPPayload request)
+ {
+ request.attachHostHeader(m_socket.lowest_layer().remote_endpoint());
+ write(request);
+ }
+ private:
+ /// Perform an SSL certificate verification.
+ bool verify_certificate(bool preverified, asio::ssl::verify_context& context)
+ {
+ return true; // ignore always valid
+ }
+
+ /// Perform an asynchronous read operation.
+ 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();
+ }
+ });
+ }
+
+ /// Perform an synchronous write operation.
+ 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 m_socket;
+
+ RequestHandlerType& m_requestHandler;
+
+ std::array 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__
diff --git a/src/common/network/rest/http/SecureHTTPClient.h b/src/common/network/rest/http/SecureHTTPClient.h
new file mode 100644
index 00000000..41216e8c
--- /dev/null
+++ b/src/common/network/rest/http/SecureHTTPClient.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
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace network
+{
+ namespace rest
+ {
+ namespace http
+ {
+
+ // ---------------------------------------------------------------------------
+ // Class Declaration
+ // This class implements top-level routines of the secure HTTP client.
+ // ---------------------------------------------------------------------------
+
+ template class ConnectionImpl = SecureClientConnection>
+ class SecureHTTPClient : private Thread {
+ public:
+ auto operator=(SecureHTTPClient&) -> SecureHTTPClient& = delete;
+ auto operator=(SecureHTTPClient&&) -> SecureHTTPClient& = delete;
+ SecureHTTPClient(SecureHTTPClient&) = delete;
+
+ /// Initializes a new instance of the SecureHTTPClient class.
+ 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 */
+ }
+ /// Finalizes a instance of the SecureHTTPClient class.
+ ~SecureHTTPClient() override
+ {
+ if (m_connection != nullptr) {
+ close();
+ }
+ }
+
+ /// Helper to set the HTTP request handlers.
+ template
+ void setHandler(Handler&& handler)
+ {
+ m_requestHandler = RequestHandlerType(std::forward(handler));
+ }
+
+ /// Send HTTP request to HTTP server.
+ bool request(HTTPPayload& request)
+ {
+ if (m_completed) {
+ return false;
+ }
+
+ asio::post(m_ioContext, [this, request]() {
+ std::lock_guard guard(m_lock);
+ {
+ if (m_connection != nullptr) {
+ m_connection->send(request);
+ }
+ }
+ });
+
+ return true;
+ }
+
+ /// Opens connection to the network.
+ bool open()
+ {
+ if (m_completed) {
+ return false;
+ }
+
+ return run();
+ }
+
+ /// Closes connection to the network.
+ void close()
+ {
+ if (m_completed) {
+ return;
+ }
+
+ m_completed = true;
+ m_ioContext.stop();
+
+ wait();
+ }
+
+ private:
+ ///
+ 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();
+ }
+ }
+
+ /// Perform an asynchronous connect operation.
+ void connect(asio::ip::basic_resolver_results& endpoints)
+ {
+ asio::connect(m_socket, endpoints);
+
+ m_connection = std::make_unique(std::move(m_socket), m_context, m_requestHandler);
+ m_connection->start();
+ }
+
+ std::string m_address;
+ uint16_t m_port;
+
+ typedef ConnectionImpl ConnectionType;
+
+ std::unique_ptr 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__
diff --git a/src/common/network/rest/http/SecureHTTPServer.h b/src/common/network/rest/http/SecureHTTPServer.h
new file mode 100644
index 00000000..218f7b05
--- /dev/null
+++ b/src/common/network/rest/http/SecureHTTPServer.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
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+namespace network
+{
+ namespace rest
+ {
+ namespace http
+ {
+
+ // ---------------------------------------------------------------------------
+ // Class Declaration
+ // This class implements top-level routines of the secure HTTP server.
+ // ---------------------------------------------------------------------------
+
+ template class ConnectionImpl = SecureServerConnection>
+ class SecureHTTPServer {
+ public:
+ auto operator=(SecureHTTPServer&) -> SecureHTTPServer& = delete;
+ auto operator=(SecureHTTPServer&&) -> SecureHTTPServer& = delete;
+ SecureHTTPServer(SecureHTTPServer&) = delete;
+
+ /// Initializes a new instance of the SecureHTTPServer class.
+ 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);
+ }
+
+ /// Helper to set the SSL certificate and private key.
+ 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;
+ }
+ }
+
+ /// Helper to set the HTTP request handlers.
+ template
+ void setHandler(Handler&& handler)
+ {
+ m_requestHandler = RequestHandlerType(std::forward(handler));
+ }
+
+ /// Open TCP acceptor.
+ 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();
+ }
+
+ /// Run the servers ASIO IO service loop.
+ 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();
+ }
+
+ /// Helper to stop running ASIO IO services.
+ 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:
+ /// Perform an asynchronous accept operation.
+ 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(std::move(m_socket), m_context, m_connectionManager, m_requestHandler));
+ }
+
+ accept();
+ });
+ }
+
+ typedef ConnectionImpl ConnectionType;
+ typedef std::shared_ptr ConnectionTypePtr;
+
+ asio::io_service m_ioService;
+ asio::ip::tcp::acceptor m_acceptor;
+
+ asio::ip::tcp::endpoint m_endpoint;
+
+ ServerConnectionManager 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__
diff --git a/src/common/network/rest/http/SecureServerConnection.h b/src/common/network/rest/http/SecureServerConnection.h
new file mode 100644
index 00000000..0fa1f7e1
--- /dev/null
+++ b/src/common/network/rest/http/SecureServerConnection.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
+#include
+#include
+#include
+#include
+#include
+
+namespace network
+{
+ namespace rest
+ {
+ namespace http
+ {
+ // ---------------------------------------------------------------------------
+ // Class Prototypes
+ // ---------------------------------------------------------------------------
+
+ template class ServerConnectionManager;
+
+ // ---------------------------------------------------------------------------
+ // Class Declaration
+ // This class represents a single connection from a client.
+ // ---------------------------------------------------------------------------
+
+ template
+ class SecureServerConnection : public std::enable_shared_from_this> {
+ typedef SecureServerConnection selfType;
+ typedef std::shared_ptr selfTypePtr;
+ typedef ServerConnectionManager ConnectionManagerType;
+ public:
+ auto operator=(SecureServerConnection&) -> SecureServerConnection& = delete;
+ auto operator=(SecureServerConnection&&) -> SecureServerConnection& = delete;
+ SecureServerConnection(SecureServerConnection&) = delete;
+
+ /// Initializes a new instance of the SecureServerConnection class.
+ 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 */
+ }
+
+ /// Start the first asynchronous operation for the connection.
+ void start() { handshake(); }
+ /// Stop all asynchronous operations associated with the connection.
+ void stop()
+ {
+ try
+ {
+ if (m_socket.lowest_layer().is_open()) {
+ m_socket.lowest_layer().close();
+ }
+ }
+ catch(const std::exception&) { /* ignore */ }
+ }
+
+ private:
+ /// Perform an asynchronous SSL handshake.
+ 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();
+ }
+ });
+ }
+
+ /// Perform an asynchronous read operation.
+ 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());
+ }
+ });
+ }
+
+ /// Perform an asynchronous write operation.
+ 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 m_socket;
+
+ ConnectionManagerType& m_connectionManager;
+ RequestHandlerType& m_requestHandler;
+
+ std::array 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__
diff --git a/src/common/network/tcp/SecureTcpClient.h b/src/common/network/tcp/SecureTcpClient.h
index cceee58a..ad20e136 100644
--- a/src/common/network/tcp/SecureTcpClient.h
+++ b/src/common/network/tcp/SecureTcpClient.h
@@ -20,6 +20,7 @@
#include "common/network/tcp/Socket.h"
#include
+#include
#include
#include
diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp
index 60904919..a118c3b5 100644
--- a/src/fne/HostFNE.cpp
+++ b/src/fne/HostFNE.cpp
@@ -364,6 +364,9 @@ bool HostFNE::initializeRESTAPI()
std::string restApiAddress = systemConf["restAddress"].as("127.0.0.1");
uint16_t restApiPort = (uint16_t)systemConf["restPort"].as(REST_API_DEFAULT_PORT);
std::string restApiPassword = systemConf["restPassword"].as();
+ bool restApiEnableSSL = systemConf["restSsl"].as(false);
+ std::string restApiSSLCert = systemConf["restSslCertificate"].as("web.crt");
+ std::string restApiSSLKey = systemConf["restSslKey"].as("web.key");
bool restApiDebug = systemConf["restDebug"].as(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) {
diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp
index 0bf5e064..0a0bccd5 100644
--- a/src/fne/network/FNENetwork.cpp
+++ b/src/fne/network/FNENetwork.cpp
@@ -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()));
}
}
diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp
index ea75fbd7..638edbc3 100644
--- a/src/fne/network/RESTAPI.cpp
+++ b/src/fne/network/RESTAPI.cpp
@@ -380,11 +380,19 @@ TalkgroupRuleGroupVoice jsonToTG(json::object& req, HTTPPayload& reply)
/// Network Hostname/IP address to connect to.
/// Network port number.
/// Authentication password.
-/// Instance of the Host class.
+///
+///
+///
+/// Instance of the HostFNE class.
///
-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();
- m_restServer.setHandler(m_dispatcher);
+#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()
///
void RESTAPI::close()
{
- m_restServer.stop();
+#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()
///
void RESTAPI::entry()
{
- m_restServer.run();
+#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
}
///
diff --git a/src/fne/network/RESTAPI.h b/src/fne/network/RESTAPI.h
index 5fd46995..4a5f6ac7 100644
--- a/src/fne/network/RESTAPI.h
+++ b/src/fne/network/RESTAPI.h
@@ -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:
/// Initializes a new instance of the RESTAPI class.
- 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);
/// Finalizes a instance of the RESTAPI class.
~RESTAPI() override;
@@ -60,6 +62,10 @@ private:
typedef network::rest::http::HTTPPayload HTTPPayload;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer m_restServer;
+#if defined(ENABLE_TCP_SSL)
+ network::rest::http::SecureHTTPServer m_restSecureServer;
+ bool m_enableSSL;
+#endif // ENABLE_TCP_SSL
std::mt19937 m_random;
diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp
index 4571ca79..274ef1d4 100644
--- a/src/host/Host.Config.cpp
+++ b/src/host/Host.Config.cpp
@@ -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("");
uint16_t restApiPort = (uint16_t)controlCh["restPort"].as(REST_API_DEFAULT_PORT);
std::string restApiPassword = controlCh["restPassword"].as();
+ bool restSsl = controlCh["restSsl"].as(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("127.0.0.1");
uint16_t restApiPort = (uint16_t)channel["restPort"].as(REST_API_DEFAULT_PORT);
std::string restApiPassword = channel["restPassword"].as();
+ bool restSsl = channel["restSsl"].as(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("127.0.0.1");
uint16_t restApiPort = (uint16_t)networkConf["restPort"].as(REST_API_DEFAULT_PORT);
std::string restApiPassword = networkConf["restPassword"].as();
+ bool restApiEnableSSL = networkConf["restSsl"].as(false);
+ std::string restApiSSLCert = networkConf["restSslCertificate"].as("web.crt");
+ std::string restApiSSLKey = networkConf["restSslKey"].as("web.key");
bool restApiDebug = networkConf["restDebug"].as(false);
uint32_t id = networkConf["id"].as(1000U);
uint32_t jitter = networkConf["talkgroupHang"].as(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) {
diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp
index 3db629ea..da8b484d 100644
--- a/src/host/dmr/Slot.cpp
+++ b/src/host/dmr/Slot.cpp
@@ -876,7 +876,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
req["clear"].set(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(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(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(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);
}
diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp
index 81eacb76..785d1729 100644
--- a/src/host/dmr/packet/ControlSignaling.cpp
+++ b/src/host/dmr/packet/ControlSignaling.cpp
@@ -900,7 +900,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
req["slot"].set(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(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(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(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(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(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);
diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp
index 42558f33..3ed65697 100644
--- a/src/host/network/RESTAPI.cpp
+++ b/src/host/network/RESTAPI.cpp
@@ -135,11 +135,19 @@ bool parseRequestBody(const HTTPPayload& request, HTTPPayload& reply, json::obje
/// Network Hostname/IP address to connect to.
/// Network port number.
/// Authentication password.
+///
+///
+///
/// Instance of the Host class.
///
-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();
- m_restServer.setHandler(m_dispatcher);
+#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()
///
void RESTAPI::close()
{
- m_restServer.stop();
+#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()
///
void RESTAPI::entry()
{
- m_restServer.run();
+#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
}
///
diff --git a/src/host/network/RESTAPI.h b/src/host/network/RESTAPI.h
index a4cf31d2..0b946694 100644
--- a/src/host/network/RESTAPI.h
+++ b/src/host/network/RESTAPI.h
@@ -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:
/// Initializes a new instance of the RESTAPI class.
- 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);
/// Finalizes a instance of the RESTAPI class.
~RESTAPI() override;
@@ -62,6 +64,10 @@ private:
typedef network::rest::http::HTTPPayload HTTPPayload;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer m_restServer;
+#if defined(ENABLE_TCP_SSL)
+ network::rest::http::SecureHTTPServer m_restSecureServer;
+ bool m_enableSSL;
+#endif // ENABLE_TCP_SSL
std::mt19937 m_random;
diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp
index 1c31a92d..2545b934 100644
--- a/src/host/nxdn/Control.cpp
+++ b/src/host/nxdn/Control.cpp
@@ -297,7 +297,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
req["dstId"].set(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(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(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);
}
diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp
index b6c53446..2bc0b04d 100644
--- a/src/host/nxdn/packet/ControlSignaling.cpp
+++ b/src/host/nxdn/packet/ControlSignaling.cpp
@@ -607,7 +607,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin
req["dstId"].set(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);
diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp
index e1b5a85e..4aca7f2d 100644
--- a/src/host/p25/Control.cpp
+++ b/src/host/p25/Control.cpp
@@ -433,7 +433,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
req["dstId"].set(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(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(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);
}
diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp
index bff12691..d53dee6b 100644
--- a/src/host/p25/packet/ControlSignaling.cpp
+++ b/src/host/p25/packet/ControlSignaling.cpp
@@ -2300,7 +2300,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
req["dstId"].set(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(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);
diff --git a/src/monitor/InhibitSubscriberWnd.h b/src/monitor/InhibitSubscriberWnd.h
index 82c078a3..7c41176e 100644
--- a/src/monitor/InhibitSubscriberWnd.h
+++ b/src/monitor/InhibitSubscriberWnd.h
@@ -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());
}
diff --git a/src/monitor/MonitorMainWnd.h b/src/monitor/MonitorMainWnd.h
index d28f662a..52eff94e 100644
--- a/src/monitor/MonitorMainWnd.h
+++ b/src/monitor/MonitorMainWnd.h
@@ -168,10 +168,11 @@ private:
std::string restApiAddress = channel["restAddress"].as("127.0.0.1");
uint16_t restApiPort = (uint16_t)channel["restPort"].as(REST_API_DEFAULT_PORT);
std::string restApiPassword = channel["restPassword"].as();
+ bool restSsl = channel["restSsl"].as(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);
diff --git a/src/monitor/NodeStatusWnd.h b/src/monitor/NodeStatusWnd.h
index fe883feb..3f8d7c77 100644
--- a/src/monitor/NodeStatusWnd.h
+++ b/src/monitor/NodeStatusWnd.h
@@ -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;
diff --git a/src/monitor/PageSubscriberWnd.h b/src/monitor/PageSubscriberWnd.h
index eff6a718..08aa31a3 100644
--- a/src/monitor/PageSubscriberWnd.h
+++ b/src/monitor/PageSubscriberWnd.h
@@ -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());
}
diff --git a/src/monitor/RadioCheckSubscriberWnd.h b/src/monitor/RadioCheckSubscriberWnd.h
index 4ea737fd..805cf679 100644
--- a/src/monitor/RadioCheckSubscriberWnd.h
+++ b/src/monitor/RadioCheckSubscriberWnd.h
@@ -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());
}
diff --git a/src/monitor/TransmitWndBase.h b/src/monitor/TransmitWndBase.h
index 6895fc70..a5246057 100644
--- a/src/monitor/TransmitWndBase.h
+++ b/src/monitor/TransmitWndBase.h
@@ -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());
}
diff --git a/src/monitor/UninhibitSubscriberWnd.h b/src/monitor/UninhibitSubscriberWnd.h
index 4be62d1b..036dec05 100644
--- a/src/monitor/UninhibitSubscriberWnd.h
+++ b/src/monitor/UninhibitSubscriberWnd.h
@@ -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());
}
diff --git a/src/remote/RESTClient.cpp b/src/remote/RESTClient.cpp
index 51e620f5..298896d7 100644
--- a/src/remote/RESTClient.cpp
+++ b/src/remote/RESTClient.cpp
@@ -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)
/// Network Hostname/IP address to connect to.
/// Network port number.
/// Authentication password.
+///
/// Flag indicating whether debug is enabled.
-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:
/// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.
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);
}
///
@@ -148,13 +152,14 @@ int RESTClient::send(const std::string method, const std::string endpoint, json:
/// REST API method.
/// REST API endpoint.
/// REST API endpoint payload.
+///
/// Flag indicating whether debug is enabled.
/// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.
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);
}
///
@@ -167,10 +172,11 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
/// REST API endpoint.
/// REST API endpoint payload.
/// REST API endpoint response.
+///
/// Flag indicating whether debug is enabled.
/// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.
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 RESTDispatcherType;
RESTDispatcherType m_dispatcher(RESTClient::responseHandler);
HTTPClient* client = nullptr;
+#if defined(ENABLE_TCP_SSL)
+ SecureHTTPClient* sslClient = nullptr;
+#endif // ENABLE_TCP_SSL
try {
// setup HTTP client for authentication payload
- client = new HTTPClient(address, port);
- if (!client->open()) {
- delete client;
- return ERRNO_SOCK_OPEN;
+#if defined(ENABLE_TCP_SSL)
+ if (m_enableSSL) {
+ sslClient = new SecureHTTPClient(address, port);
+ if (!sslClient->open()) {
+ delete sslClient;
+ return ERRNO_SOCK_OPEN;
+ }
+ sslClient->setHandler(m_dispatcher);
+ } else {
+#endif // ENABLE_TCP_SSL
+ client = new HTTPClient(address, port);
+ if (!client->open()) {
+ delete client;
+ return ERRNO_SOCK_OPEN;
+ }
+ client->setHandler(m_dispatcher);
+#if defined(ENABLE_TCP_SSL)
}
- client->setHandler(m_dispatcher);
+#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);
- client->request(httpPayload);
+#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()) {
- client->close();
- delete client;
+#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();
}
else {
- client->close();
- delete client;
+#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;
}
- client->close();
- delete client;
+#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
- client = new HTTPClient(address, port);
- if (!client->open())
- return ERRNO_SOCK_OPEN;
- client->setHandler(m_dispatcher);
+#if defined(ENABLE_TCP_SSL)
+ if (m_enableSSL) {
+ sslClient = new SecureHTTPClient(address, port);
+ if (!sslClient->open()) {
+ delete sslClient;
+ return ERRNO_SOCK_OPEN;
+ }
+ sslClient->setHandler(m_dispatcher);
+ } else {
+#endif // ENABLE_TCP_SSL
+ client = new HTTPClient(address, port);
+ 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);
- client->request(httpPayload);
+#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()) {
- client->close();
- delete client;
+#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
}
}
- client->close();
- delete client;
+#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 (client != nullptr) {
- delete client;
+#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;
}
diff --git a/src/remote/RESTClient.h b/src/remote/RESTClient.h
index 246a3d18..28b20496 100644
--- a/src/remote/RESTClient.h
+++ b/src/remote/RESTClient.h
@@ -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:
/// Initializes a new instance of the RESTClient class.
- 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);
/// Finalizes a instance of the RESTClient class.
~RESTClient();
@@ -39,10 +39,10 @@ public:
/// Sends remote control command to the specified modem.
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);
/// Sends remote control command to the specified modem.
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;
};
diff --git a/src/remote/RESTClientMain.cpp b/src/remote/RESTClientMain.cpp
index 93521851..1bac4df1 100644
--- a/src/remote/RESTClientMain.cpp
+++ b/src/remote/RESTClientMain.cpp
@@ -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 ]"
"[-p ]"
"[-P ]"
@@ -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 args = std::vector();