diff --git a/README.md b/README.md index b0a4c0e8..c7de8056 100644 --- a/README.md +++ b/README.md @@ -153,15 +153,15 @@ You can also check this example on [github](https://github.com/etr/libhttpserver ## Structures and classes type definition * _webserver:_ Represents the daemon listening on a socket for HTTP traffic. - * _create_webserver:_ Builder class to support the creation of a webserver. + * _create_webserver:_ Builder class to support the creation of a webserver. * _http_resource:_ Represents the resource associated with a specific http endpoint. * _http_request:_ Represents the request received by the resource that process it. * _http_response:_ Represents the response sent by the server once the resource finished its work. - * _string_response:_ A simple string response. - * _file_response:_ A response getting content from a file. - * _basic_auth_fail_response:_ A failure in basic authentication. - * _digest_auth_fail_response:_ A failure in digest authentication. - * _deferred_response:_ A response getting content from a callback. + * _string_response:_ A simple string response. + * _file_response:_ A response getting content from a file. + * _basic_auth_fail_response:_ A failure in basic authentication. + * _digest_auth_fail_response:_ A failure in digest authentication. + * _deferred_response:_ A response getting content from a callback. [Back to TOC](#table-of-contents) @@ -193,9 +193,9 @@ For example, if your connection limit is “1”, a browser may open a first con * _.post_process() and .no_post_process():_ Enables/Disables the library to automatically parse the body of the http request as arguments if in querystring format. Read more [here](#parsing-requests). `on` by default. * _.put_processed_data_to_content() and .no_put_processed_data_to_content():_ Enables/Disables the library to copy parsed body data to the content or to only store it in the arguments map. `on` by default. * _.file_upload_target(**file_upload_target_T** file_upload_target):_ Controls, how the library stores uploaded files. Default value is `FILE_UPLOAD_MEMORY_ONLY`. - * `FILE_UPLOAD_MEMORY_ONLY`: The content of the file is only stored in memory. Depending on `put_processed_data_to_content` only as part of the arguments map or additionally in the content. - * `FILE_UPLOAD_DISK_ONLY`: The content of the file is stored only in the file system. The path is created from `file_upload_dir` and either a random name (if `generate_random_filename_on_upload` is true) or the actually uploaded file name. - * `FILE_UPLOAD_MEMORY_AND_DISK`: The content of the file is stored in memory and on the file system. + * `FILE_UPLOAD_MEMORY_ONLY`: The content of the file is only stored in memory. Depending on `put_processed_data_to_content` only as part of the arguments map or additionally in the content. + * `FILE_UPLOAD_DISK_ONLY`: The content of the file is stored only in the file system. The path is created from `file_upload_dir` and either a random name (if `generate_random_filename_on_upload` is true) or the actually uploaded file name. + * `FILE_UPLOAD_MEMORY_AND_DISK`: The content of the file is stored in memory and on the file system. * _.file_upload_dir(**const std::string&** file_upload_dir):_ Specifies the directory to store all uploaded files. Default value is `/tmp`. * _.generate_random_filename_on_upload() and .no_generate_random_filename_on_upload():_ Enables/Disables the library to generate a unique and unused filename to store the uploaded file to. Otherwise the actually uploaded file name is used. `off` by default. * _.deferred()_ and _.no_deferred():_ Enables/Disables the ability for the server to suspend and resume connections. Simply put, it enables/disables the ability to use `deferred_response`. Read more [here](#building-responses-to-requests). `on` by default. @@ -203,8 +203,8 @@ For example, if your connection limit is “1”, a browser may open a first con ### Threading Models * _.start_method(**const http::http_utils::start_method_T&** start_method):_ libhttpserver can operate with two different threading models that can be selected through this method. Default value is `INTERNAL_SELECT`. - * `http::http_utils::INTERNAL_SELECT`: In this mode, libhttpserver uses only a single thread to handle listening on the port and processing of requests. This mode is preferable if spawning a thread for each connection would be costly. If the HTTP server is able to quickly produce responses without much computational overhead for each connection, this mode can be a great choice. Note that libhttpserver will still start a single thread for itself -- this way, the main program can continue with its operations after calling the start method. Naturally, if the HTTP server needs to interact with shared state in the main application, synchronization will be required. If such synchronization in code providing a response results in blocking, all HTTP server operations on all connections will stall. This mode is a bad choice if response data cannot always be provided instantly. The reason is that the code generating responses should not block (since that would block all other connections) and on the other hand, if response data is not available immediately, libhttpserver will start to busy wait on it. If you need to scale along the number of concurrent connection and scale on multiple thread you can specify a value for `max_threads` (see below) thus enabling a thread pool - this is different from `THREAD_PER_CONNECTION` below where a new thread is spawned for each connection. - * `http::http_utils::THREAD_PER_CONNECTION`: In this mode, libhttpserver starts one thread to listen on the port for new connections and then spawns a new thread to handle each connection. This mode is great if the HTTP server has hardly any state that is shared between connections (no synchronization issues!) and may need to perform blocking operations (such as extensive IO or running of code) to handle an individual connection. + * `http::http_utils::INTERNAL_SELECT`: In this mode, libhttpserver uses only a single thread to handle listening on the port and processing of requests. This mode is preferable if spawning a thread for each connection would be costly. If the HTTP server is able to quickly produce responses without much computational overhead for each connection, this mode can be a great choice. Note that libhttpserver will still start a single thread for itself -- this way, the main program can continue with its operations after calling the start method. Naturally, if the HTTP server needs to interact with shared state in the main application, synchronization will be required. If such synchronization in code providing a response results in blocking, all HTTP server operations on all connections will stall. This mode is a bad choice if response data cannot always be provided instantly. The reason is that the code generating responses should not block (since that would block all other connections) and on the other hand, if response data is not available immediately, libhttpserver will start to busy wait on it. If you need to scale along the number of concurrent connection and scale on multiple thread you can specify a value for `max_threads` (see below) thus enabling a thread pool - this is different from `THREAD_PER_CONNECTION` below where a new thread is spawned for each connection. + * `http::http_utils::THREAD_PER_CONNECTION`: In this mode, libhttpserver starts one thread to listen on the port for new connections and then spawns a new thread to handle each connection. This mode is great if the HTTP server has hardly any state that is shared between connections (no synchronization issues!) and may need to perform blocking operations (such as extensive IO or running of code) to handle an individual connection. * _.max_threads(**int** max_threads):_ A thread pool can be combined with the `INTERNAL_SELECT` mode to benefit implementations that require scalability. As said before, by default this mode only uses a single thread. When combined with the thread pool option, it is possible to handle multiple connections with multiple threads. Any value greater than one for this option will activate the use of the thread pool. In contrast to the `THREAD_PER_CONNECTION` mode (where each thread handles one and only one connection), threads in the pool can handle a large number of concurrent connections. Using `INTERNAL_SELECT` in combination with a thread pool is typically the most scalable (but also hardest to debug) mode of operation for libhttpserver. Default value is `1`. This option is incompatible with `THREAD_PER_CONNECTION`. ### Custom defaulted error messages @@ -303,16 +303,17 @@ You can also check this example on [github](https://github.com/etr/libhttpserver ### TLS/HTTPS * _.use_ssl() and .no_ssl():_ Determines whether to run in HTTPS-mode or not. If you set this as on and libhttpserver was compiled without SSL support, the library will throw an exception at start of the server. `off` by default. * _.cred_type(**const http::http_utils::cred_type_T&** cred_type):_ Daemon credentials type. Either certificate or anonymous. Acceptable values are: - * `NONE`: No credentials. - * `CERTIFICATE`: Certificate credential. - * `ANON`: Anonymous credential. - * `SRP`: SRP credential. - * `PSK`: PSK credential. - * `IA`: IA credential. + * `NONE`: No credentials. + * `CERTIFICATE`: Certificate credential. + * `ANON`: Anonymous credential. + * `SRP`: SRP credential. + * `PSK`: PSK credential. + * `IA`: IA credential. * _.https_mem_key(**const std::string&** filename):_ String representing the path to a file containing the private key to be used by the HTTPS daemon. This must be used in conjunction with `https_mem_cert`. * _.https_mem_cert(**const std::string&** filename):_ String representing the path to a file containing the certificate to be used by the HTTPS daemon. This must be used in conjunction with `https_mem_key`. * _.https_mem_trust(**const std::string&** filename):_ String representing the path to a file containing the CA certificate to be used by the HTTPS daemon to authenticate and trust clients certificates. The presence of this option activates the request of certificate to the client. The request to the client is marked optional, and it is the responsibility of the server to check the presence of the certificate if needed. Note that most browsers will only present a client certificate only if they have one matching the specified CA, not sending any certificate otherwise. * _.https_priorities(**const std::string&** priority_string):_ SSL/TLS protocol version and ciphers. Must be followed by a string specifying the SSL/TLS protocol versions and ciphers that are acceptable for the application. The string is passed unchanged to gnutls_priority_init. If this option is not specified, `"NORMAL"` is used. +* _.psk_cred_handler(**std::function** callback):_ Assign a callback function for handling TLS with pre-shared key (PSK) authentication. The function is called in the TLS handshake with an identity name and returns the associated pre-shared key as a hexadecimal encoded string. This key is the same as it is created, for example, by *psktool* from the GnuTLS suite. #### Minimal example using HTTPS ```cpp @@ -344,7 +345,60 @@ To test the above example, you can run the following command from a terminal: curl -XGET -v -k 'https://localhost:8080/hello' -You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_https.cpp). +You can also check this example on [github](https://github.com/oberdorc/libhttpserver/blob/master/examples/minimal_https.cpp). + +#### Minimal example using HTTPS with PSK +```cpp + #include + + using namespace http_server; + + class hello_world_resource : public http_resource + { + public: + std::shared_ptr render(const http_request&) + { + return std::shared_ptr( + new string_response("Hello, World!")); + } + }; + + std::string psk_callback(const std::string& username) + { + // Known identity from the database. + auto identity = std::string {"oFWv9Cv3N9tUsm"}; + + // Pre-shared key in hex-encoded format. + auto key = std::string { + "467427f5a4696f8e0ac285f36840efcbf6fd8da88703d26ff68c1faac7f4e2ae"}; + + return (username == identity) ? key : std::string {}; + } + + int main() + { + webserver ws = + create_webserver(8080) + .use_ssl() + .https_priorities("NORMAL:+PSK:+ECDHE-PSK:+DHE-PSK") + .cred_type(http::http_utils::PSK) + .psk_cred_handler(psk_callback); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` + +To test the above example, you can run the following command from a terminal: + + openssl s_client -msg -brief -psk_identity oFWv9Cv3N9tUsm -psk 467427f5a4696f8e0ac285f36840efcbf6fd8da88703d26ff68c1faac7f4e2ae -connect localhost:8080 + +Once the connection is made, enter the HTTP request, i.e. "GET /hello HTTP/1.1" followed by an empty line and you will see the server response. + +You can also check this example on [github](https://github.com/oberdorc/libhttpserver/blob/master/examples/minimal_https_psk.cpp). ### IP Blacklisting/Whitelisting libhttpserver supports IP blacklisting and whitelisting as an internal feature. This section explains the startup options related with IP blacklisting/whitelisting. See the [specific section](#ip-blacklisting-and-whitelisting) to read more about the topic. @@ -643,9 +697,9 @@ There are 5 types of response that you can create - we will describe them here t * _basic_auth_fail_response(**const std::string&** content, **const std::string&** realm = `""`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response in return to a failure during basic authentication. It allows to specify a `content` string as a message to send back to the client. The `realm` parameter should contain your realm of authentication (if any). The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. * _digest_auth_fail_response(**const std::string&** content, **const std::string&** realm = `""`, **const std::string&** opaque = `""`, **bool** reload_nonce = `false`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response in return to a failure during digest authentication. It allows to specify a `content` string as a message to send back to the client. The `realm` parameter should contain your realm of authentication (if any). The `opaque` represents a value that gets passed to the client and expected to be passed again to the server as-is. This value can be a hexadecimal or base64 string. The `reload_nonce` parameter tells the server to reload the nonce (you should use the value returned by the `check_digest_auth` method on the `http_request`. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. * _deferred_response(**ssize_t(*cycle_callback_ptr)(shared_ptr<T>, char*, size_t)** cycle_callback, **const std::string&** content = `""`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response that obtains additional content from a callback executed in a deferred way. It leaves the client in pending state (returning a `100 CONTINUE` message) and suspends the connection. Besides the callback, optionally, you can provide a `content` parameter that sets the initial message sent immediately to the client. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. To use `deferred_response` you need to have the `deferred` option active on your webserver (enabled by default). - * The `cycle_callback_ptr` has this shape: - _**ssize_t** cycle_callback(**shared_ptr<T> closure_data, char*** buf, **size_t** max_size)_. - You are supposed to implement a function in this shape and provide it to the `deferred_repsonse` method. The webserver will provide a `char*` to the function. It is responsibility of the function to allocate it and fill its content. The method is supposed to respect the `max_size` parameter passed in input. The function must return a `ssize_t` value representing the actual size you filled the `buf` with. Any value different from `-1` will keep the resume the connection, deliver the content and suspend it again (with a `100 CONTINUE`). If the method returns `-1`, the webserver will complete the communication with the client and close the connection. You can also pass a `shared_ptr` pointing to a data object of your choice (this will be templetized with a class of your choice). The server will guarantee that this object is passed at each invocation of the method allowing the client code to use it as a memory buffer during computation. + * The `cycle_callback_ptr` has this shape: + _**ssize_t** cycle_callback(**shared_ptr<T> closure_data, char*** buf, **size_t** max_size)_. + You are supposed to implement a function in this shape and provide it to the `deferred_repsonse` method. The webserver will provide a `char*` to the function. It is responsibility of the function to allocate it and fill its content. The method is supposed to respect the `max_size` parameter passed in input. The function must return a `ssize_t` value representing the actual size you filled the `buf` with. Any value different from `-1` will keep the resume the connection, deliver the content and suspend it again (with a `100 CONTINUE`). If the method returns `-1`, the webserver will complete the communication with the client and close the connection. You can also pass a `shared_ptr` pointing to a data object of your choice (this will be templetized with a class of your choice). The server will guarantee that this object is passed at each invocation of the method allowing the client code to use it as a memory buffer during computation. ### Setting additional properties of the response The `http_response` class offers an additional set of methods to "decorate" your responses. This set of methods is: diff --git a/configure.ac b/configure.ac index 9d5c63e2..eb243a15 100644 --- a/configure.ac +++ b/configure.ac @@ -25,7 +25,7 @@ m4_define([libhttpserver_MINOR_VERSION],[19])dnl m4_define([libhttpserver_REVISION],[0])dnl m4_define([libhttpserver_PKG_VERSION],[libhttpserver_MAJOR_VERSION.libhttpserver_MINOR_VERSION.libhttpserver_REVISION])dnl m4_define([libhttpserver_LDF_VERSION],[libhttpserver_MAJOR_VERSION:libhttpserver_MINOR_VERSION:libhttpserver_REVISION])dnl -AC_INIT([libhttpserver], libhttpserver_PKG_VERSION, [electrictwister2000@gmail.com]) +AC_INIT([libhttpserver], libhttpserver_PKG_VERSION, electrictwister2000@gmail.com) AM_INIT_AUTOMAKE([subdir-objects]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) @@ -83,7 +83,6 @@ case "$host" in esac # Checks for header files. -AC_HEADER_STDC AC_CHECK_HEADER([stdint.h],[],[AC_MSG_ERROR("stdint.h not found")]) AC_CHECK_HEADER([inttypes.h],[],[AC_MSG_ERROR("inttypes.h not found")]) AC_CHECK_HEADER([errno.h],[],[AC_MSG_ERROR("errno.h not found")]) @@ -245,11 +244,14 @@ AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$enable_examples" = "xyes"]) AM_CONDITIONAL([COND_GCOV],[test x"$cond_gcov" = x"yes"]) AC_SUBST(COND_GCOV) -if test x"have_gnutls" = x"yes"; then +if test "x$have_gnutls" = "xyes"; then + AC_DEFINE([HAVE_GNUTLS],[1],[Uses GNU TLS]) AM_CXXFLAGS="$AM_CXXFLAGS -DHAVE_GNUTLS" - AM_CFLAGS="$AM_CXXFLAGS -DHAVE_GNUTLS" + AM_CFLAGS="$AM_CFLAGS -DHAVE_GNUTLS" fi +AM_CONDITIONAL([HAVE_GNUTLS], [test "x$have_gnutls" = "xyes"]) + DX_HTML_FEATURE(ON) DX_CHM_FEATURE(OFF) DX_CHI_FEATURE(OFF) @@ -284,14 +286,14 @@ AC_CONFIG_FILES([examples/cert.pem:examples/cert.pem]) AC_CONFIG_FILES([examples/key.pem:examples/key.pem]) AC_CONFIG_FILES([examples/test_content:examples/test_content]) -AC_OUTPUT( - libhttpserver.pc +AC_CONFIG_FILES([libhttpserver.pc Makefile doc/Makefile src/Makefile test/Makefile examples/Makefile -) +]) +AC_OUTPUT AC_MSG_NOTICE([Configuration Summary: Operating System: ${host_os} diff --git a/examples/Makefile.am b/examples/Makefile.am index 0fe116af..681dac49 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -17,9 +17,14 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA LDADD = $(top_builddir)/src/libhttpserver.la + +if HAVE_GNUTLS +LDADD += -lgnutls +endif + AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ METASOURCES = AUTO -noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload +noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_https_psk minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload hello_world_SOURCES = hello_world.cpp service_SOURCES = service.cpp @@ -33,6 +38,7 @@ custom_access_log_SOURCES = custom_access_log.cpp basic_authentication_SOURCES = basic_authentication.cpp digest_authentication_SOURCES = digest_authentication.cpp minimal_https_SOURCES = minimal_https.cpp +minimal_https_psk_SOURCES = minimal_https_psk.cpp minimal_file_response_SOURCES = minimal_file_response.cpp minimal_deferred_SOURCES = minimal_deferred.cpp deferred_with_accumulator_SOURCES = deferred_with_accumulator.cpp diff --git a/examples/minimal_https_psk.cpp b/examples/minimal_https_psk.cpp new file mode 100644 index 00000000..605081e4 --- /dev/null +++ b/examples/minimal_https_psk.cpp @@ -0,0 +1,58 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr( + new httpserver::string_response("Hello, World!")); + } +}; + +std::string psk_callback(const std::string& username) { + // Known identity from the database. + auto identity = std::string {"oFWv9Cv3N9tUsm"}; + + // Pre-shared key in hex-encoded format. + auto key = std::string { + "467427f5a4696f8e0ac285f36840efcbf6fd8da88703d26ff68c1faac7f4e2ae"}; + + return (username == identity) ? key : std::string {}; +} + +int main() { +#ifdef HAVE_GNUTLS + httpserver::webserver ws = + httpserver::create_webserver(8080) + .use_ssl() + .https_priorities("NORMAL:+PSK:+ECDHE-PSK:+DHE-PSK") + .cred_type(httpserver::http::http_utils::PSK) + .psk_cred_handler(psk_callback); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +#else + return -1; +#endif +} diff --git a/src/httpserver/create_webserver.hpp b/src/httpserver/create_webserver.hpp index c6d61e4d..0a06c9cf 100644 --- a/src/httpserver/create_webserver.hpp +++ b/src/httpserver/create_webserver.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "httpserver/http_response.hpp" #include "httpserver/http_utils.hpp" @@ -42,6 +43,8 @@ namespace httpserver { class webserver; class http_request; +typedef std::function psk_cred_handler_callback; + typedef std::function(const http_request&)> render_ptr; typedef std::function validator_ptr; typedef std::function log_access_ptr; @@ -213,6 +216,11 @@ class create_webserver { return *this; } + create_webserver& psk_cred_handler(psk_cred_handler_callback handler) { + _psk_cred_handler = std::move(handler); + return *this; + } + create_webserver& https_priorities(const std::string& https_priorities) { _https_priorities = https_priorities; return *this; @@ -383,6 +391,7 @@ class create_webserver { std::string _https_mem_cert = ""; std::string _https_mem_trust = ""; std::string _https_priorities = ""; + psk_cred_handler_callback _psk_cred_handler; http::http_utils::cred_type_T _cred_type = http::http_utils::NONE; std::string _digest_auth_random = ""; int _nonce_nc_size = 0; diff --git a/src/httpserver/webserver.hpp b/src/httpserver/webserver.hpp index b2cbb1a6..b31c4f4f 100644 --- a/src/httpserver/webserver.hpp +++ b/src/httpserver/webserver.hpp @@ -152,6 +152,7 @@ class webserver { const std::string https_mem_cert; const std::string https_mem_trust; const std::string https_priorities; + const psk_cred_handler_callback psk_cred_handler; const http::http_utils::cred_type_T cred_type; const std::string digest_auth_random; const int nonce_nc_size; @@ -186,6 +187,8 @@ class webserver { std::shared_ptr internal_error_page(details::modded_request* mr, bool force_our = false) const; std::shared_ptr not_found_page(details::modded_request* mr) const; + static void psk_cred_handler_func(void* cls, const struct MHD_Connection* connection, const char* username, void** psk, size_t* psk_size); + static void request_completed(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe); diff --git a/src/webserver.cpp b/src/webserver.cpp index 86a34c75..d9c4ef3b 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -47,6 +47,10 @@ #include #include +#ifdef HAVE_GNUTLS +#include +#endif + #include "httpserver/create_webserver.hpp" #include "httpserver/details/http_endpoint.hpp" #include "httpserver/details/modded_request.hpp" @@ -141,6 +145,7 @@ webserver::webserver(const create_webserver& params): https_mem_cert(params._https_mem_cert), https_mem_trust(params._https_mem_trust), https_priorities(params._https_priorities), + psk_cred_handler(params._psk_cred_handler), cred_type(params._cred_type), digest_auth_random(params._digest_auth_random), nonce_nc_size(params._nonce_nc_size), @@ -176,6 +181,18 @@ void webserver::sweet_kill() { stop(); } +void webserver::psk_cred_handler_func(void* cls, const struct MHD_Connection*, const char* username, void** psk, size_t* psk_size) { + [[maybe_unused]] std::string key = std::invoke(static_cast(cls)->psk_cred_handler, username); + +#ifdef HAVE_GNUTLS + *psk_size = key.length(); + *psk = malloc(*psk_size); + + if (gnutls_hex2bin(key.data(), key.length(), *psk,psk_size) != GNUTLS_E_SUCCESS) + *psk_size = 0; +#endif +} + void webserver::request_completed(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { // These parameters are passed to respect the MHD interface, but are not needed here. std::ignore = cls; @@ -210,9 +227,11 @@ bool webserver::start(bool blocking) { } gen; vector iov; + // Must be assigned first to options to ensure that all output is handled by external logger! + iov.push_back(gen(MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) &error_log, this)); + iov.push_back(gen(MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) &request_completed, nullptr)); iov.push_back(gen(MHD_OPTION_URI_LOG_CALLBACK, (intptr_t) &uri_log, this)); - iov.push_back(gen(MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) &error_log, this)); iov.push_back(gen(MHD_OPTION_UNESCAPE_CALLBACK, (intptr_t) &unescaper_func, this)); iov.push_back(gen(MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout)); if (bind_socket != 0) { @@ -247,12 +266,12 @@ bool webserver::start(bool blocking) { iov.push_back(gen(MHD_OPTION_NONCE_NC_SIZE, nonce_nc_size)); } - if (use_ssl) { + if (https_mem_key != "" && use_ssl) { // Need for const_cast to respect MHD interface that needs a void* iov.push_back(gen(MHD_OPTION_HTTPS_MEM_KEY, 0, reinterpret_cast(const_cast(https_mem_key.c_str())))); } - if (use_ssl) { + if (https_mem_cert != "" && use_ssl) { // Need for const_cast to respect MHD interface that needs a void* iov.push_back(gen(MHD_OPTION_HTTPS_MEM_CERT, 0, reinterpret_cast(const_cast(https_mem_cert.c_str())))); } @@ -273,9 +292,13 @@ bool webserver::start(bool blocking) { } #ifdef HAVE_GNUTLS - if (cred_type != http_utils::NONE) { + if (cred_type != http_utils::NONE && use_ssl) { iov.push_back(gen(MHD_OPTION_HTTPS_CRED_TYPE, cred_type)); } + + if (psk_cred_handler && use_ssl) { + iov.push_back(gen(MHD_OPTION_GNUTLS_PSK_CRED_HANDLER, (intptr_t) psk_cred_handler_func, this)); + } #endif // HAVE_GNUTLS iov.push_back(gen(MHD_OPTION_END, 0, nullptr)); diff --git a/test/Makefile.am b/test/Makefile.am index 68ddb554..d8a780e2 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -17,6 +17,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA LDADD = $(top_builddir)/src/libhttpserver.la + +if HAVE_GNUTLS +LDADD += -lgnutls +endif + AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ METASOURCES = AUTO check_PROGRAMS = basic file_upload http_utils threaded nodelay string_utilities http_endpoint ban_system ws_start_stop authentication deferred http_resource