diff --git a/src/tests/client_server/Makefile.am b/src/tests/client_server/Makefile.am index 76d0656e..4b7ab3cf 100644 --- a/src/tests/client_server/Makefile.am +++ b/src/tests/client_server/Makefile.am @@ -26,6 +26,7 @@ $(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile check_PROGRAMS = \ test_client_server \ + test_authentication \ test_postprocessor if MHD_ENABLE_HTTPS @@ -56,6 +57,11 @@ test_client_server_SOURCES = \ test_client_server_LDADD = \ libmhdt.la +test_authentication_SOURCES = \ + test_authentication.c +test_authentication_LDADD = \ + libmhdt.la + test_tls_SOURCES = \ test_tls.c test_tls_LDADD = \ diff --git a/src/tests/client_server/libtest.h b/src/tests/client_server/libtest.h index 6713d451..d88f04ee 100644 --- a/src/tests/client_server/libtest.h +++ b/src/tests/client_server/libtest.h @@ -375,6 +375,34 @@ MHDT_client_do_post ( struct MHDT_PhaseContext *pc); +/** + * Perform GET request and send some HTTP basic authentication header + * to authorize the request. + * + * @param cls a string with "$USERNAME:$PASSWORD" + * @param pc context for the client + * @return error message, NULL on success + */ +const char * +MHDT_client_send_basic_auth ( + const void *cls, + struct MHDT_PhaseContext *pc); + + +/** + * Perform GET request and send some HTTP basic authentication header + * to authorize the request. Expect authentication to fail. + * + * @param cls a string with "$USERNAME:$PASSWORD" + * @param pc context for the client + * @return error message, NULL on success + */ +const char * +MHDT_client_fail_basic_auth ( + const void *cls, + struct MHDT_PhaseContext *pc); + + /** * Returns the text from @a cls as the response to any * request. @@ -602,6 +630,35 @@ MHDT_server_reply_check_post ( uint_fast64_t upload_size); +/** + * Checks that the client request includes the given + * username and password in HTTP basic authetnication. + * If so, returns #MHD_HTTP_STATUS_NO_CONTENT, otherwise + * an #MHD_HTTP_STATUS_UNAUTHORIZED. + * + * @param cls expected upload data as a 0-terminated string. + * @param request the request object + * @param path the requested uri (without arguments after "?") + * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, + * #MHD_HTTP_METHOD_PUT, etc.) + * @param upload_size the size of the message upload content payload, + * #MHD_SIZE_UNKNOWN for chunked uploads (if the + * final chunk has not been processed yet) + * @return action how to proceed, NULL + * if the request must be aborted due to a serious + * error while handling the request (implies closure + * of underling data stream, for HTTP/1.1 it means + * socket closure). + */ +const struct MHD_Action * +MHDT_server_reply_check_basic_auth ( + void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size); + + /** * Initialize options for an MHD daemon for a test. * diff --git a/src/tests/client_server/libtest_convenience_client_request.c b/src/tests/client_server/libtest_convenience_client_request.c index 9ea9dd5c..0f4ab913 100644 --- a/src/tests/client_server/libtest_convenience_client_request.c +++ b/src/tests/client_server/libtest_convenience_client_request.c @@ -19,7 +19,7 @@ */ /** - * @file libtest_convenience.c + * @file libtest_convenience_client_request.c * @brief convenience functions implementing clients making requests for libtest users * @author Christian Grothoff */ @@ -814,3 +814,114 @@ MHDT_client_do_post ( } return NULL; } + + +/** + * Send HTTP request with basic authentication. + * + * @param cred $USERNAME:$PASSWORD to use + * @param[in,out] phase context + * @param[out] http_status set to HTTP status + * @return error message, NULL on success + */ +static const char * +send_basic_auth (const char *cred, + struct MHDT_PhaseContext *pc, + unsigned int *http_status) +{ + CURL *c; + const char *err; + long status; + char *pass = strchr (cred, ':'); + char *user; + + if (NULL == pass) + return "invalid credential given"; + user = strndup (cred, + pass - user); + pass++; + c = curl_easy_init (); + if (NULL == c) + { + free (user); + return "Failed to initialize Curl handle"; + } + err = set_url (c, + pc->base_url, + pc); + if (NULL != err) + { + free (user); + curl_easy_cleanup (c); + return err; + } + if ( (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_HTTPAUTH, + (long) CURLAUTH_BASIC)) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_USERNAME, + "user")) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_PASSWORD, + "password")) ) + { + curl_easy_cleanup (c); + free (user); + return "Failed to set basic authentication header for curl request"; + } + free (user); + PERFORM_REQUEST (c); + if (CURLE_OK != + curl_easy_getinfo (c, + CURLINFO_RESPONSE_CODE, + &status)) + { + return "Failed to get HTTP status"; + } + *http_status = (unsigned int) status; + curl_easy_cleanup (c); + return NULL; +} + + +const char * +MHDT_client_send_basic_auth ( + const void *cls, + struct MHDT_PhaseContext *pc) +{ + const char *cred = cls; + const char *ret; + unsigned int status; + + ret = send_basic_auth (cred, + pc, + &status); + if (NULL != ret) + return ret; + if (MHD_HTTP_STATUS_NO_CONTENT != status) + return "invalid HTTP response code"; + return NULL; +} + + +const char * +MHDT_client_fail_basic_auth ( + const void *cls, + struct MHDT_PhaseContext *pc) +{ + const char *cred = cls; + const char *ret; + unsigned int status; + + ret = send_basic_auth (cred, + pc, + &status); + if (NULL != ret) + return ret; + if (MHD_HTTP_STATUS_UNAUTHORIZED != status) + return "invalid HTTP response code"; + return NULL; +} diff --git a/src/tests/client_server/libtest_convenience_server_reply.c b/src/tests/client_server/libtest_convenience_server_reply.c index ee65b0d6..178156a5 100644 --- a/src/tests/client_server/libtest_convenience_server_reply.c +++ b/src/tests/client_server/libtest_convenience_server_reply.c @@ -723,3 +723,69 @@ MHDT_server_reply_check_post ( &post_stream_done, pi); } + + +const struct MHD_Action * +MHDT_server_reply_check_basic_auth ( + void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + const char *cred = cls; + union MHD_RequestInfoDynamicData dd; + enum MHD_StatusCode sc; + struct MHD_BasicAuthInfo *ba; + struct MHD_Connection *connection; // FIXME - suboptimal API + union MHD_RequestInfoFixedData rif; + +#define FIXME 1 +#if FIXME + sc = MHD_request_get_info_fixed (request, + MHD_REQUEST_INFO_FIXED_CONNECTION, + &rif); + if (MHD_SC_OK != sc) + return NULL; + connection = rif.v_connection; +#endif + /* should not be needed, except to make gcc happy */ + memset (&dd, + 0, + sizeof (dd)); + sc = MHD_request_get_info_dynamic (request, + MHD_REQUEST_INFO_DYNAMIC_BAUTH_REQ_INFO, + &dd); + if (MHD_SC_OK != sc) + return MHD_action_basic_auth_required_response ( + connection, // FIXME: I'd like to see 'request' here. + "test-realm", + MHD_YES, + MHD_response_from_empty ( + MHD_HTTP_STATUS_FORBIDDEN)); + ba = dd.v_bauth_info; + if (NULL == ba) + return MHD_action_basic_auth_required_response ( + connection, // FIXME: I'd like to see 'request' here. + "test-realm", + MHD_YES, + MHD_response_from_empty ( + MHD_HTTP_STATUS_FORBIDDEN)); + if ( (0 != strncmp (ba->username.cstr, + cred, + ba->username.len)) || + (':' != cred[ba->username.len]) || + (NULL == ba->password.cstr) || + (0 != strcmp (ba->password.cstr, + &cred[ba->username.len + 1])) ) + return MHD_action_basic_auth_required_response ( + connection, // FIXME: I'd like to see 'request' here. + "test-realm", + MHD_YES, + MHD_response_from_empty ( + MHD_HTTP_STATUS_FORBIDDEN)); + return MHD_action_from_response ( + request, + MHD_response_from_empty ( + MHD_HTTP_STATUS_NO_CONTENT)); +} diff --git a/src/tests/client_server/test_authentication.c b/src/tests/client_server/test_authentication.c new file mode 100644 index 00000000..6b7676b1 --- /dev/null +++ b/src/tests/client_server/test_authentication.c @@ -0,0 +1,104 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2016, 2024 Christian Grothoff & Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 + +*/ + +/** + * @file test_authentication.c + * @brief test for HTTP authentication + * @author Christian Grothoff + */ +#include "libtest.h" + + +int +main (int argc, char *argv[]) +{ + struct MHD_DaemonOptionAndValue thread1auto[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_TERMINATE () + }; + struct ServerType + { + const char *label; + MHDT_ServerSetup server_setup; + void *server_setup_cls; + MHDT_ServerRunner server_runner; + void *server_runner_cls; + } configs[] = { + { + .label = "auto-selected mode, single threaded", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread1auto, + .server_runner = &MHDT_server_run_minimal, + }, + { + .label = "END" + }, + }; + struct MHDT_Phase phases[] = { + { + .label = "simple basic authentication", + .server_cb = &MHDT_server_reply_check_basic_auth, + .server_cb_cls = (void *) "username:password", + .client_cb = &MHDT_client_send_basic_auth, + .client_cb_cls = (void *) "username:password", + .timeout_ms = 200, + }, + { + .label = "failing basic authentication", + .server_cb = &MHDT_server_reply_check_basic_auth, + .server_cb_cls = (void *) "username:password", + .client_cb = &MHDT_client_fail_basic_auth, + .client_cb_cls = (void *) "username:word", /* incorrect on purpose */ + .timeout_ms = 200, + }, + // TODO: digest auth + { + .label = NULL, + }, + }; + unsigned int i; + + (void) argc; /* Unused. Silence compiler warning. */ + (void) argv; /* Unused. Silence compiler warning. */ + + for (i = 0; NULL != configs[i].server_setup; i++) + { + int ret; + + fprintf (stderr, + "Running tests with server setup '%s'\n", + configs[i].label); + ret = MHDT_test (configs[i].server_setup, + configs[i].server_setup_cls, + configs[i].server_runner, + configs[i].server_runner_cls, + phases); + if (0 != ret) + { + fprintf (stderr, + "Test failed with server of type '%s' (%u)\n", + configs[i].label, + i); + return ret; + } + } + return 0; +}