diff --git a/ChangeLog b/ChangeLog index 6760a4ff..7bb0aefb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ -Version 0.19.0 - 2023-06-15 +Version 0.20.0 + Added conditional compilation for basic auth (HAVE_BAUTH), mirroring + existing HAVE_DAUTH pattern for digest auth. Basic auth support + is auto-detected via AC_CHECK_LIB and can be disabled at build time. Fixed path traversal vulnerability in file uploads when generate_random_filename_on_upload is disabled. Fixed TOCTOU race in file_response by replacing stat-then-open with @@ -12,6 +15,9 @@ Version 0.19.0 - 2023-06-15 Fixed auth skip path bypass via path traversal (e.g. /public/../protected). Fixed use of free() instead of MHD_free() for digest auth username. Fixed unchecked write error during file upload. + +Version 0.19.0 - 2023-06-15 + Considering family_url as part of the priority when selecting a URL to match. More explicit selection of C++ version. Ability to handle multiple parameters with the same name on the URL. diff --git a/configure.ac b/configure.ac index 5754ae7c..003170c6 100644 --- a/configure.ac +++ b/configure.ac @@ -21,7 +21,7 @@ AC_PREREQ(2.57) m4_define([libhttpserver_MAJOR_VERSION],[0])dnl -m4_define([libhttpserver_MINOR_VERSION],[19])dnl +m4_define([libhttpserver_MINOR_VERSION],[20])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 @@ -149,6 +149,11 @@ fi AM_CONDITIONAL([COND_CROSS_COMPILE],[test x"$cond_cross_compile" = x"yes"]) AC_SUBST(COND_CROSS_COMPILE) +# Check for basic auth support in libmicrohttpd +AC_CHECK_LIB([microhttpd], [MHD_queue_basic_auth_fail_response], + [have_bauth="yes"], + [have_bauth="no"; AC_MSG_WARN("libmicrohttpd basic auth support not found. Basic auth will be disabled")]) + # Check for digest auth support in libmicrohttpd AC_CHECK_LIB([microhttpd], [MHD_queue_auth_fail_response], [have_dauth="yes"], @@ -264,6 +269,13 @@ fi AM_CONDITIONAL([HAVE_GNUTLS],[test x"$have_gnutls" = x"yes"]) +if test x"$have_bauth" = x"yes"; then + AM_CXXFLAGS="$AM_CXXFLAGS -DHAVE_BAUTH" + AM_CFLAGS="$AM_CXXFLAGS -DHAVE_BAUTH" +fi + +AM_CONDITIONAL([HAVE_BAUTH],[test x"$have_bauth" = x"yes"]) + if test x"$have_dauth" = x"yes"; then AM_CXXFLAGS="$AM_CXXFLAGS -DHAVE_DAUTH" AM_CFLAGS="$AM_CXXFLAGS -DHAVE_DAUTH" @@ -327,6 +339,7 @@ AC_MSG_NOTICE([Configuration Summary: License : LGPL only Debug : ${debugit} TLS Enabled : ${have_gnutls} + Basic Auth : ${have_bauth} Digest Auth : ${have_dauth} TCP_FASTOPEN : ${is_fastopen_supported} Static : ${static} diff --git a/examples/Makefile.am b/examples/Makefile.am index 37930db0..c04e0acf 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -19,7 +19,7 @@ LDADD = $(top_builddir)/src/libhttpserver.la 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 args_processing setting_headers custom_access_log basic_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload file_upload_with_callback +noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg args_processing setting_headers custom_access_log minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload file_upload_with_callback hello_world_SOURCES = hello_world.cpp service_SOURCES = service.cpp @@ -31,7 +31,6 @@ hello_with_get_arg_SOURCES = hello_with_get_arg.cpp args_processing_SOURCES = args_processing.cpp setting_headers_SOURCES = setting_headers.cpp custom_access_log_SOURCES = custom_access_log.cpp -basic_authentication_SOURCES = basic_authentication.cpp minimal_https_SOURCES = minimal_https.cpp minimal_file_response_SOURCES = minimal_file_response.cpp minimal_deferred_SOURCES = minimal_deferred.cpp @@ -44,6 +43,12 @@ benchmark_nodelay_SOURCES = benchmark_nodelay.cpp file_upload_SOURCES = file_upload.cpp file_upload_with_callback_SOURCES = file_upload_with_callback.cpp +if HAVE_BAUTH +noinst_PROGRAMS += basic_authentication centralized_authentication +basic_authentication_SOURCES = basic_authentication.cpp +centralized_authentication_SOURCES = centralized_authentication.cpp +endif + if HAVE_GNUTLS LDADD += -lgnutls noinst_PROGRAMS += minimal_https_psk diff --git a/src/Makefile.am b/src/Makefile.am index 63a16a50..ed8dc8f4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,9 +19,14 @@ AM_CPPFLAGS = -I../ -I$(srcdir)/httpserver/ METASOURCES = AUTO lib_LTLIBRARIES = libhttpserver.la -libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp string_response.cpp basic_auth_fail_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp create_webserver.cpp details/http_endpoint.cpp +libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp string_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp create_webserver.cpp details/http_endpoint.cpp noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp gettext.h -nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/details/http_endpoint.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/basic_auth_fail_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp httpserver/http_arg_value.hpp +nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/details/http_endpoint.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp httpserver/http_arg_value.hpp + +if HAVE_BAUTH +libhttpserver_la_SOURCES += basic_auth_fail_response.cpp +nobase_include_HEADERS += httpserver/basic_auth_fail_response.hpp +endif AM_CXXFLAGS += -fPIC -Wall diff --git a/src/basic_auth_fail_response.cpp b/src/basic_auth_fail_response.cpp index 0e00cdc1..1e6aa0e5 100644 --- a/src/basic_auth_fail_response.cpp +++ b/src/basic_auth_fail_response.cpp @@ -18,6 +18,8 @@ USA */ +#ifdef HAVE_BAUTH + #include "httpserver/basic_auth_fail_response.hpp" #include #include @@ -32,3 +34,5 @@ int basic_auth_fail_response::enqueue_response(MHD_Connection* connection, MHD_R } } // namespace httpserver + +#endif // HAVE_BAUTH diff --git a/src/create_test_request.cpp b/src/create_test_request.cpp index 31c7d3df..985acd39 100644 --- a/src/create_test_request.cpp +++ b/src/create_test_request.cpp @@ -49,8 +49,10 @@ http_request create_test_request::build() { req.cache->querystring = std::move(_querystring); } +#ifdef HAVE_BAUTH req.cache->username = std::move(_user); req.cache->password = std::move(_pass); +#endif // HAVE_BAUTH #ifdef HAVE_DAUTH req.cache->digested_user = std::move(_digested_user); diff --git a/src/http_request.cpp b/src/http_request.cpp index 1de47519..4d67bf39 100644 --- a/src/http_request.cpp +++ b/src/http_request.cpp @@ -310,6 +310,7 @@ MHD_Result http_request::build_request_querystring(void *cls, enum MHD_ValueKind return MHD_YES; } +#ifdef HAVE_BAUTH void http_request::fetch_user_pass() const { char* password = nullptr; auto* username = MHD_basic_auth_get_username_password(underlying_connection, &password); @@ -339,6 +340,7 @@ std::string_view http_request::get_pass() const { fetch_user_pass(); return cache->password; } +#endif // HAVE_BAUTH #ifdef HAVE_DAUTH std::string_view http_request::get_digested_user() const { @@ -557,8 +559,11 @@ uint16_t http_request::get_requestor_port() const { } std::ostream &operator<< (std::ostream &os, const http_request &r) { - os << r.get_method() << " Request [user:\"" << r.get_user() << "\" pass:\"" << r.get_pass() << "\"] path:\"" - << r.get_path() << "\"" << std::endl; + os << r.get_method() << " Request ["; +#ifdef HAVE_BAUTH + os << "user:\"" << r.get_user() << "\" pass:\"" << r.get_pass() << "\""; +#endif // HAVE_BAUTH + os << "] path:\"" << r.get_path() << "\"" << std::endl; http::dump_header_map(os, "Headers", r.get_headers()); http::dump_header_map(os, "Footers", r.get_footers()); diff --git a/src/httpserver.hpp b/src/httpserver.hpp index 4f19de48..b2bba186 100644 --- a/src/httpserver.hpp +++ b/src/httpserver.hpp @@ -27,7 +27,9 @@ #define _HTTPSERVER_HPP_INSIDE_ +#ifdef HAVE_BAUTH #include "httpserver/basic_auth_fail_response.hpp" +#endif // HAVE_BAUTH #include "httpserver/deferred_response.hpp" #ifdef HAVE_DAUTH #include "httpserver/digest_auth_fail_response.hpp" diff --git a/src/httpserver/basic_auth_fail_response.hpp b/src/httpserver/basic_auth_fail_response.hpp index 87a124f5..d88bbbff 100644 --- a/src/httpserver/basic_auth_fail_response.hpp +++ b/src/httpserver/basic_auth_fail_response.hpp @@ -25,6 +25,8 @@ #ifndef SRC_HTTPSERVER_BASIC_AUTH_FAIL_RESPONSE_HPP_ #define SRC_HTTPSERVER_BASIC_AUTH_FAIL_RESPONSE_HPP_ +#ifdef HAVE_BAUTH + #include #include "httpserver/http_utils.hpp" #include "httpserver/string_response.hpp" @@ -60,4 +62,7 @@ class basic_auth_fail_response : public string_response { }; } // namespace httpserver + +#endif // HAVE_BAUTH + #endif // SRC_HTTPSERVER_BASIC_AUTH_FAIL_RESPONSE_HPP_ diff --git a/src/httpserver/create_test_request.hpp b/src/httpserver/create_test_request.hpp index 177257c2..a1f193d0 100644 --- a/src/httpserver/create_test_request.hpp +++ b/src/httpserver/create_test_request.hpp @@ -83,6 +83,7 @@ class create_test_request { return *this; } +#ifdef HAVE_BAUTH create_test_request& user(const std::string& user) { _user = user; return *this; @@ -92,6 +93,7 @@ class create_test_request { _pass = pass; return *this; } +#endif // HAVE_BAUTH #ifdef HAVE_DAUTH create_test_request& digested_user(const std::string& digested_user) { @@ -129,8 +131,10 @@ class create_test_request { http::header_map _cookies; std::map, http::arg_comparator> _args; std::string _querystring; +#ifdef HAVE_BAUTH std::string _user; std::string _pass; +#endif // HAVE_BAUTH #ifdef HAVE_DAUTH std::string _digested_user; #endif // HAVE_DAUTH diff --git a/src/httpserver/create_webserver.hpp b/src/httpserver/create_webserver.hpp index 7ade5e17..991b8501 100644 --- a/src/httpserver/create_webserver.hpp +++ b/src/httpserver/create_webserver.hpp @@ -261,6 +261,7 @@ class create_webserver { return *this; } +#ifdef HAVE_BAUTH create_webserver& basic_auth() { _basic_auth_enabled = true; return *this; @@ -270,6 +271,7 @@ class create_webserver { _basic_auth_enabled = false; return *this; } +#endif // HAVE_BAUTH create_webserver& digest_auth() { _digest_auth_enabled = true; @@ -438,7 +440,9 @@ class create_webserver { std::string _digest_auth_random = ""; int _nonce_nc_size = 0; http::http_utils::policy_T _default_policy = http::http_utils::ACCEPT; +#ifdef HAVE_BAUTH bool _basic_auth_enabled = true; +#endif // HAVE_BAUTH bool _digest_auth_enabled = true; bool _regex_checking = true; bool _ban_system_enabled = true; diff --git a/src/httpserver/http_request.hpp b/src/httpserver/http_request.hpp index b6a015c8..2b621b11 100644 --- a/src/httpserver/http_request.hpp +++ b/src/httpserver/http_request.hpp @@ -60,11 +60,13 @@ class http_request { public: static const char EMPTY[]; +#ifdef HAVE_BAUTH /** * Method used to get the username eventually passed through basic authentication. * @return string representation of the username. **/ std::string_view get_user() const; +#endif // HAVE_BAUTH #ifdef HAVE_DAUTH /** @@ -74,11 +76,13 @@ class http_request { std::string_view get_digested_user() const; #endif // HAVE_DAUTH +#ifdef HAVE_BAUTH /** * Method used to get the password eventually passed through basic authentication. * @return string representation of the password. **/ std::string_view get_pass() const; +#endif // HAVE_BAUTH /** * Method used to get the path requested @@ -380,7 +384,9 @@ class http_request { static MHD_Result build_request_querystring(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); +#ifdef HAVE_BAUTH void fetch_user_pass() const; +#endif // HAVE_BAUTH /** * Method used to set an argument value by key. @@ -485,8 +491,10 @@ class http_request { // Others (username, password, digested_user) MHD returns as char* that we need // to make a copy of and free anyway. struct http_request_data_cache { +#ifdef HAVE_BAUTH std::string username; std::string password; +#endif // HAVE_BAUTH std::string querystring; std::string requestor_ip; #ifdef HAVE_DAUTH diff --git a/src/httpserver/webserver.hpp b/src/httpserver/webserver.hpp index ea4a404d..66d81ddd 100644 --- a/src/httpserver/webserver.hpp +++ b/src/httpserver/webserver.hpp @@ -168,7 +168,9 @@ class webserver { const int nonce_nc_size; bool running; const http::http_utils::policy_T default_policy; +#ifdef HAVE_BAUTH const bool basic_auth_enabled; +#endif // HAVE_BAUTH const bool digest_auth_enabled; const bool regex_checking; const bool ban_system_enabled; diff --git a/src/webserver.cpp b/src/webserver.cpp index 92d754a2..971d3d5a 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -158,7 +158,9 @@ webserver::webserver(const create_webserver& params): nonce_nc_size(params._nonce_nc_size), running(false), default_policy(params._default_policy), +#ifdef HAVE_BAUTH basic_auth_enabled(params._basic_auth_enabled), +#endif // HAVE_BAUTH digest_auth_enabled(params._digest_auth_enabled), regex_checking(params._regex_checking), ban_system_enabled(params._ban_system_enabled), diff --git a/test/integ/authentication.cpp b/test/integ/authentication.cpp index 57440b2e..b043f566 100644 --- a/test/integ/authentication.cpp +++ b/test/integ/authentication.cpp @@ -43,7 +43,9 @@ using std::shared_ptr; using httpserver::webserver; using httpserver::create_webserver; using httpserver::http_response; +#ifdef HAVE_BAUTH using httpserver::basic_auth_fail_response; +#endif // HAVE_BAUTH #ifdef HAVE_DAUTH using httpserver::digest_auth_fail_response; #endif // HAVE_DAUTH @@ -66,6 +68,7 @@ size_t writefunc(void *ptr, size_t size, size_t nmemb, std::string *s) { return size*nmemb; } +#ifdef HAVE_BAUTH class user_pass_resource : public http_resource { public: shared_ptr render_GET(const http_request& req) { @@ -75,6 +78,7 @@ class user_pass_resource : public http_resource { return std::make_shared(std::string(req.get_user()) + " " + std::string(req.get_pass()), 200, "text/plain"); } }; +#endif // HAVE_BAUTH #ifdef HAVE_DAUTH class digest_resource : public http_resource { @@ -101,6 +105,7 @@ LT_BEGIN_SUITE(authentication_suite) } LT_END_SUITE(authentication_suite) +#ifdef HAVE_BAUTH LT_BEGIN_AUTO_TEST(authentication_suite, base_auth) webserver ws = create_webserver(PORT); @@ -150,6 +155,7 @@ LT_BEGIN_AUTO_TEST(authentication_suite, base_auth_fail) ws.stop(); LT_END_AUTO_TEST(base_auth_fail) +#endif // HAVE_BAUTH // do not run the digest auth tests on windows as curl // appears to have problems with it. @@ -555,6 +561,7 @@ LT_END_AUTO_TEST(digest_user_cache_with_auth) #endif +#ifdef HAVE_BAUTH // Simple resource for centralized auth tests class simple_resource : public http_resource { public: @@ -1122,6 +1129,7 @@ LT_BEGIN_AUTO_TEST(authentication_suite, auth_skip_path_traversal_bypass) ws.stop(); LT_END_AUTO_TEST(auth_skip_path_traversal_bypass) +#endif // HAVE_BAUTH LT_BEGIN_AUTO_TEST_ENV() AUTORUN_TESTS() diff --git a/test/integ/basic.cpp b/test/integ/basic.cpp index d36f2f48..14d4eea0 100644 --- a/test/integ/basic.cpp +++ b/test/integ/basic.cpp @@ -2640,6 +2640,7 @@ class null_value_query_resource : public http_resource { } }; +#ifdef HAVE_BAUTH // Resource that tests auth caching (get_user/get_pass called multiple times) class auth_cache_resource : public http_resource { public: @@ -2654,7 +2655,9 @@ class auth_cache_resource : public http_resource { return std::make_shared(result, 200, "text/plain"); } }; +#endif // HAVE_BAUTH +#ifdef HAVE_BAUTH LT_BEGIN_AUTO_TEST(basic_suite, auth_caching) auth_cache_resource resource; LT_ASSERT_EQ(true, ws->register_resource("auth_cache", &resource)); @@ -2672,6 +2675,7 @@ LT_BEGIN_AUTO_TEST(basic_suite, auth_caching) LT_CHECK_EQ(s, "NO_AUTH"); curl_easy_cleanup(curl); LT_END_AUTO_TEST(auth_caching) +#endif // HAVE_BAUTH // Test query parameters with null/empty values (e.g., ?keyonly&normal=value) // This covers http_request.cpp lines 234 and 248 (arg_value == nullptr branches) diff --git a/test/integ/ws_start_stop.cpp b/test/integ/ws_start_stop.cpp index d664301a..a754cad3 100644 --- a/test/integ/ws_start_stop.cpp +++ b/test/integ/ws_start_stop.cpp @@ -279,7 +279,9 @@ LT_BEGIN_AUTO_TEST(ws_start_stop_suite, disable_options) .no_ipv6() .no_debug() .no_pedantic() +#ifdef HAVE_BAUTH .no_basic_auth() +#endif // HAVE_BAUTH .no_digest_auth() .no_deferred() .no_regex_checking() diff --git a/test/unit/create_test_request_test.cpp b/test/unit/create_test_request_test.cpp index 8b3843a8..db94d176 100644 --- a/test/unit/create_test_request_test.cpp +++ b/test/unit/create_test_request_test.cpp @@ -126,6 +126,7 @@ LT_BEGIN_AUTO_TEST(create_test_request_suite, build_content) LT_CHECK_EQ(std::string(req.get_content()), std::string("{\"key\":\"value\"}")); LT_END_AUTO_TEST(build_content) +#ifdef HAVE_BAUTH // Test basic auth LT_BEGIN_AUTO_TEST(create_test_request_suite, build_basic_auth) auto req = create_test_request() @@ -135,6 +136,7 @@ LT_BEGIN_AUTO_TEST(create_test_request_suite, build_basic_auth) LT_CHECK_EQ(std::string(req.get_user()), std::string("admin")); LT_CHECK_EQ(std::string(req.get_pass()), std::string("secret")); LT_END_AUTO_TEST(build_basic_auth) +#endif // HAVE_BAUTH // Test requestor LT_BEGIN_AUTO_TEST(create_test_request_suite, build_requestor) diff --git a/test/unit/create_webserver_test.cpp b/test/unit/create_webserver_test.cpp index efae3812..49bc10ff 100644 --- a/test/unit/create_webserver_test.cpp +++ b/test/unit/create_webserver_test.cpp @@ -120,12 +120,14 @@ LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_pedantic_toggle) LT_CHECK_EQ(true, true); LT_END_AUTO_TEST(builder_pedantic_toggle) +#ifdef HAVE_BAUTH // Test basic_auth / no_basic_auth toggle LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_basic_auth_toggle) create_webserver cw1 = create_webserver(8080).basic_auth(); create_webserver cw2 = create_webserver(8080).no_basic_auth(); LT_CHECK_EQ(true, true); LT_END_AUTO_TEST(builder_basic_auth_toggle) +#endif // HAVE_BAUTH // Test digest_auth / no_digest_auth toggle LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_digest_auth_toggle)