From ad9628158865b5af93f9c92b9e0f5178877a7bca Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 20:24:36 +0000 Subject: [PATCH 01/12] api: Abstract datum_api_submit_uncached_response --- src/datum_api.c | 59 ++++++++++++++++--------------------------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/src/datum_api.c b/src/datum_api.c index ef0d8da..55b0645 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -390,14 +390,18 @@ bool datum_api_formdata_to_json(struct MHD_Connection * const connection, char * return true; } -int datum_api_do_error(struct MHD_Connection * const connection, const unsigned int status_code) { - struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); +int datum_api_submit_uncached_response(struct MHD_Connection * const connection, const unsigned int status_code, struct MHD_Response * const response) { http_resp_prevent_caching(response); int ret = MHD_queue_response(connection, status_code, response); MHD_destroy_response(response); return ret; } +int datum_api_do_error(struct MHD_Connection * const connection, const unsigned int status_code) { + struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + return datum_api_submit_uncached_response(connection, status_code, response); +} + bool datum_api_check_admin_password_only(struct MHD_Connection * const connection, const char * const password) { if (datum_secure_strequals(datum_config.api_admin_password, datum_config.api_admin_password_len, password) && datum_config.api_admin_password_len) { return true; @@ -518,7 +522,7 @@ void datum_api_cmd_kill_client2(const char * const data, const size_t size, cons int datum_api_cmd(struct MHD_Connection *connection, char *post, int len) { struct MHD_Response *response; char output[1024]; - int ret, sz=0; + int sz = 0; json_t *root, *cmd, *param; json_error_t error; const char *cstr; @@ -603,27 +607,21 @@ int datum_api_cmd(struct MHD_Connection *connection, char *post, int len) { } response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); - http_resp_prevent_caching(response); MHD_add_response_header(response, "Location", redirect); - ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); - MHD_destroy_response(response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_FOUND, response); } } sprintf(output, "{}"); response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_COPY); MHD_add_response_header(response, "Content-Type", "application/json"); - http_resp_prevent_caching(response); - ret = MHD_queue_response (connection, MHD_HTTP_OK, response); - MHD_destroy_response (response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } int datum_api_coinbaser(struct MHD_Connection *connection) { struct MHD_Response *response; T_DATUM_STRATUM_JOB *sjob; - int j,i,max_sz = 0,sz=0,ret; + int j, i, max_sz = 0, sz = 0; char tempaddr[256]; uint64_t tv = 0; char *output = NULL; @@ -660,15 +658,12 @@ int datum_api_coinbaser(struct MHD_Connection *connection) { response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_FREE); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); - ret = MHD_queue_response (connection, MHD_HTTP_OK, response); - MHD_destroy_response (response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } int datum_api_thread_dashboard(struct MHD_Connection *connection) { struct MHD_Response *response; - int sz=0, ret, max_sz = 0, j, ii; + int sz=0, max_sz = 0, j, ii; char *output = NULL; T_DATUM_MINER_DATA *m = NULL; uint64_t tsms; @@ -729,16 +724,13 @@ int datum_api_thread_dashboard(struct MHD_Connection *connection) { response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_FREE); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); - ret = MHD_queue_response (connection, MHD_HTTP_OK, response); - MHD_destroy_response (response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } int datum_api_client_dashboard(struct MHD_Connection *connection) { struct MHD_Response *response; int connected_clients = 0; - int i,sz=0,ret,max_sz = 0,j,ii; + int i, sz = 0, max_sz = 0, j, ii; char *output = NULL; T_DATUM_MINER_DATA *m = NULL; uint64_t tsms; @@ -766,10 +758,7 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { response = MHD_create_response_from_buffer(sz, output, MHD_RESPMEM_MUST_FREE); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); - ret = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } if (!datum_api_check_admin_password_httponly(connection)) { return MHD_YES; @@ -845,16 +834,13 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { // return the home page with some data and such response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_FREE); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); - ret = MHD_queue_response (connection, MHD_HTTP_OK, response); - MHD_destroy_response (response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } int datum_api_homepage(struct MHD_Connection *connection) { struct MHD_Response *response; char output[DATUM_API_HOMEPAGE_MAX_SIZE]; - int j, k = 0, kk = 0, ii, ret; + int j, k = 0, kk = 0, ii; T_DATUM_MINER_DATA *m; T_DATUM_API_DASH_VARS vardata; unsigned char astat; @@ -910,22 +896,15 @@ int datum_api_homepage(struct MHD_Connection *connection) { // return the home page with some data and such response = MHD_create_response_from_buffer (strlen(output), (void *) output, MHD_RESPMEM_MUST_COPY); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); - ret = MHD_queue_response (connection, MHD_HTTP_OK, response); - MHD_destroy_response (response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } int datum_api_OK(struct MHD_Connection *connection) { - enum MHD_Result ret; struct MHD_Response *response; const char *ok_response = "OK"; response = MHD_create_response_from_buffer(strlen(ok_response), (void *)ok_response, MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); - ret = MHD_queue_response (connection, MHD_HTTP_OK, response); - MHD_destroy_response (response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } int datum_api_testnet_fastforward(struct MHD_Connection * const connection) { From f0aa212ec417f0e3df483a5e7af9960a27dfcf26 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 20:25:21 +0000 Subject: [PATCH 02/12] api: Abstract datum_api_create_empty_mhd_response --- src/datum_api.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/datum_api.c b/src/datum_api.c index 55b0645..db7a162 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -68,6 +68,10 @@ const char *cbnames[] = { "Antmain2" }; +static struct MHD_Response *datum_api_create_empty_mhd_response() { + return MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); +} + static void html_leading_zeros(char * const buffer, const size_t buffer_size, const char * const numstr) { int zeros = 0; while (numstr[zeros] == '0') { @@ -398,7 +402,7 @@ int datum_api_submit_uncached_response(struct MHD_Connection * const connection, } int datum_api_do_error(struct MHD_Connection * const connection, const unsigned int status_code) { - struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + struct MHD_Response *response = datum_api_create_empty_mhd_response(); return datum_api_submit_uncached_response(connection, status_code, response); } @@ -424,7 +428,7 @@ bool datum_api_check_admin_password_httponly(struct MHD_Connection * const conne } if (ret != MHD_YES) { DLOG_DEBUG("Wrong password in HTTP authentication"); - struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + struct MHD_Response *response = datum_api_create_empty_mhd_response(); ret = MHD_queue_auth_fail_response2(connection, realm, datum_config.api_csrf_token, response, (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO, MHD_DIGEST_ALG_SHA256); MHD_destroy_response(response); return false; @@ -606,7 +610,7 @@ int datum_api_cmd(struct MHD_Connection *connection, char *post, int len) { datum_api_cmd_kill_client2(data, size, &redirect); } - response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + response = datum_api_create_empty_mhd_response(); MHD_add_response_header(response, "Location", redirect); return datum_api_submit_uncached_response(connection, MHD_HTTP_FOUND, response); } From 90b51d2aded561a9094d790feac40b3b7dc22b34 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 20:27:00 +0000 Subject: [PATCH 03/12] api: Pass MHD_Response creator function to datum_api_check_admin_password* --- src/datum_api.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/datum_api.c b/src/datum_api.c index db7a162..e30ced1 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -68,6 +68,8 @@ const char *cbnames[] = { "Antmain2" }; +typedef struct MHD_Response *(*create_response_func_t)(); + static struct MHD_Response *datum_api_create_empty_mhd_response() { return MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); } @@ -406,16 +408,16 @@ int datum_api_do_error(struct MHD_Connection * const connection, const unsigned return datum_api_submit_uncached_response(connection, status_code, response); } -bool datum_api_check_admin_password_only(struct MHD_Connection * const connection, const char * const password) { +bool datum_api_check_admin_password_only(struct MHD_Connection * const connection, const char * const password, const create_response_func_t auth_failure_response_creator) { if (datum_secure_strequals(datum_config.api_admin_password, datum_config.api_admin_password_len, password) && datum_config.api_admin_password_len) { return true; } DLOG_DEBUG("Wrong password in request"); - datum_api_do_error(connection, MHD_HTTP_FORBIDDEN); + datum_api_submit_uncached_response(connection, MHD_HTTP_FORBIDDEN, auth_failure_response_creator()); return false; } -bool datum_api_check_admin_password_httponly(struct MHD_Connection * const connection) { +bool datum_api_check_admin_password_httponly(struct MHD_Connection * const connection, const create_response_func_t auth_failure_response_creator) { int ret; char * const username = MHD_digest_auth_get_username(connection); @@ -428,7 +430,7 @@ bool datum_api_check_admin_password_httponly(struct MHD_Connection * const conne } if (ret != MHD_YES) { DLOG_DEBUG("Wrong password in HTTP authentication"); - struct MHD_Response *response = datum_api_create_empty_mhd_response(); + struct MHD_Response * const response = auth_failure_response_creator(); ret = MHD_queue_auth_fail_response2(connection, realm, datum_config.api_csrf_token, response, (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO, MHD_DIGEST_ALG_SHA256); MHD_destroy_response(response); return false; @@ -437,26 +439,26 @@ bool datum_api_check_admin_password_httponly(struct MHD_Connection * const conne return true; } -bool datum_api_check_admin_password(struct MHD_Connection * const connection, const json_t * const j) { +bool datum_api_check_admin_password(struct MHD_Connection * const connection, const json_t * const j, const create_response_func_t auth_failure_response_creator) { const json_t * const j_password = json_object_get(j, "password"); if (json_is_string(j_password)) { - return datum_api_check_admin_password_only(connection, json_string_value(j_password)); + return datum_api_check_admin_password_only(connection, json_string_value(j_password), auth_failure_response_creator); } // Only accept HTTP authentication if there's an anti-CSRF token const json_t * const j_csrf = json_object_get(j, "csrf"); if (!json_is_string(j_csrf)) { DLOG_DEBUG("Missing CSRF token in request"); - datum_api_do_error(connection, MHD_HTTP_FORBIDDEN); + datum_api_submit_uncached_response(connection, MHD_HTTP_FORBIDDEN, auth_failure_response_creator()); return false; } if (!datum_secure_strequals(datum_config.api_csrf_token, sizeof(datum_config.api_csrf_token)-1, json_string_value(j_csrf))) { DLOG_DEBUG("Wrong CSRF token in request"); - datum_api_do_error(connection, MHD_HTTP_FORBIDDEN); + datum_api_submit_uncached_response(connection, MHD_HTTP_FORBIDDEN, auth_failure_response_creator()); return false; } - return datum_api_check_admin_password_httponly(connection); + return datum_api_check_admin_password_httponly(connection, auth_failure_response_creator); } static int datum_api_asset(struct MHD_Connection * const connection, const char * const mimetype, const char * const data, const size_t datasz) { @@ -540,7 +542,7 @@ int datum_api_cmd(struct MHD_Connection *connection, char *post, int len) { root = json_loadb(post, len, 0, &error); if (root) { if (json_is_object(root) && (cmd = json_object_get(root, "cmd"))) { - if (!datum_api_check_admin_password(connection, root)) { + if (!datum_api_check_admin_password(connection, root, datum_api_create_empty_mhd_response)) { json_decref(root); return MHD_YES; } @@ -587,7 +589,7 @@ int datum_api_cmd(struct MHD_Connection *connection, char *post, int len) { return datum_api_do_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR); } - if (!datum_api_check_admin_password(connection, root)) { + if (!datum_api_check_admin_password(connection, root, datum_api_create_empty_mhd_response)) { json_decref(root); return MHD_YES; } @@ -764,7 +766,7 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { MHD_add_response_header(response, "Content-Type", "text/html"); return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } - if (!datum_api_check_admin_password_httponly(connection)) { + if (!datum_api_check_admin_password_httponly(connection, datum_api_create_empty_mhd_response)) { return MHD_YES; } @@ -915,7 +917,7 @@ int datum_api_testnet_fastforward(struct MHD_Connection * const connection) { const char *time_str; time_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "password"); - if (!datum_api_check_admin_password_only(connection, time_str)) { + if (!datum_api_check_admin_password_only(connection, time_str, datum_api_create_empty_mhd_response)) { return MHD_YES; } From 86dd75305e66ed77ceda1061f5832f8860cd5c79 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 20:27:39 +0000 Subject: [PATCH 04/12] api: Return error pages with authentication failures --- CMakeLists.txt | 1 + src/datum_api.c | 35 ++++++++++++++++++++++++++++++++--- www/auth_failed.html | 1 + 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 www/auth_failed.html diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ce6d8b..a291740 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(datum_gateway install(TARGETS datum_gateway DESTINATION bin) set(WEB_RESOURCES + www/auth_failed.html www/home.html www/clients_top.html www/coinbaser_top.html diff --git a/src/datum_api.c b/src/datum_api.c index e30ced1..5f41aa6 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -461,6 +461,34 @@ bool datum_api_check_admin_password(struct MHD_Connection * const connection, co return datum_api_check_admin_password_httponly(connection, auth_failure_response_creator); } +static struct MHD_Response *datum_api_create_response_authfail(const char * const head, const size_t head_sz) { + const size_t max_sz = head_sz + www_auth_failed_html_sz + www_foot_html_sz + 1; + size_t sz = 0; + char * const output = malloc(max_sz); + if (!output) { + return datum_api_create_empty_mhd_response(); + } + + memcpy(&output[sz], head, head_sz); + sz += head_sz; + memcpy(&output[sz], www_auth_failed_html, www_auth_failed_html_sz); + sz += www_auth_failed_html_sz; + memcpy(&output[sz], www_foot_html, www_foot_html_sz); + sz += www_foot_html_sz; + + struct MHD_Response * const response = MHD_create_response_from_buffer(sz, output, MHD_RESPMEM_MUST_FREE); + MHD_add_response_header(response, "Content-Type", "text/html"); + return response; +} + +static struct MHD_Response *datum_api_create_response_authfail_clients() { + return datum_api_create_response_authfail(www_clients_top_html, www_clients_top_html_sz); +} + +static struct MHD_Response *datum_api_create_response_authfail_threads() { + return datum_api_create_response_authfail(www_threads_top_html, www_threads_top_html_sz); +} + static int datum_api_asset(struct MHD_Connection * const connection, const char * const mimetype, const char * const data, const size_t datasz) { struct MHD_Response * const response = MHD_create_response_from_buffer(datasz, (void*)data, MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Content-Type", mimetype); @@ -589,14 +617,15 @@ int datum_api_cmd(struct MHD_Connection *connection, char *post, int len) { return datum_api_do_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR); } - if (!datum_api_check_admin_password(connection, root, datum_api_create_empty_mhd_response)) { + param = json_object_get(root, "empty_thread"); + if (!datum_api_check_admin_password(connection, root, param ? datum_api_create_response_authfail_threads : datum_api_create_response_authfail_clients)) { json_decref(root); return MHD_YES; } const char *redirect = "/"; - param = json_object_get(root, "empty_thread"); + // param set for "empty_thread" above if (param) { tid = datum_atoi_strict(json_string_value(param), json_string_length(param)); if (tid != -1) { @@ -766,7 +795,7 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { MHD_add_response_header(response, "Content-Type", "text/html"); return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } - if (!datum_api_check_admin_password_httponly(connection, datum_api_create_empty_mhd_response)) { + if (!datum_api_check_admin_password_httponly(connection, datum_api_create_response_authfail_clients)) { return MHD_YES; } diff --git a/www/auth_failed.html b/www/auth_failed.html new file mode 100644 index 0000000..a473d9f --- /dev/null +++ b/www/auth_failed.html @@ -0,0 +1 @@ +This action requires admin access. From 382377f7ee69e1a1a962f97d2a901953a3f95f41 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 19:49:37 +0000 Subject: [PATCH 05/12] api: Move sendPostRequest Javascript function to post.js asset --- CMakeLists.txt | 1 + src/datum_api.c | 9 ++++++--- www/assets/post.js | 10 ++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 www/assets/post.js diff --git a/CMakeLists.txt b/CMakeLists.txt index a291740..b2bf1fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set(WEB_RESOURCES www/coinbaser_top.html www/threads_top.html www/foot.html + www/assets/post.js www/assets/style.css www/assets/icons/datum_logo.svg www/assets/icons/favicon.ico diff --git a/src/datum_api.c b/src/datum_api.c index 5f41aa6..6f5367c 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -753,7 +753,9 @@ int datum_api_thread_dashboard(struct MHD_Connection *connection) { } sz += snprintf(&output[sz], max_sz-1-sz, ""); if (have_admin) { - sz += snprintf(&output[sz], max_sz-1-sz, "", datum_config.api_csrf_token); + sz += snprintf(&output[sz], max_sz-1-sz, ""); } sz += snprintf(&output[sz], max_sz-1-sz, "%s", www_foot_html); @@ -863,8 +865,9 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { } } - sz += snprintf(&output[sz], max_sz-1-sz, "

Total active hashrate estimate: %.2f Th/s

", thr, datum_config.api_csrf_token); - sz += snprintf(&output[sz], max_sz-1-sz, "%s", www_foot_html); + sz += snprintf(&output[sz], max_sz-1-sz, "

Total active hashrate estimate: %.2f Th/s

%s", www_foot_html); // return the home page with some data and such response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_FREE); diff --git a/www/assets/post.js b/www/assets/post.js new file mode 100644 index 0000000..89deb10 --- /dev/null +++ b/www/assets/post.js @@ -0,0 +1,10 @@ +function sendPostRequest(url, data) { + data.csrf = '%s'; + fetch(url, { + method: 'POST', + headers: { + 'Content-Type':'application/json' + }, + body: JSON.stringify(data) + }); +} From e3dae9914792305852ccbe665561a3692615acfa Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 20:09:39 +0000 Subject: [PATCH 06/12] api: Handle error responses in sendPostRequest --- www/assets/post.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/www/assets/post.js b/www/assets/post.js index 89deb10..46a3f98 100644 --- a/www/assets/post.js +++ b/www/assets/post.js @@ -1,10 +1,16 @@ -function sendPostRequest(url, data) { +async function sendPostRequest(url, data) { data.csrf = '%s'; - fetch(url, { + var r = await fetch(url, { method: 'POST', headers: { 'Content-Type':'application/json' }, body: JSON.stringify(data) }); + if (r.ok) return; + if (r.status == 401) { + alert('This action requires admin access.'); + } else { + alert('Error ' + r.status); + } } From a969f76b470076c717a75b838d81f69218088907 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 20:30:22 +0000 Subject: [PATCH 07/12] api: Adjust denial messages to explain new admin_password setting --- src/datum_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datum_api.c b/src/datum_api.c index 6f5367c..35a8393 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -790,7 +790,7 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { sz = snprintf(output, max_sz-1-sz, "%s", www_clients_top_html); if (!datum_config.api_admin_password_len) { - sz += snprintf(&output[sz], max_sz-1-sz, "This page requires admin access (not configured)"); + sz += snprintf(&output[sz], max_sz-1-sz, "This page requires admin access (add \"admin_password\" to \"api\" section of config file)"); sz += snprintf(&output[sz], max_sz-1-sz, "%s", www_foot_html); response = MHD_create_response_from_buffer(sz, output, MHD_RESPMEM_MUST_FREE); From 2540808420ac28ebd58073f568c20db123330e33 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 18:04:30 +0000 Subject: [PATCH 08/12] api: Use datum_api_submit_uncached_response and datum_api_create_empty_mhd_response for config page --- src/datum_api.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/datum_api.c b/src/datum_api.c index c6c562b..7d0f06f 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -1037,7 +1037,6 @@ size_t datum_api_fill_config_var(const char *var_start, const size_t var_name_le int datum_api_config_dashboard(struct MHD_Connection *connection) { struct MHD_Response *response; size_t sz = 0, max_sz = 0; - int ret; char *output = NULL; max_sz = www_config_html_sz * 2; @@ -1050,10 +1049,7 @@ int datum_api_config_dashboard(struct MHD_Connection *connection) { response = MHD_create_response_from_buffer(sz, output, MHD_RESPMEM_MUST_FREE); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); - ret = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - return ret; + return datum_api_submit_uncached_response(connection, MHD_HTTP_OK, response); } #ifndef JSON_PRESERVE_ORDER @@ -1443,20 +1439,16 @@ int datum_api_config_post(struct MHD_Connection * const connection, char * const response = MHD_create_response_from_buffer(sz, output, MHD_RESPMEM_MUST_FREE); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); } else if (status.need_restart) { response = MHD_create_response_from_buffer(www_config_restart_html_sz, (void*)www_config_restart_html, MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); } else { - response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); - http_resp_prevent_caching(response); + response = datum_api_create_empty_mhd_response(); MHD_add_response_header(response, "Location", "/config"); } json_decref(errors); - ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); - MHD_destroy_response(response); + ret = datum_api_submit_uncached_response(connection, MHD_HTTP_FOUND, response); if (status.need_restart) { DLOG_INFO("Config change requires restarting gateway, proceeding"); From f42c0849fe163c9a3be79bafd465fc3fb1abbfe3 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 18:14:55 +0000 Subject: [PATCH 09/12] api: Use datum_api_create_empty_mhd_response for Etag not-modified response --- src/datum_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datum_api.c b/src/datum_api.c index 7d0f06f..e9f0f0e 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -514,7 +514,7 @@ static struct MHD_Response *datum_api_create_response_authfail_threads() { static int datum_api_asset(struct MHD_Connection * const connection, const char * const mimetype, const char * const data, const size_t datasz, const char * const etag) { const char * const if_none_match_header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "If-None-Match"); if (if_none_match_header && 0 == strcmp(if_none_match_header, etag)) { - struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + struct MHD_Response *response = datum_api_create_empty_mhd_response(); MHD_add_response_header(response, "Etag", etag); int ret = MHD_queue_response(connection, MHD_HTTP_NOT_MODIFIED, response); MHD_destroy_response(response); From 999d7670c2588eb984f62648f03393cbd816b205 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 19:39:34 +0000 Subject: [PATCH 10/12] api: Return error page for authentication failure on config change --- src/datum_api.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/datum_api.c b/src/datum_api.c index e9f0f0e..1c9477c 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -507,6 +507,27 @@ static struct MHD_Response *datum_api_create_response_authfail_clients() { return datum_api_create_response_authfail(www_clients_top_html, www_clients_top_html_sz); } +size_t datum_api_fill_authfail_error(const char * const var_start, const size_t var_name_len, char * const replacement, const size_t replacement_max_len, const T_DATUM_API_DASH_VARS * const vardata) { + assert(replacement_max_len >= www_auth_failed_html_sz); + memcpy(replacement, www_auth_failed_html, www_auth_failed_html_sz); + return www_auth_failed_html_sz; +} + +static struct MHD_Response *datum_api_create_response_authfail_config() { + const size_t max_sz = www_config_errors_html_sz + www_auth_failed_html_sz; + + char * const output = malloc(max_sz); + if (!output) { + return datum_api_create_empty_mhd_response(); + } + + const size_t sz = datum_api_fill_vars(www_config_errors_html, output, max_sz, datum_api_fill_authfail_error, NULL); + + struct MHD_Response * const response = MHD_create_response_from_buffer(sz, output, MHD_RESPMEM_MUST_FREE); + MHD_add_response_header(response, "Content-Type", "text/html"); + return response; +} + static struct MHD_Response *datum_api_create_response_authfail_threads() { return datum_api_create_response_authfail(www_threads_top_html, www_threads_top_html_sz); } @@ -1368,7 +1389,7 @@ int datum_api_config_post(struct MHD_Connection * const connection, char * const return datum_api_do_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR); } - if (!datum_api_check_admin_password(connection, j, datum_api_create_empty_mhd_response)) { + if (!datum_api_check_admin_password(connection, j, datum_api_create_response_authfail_config)) { json_decref(j); return MHD_YES; } From 4f0ff9380ff80746b845c1bf397f4d31712ff8e0 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 20:18:36 +0000 Subject: [PATCH 11/12] Bugfix: api: Show config page as readonly if admin password isn't set --- src/datum_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datum_api.c b/src/datum_api.c index 1c9477c..96845ac 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -939,7 +939,7 @@ size_t datum_api_fill_config_var(const char *var_start, const size_t var_name_le const char * const underscore_pos = memchr(var_start_2, '_', var_name_len_2); int val; if (var_name_len_2 == 3 && 0 == strncmp(var_start_2, "*ro", 3)) { - val = !datum_config.api_modify_conf; + val = !(datum_config.api_modify_conf && datum_config.api_admin_password_len); if (!colon_pos) { var_start = "readonly:"; colon_pos = &var_start[8]; From fa0e2d0bbe283c06e58cba45f92dc46429732166 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 11 Mar 2025 20:19:21 +0000 Subject: [PATCH 12/12] api: Adjust config-change denial messages to explain new admin_password setting --- src/datum_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datum_api.c b/src/datum_api.c index 96845ac..bed71f5 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -1039,7 +1039,7 @@ size_t datum_api_fill_config_var(const char *var_start, const size_t var_name_le return attr_len; } else if (0 == strncmp(var_start, "msg:", 4)) { if (val) { - static const char * const msg = "
Config file disallows editing"; + static const char * const msg = "
Config file disallows editing (set \"admin_password\" and \"modify_conf\" in \"api\" section of config file)"; const size_t len = strlen(msg); memcpy(replacement, msg, len); return len;