From af4d1dc6c36b6869136f1720a791a73e9e908321 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Feb 2025 18:33:25 +0100 Subject: [PATCH 1/4] Expose bound-to addresses from CivetWeb to the frontend Signed-off-by: DL6ER --- src/webserver/civetweb/civetweb.c | 1 + src/webserver/civetweb/civetweb.h | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c index fa908e54f..66fcab01b 100644 --- a/src/webserver/civetweb/civetweb.c +++ b/src/webserver/civetweb/civetweb.c @@ -3339,6 +3339,7 @@ mg_get_server_ports(const struct mg_context *ctx, ports[cnt].is_ssl = ctx->listening_sockets[i].is_ssl; ports[cnt].is_redirect = ctx->listening_sockets[i].ssl_redir; ports[cnt].is_optional = ctx->listening_sockets[i].is_optional; + memcpy(&ports[cnt].addr, &ctx->listening_sockets[i].lsa, sizeof(ports[cnt].addr)); if (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET) { /* IPv4 */ diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h index eee958b44..6dcfa4573 100644 --- a/src/webserver/civetweb/civetweb.h +++ b/src/webserver/civetweb/civetweb.h @@ -23,6 +23,8 @@ #ifndef CIVETWEB_HEADER_INCLUDED #define CIVETWEB_HEADER_INCLUDED +#include /* Pi-hole extension */ + #define CIVETWEB_VERSION "1.17" #define CIVETWEB_VERSION_MAJOR (1) #define CIVETWEB_VERSION_MINOR (17) @@ -721,6 +723,10 @@ struct mg_server_port { int _reserved2; int _reserved3; int _reserved4; + union { + struct sockaddr_in sa4; /* Pi-hole extension */ + struct sockaddr_in6 sa6; /* Pi-hole extension */ + } addr; }; /* Legacy name */ From 2dc36c088705924c7b8990cb823da5c9b3ecbd24 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Feb 2025 18:34:42 +0100 Subject: [PATCH 2/4] Add new CivetWeb patch Signed-off-by: DL6ER --- patch/civetweb.sh | 3 ++ ...addresses-from-CivetWeb-to-the-front.patch | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 patch/civetweb/0001-Expose-bound-to-addresses-from-CivetWeb-to-the-front.patch diff --git a/patch/civetweb.sh b/patch/civetweb.sh index 3fcc732a0..8896d0bb7 100644 --- a/patch/civetweb.sh +++ b/patch/civetweb.sh @@ -20,4 +20,7 @@ patch -p1 < patch/civetweb/0001-Register-CSRF-token-in-conn-request_info.patch echo "Applying patch 0001-Log-debug-messages-to-webserver.log-when-debug.webse.patch" patch -p1 < patch/civetweb/0001-Log-debug-messages-to-webserver.log-when-debug.webse.patch +echo "Applying patch 0001-Expose-bound-to-addresses-from-CivetWeb-to-the-front.patch" +patch -p1 < patch/civetweb/0001-Expose-bound-to-addresses-from-CivetWeb-to-the-front.patch + echo "ALL PATCHES APPLIED OKAY" diff --git a/patch/civetweb/0001-Expose-bound-to-addresses-from-CivetWeb-to-the-front.patch b/patch/civetweb/0001-Expose-bound-to-addresses-from-CivetWeb-to-the-front.patch new file mode 100644 index 000000000..d5b10dd89 --- /dev/null +++ b/patch/civetweb/0001-Expose-bound-to-addresses-from-CivetWeb-to-the-front.patch @@ -0,0 +1,50 @@ +From 0bee9e9f7942c5b73a715eaeadf5ab2d09a8c74d Mon Sep 17 00:00:00 2001 +From: DL6ER +Date: Sat, 22 Feb 2025 18:33:25 +0100 +Subject: [PATCH] Expose bound-to addresses from CivetWeb to the frontend + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/civetweb.c | 1 + + src/webserver/civetweb/civetweb.h | 6 ++++++ + 2 files changed, 7 insertions(+) + +diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c +index fa908e54..66fcab01 100644 +--- a/src/webserver/civetweb/civetweb.c ++++ b/src/webserver/civetweb/civetweb.c +@@ -3339,6 +3339,7 @@ mg_get_server_ports(const struct mg_context *ctx, + ports[cnt].is_ssl = ctx->listening_sockets[i].is_ssl; + ports[cnt].is_redirect = ctx->listening_sockets[i].ssl_redir; + ports[cnt].is_optional = ctx->listening_sockets[i].is_optional; ++ memcpy(&ports[cnt].addr, &ctx->listening_sockets[i].lsa, sizeof(ports[cnt].addr)); + + if (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET) { + /* IPv4 */ +diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h +index eee958b4..6dcfa457 100644 +--- a/src/webserver/civetweb/civetweb.h ++++ b/src/webserver/civetweb/civetweb.h +@@ -23,6 +23,8 @@ + #ifndef CIVETWEB_HEADER_INCLUDED + #define CIVETWEB_HEADER_INCLUDED + ++#include /* Pi-hole extension */ ++ + #define CIVETWEB_VERSION "1.17" + #define CIVETWEB_VERSION_MAJOR (1) + #define CIVETWEB_VERSION_MINOR (17) +@@ -721,6 +723,10 @@ struct mg_server_port { + int _reserved2; + int _reserved3; + int _reserved4; ++ union { ++ struct sockaddr_in sa4; /* Pi-hole extension */ ++ struct sockaddr_in6 sa6; /* Pi-hole extension */ ++ } addr; + }; + + /* Legacy name */ +-- +2.43.0 + From 7dab630485d0aef9babef2112668794b1dcfce12 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Feb 2025 18:36:47 +0100 Subject: [PATCH 3/4] Use really bound-to IP addresses in CHAOS TXT local.api.ftl Signed-off-by: DL6ER --- src/webserver/webserver.c | 62 ++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index 18c5b78e1..21b419d3f 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -207,8 +207,9 @@ static struct serverports bool is_secure :1; bool is_redirect :1; bool is_optional :1; - unsigned char protocol; // 1 = IPv4, 2 = IPv4+IPv6, 3 = IPv6 - in_port_t port; + char addr[INET6_ADDRSTRLEN + 2]; // +2 for square brackets around IPv6 address + int port; + int protocol; // 1 = IPv4, 3 = IPv6 } server_ports[MAXPORTS] = { 0 }; static in_port_t https_port = 0; /** @@ -252,8 +253,23 @@ static void get_server_ports(void) server_ports[i].is_secure = mgports[i].is_ssl; server_ports[i].is_redirect = mgports[i].is_redirect; server_ports[i].is_optional = mgports[i].is_optional; + // 1 = IPv4, 3 = IPv6 (can also be a combo-socker serving both), + // the documentation in civetweb.h is wrong server_ports[i].protocol = mgports[i].protocol; + // Convert listening address to string + if(server_ports[i].protocol == 1) + inet_ntop(AF_INET, &mgports[i].addr.sa4.sin_addr, server_ports[i].addr, INET_ADDRSTRLEN); + else if(server_ports[i].protocol == 3) + { + char tmp[INET6_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET6, &mgports[i].addr.sa6.sin6_addr, tmp, INET6_ADDRSTRLEN); + // Enclose IPv6 address in square brackets + snprintf(server_ports[i].addr, sizeof(server_ports[i].addr), "[%s]", tmp); + } + else + log_warn("Unsupported protocol for port %d", mgports[i].port); + // Store (first) HTTPS port if not already set if(mgports[i].is_ssl && https_port == 0) https_port = mgports[i].port; @@ -261,11 +277,13 @@ static void get_server_ports(void) // Print port information if(i == 0) log_info("Web server ports:"); - log_info(" - %d (HTTP%s, IPv%s%s%s)", - mgports[i].port, mgports[i].is_ssl ? "S" : "", - mgports[i].protocol == 1 ? "4" : (mgports[i].protocol == 3 ? "6" : "4+6"), - mgports[i].is_redirect ? ", redirecting" : "", - mgports[i].is_optional ? ", optional" : ""); + log_info(" - %s:%d (HTTP%s, IPv%s%s%s)", + server_ports[i].addr, + server_ports[i].port, + server_ports[i].is_secure ? "S" : "", + server_ports[i].protocol == 1 ? "4" : "6", + server_ports[i].is_redirect ? ", redirecting" : "", + server_ports[i].is_optional ? ", optional" : ""); } } @@ -275,21 +293,13 @@ in_port_t __attribute__((pure)) get_https_port(void) return https_port; } +#define MAX_URL_LEN 255 unsigned short get_api_string(char **buf, const bool domain) { // Initialize buffer to empty string size_t len = 0; // First byte has the length of the first string **buf = 0; - const char *domain_str = domain ? config.webserver.domain.v.s : "pi.hole"; - size_t api_str_size = strlen(domain_str) + 20; - - // Check if the string is too long for the TXT record - if(api_str_size > 255) - { - log_err("API URL too long for TXT record!"); - return 0; - } // TXT record format: // @@ -311,20 +321,30 @@ unsigned short get_api_string(char **buf, const bool domain) continue; // Reallocate additional memory for every port - if((*buf = realloc(*buf, (i+1)*api_str_size)) == NULL) + const size_t bufsz = (i + 1) * MAX_URL_LEN; + if((*buf = realloc(*buf, bufsz)) == NULL) { log_err("Failed to reallocate API URL buffer!"); return 0; } - const size_t bufsz = (i+1)*api_str_size; + + // Use appropriate domain + const char *addr = domain ? config.webserver.domain.v.s : server_ports[i].addr; + + // If we bound to the wildcard address, substitute it with + // 127.0.0.1 + if(strcmp(addr, "0.0.0.0") == 0) + addr = "127.0.0.1"; + else if(strcasecmp(addr, "[::]") == 0) + addr = "[::1]"; // Append API URL to buffer // We add this at buffer + 1 because the first byte is the // length of the string, which we don't know yet - char *api_str = calloc(api_str_size, sizeof(char)); - const ssize_t this_len = snprintf(api_str, bufsz - len - 1, "http%s://%s:%d/api/", + char *api_str = calloc(MAX_URL_LEN, sizeof(char)); + const ssize_t this_len = snprintf(api_str, MAX_URL_LEN, "http%s://%s:%d/api/", server_ports[i].is_secure ? "s" : "", - domain_str, server_ports[i].port); + addr, server_ports[i].port); // Check if snprintf() failed if(this_len < 0) { From 62676f338523ae593bb3ea7a215c707b9f7417f7 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Feb 2025 18:50:40 +0100 Subject: [PATCH 4/4] Adjust tests Signed-off-by: DL6ER --- test/test_suite.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index 1891abde8..32ce60476 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -1179,7 +1179,7 @@ @test "API addresses reported correctly by CHAOS TXT local.api.ftl" { run bash -c 'dig CHAOS TXT local.api.ftl +short @127.0.0.1' printf "dig (full): %s\n" "${lines[@]}" - [[ ${lines[0]} == '"http://pi.hole:80/api/" "https://pi.hole:443/api/"' ]] + [[ ${lines[0]} == '"http://127.0.0.1:80/api/" "https://127.0.0.1:443/api/" "http://[::1]:80/api/" "https://[::1]:443/api/"' ]] } @test "API addresses reported by CHAOS TXT api.ftl identical to domain.api.ftl" {