libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit ed09a257495bc667f5ca043ffbe96800f418ff9c
parent bcb00ec1a1dacd1caebcc7303125558a4f36fa4b
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Mon,  6 Jan 2025 15:00:00 +0100

work on test

Diffstat:
Msrc/tests/client_server/Makefile.am | 6++++++
Msrc/tests/client_server/libtest.h | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/tests/client_server/libtest_convenience_client_request.c | 224++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/tests/client_server/libtest_convenience_server_reply.c | 166++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Asrc/tests/client_server/test_authentication.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 632 insertions(+), 2 deletions(-)

diff --git 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 @@ -376,6 +376,62 @@ MHDT_client_do_post ( /** + * 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); + + +/** + * Perform GET request and send some HTTP digest 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_digest_auth ( + const void *cls, + struct MHDT_PhaseContext *pc); + + +/** + * Perform GET request and send some HTTP digest 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_digest_auth ( + const void *cls, + struct MHDT_PhaseContext *pc); + + +/** * Returns the text from @a cls as the response to any * request. * @@ -603,6 +659,64 @@ MHDT_server_reply_check_post ( /** + * 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); + + +/** + * Checks that the client request includes the given + * username and password in HTTP digest 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_digest_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. * * @param cls closure diff --git 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,225 @@ 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 - cred); + 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, + pass)) ) + { + 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; +} + + +/** + * Send HTTP request with digest 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_digest_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 - cred); + 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_DIGEST)) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_USERNAME, + user)) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_PASSWORD, + pass)) ) + { + curl_easy_cleanup (c); + free (user); + return "Failed to set digest 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_digest_auth ( + const void *cls, + struct MHDT_PhaseContext *pc) +{ + const char *cred = cls; + const char *ret; + unsigned int status; + + ret = send_digest_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_digest_auth ( + const void *cls, + struct MHDT_PhaseContext *pc) +{ + const char *cred = cls; + const char *ret; + unsigned int status; + + ret = send_digest_auth (cred, + pc, + &status); + if (NULL != ret) + return ret; + if (MHD_HTTP_STATUS_FORBIDDEN != 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 @@ -33,7 +33,7 @@ #include <unistd.h> #include <errno.h> #include <curl/curl.h> - +#include <assert.h> const struct MHD_Action * MHDT_server_reply_text ( @@ -723,3 +723,167 @@ 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; + const struct MHD_AuthBasicCreds *ba; + + /* 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_AUTH_BASIC_CREDS, + &dd); + if (MHD_SC_OK != sc) + { + fprintf (stderr, + "No credentials?\n"); + return MHD_action_basic_auth_challenge_p ( + request, + "test-realm", + MHD_YES, + MHD_response_from_empty ( + MHD_HTTP_STATUS_UNAUTHORIZED)); + } + ba = dd.v_auth_basic_creds; + assert (NULL != ba); + 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])) ) + { + fprintf (stderr, + "Wrong credentials (Got: %s/%s Want: %s)!\n", + ba->username.cstr, + ba->password.cstr, + cred); + return MHD_action_basic_auth_challenge_p ( + request, + "test-realm", + MHD_YES, + MHD_response_from_empty ( + MHD_HTTP_STATUS_UNAUTHORIZED)); + } + return MHD_action_from_response ( + request, + MHD_response_from_empty ( + MHD_HTTP_STATUS_NO_CONTENT)); +} + + +const struct MHD_Action * +MHDT_server_reply_check_digest_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; + const char *colon = strchr (cred, ':'); + char *username; + const char *password; + enum MHD_DigestAuthResult dar; + const char *realm = "test-realm"; + enum MHD_DigestAuthAlgo algo = MHD_DIGEST_AUTH_ALGO_SHA256; + size_t digest_len = MHD_digest_get_hash_size (algo); + + if (0 == digest_len) + return NULL; + assert (NULL != colon); + password = colon + 1; + username = strndup (cred, + colon - cred); + assert (NULL != username); + { + enum MHD_StatusCode sc; + char digest[digest_len]; + + // FIXME: why is this needed? We should not get a warning + // even without this memset! + memset (digest, 0, sizeof (digest)); + sc = MHD_digest_auth_calc_userdigest (algo, + username, + realm, + password, + sizeof (digest), + digest); + if (MHD_SC_OK != sc) + { + fprintf (stderr, + "MHD_digest_auth_calc_userdigest: %d\n", + (int) sc); + return NULL; + } + dar = MHD_digest_auth_check_digest (request, + realm, + username, + sizeof (digest), + digest, + 0, /* maximum nonce counter; 0: default */ + MHD_DIGEST_AUTH_MULT_QOP_AUTH, + (enum MHD_DigestAuthMultiAlgo) algo); + } + free (username); + if ((MHD_DAUTH_HEADER_MISSING == dar) + || (MHD_DAUTH_NONCE_STALE == dar)) + { + struct MHD_Response *resp; + enum MHD_StatusCode sc; + + resp = MHD_response_from_empty ( + MHD_HTTP_STATUS_UNAUTHORIZED); + if (NULL == resp) + { + fprintf (stderr, + "Failed to create response body\n"); + return NULL; + } + sc = MHD_response_add_auth_digest_challenge ( + resp, + "test-realm", + "opaque", + NULL, /* domain */ + (MHD_DAUTH_NONCE_STALE == dar) ? MHD_YES : MHD_NO, /* indicate stale */ + MHD_DIGEST_AUTH_MULT_QOP_AUTH, + MHD_DIGEST_AUTH_MULT_ALGO_SHA256, + MHD_NO /* userhash_support */, + MHD_YES /* prefer UTF8 */); + if (MHD_SC_OK != sc) + { + fprintf (stderr, + "MHD_response_add_auth_digest_challenge failed: %d\n", + (int) sc); + return NULL; + } + return MHD_action_from_response ( + request, + resp); + } + if (MHD_DAUTH_RESPONSE_WRONG == dar) + return MHD_action_from_response ( + request, + MHD_response_from_empty (MHD_HTTP_STATUS_FORBIDDEN)); + + if (MHD_DAUTH_OK == dar) + return MHD_action_from_response ( + request, + MHD_response_from_empty ( + MHD_HTTP_STATUS_NO_CONTENT)); + + return MHD_action_abort_request (request); +} diff --git a/src/tests/client_server/test_authentication.c b/src/tests/client_server/test_authentication.c @@ -0,0 +1,124 @@ +/* + 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[]) +{ + unsigned char pseudo_entropy[] = { 0, 1, 2, 3 }; + struct MHD_DaemonOptionAndValue thread1auto[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_RANDOM_ENTROPY (sizeof(pseudo_entropy), + pseudo_entropy), + 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[] = { +#if FIXME + { + .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 = 2500, + }, + { + .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 = 2500, + }, +#endif + { + .label = "simple digest authentication", + .server_cb = &MHDT_server_reply_check_digest_auth, + .server_cb_cls = (void *) "username:password", + .client_cb = &MHDT_client_send_digest_auth, + .client_cb_cls = (void *) "username:password", + .timeout_ms = 2500, + }, + { + .label = "failing digest authentication", + .server_cb = &MHDT_server_reply_check_digest_auth, + .server_cb_cls = (void *) "username:password", + .client_cb = &MHDT_client_fail_digest_auth, + .client_cb_cls = (void *) "username:word", /* incorrect on purpose */ + .timeout_ms = 2500, + }, + { + .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; +}