diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 11c8e7ae..c7dfb44b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -180,6 +180,7 @@ add_executable(pihole-FTL $ $ $ + $ $ $ $ @@ -258,6 +259,7 @@ install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW add_subdirectory(api) add_subdirectory(webserver) add_subdirectory(civetweb) +add_subdirectory(compression) add_subdirectory(cJSON) add_subdirectory(miniz) add_subdirectory(ph7) diff --git a/src/api/api.c b/src/api/api.c index a9b7a1bf..32c96afc 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -31,58 +31,59 @@ static struct { bool require_auth; enum http_method methods; } api_request[] = { - // URI ARGUMENTS FUNCTION OPTIONS AUTH ALLOWED METHODS + // URI ARGUMENTS FUNCTION OPTIONS AUTH ALLOWED METHODS + // domains json fifo // Note: The order of appearance matters here, more specific URIs have to // appear *before* less specific URIs: 1. "/a/b/c", 2. "/a/b", 3. "/a" - { "/api/dns/blocking", "", api_dns_blocking, { false, 0 }, true, HTTP_GET | HTTP_POST }, - { "/api/dns/cache", "", api_dns_cache, { false, 0 }, true, HTTP_GET }, - { "/api/clients", "/{client}", api_list, { false, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, - { "/api/domains", "/{type}/{kind}/{domain}", api_list, { false, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, - { "/api/groups", "/{name}", api_list, { false, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, - { "/api/lists", "/{list}", api_list, { false, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, - { "/api/info/client", "", api_info_client, { false, 0 }, false, HTTP_GET }, - { "/api/info/system", "", api_info_system, { false, 0 }, true, HTTP_GET }, - { "/api/info/database", "", api_info_database, { false, 0 }, true, HTTP_GET }, - { "/api/info/sensors", "", api_info_sensors, { false, 0 }, true, HTTP_GET }, - { "/api/info/host", "", api_info_host, { false, 0 }, true, HTTP_GET }, - { "/api/info/ftl", "", api_info_ftl, { false, 0 }, true, HTTP_GET }, - { "/api/info/version", "", api_info_version, { false, 0 }, true, HTTP_GET }, - { "/api/logs/dnsmasq", "", api_logs, { false, FIFO_DNSMASQ }, true, HTTP_GET }, - { "/api/logs/ftl", "", api_logs, { false, FIFO_FTL }, true, HTTP_GET }, - { "/api/logs/http", "", api_logs, { false, FIFO_CIVETWEB }, true, HTTP_GET }, - { "/api/logs/ph7", "", api_logs, { false, FIFO_PH7 }, true, HTTP_GET }, - { "/api/history/clients", "", api_history_clients, { false, 0 }, true, HTTP_GET }, - { "/api/history/database/clients", "", api_history_database_clients, { false, 0 }, true, HTTP_GET }, - { "/api/history/database", "", api_history_database, { false, 0 }, true, HTTP_GET }, - { "/api/history", "", api_history, { false, 0 }, true, HTTP_GET }, - { "/api/queries/suggestions", "", api_queries_suggestions, { false, 0 }, true, HTTP_GET }, - { "/api/queries", "", api_queries, { false, 0 }, true, HTTP_GET }, - { "/api/stats/summary", "", api_stats_summary, { false, 0 }, true, HTTP_GET }, - { "/api/stats/query_types", "", api_stats_query_types, { false, 0 }, true, HTTP_GET }, - { "/api/stats/upstreams", "", api_stats_upstreams, { false, 0 }, true, HTTP_GET }, - { "/api/stats/top_domains", "", api_stats_top_domains, { false, 0 }, true, HTTP_GET }, - { "/api/stats/top_clients", "", api_stats_top_clients, { false, 0 }, true, HTTP_GET }, - { "/api/stats/recent_blocked", "", api_stats_recentblocked, { false, 0 }, true, HTTP_GET }, - { "/api/stats/database/top_domains", "", api_stats_database_top_items, { true, 0 }, true, HTTP_GET }, - { "/api/stats/database/top_clients", "", api_stats_database_top_items, { false, 0 }, true, HTTP_GET }, - { "/api/stats/database/summary", "", api_stats_database_summary, { false, 0 }, true, HTTP_GET }, - { "/api/stats/database/query_types", "", api_stats_database_query_types, { false, 0 }, true, HTTP_GET }, - { "/api/stats/database/upstreams", "", api_stats_database_upstreams, { false, 0 }, true, HTTP_GET }, - { "/api/auth", "", api_auth, { false, 0 }, false, HTTP_GET | HTTP_POST | HTTP_DELETE }, - { "/api/config/_topics", "", api_config_topics, { false, 0 }, true, HTTP_GET }, - { "/api/config/_server", "", api_config_server, { false, 0 }, true, HTTP_GET }, - { "/api/config", "", api_config, { false, 0 }, true, HTTP_GET | HTTP_PATCH }, - { "/api/config", "/{element}", api_config, { false, 0 }, true, HTTP_GET | HTTP_PATCH }, - { "/api/config", "/{element}/{value}", api_config, { false, 0 }, true, HTTP_DELETE | HTTP_PUT }, - { "/api/network/gateway", "", api_network_gateway, { false, 0 }, true, HTTP_GET }, - { "/api/network/interfaces", "", api_network_interfaces, { false, 0 }, true, HTTP_GET }, - { "/api/network/devices", "", api_network_devices, { false, 0 }, true, HTTP_GET }, - { "/api/endpoints", "", api_endpoints, { false, 0 }, true, HTTP_GET }, - { "/api/teleporter", "", api_teleporter, { false, 0 }, true, HTTP_GET | HTTP_POST }, - { "/api/action/gravity", "", api_action_gravity, { false, 0 }, true, HTTP_POST }, - { "/api/action/reboot", "", api_action_reboot, { false, 0 }, true, HTTP_POST }, - { "/api/action/poweroff", "", api_action_poweroff, { false, 0 }, true, HTTP_POST }, - { "/api/docs", "", api_docs, { false, 0 }, false, HTTP_GET }, + { "/api/dns/blocking", "", api_dns_blocking, { false, true, 0 }, true, HTTP_GET | HTTP_POST }, + { "/api/dns/cache", "", api_dns_cache, { false, true, 0 }, true, HTTP_GET }, + { "/api/clients", "/{client}", api_list, { false, true, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, + { "/api/domains", "/{type}/{kind}/{domain}", api_list, { false, true, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, + { "/api/groups", "/{name}", api_list, { false, true, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, + { "/api/lists", "/{list}", api_list, { false, true, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, + { "/api/info/client", "", api_info_client, { false, true, 0 }, false, HTTP_GET }, + { "/api/info/system", "", api_info_system, { false, true, 0 }, true, HTTP_GET }, + { "/api/info/database", "", api_info_database, { false, true, 0 }, true, HTTP_GET }, + { "/api/info/sensors", "", api_info_sensors, { false, true, 0 }, true, HTTP_GET }, + { "/api/info/host", "", api_info_host, { false, true, 0 }, true, HTTP_GET }, + { "/api/info/ftl", "", api_info_ftl, { false, true, 0 }, true, HTTP_GET }, + { "/api/info/version", "", api_info_version, { false, true, 0 }, true, HTTP_GET }, + { "/api/logs/dnsmasq", "", api_logs, { false, true, FIFO_DNSMASQ }, true, HTTP_GET }, + { "/api/logs/ftl", "", api_logs, { false, true, FIFO_FTL }, true, HTTP_GET }, + { "/api/logs/http", "", api_logs, { false, true, FIFO_CIVETWEB }, true, HTTP_GET }, + { "/api/logs/ph7", "", api_logs, { false, true, FIFO_PH7 }, true, HTTP_GET }, + { "/api/history/clients", "", api_history_clients, { false, true, 0 }, true, HTTP_GET }, + { "/api/history/database/clients", "", api_history_database_clients, { false, true, 0 }, true, HTTP_GET }, + { "/api/history/database", "", api_history_database, { false, true, 0 }, true, HTTP_GET }, + { "/api/history", "", api_history, { false, true, 0 }, true, HTTP_GET }, + { "/api/queries/suggestions", "", api_queries_suggestions, { false, true, 0 }, true, HTTP_GET }, + { "/api/queries", "", api_queries, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/summary", "", api_stats_summary, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/query_types", "", api_stats_query_types, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/upstreams", "", api_stats_upstreams, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/top_domains", "", api_stats_top_domains, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/top_clients", "", api_stats_top_clients, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/recent_blocked", "", api_stats_recentblocked, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/database/top_domains", "", api_stats_database_top_items, { true, true, 0 }, true, HTTP_GET }, + { "/api/stats/database/top_clients", "", api_stats_database_top_items, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/database/summary", "", api_stats_database_summary, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/database/query_types", "", api_stats_database_query_types, { false, true, 0 }, true, HTTP_GET }, + { "/api/stats/database/upstreams", "", api_stats_database_upstreams, { false, true, 0 }, true, HTTP_GET }, + { "/api/auth", "", api_auth, { false, true, 0 }, false, HTTP_GET | HTTP_POST | HTTP_DELETE }, + { "/api/config/_topics", "", api_config_topics, { false, true, 0 }, true, HTTP_GET }, + { "/api/config/_server", "", api_config_server, { false, true, 0 }, true, HTTP_GET }, + { "/api/config", "", api_config, { false, true, 0 }, true, HTTP_GET | HTTP_PATCH }, + { "/api/config", "/{element}", api_config, { false, true, 0 }, true, HTTP_GET | HTTP_PATCH }, + { "/api/config", "/{element}/{value}", api_config, { false, true, 0 }, true, HTTP_DELETE | HTTP_PUT }, + { "/api/network/gateway", "", api_network_gateway, { false, true, 0 }, true, HTTP_GET }, + { "/api/network/interfaces", "", api_network_interfaces, { false, true, 0 }, true, HTTP_GET }, + { "/api/network/devices", "", api_network_devices, { false, true, 0 }, true, HTTP_GET }, + { "/api/endpoints", "", api_endpoints, { false, true, 0 }, true, HTTP_GET }, + { "/api/teleporter", "", api_teleporter, { false, false, 0 }, true, HTTP_GET | HTTP_POST }, + { "/api/action/gravity", "", api_action_gravity, { false, true, 0 }, true, HTTP_POST }, + { "/api/action/reboot", "", api_action_reboot, { false, true, 0 }, true, HTTP_POST }, + { "/api/action/poweroff", "", api_action_poweroff, { false, true, 0 }, true, HTTP_POST }, + { "/api/docs", "", api_docs, { false, true, 0 }, false, HTTP_GET }, }; int api_handler(struct mg_connection *conn, void *ignored) @@ -96,22 +97,9 @@ int api_handler(struct mg_connection *conn, void *ignored) NULL, { false, NULL, NULL, NULL, 0u }, { false }, - { false, 0 } + { false, false, 0 } }; - // Allocate memory for the payload - api.payload.raw = calloc(MAX_PAYLOAD_BYTES, sizeof(char)); - if(!api.payload.raw) - { - log_crit("Cannot handle API request %s %s: %s", - api.request->request_method, - api.request->local_uri_raw, - strerror(ENOMEM)); - } - - // Read and try to parse payload - read_and_parse_payload(&api); - log_debug(DEBUG_API, "Requested API URI: %s %s ? %s", api.request->request_method, api.request->local_uri_raw, @@ -122,7 +110,7 @@ int api_handler(struct mg_connection *conn, void *ignored) // Loop over all API endpoints and check if the requested URI matches bool unauthorized = false; enum http_method allowed_methods = 0; - for(unsigned int i = 0; i < sizeof(api_request)/sizeof(api_request[0]); i++) + for(unsigned int i = 0; i < ArraySize(api_request); i++) { // Check if the requested method is allowed if(!(api_request[i].methods & api.method) && api.method != HTTP_OPTIONS) @@ -143,6 +131,22 @@ int api_handler(struct mg_connection *conn, void *ignored) continue; } + if(api_request[i].opts.parse_json) + { + // Allocate memory for the payload + api.payload.raw = calloc(MAX_PAYLOAD_BYTES, sizeof(char)); + if(!api.payload.raw) + { + log_crit("Cannot handle API request %s %s: %s", + api.request->request_method, + api.request->local_uri_raw, + strerror(ENOMEM)); + } + + // Read and try to parse payload + read_and_parse_payload(&api); + } + // Verify requesting client is allowed to see this ressource if(api_request[i].require_auth && check_client_auth(&api) == API_AUTH_UNAUTHORIZED) { @@ -172,9 +176,12 @@ int api_handler(struct mg_connection *conn, void *ignored) api.payload.json = NULL; } - // Free raw payload bytes (always allocated) - free(api.payload.raw); - api.payload.raw = NULL; + // Free raw payload bytes + if(api.payload.raw != NULL) + { + free(api.payload.raw); + api.payload.raw = NULL; + } // Check if we need to return with unauthorized payload if(unauthorized) @@ -240,7 +247,7 @@ static int api_endpoints(struct ftl_conn *api) cJSON *delete = JSON_NEW_ARRAY(); // Add endpoints to JSON array - for(unsigned int i = 0; i < sizeof(api_request)/sizeof(api_request[0]); i++) + for(unsigned int i = 0; i < ArraySize(api_request); i++) { for(enum http_method method = HTTP_GET; method <= HTTP_DELETE; method <<= 1) { diff --git a/src/api/auth.c b/src/api/auth.c index f6721e0a..7e5fdb42 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -142,7 +142,8 @@ int check_client_auth(struct ftl_conn *api) { const char *sid_header = NULL; // Try to extract SID from header - if((sid_header = mg_get_header(api->conn, "sid")) != NULL) + if((sid_header = mg_get_header(api->conn, "sid")) != NULL || + (sid_header = mg_get_header(api->conn, "X-FTL-SID")) != NULL) { // Copy SID string strncpy(sid, sid_header, SID_SIZE - 1u); diff --git a/src/api/config.c b/src/api/config.c index f91c8af4..ba4bd852 100644 --- a/src/api/config.c +++ b/src/api/config.c @@ -525,33 +525,33 @@ static int api_config_patch(struct ftl_conn *api) bool config_changed = false; bool dnsmasq_changed = false; bool rewrite_custom_list = false; - struct config conf_copy; - duplicate_config(&conf_copy); + struct config newconf; + duplicate_config(&newconf, &config); for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { // Get pointer to memory location of this conf_item (copy) - struct conf_item *copy_item = get_conf_item(&conf_copy, i); + struct conf_item *new_item = get_conf_item(&newconf, i); // Get path depth - unsigned int level = config_path_depth(copy_item->p); + unsigned int level = config_path_depth(new_item->p); cJSON *elem = conf; // Parse tree of properties and get the individual JSON elements for(unsigned int j = 0; j < level; j++) - elem = cJSON_GetObjectItem(elem, copy_item->p[j]); + elem = cJSON_GetObjectItem(elem, new_item->p[j]); // Check if this element is present - it doesn't have to be! if(elem == NULL) { - log_debug(DEBUG_CONFIG, "%s not in JSON payload", copy_item->k); + log_debug(DEBUG_CONFIG, "%s not in JSON payload", new_item->k); continue; } // Try to set value and report error on failure - const char *response = getJSONvalue(copy_item, elem); + const char *response = getJSONvalue(new_item, elem); if(response != NULL) { - log_err("/api/config: %s invalid: %s", copy_item->k, response); + log_err("/api/config: %s invalid: %s", new_item->k, response); continue; } @@ -559,7 +559,7 @@ static int api_config_patch(struct ftl_conn *api) struct conf_item *conf_item = get_conf_item(&config, i); // Skip processing if value didn't change compared to current value - if(compare_config_item(copy_item, conf_item)) + if(compare_config_item(new_item, conf_item)) { log_debug(DEBUG_CONFIG, "Config item %s: Unchanged", conf_item->k); continue; @@ -574,7 +574,7 @@ static int api_config_patch(struct ftl_conn *api) dnsmasq_changed = true; // Check if this item requires a rewrite of the custom.list file - if(conf_item == &conf_copy.dns.hosts) + if(conf_item == &newconf.dns.hosts) rewrite_custom_list = true; } @@ -585,7 +585,7 @@ static int api_config_patch(struct ftl_conn *api) if(dnsmasq_changed) { char errbuf[ERRBUF_SIZE] = { 0 }; - if(write_dnsmasq_config(&conf_copy, true, errbuf)) + if(write_dnsmasq_config(&newconf, true, errbuf)) api->ftl.restart = true; else { @@ -605,15 +605,7 @@ static int api_config_patch(struct ftl_conn *api) } // Install new configuration - lock_shm(); - // Backup old config struct (so we can free it) - struct config old_conf; - memcpy(&old_conf, &config, sizeof(struct config)); - // Replace old config struct by changed one - memcpy(&config, &conf_copy, sizeof(struct config)); - // Free old backup struct - free_config(&old_conf); - unlock_shm(); + replace_config(&newconf); // Store changed configuration to disk writeFTLtoml(true); @@ -624,7 +616,7 @@ static int api_config_patch(struct ftl_conn *api) else { // Nothing changed, merely release copied config memory - free_config(&conf_copy); + free_config(&newconf); } // Return full config after possible changes above @@ -663,7 +655,7 @@ static int api_config_put_delete(struct ftl_conn *api) hint); } - char *new_item = requested_path[min_level - 1]; + char *new_item_str = requested_path[min_level - 1]; // Convert path to items, e.g., // dnsmasq/dhcp/active -> dhcp.active @@ -674,26 +666,26 @@ static int api_config_put_delete(struct ftl_conn *api) bool dnsmasq_changed = false; bool rewrite_custom_list = false; bool found = false; - struct config conf_copy; - duplicate_config(&conf_copy); + struct config newconf; + duplicate_config(&newconf, &config); for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { // Get pointer to memory location of this conf_item - struct conf_item *conf_item = get_conf_item(&conf_copy, i); + struct conf_item *new_item = get_conf_item(&newconf, i); // We support PUT only for adding to string arrays - if(conf_item->t != CONF_JSON_STRING_ARRAY) + if(new_item->t != CONF_JSON_STRING_ARRAY) continue; // Get path depth - const unsigned int level = config_path_depth(conf_item->p); + const unsigned int level = config_path_depth(new_item->p); // Check equality of paths up to the requested level (if any) // Examples: // requested was /config/dnsmasq -> skip all entries that do not start in dnsmasq. // requested was /config/dnsmasq/dhcp -> skip all entries that do not start in dhcp // etc. - if(!check_paths_equal(conf_item->p, requested_path, max(min_level - 2, level - 1))) + if(!check_paths_equal(new_item->p, requested_path, max(min_level - 2, level - 1))) continue; // Check if this is a property where we want to add an item @@ -702,11 +694,11 @@ static int api_config_put_delete(struct ftl_conn *api) // Check if this entry does already exist in the array int idx = 0; - for(; idx < cJSON_GetArraySize(conf_item->v.json); idx++) + for(; idx < cJSON_GetArraySize(new_item->v.json); idx++) { - cJSON *elem = cJSON_GetArrayItem(conf_item->v.json, idx); + cJSON *elem = cJSON_GetArrayItem(new_item->v.json, idx); if(elem != NULL && elem->valuestring != NULL && - strcmp(elem->valuestring, new_item) == 0) + strcmp(elem->valuestring, new_item_str) == 0) { found = true; break; @@ -725,7 +717,7 @@ static int api_config_put_delete(struct ftl_conn *api) else { // Add new item to array - JSON_COPY_STR_TO_ARRAY(conf_item->v.json, new_item); + JSON_COPY_STR_TO_ARRAY(new_item->v.json, new_item); found = true; } } @@ -734,7 +726,7 @@ static int api_config_put_delete(struct ftl_conn *api) if(found) { // Remove item from array - cJSON_DeleteItemFromArray(conf_item->v.json, idx); + cJSON_DeleteItemFromArray(new_item->v.json, idx); } else { @@ -747,11 +739,11 @@ static int api_config_put_delete(struct ftl_conn *api) // If we reach this point, a valid setting was found and changed // Check if this item requires a config-rewrite + restart of dnsmasq - if(conf_item->f & FLAG_RESTART_DNSMASQ) + if(new_item->f & FLAG_RESTART_DNSMASQ) dnsmasq_changed = true; // Check if this item requires a rewrite of the custom.list file - if(conf_item == &conf_copy.dns.hosts) + if(new_item == &newconf.dns.hosts) rewrite_custom_list = true; break; } @@ -779,7 +771,7 @@ static int api_config_put_delete(struct ftl_conn *api) { char errbuf[ERRBUF_SIZE] = { 0 }; // Request restart of FTL - if(write_dnsmasq_config(&conf_copy, true, errbuf)) + if(write_dnsmasq_config(&newconf, true, errbuf)) api->ftl.restart = true; else { @@ -800,15 +792,7 @@ static int api_config_put_delete(struct ftl_conn *api) } // Install new configuration - lock_shm(); - // Backup old config struct (so we can free it) - struct config old_conf; - memcpy(&old_conf, &config, sizeof(struct config)); - // Replace old config struct by changed one - memcpy(&config, &conf_copy, sizeof(struct config)); - // Free old backup struct - free_config(&old_conf); - unlock_shm(); + replace_config(&newconf); // Store changed configuration to disk writeFTLtoml(true); @@ -840,7 +824,7 @@ int api_config(struct ftl_conn *api) int api_config_topics(struct ftl_conn *api) { cJSON *topics = JSON_NEW_ARRAY(); - for(unsigned int i = 0; i < sizeof(config_topics)/sizeof(*config_topics); i++) + for(unsigned int i = 0; i < ArraySize(config_topics); i++) { cJSON *topic = JSON_NEW_OBJECT(); JSON_REF_STR_IN_OBJECT(topic, "name", config_topics[i].name); @@ -857,7 +841,7 @@ int api_config_topics(struct ftl_conn *api) int api_config_server(struct ftl_conn *api) { cJSON *servers = JSON_NEW_ARRAY(); - for(unsigned int i = 0; i < sizeof(dns_server)/sizeof(*dns_server); i++) + for(unsigned int i = 0; i < ArraySize(dns_server); i++) { cJSON *server = JSON_NEW_OBJECT(); JSON_REF_STR_IN_OBJECT(server, "name", dns_server[i].name); diff --git a/src/api/docs/content/specs/teleporter.yaml b/src/api/docs/content/specs/teleporter.yaml index a60489b6..936bbe97 100644 --- a/src/api/docs/content/specs/teleporter.yaml +++ b/src/api/docs/content/specs/teleporter.yaml @@ -8,7 +8,8 @@ components: - "Pi-hole configuration" operationId: "get_teleporter" description: | - Request an archived copy of your Pi-hole's current configuration + Request an archived copy of your Pi-hole's current configuration. + Authentication via header or cookie is required for the endpoint. responses: '200': description: OK @@ -22,4 +23,79 @@ components: content: application/json: schema: - $ref: 'common.yaml#/components/errors/unauthorized' \ No newline at end of file + $ref: 'common.yaml#/components/errors/unauthorized' + post: + summary: Import Pi-hole settings + tags: + - "Pi-hole configuration" + operationId: "post_teleporter" + description: | + Upload a Pi-hole Teleporter archive to (partially) restore from it. Note that this will overwrite your current configuration. + Authentication via header or cookie is required for the endpoint. + requestBody: + content: + multipart/form-data: + schema: + properties: + file: + type: string + format: binary + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'teleporter.yaml#/components/schemas/teleporter/post' + examples: + teleporter: + $ref: 'teleporter.yaml#/components/examples/teleporter' + '400': + description: Bad request + content: + application/json: + schema: + $ref: 'teleporter.yaml#/components/errors/invalid_zip' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: 'common.yaml#/components/errors/unauthorized' + schemas: + teleporter: + post: + type: object + properties: + processed: + type: array + items: + type: string + errors: + invalid_zip: + type: object + description: Invalid ZIP archive uploaded + properties: + error: + type: object + properties: + key: + type: string + description: "Machine-readable error type" + example: "invalid_zip" + message: + type: string + description: "Human-readable error message" + example: "Invalid ZIP file uploaded" + hint: + type: string + nullable: true + description: "No additional data available" + example: null + + examples: + teleporter: + value: + teleporter: + - a + - b diff --git a/src/api/stats.c b/src/api/stats.c index 066c97bb..dbfd890d 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -397,7 +397,7 @@ int api_stats_upstreams(struct ftl_conn *api) temparray[upstreamID][0] = upstreamID; unsigned int count = 0; - for(unsigned i = 0; i < (sizeof(upstream->overTime)/sizeof(*upstream->overTime)); i++) + for(unsigned i = 0; i < ArraySize(upstream->overTime); i++) count += upstream->overTime[i]; temparray[upstreamID][1] = count; totalcount += count; diff --git a/src/api/teleporter.c b/src/api/teleporter.c index 0d3ec266..cc177c67 100644 --- a/src/api/teleporter.c +++ b/src/api/teleporter.c @@ -11,8 +11,12 @@ #include "FTL.h" #include "webserver/http-common.h" #include "webserver/json_macros.h" -#include "miniz/teleporter.h" +#include "compression/teleporter.h" #include "api/api.h" +// ERRBUF_SIZE +#include "config/dnsmasq_config.h" + +#define MAXZIPSIZE (50u*1024*1024) static int api_teleporter_GET(struct ftl_conn *api) { @@ -50,10 +54,216 @@ static int api_teleporter_GET(struct ftl_conn *api) return 200; } +// Struct to store the data we want to process +struct upload_data { + bool too_large; + char *sid; + char *zip_data; + char *zip_filename; + size_t zip_size; +}; + +// Callback function for CivetWeb to determine which fields we want to receive +static bool is_file = false; +static bool is_sid = false; +static int field_found(const char *key, + const char *filename, + char *path, + size_t pathlen, + void *user_data) +{ + struct upload_data *data = (struct upload_data *)user_data; + log_debug(DEBUG_API, "Found field: \"%s\", filename: \"%s\"", key, filename); + + is_file = false; + is_sid = false; + if(strcasecmp(key, "file") == 0 && filename && *filename) + { + data->zip_filename = strdup(filename); + is_file = true; + return MG_FORM_FIELD_STORAGE_GET; + } + else if(strcasecmp(key, "import") == 0) + { + is_sid = true; + return MG_FORM_FIELD_STORAGE_GET; + } + + // Ignore any other fields + return MG_FORM_FIELD_STORAGE_SKIP; +} + +// Callback function for CivetWeb to receive the data of the fields we want to process. +// This function might be called several times for the same field (large (> 8KB) +// or chunked data), so we may need to append new data to existing data. +static int field_get(const char *key, const char *value, size_t valuelen, void *user_data) +{ + struct upload_data *data = (struct upload_data *)user_data; + log_debug(DEBUG_API, "Received field: \"%s\" (length %zu bytes)", key, valuelen); + + if(is_file) + { + if(data->zip_size + valuelen > MAXZIPSIZE) + { + log_warn("Uploaded Teleporter ZIP archive is too large (limit is %u bytes)", + MAXZIPSIZE); + data->too_large = true; + return MG_FORM_FIELD_HANDLE_ABORT; + } + // Allocate memory for the raw ZIP archive data + data->zip_data = realloc(data->zip_data, data->zip_size + valuelen); + // Copy the raw ZIP archive data + memcpy(data->zip_data + data->zip_size, value, valuelen); + // Store the size of the ZIP archive raw data + data->zip_size += valuelen; + log_debug(DEBUG_API, "Received ZIP archive (%zu bytes, buffer is now %zu bytes)", + valuelen, data->zip_size); + } + else if(is_sid) + { + // Allocate memory for the SID + data->sid = calloc(valuelen + 1, sizeof(char)); + // Copy the SID string + memcpy(data->sid, value, valuelen); + // Add terminating NULL byte (memcpy does not do this) + data->sid[valuelen] = '\0'; + } + + // If there is more data in this field, get the next chunk. + // Otherwise: handle the next field. + return MG_FORM_FIELD_HANDLE_GET; +} + +// We don't use this function, but it is required by the CivetWeb API +static int field_stored(const char *path, long long file_size, void *user_data) +{ + return 0; +} + +static int free_upload_data(struct upload_data *data) +{ + // Free allocated memory + if(data->zip_filename) + { + free(data->zip_filename); + data->zip_filename = NULL; + } + if(data->sid) + { + free(data->sid); + data->sid = NULL; + } + if(data->zip_data) + { + free(data->zip_data); + data->zip_data = NULL; + } + return 0; +} + +static int api_teleporter_POST(struct ftl_conn *api) +{ + struct upload_data data; + memset(&data, 0, sizeof(struct upload_data)); + const struct mg_request_info *req_info = mg_get_request_info(api->conn); + struct mg_form_data_handler fdh = {field_found, field_get, field_stored, &data}; + + // Disallow large ZIP archives (> 50 MB) to prevent DoS attacks. + // Typically, the ZIP archive size should be around 30-100 kB. + if(req_info->content_length > MAXZIPSIZE) + { + free_upload_data(&data); + return send_json_error(api, 400, + "bad_request", + "ZIP archive too large", + NULL); + } + + // Call the form handler to process the POST request content + const int ret = mg_handle_form_request(api->conn, &fdh); + if(ret < 0) + { + free_upload_data(&data); + return send_json_error(api, 400, + "bad_request", + "Invalid form request", + NULL); + } + + // Check if we received something we consider being a file + if(data.zip_data == NULL || data.zip_size == 0) + { + free_upload_data(&data); + return send_json_error(api, 400, + "bad_request", + "No ZIP archive received", + NULL); + } + + // Check if the file we received is too large + if(data.too_large) + { + free_upload_data(&data); + return send_json_error(api, 400, + "bad_request", + "ZIP archive too large", + NULL); + } +/* + // Set the payload to the SID we received (if available) + if(data.sid != NULL) + { + const size_t bufsize = strlen(data.sid) + 5; + api->payload.raw = calloc(bufsize, sizeof(char)); + strncpy(api->payload.raw, "sid=", 5); + strncat(api->payload.raw, data.sid, bufsize - 4); + } + + // Check if the client is authorized to use this API endpoint + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) + { + free_upload_data(&data); + return send_json_unauthorized(api); + } +*/ + // Process what we received + char hint[ERRBUF_SIZE]; + memset(hint, 0, sizeof(hint)); + cJSON *json_files = JSON_NEW_ARRAY(); + const char *error = read_teleporter_zip(data.zip_data, data.zip_size, hint, json_files); + if(error != NULL) + { + char msg[strlen(error) + strlen(hint) + 4]; + memset(msg, 0, sizeof(msg)); + strncpy(msg, error, sizeof(msg)); + if(strlen(hint) > 0) + { + // Concatenate error message and hint into a single string + strcat(msg, ": "); + strcat(msg, hint); + } + free_upload_data(&data); + return send_json_error(api, 400, + "bad_request", + "Invalid ZIP archive", + msg); + } + + // Free allocated memory + free_upload_data(&data); + + // Send response + cJSON *json = JSON_NEW_OBJECT(); + JSON_ADD_ITEM_TO_OBJECT(json, "files", json_files); + JSON_SEND_OBJECT(json); +} + int api_teleporter(struct ftl_conn *api) { if(api->method == HTTP_GET) return api_teleporter_GET(api); + if(api->method == HTTP_POST) + return api_teleporter_POST(api); return 0; -} \ No newline at end of file +} diff --git a/src/args.c b/src/args.c index 30aa83cf..d582c8ec 100644 --- a/src/args.c +++ b/src/args.c @@ -43,9 +43,9 @@ #include "config/cli.h" #include "config/config.h" // compression functions -#include "miniz/compression.h" +#include "compression/gzip.h" // teleporter functions -#include "miniz/teleporter.h" +#include "compression/teleporter.h" // defined in dnsmasq.c extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal); @@ -234,7 +234,8 @@ void parse_args(int argc, char* argv[]) { // Enable stdout printing cli_mode = true; - readFTLconf(false); + log_ctrl(false, true); + readFTLconf(&config, false); if(argc == 3) exit(get_config_from_CLI(argv[2]) ? EXIT_SUCCESS : EXIT_FAILURE); else if(argc == 4) @@ -252,7 +253,8 @@ void parse_args(int argc, char* argv[]) { // Enable stdout printing cli_mode = true; - readFTLconf(false); + log_ctrl(false, true); + readFTLconf(&config, false); exit(write_teleporter_zip_to_disk() ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/capabilities.c b/src/capabilities.c index 8b571a89..00f6e897 100644 --- a/src/capabilities.c +++ b/src/capabilities.c @@ -19,7 +19,6 @@ static const unsigned int capabilityIDs[] = { CAP_CHOWN , CAP_DAC_OVERRIDE , CAP_DAC_READ_SEARCH , CAP_FOWNER , CAP_FSETID , CAP_KILL , CAP_SETGID , CAP_SETUID , CAP_SETPCAP , CAP_LINUX_IMMUTABLE , CAP_NET_BIND_SERVICE , CAP_NET_BROADCAST , CAP_NET_ADMIN , CAP_NET_RAW , CAP_IPC_LOCK , CAP_IPC_OWNER , CAP_SYS_MODULE , CAP_SYS_RAWIO , CAP_SYS_CHROOT , CAP_SYS_PTRACE , CAP_SYS_PACCT , CAP_SYS_ADMIN , CAP_SYS_BOOT , CAP_SYS_NICE , CAP_SYS_RESOURCE , CAP_SYS_TIME , CAP_SYS_TTY_CONFIG , CAP_MKNOD , CAP_LEASE , CAP_AUDIT_WRITE , CAP_AUDIT_CONTROL , CAP_SETFCAP }; static const char* capabilityNames[] = {"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE", "CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT", "CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE", "CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE", "CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP"}; -static const unsigned int numCaps = sizeof(capabilityIDs) / sizeof(*capabilityIDs); bool check_capabilities(void) { @@ -54,7 +53,7 @@ bool check_capabilities(void) log_debug(DEBUG_CAPS, "***************************************"); log_debug(DEBUG_CAPS, "* Linux capability debugging enabled *"); - for(unsigned int i = 0u; i < numCaps; i++) + for(unsigned int i = 0u; i < ArraySize(capabilityIDs); i++) { const unsigned int capid = capabilityIDs[i]; log_debug(DEBUG_CAPS, "* %-24s (%02u) = %s%s%s *", diff --git a/src/civetweb/handle_form.inl b/src/civetweb/handle_form.inl index eaad88d7..84109476 100644 --- a/src/civetweb/handle_form.inl +++ b/src/civetweb/handle_form.inl @@ -216,7 +216,7 @@ mg_handle_form_request(struct mg_connection *conn, if (0 != strcmp(conn->request_info.request_method, "GET")) { /* No body data, but not a GET request. * This is not a valid form request. */ - return -1; + return -2; } /* GET request: form data is in the query string. */ @@ -226,7 +226,7 @@ mg_handle_form_request(struct mg_connection *conn, data = conn->request_info.query_string; if (!data) { /* No query string. */ - return -1; + return -3; } /* Split data in a=1&b=xy&c=3&c=4 ... */ @@ -400,7 +400,7 @@ mg_handle_form_request(struct mg_connection *conn, r = mg_read(conn, buf + (size_t)buf_fill, to_read); if ((r < 0) || ((r == 0) && all_data_read)) { /* read error */ - return -1; + return -4; } if (r == 0) { /* TODO: Create a function to get "all_data_read" from @@ -538,7 +538,7 @@ mg_handle_form_request(struct mg_connection *conn, mg_fclose(&fstore.access); remove_bad_file(conn, path); } - return -1; + return -5; #endif /* NO_FILESYSTEMS */ } if (r == 0) { @@ -620,7 +620,7 @@ mg_handle_form_request(struct mg_connection *conn, /* There has to be a BOUNDARY definition in the Content-Type header */ if (mg_strncasecmp(content_type + bl, "BOUNDARY=", 9)) { /* Malformed request */ - return -1; + return -6; } /* Copy boundary string to variable "boundary" */ @@ -633,7 +633,7 @@ mg_handle_form_request(struct mg_connection *conn, "%s: Cannot allocate memory for boundary [%lu]", __func__, (unsigned long)bl); - return -1; + return -7; } memcpy(boundary, fbeg, bl); boundary[bl] = 0; @@ -645,7 +645,7 @@ mg_handle_form_request(struct mg_connection *conn, if ((!hbuf) || (*hbuf != '"')) { /* Malformed request */ mg_free(boundary); - return -1; + return -8; } *hbuf = 0; memmove(boundary, boundary + 1, bl); @@ -666,13 +666,13 @@ mg_handle_form_request(struct mg_connection *conn, * Requests with long boundaries are not RFC compliant, maybe they * are intended attacks to interfere with this algorithm. */ mg_free(boundary); - return -1; + return -9; } if (bl < 4) { /* Sanity check: A boundary string of less than 4 bytes makes * no sense either. */ mg_free(boundary); - return -1; + return -10; } for (part_no = 0;; part_no++) { @@ -687,7 +687,7 @@ mg_handle_form_request(struct mg_connection *conn, if ((r < 0) || ((r == 0) && all_data_read)) { /* read error */ mg_free(boundary); - return -1; + return -11; } if (r == 0) { all_data_read = (buf_fill == 0); @@ -698,7 +698,7 @@ mg_handle_form_request(struct mg_connection *conn, if (buf_fill < 1) { /* No data */ mg_free(boundary); - return -1; + return -12; } if (part_no == 0) { @@ -716,12 +716,12 @@ mg_handle_form_request(struct mg_connection *conn, if (buf[0] != '-' || buf[1] != '-') { /* Malformed request */ mg_free(boundary); - return -1; + return -13; } if (0 != strncmp(buf + 2, boundary, bl)) { /* Malformed request */ mg_free(boundary); - return -1; + return -14; } if (buf[bl + 2] != '\r' || buf[bl + 3] != '\n') { /* Every part must end with \r\n, if there is another part. @@ -730,7 +730,7 @@ mg_handle_form_request(struct mg_connection *conn, || (strncmp(buf + bl + 2, "--\r\n", 4))) { /* Malformed request */ mg_free(boundary); - return -1; + return -15; } /* End of the request */ break; @@ -742,7 +742,7 @@ mg_handle_form_request(struct mg_connection *conn, if (!hend) { /* Malformed request */ mg_free(boundary); - return -1; + return -16; } part_header.num_headers = @@ -750,7 +750,7 @@ mg_handle_form_request(struct mg_connection *conn, if ((hend + 2) != hbuf) { /* Malformed request */ mg_free(boundary); - return -1; + return -17; } /* Skip \r\n\r\n */ @@ -764,7 +764,7 @@ mg_handle_form_request(struct mg_connection *conn, if (!content_disp) { /* Malformed request */ mg_free(boundary); - return -1; + return -18; } /* Get the mandatory name="..." part of the Content-Disposition @@ -788,7 +788,7 @@ mg_handle_form_request(struct mg_connection *conn, if (!nend) { /* Malformed request */ mg_free(boundary); - return -1; + return -19; } } else { /* name= without quotes is also allowed */ @@ -800,7 +800,7 @@ mg_handle_form_request(struct mg_connection *conn, if (!nbeg) { /* Malformed request */ mg_free(boundary); - return -1; + return -20; } nbeg += 5; @@ -835,7 +835,7 @@ mg_handle_form_request(struct mg_connection *conn, /* Malformed request (the filename field is optional, but if * it exists, it needs to be terminated correctly). */ mg_free(boundary); - return -1; + return -21; } /* TODO: check Content-Type */ @@ -868,7 +868,7 @@ mg_handle_form_request(struct mg_connection *conn, if (!(((ptrdiff_t)fbeg > (ptrdiff_t)nend) || ((ptrdiff_t)nbeg > (ptrdiff_t)fend))) { mg_free(boundary); - return -1; + return -22; } /* Call callback for new field */ @@ -917,7 +917,7 @@ mg_handle_form_request(struct mg_connection *conn, /* Not enough data stored. */ /* Incomplete request. */ mg_free(boundary); - return -1; + return -23; } /* Subtract the boundary length, to deal with @@ -980,7 +980,7 @@ mg_handle_form_request(struct mg_connection *conn, } #endif /* NO_FILESYSTEMS */ mg_free(boundary); - return -1; + return -24; } /* r==0 already handled, all_data_read is false here */ @@ -1076,7 +1076,7 @@ mg_handle_form_request(struct mg_connection *conn, } /* Unknown Content-Type */ - return -1; + return -25; } /* End of handle_form.inl */ diff --git a/src/compression/CMakeLists.txt b/src/compression/CMakeLists.txt new file mode 100644 index 00000000..5a7f208a --- /dev/null +++ b/src/compression/CMakeLists.txt @@ -0,0 +1,21 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2023 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/compression/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +set(sources + gzip.c + gzip.h + teleporter.c + teleporter.h + ) + +add_library(compression OBJECT ${sources}) +target_compile_options(compression PRIVATE) +target_compile_options(compression PRIVATE ${EXTRAWARN}) +target_include_directories(compression PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/miniz/compression.c b/src/compression/gzip.c similarity index 84% rename from src/miniz/compression.c rename to src/compression/gzip.c index 9ded839a..aa8d3f94 100644 --- a/src/miniz/compression.c +++ b/src/compression/gzip.c @@ -3,7 +3,7 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* Compression routines +* GZIP compression routines * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ @@ -14,8 +14,8 @@ #include // le32toh and friends #include -#include "miniz.h" -#include "compression.h" +#include "miniz/miniz.h" +#include "gzip.h" #include "log.h" static int mz_uncompress2_raw(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len); @@ -152,7 +152,10 @@ static bool inflate_buffer(unsigned char *buffer_compressed, mz_ulong size_compr // +---+---+---+---+---+---+---+---+ // // XLEN: This contains the length of the extra field, in bytes. - const uint16_t xlen = le16toh(*(uint16_t*)(buffer_compressed + 10)); + uint16_t xlen = 0u; + for(unsigned int i = 0; i < 2; i++) + xlen |= buffer_compressed[10 + i] << (i * 8); + xlen = le16toh(xlen); if(size_compressed < 12u + xlen) { log_warn("Invalid GZIP header"); @@ -161,6 +164,7 @@ static bool inflate_buffer(unsigned char *buffer_compressed, mz_ulong size_compr // Move compressed data to the left memmove(buffer_compressed + 10, buffer_compressed + 12 + xlen, size_compressed - (12 + xlen)); + size_compressed -= 2 + xlen; } // Skip file name (if present) @@ -196,6 +200,7 @@ static bool inflate_buffer(unsigned char *buffer_compressed, mz_ulong size_compr // Move compressed blocks to the beginning of the in buffer memmove(buffer_compressed + 10, buffer_compressed + i, size_compressed - i); + size_compressed -= i - 10; } // Skip file comment (if present) @@ -231,11 +236,15 @@ static bool inflate_buffer(unsigned char *buffer_compressed, mz_ulong size_compr // Move compressed blocks to the beginning of the in memmove(buffer_compressed + 10, buffer_compressed + i, size_compressed - i); + size_compressed -= i - 10; } // Get the size of the uncompressed file from the GZIP footer (last 4 // bytes of the file) - *size_uncompressed = le32toh(*(uint32_t*)(buffer_compressed + size_compressed - 4)); + *size_uncompressed = 0u; + for(unsigned int i = 0; i < 4; i++) + *size_uncompressed |= buffer_compressed[size_compressed - 4 + i] << (i * 8); + *size_uncompressed = le32toh(*size_uncompressed); if(*size_uncompressed == 0 || *size_uncompressed > 0x10000000) { log_warn("File is empty or too large"); @@ -248,7 +257,10 @@ static bool inflate_buffer(unsigned char *buffer_compressed, mz_ulong size_compr // Extract checksum (stored in the first 4 of the last 8 bytes of the // file) - uint32_t crc = le32toh(*(uint32_t*)(buffer_compressed + size_compressed - 8)); + uint32_t crc = 0u; + for(unsigned int i = 0; i < 4; i++) + crc |= buffer_compressed[size_compressed - 8 + i] << (i * 8); + crc = le32toh(crc); // ZLIB trailer/footer is an Adler-32 checksum of the uncompressed data. // We have to strip the uncompressed size from the GZIP footer. @@ -281,36 +293,44 @@ static bool inflate_buffer(unsigned char *buffer_compressed, mz_ulong size_compr return true; } -bool inflate_file(const char *infile, const char *outfile, bool verbose) +bool inflate_file(const char *infilename, const char *outfilename, bool verbose) { // Read entire file into memory - FILE *fp = fopen(infile, "rb"); - if(fp == NULL) + FILE *infile = fopen(infilename, "rb"); + if(infile == NULL) { - log_warn("Failed to open %s: %s", infile, strerror(errno)); + log_warn("Failed to open %s: %s", infilename, strerror(errno)); + return false; + } + + // Create compressed file + FILE *outfile = fopen(outfilename, "wb"); + if(outfile == NULL) + { + log_warn("Failed to open %s: %s", outfilename, strerror(errno)); return false; } // Get file size - fseek(fp, 0, SEEK_END); - const mz_ulong size_compressed = ftell(fp); - fseek(fp, 0, SEEK_SET); + fseek(infile, 0, SEEK_END); + const mz_ulong size_compressed = ftell(infile); + fseek(infile, 0, SEEK_SET); // Read file into memory unsigned char *buffer_compressed = malloc(size_compressed); if(buffer_compressed == NULL) { log_warn("Failed to allocate %lu bytes of memory", (unsigned long)size_compressed); - fclose(fp); + fclose(infile); return false; } - if(fread(buffer_compressed, 1, size_compressed, fp) != size_compressed) + if(fread(buffer_compressed, 1, size_compressed, infile) != size_compressed) { - log_warn("Failed to read %lu bytes from %s", (unsigned long)size_compressed, infile); - fclose(fp); + log_warn("Failed to read %lu bytes from %s", (unsigned long)size_compressed, infilename); + fclose(infile); return false; } - fclose(fp); + fclose(infile); unsigned char *buffer_uncompressed = NULL; mz_ulong size_uncompressed = 0; @@ -322,26 +342,20 @@ bool inflate_file(const char *infile, const char *outfile, bool verbose) // Check if uncompression was successful if(!success) { - log_warn("Failed to uncompress %s", infile); + log_warn("Failed to uncompress %s", infilename); free(buffer_uncompressed); - fclose(fp); + fclose(outfile); return false; } - // Create compressed file - fp = fopen(outfile, "wb"); - if(fp == NULL) + // Write uncompressed file to disk + if(fwrite(buffer_uncompressed, sizeof(char), size_uncompressed, outfile) != size_uncompressed) { - log_warn("Failed to open %s: %s", outfile, strerror(errno)); + log_warn("Failed to write %lu bytes to %s", (unsigned long)size_uncompressed, outfilename); + fclose(outfile); return false; } - if(fwrite(buffer_uncompressed, sizeof(char), size_uncompressed, fp) != size_uncompressed) - { - log_warn("Failed to write %lu bytes to %s", (unsigned long)size_uncompressed, outfile); - fclose(fp); - return false; - } - fclose(fp); + fclose(outfile); free(buffer_uncompressed); @@ -353,44 +367,52 @@ bool inflate_file(const char *infile, const char *outfile, bool verbose) format_memory_size(raw_prefix, size_compressed, &raw_size); format_memory_size(comp_prefix, size_uncompressed, &comp_size); log_info("Uncompressed %s (%.1f%sB) to %s (%.1f%sB), %.1f%% size increase", - infile, raw_size, raw_prefix, outfile, comp_size, comp_prefix, + infilename, raw_size, raw_prefix, outfilename, comp_size, comp_prefix, 100.0*size_uncompressed / size_compressed - 100.0); } return true; } -bool deflate_file(const char *infile, const char *outfile, bool verbose) +bool deflate_file(const char *infilename, const char *outfilename, bool verbose) { // Read entire file into memory - FILE *fp = fopen(infile, "rb"); - if(fp == NULL) + FILE *infile = fopen(infilename, "rb"); + if(infile == NULL) { - log_warn("Failed to open %s: %s", infile, strerror(errno)); + log_warn("Failed to open %s for reading: %s", infilename, strerror(errno)); + return false; + } + + // Create compressed file + FILE* outfile = fopen(outfilename, "wb"); + if(outfile == NULL) + { + log_warn("Failed to open %s for writing: %s", outfilename, strerror(errno)); return false; } // Get file size - fseek(fp, 0, SEEK_END); - const mz_ulong size_uncompressed = ftell(fp); - fseek(fp, 0, SEEK_SET); + fseek(infile, 0, SEEK_END); + const mz_ulong size_uncompressed = ftell(infile); + fseek(infile, 0, SEEK_SET); // Read file into memory unsigned char *buffer_uncompressed = malloc(size_uncompressed); if(buffer_uncompressed == NULL) { log_warn("Failed to allocate %lu bytes of memory", (unsigned long)size_uncompressed); - fclose(fp); + fclose(infile); return false; } - if(fread(buffer_uncompressed, 1, size_uncompressed, fp) != size_uncompressed) + if(fread(buffer_uncompressed, 1, size_uncompressed, infile) != size_uncompressed) { - log_warn("Failed to read %lu bytes from %s", (unsigned long)size_uncompressed, infile); - fclose(fp); + log_warn("Failed to read %lu bytes from %s", (unsigned long)size_uncompressed, infilename); + fclose(infile); free(buffer_uncompressed); return false; } - fclose(fp); + fclose(infile); unsigned char *buffer_compressed = NULL; mz_ulong size_compressed = 0; @@ -403,32 +425,24 @@ bool deflate_file(const char *infile, const char *outfile, bool verbose) // Check if compression was successful if(!success) { - log_warn("Failed to compress %s", infile); + log_warn("Failed to compress %s", infilename); free(buffer_uncompressed); if(buffer_compressed) free(buffer_compressed); - fclose(fp); + fclose(outfile); return false; } - // Create compressed file - fp = fopen(outfile, "wb"); - if(fp == NULL) + // Write compressed data to file + if(fwrite(buffer_compressed, sizeof(char), size_compressed, outfile) != size_compressed) { - log_warn("Failed to open %s: %s", outfile, strerror(errno)); + log_warn("Failed to write %lu bytes to %s", (unsigned long)size_compressed, outfilename); + fclose(outfile); free(buffer_uncompressed); free(buffer_compressed); return false; } - if(fwrite(buffer_compressed, sizeof(char), size_compressed, fp) != size_compressed) - { - log_warn("Failed to write %lu bytes to %s", (unsigned long)size_compressed, outfile); - fclose(fp); - free(buffer_uncompressed); - free(buffer_compressed); - return false; - } - fclose(fp); + fclose(outfile); free(buffer_compressed); @@ -444,7 +458,8 @@ bool deflate_file(const char *infile, const char *outfile, bool verbose) format_memory_size(raw_prefix, size_uncompressed, &raw_size); format_memory_size(comp_prefix, csize, &comp_size); log_info("Compressed %s (%.1f%sB) to %s (%.1f%sB), %.1f%% size reduction", - infile, raw_size, raw_prefix, outfile, comp_size, comp_prefix, + infilename, raw_size, raw_prefix, + outfilename, comp_size, comp_prefix, 100.0 - 100.0*csize / size_uncompressed); } diff --git a/src/miniz/compression.h b/src/compression/gzip.h similarity index 83% rename from src/miniz/compression.h rename to src/compression/gzip.h index 458aa77c..7839602c 100644 --- a/src/miniz/compression.h +++ b/src/compression/gzip.h @@ -3,16 +3,16 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* Compression routines +* GZIP compression routines * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#ifndef COMPRESSION_H -#define COMPRESSION_H +#ifndef GZIP_H +#define GZIP_H #include bool deflate_file(const char *in, const char *out, bool verbose); bool inflate_file(const char *infile, const char *outfile, bool verbose); -#endif // COMPRESSION_H +#endif // GZIP_H diff --git a/src/compression/teleporter.c b/src/compression/teleporter.c new file mode 100644 index 00000000..1a0f393c --- /dev/null +++ b/src/compression/teleporter.c @@ -0,0 +1,678 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2023 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Teleporter un-/compression routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "compression/teleporter.h" +#include "config/config.h" +// hostname() +#include "daemon.h" +// get_timestr(), TIMESTR_SIZE +#include "log.h" +// directory_exists() +#include "files.h" +// DIR, dirent, opendir(), readdir(), closedir() +#include +// sqlite3 +#include "database/sqlite3.h" +// toml_parse() +#include "config/tomlc99/toml.h" +// readFTLtoml() +#include "config/toml_reader.h" +// writeFTLtoml() +#include "config/toml_writer.h" +// write_dnsmasq_config() +#include "config/dnsmasq_config.h" +// lock_shm(), unlock_shm() +#include "shmem.h" +// rotate_file() +#include "files.h" +// cJSON +#include "cJSON/cJSON.h" +// set_event() +#include "events.h" + + +// Tables to copy from the gravity database to the Teleporter database +static const char *gravity_tables[] = { + "info", + "group", + "adlist", + "adlist_by_group", + "domainlist", + "domainlist_by_group", + "client", + "client_by_group", + "domain_audit" +}; + +// Tables to copy from the FTL database to the Teleporter database +static const char *ftl_tables[] = { + "message", + "aliasclient", + "network", + "network_addresses" +}; + +// List of files to process from a Teleporter ZIP archive +static const char *extract_files[] = { + "etc/pihole/pihole.toml", + "etc/pihole/dhcp.leases", + "etc/pihole/gravity.db" +}; + +// Create database in memory, copy selected tables to it, serialize and return a memory pointer to it +static bool create_teleporter_database(const char *filename, const char **tables, const unsigned int num_tables, + void **buffer, size_t *size) +{ + // Open in-memory sqlite3 database + sqlite3 *db; + if(sqlite3_open_v2(":memory:", &db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK) + { + log_warn("Failed to open in-memory database: %s", sqlite3_errmsg(db)); + return false; + } + // Attach the FTL database to the in-memory database + char *err = NULL; + char attach_stmt[128] = ""; + + snprintf(attach_stmt, sizeof(attach_stmt), "ATTACH DATABASE '%s' AS \"disk\";", filename); + + if(sqlite3_exec(db, attach_stmt, NULL, NULL, &err) != SQLITE_OK) + { + log_warn("Failed to attach database \"%s\" to in-memory database: %s", filename, err); + sqlite3_free(err); + sqlite3_close(db); + return false; + } + + // Loop over the tables and copy them to the in-memory database + for(unsigned int i = 0; i < num_tables; i++) + { + char create_stmt[128] = ""; + + // Create in-memory table copy + snprintf(create_stmt, sizeof(create_stmt), "CREATE TABLE \"%s\" AS SELECT * FROM disk.\"%s\";", tables[i], tables[i]); + if(sqlite3_exec(db, create_stmt, NULL, NULL, &err) != SQLITE_OK) + { + log_warn("Failed to create %s in in-memory database: %s", tables[i], err); + sqlite3_free(err); + sqlite3_close(db); + return false; + } + } + + // Detach the FTL database from the in-memory database + if(sqlite3_exec(db, "DETACH DATABASE 'disk';", NULL, NULL, &err) != SQLITE_OK) + { + log_warn("Failed to detach FTL database from in-memory database: %s", err); + sqlite3_free(err); + sqlite3_close(db); + return false; + } + + // Serialize the in-memory database to a buffer + // The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory that + // is a serialization of the S database on database connection D. If P is + // not a NULL pointer, then the size of the database in bytes is written + // into *P. + // For an ordinary on-disk database file, the serialization is just a copy + // of the disk file. For an in-memory database or a "TEMP" database, the + // serialization is the same sequence of bytes which would be written to + // disk if that database where backed up to disk. + // The usual case is that sqlite3_serialize() copies the serialization of + // the database into memory obtained from sqlite3_malloc64() and returns a + // pointer to that memory. The caller is responsible for freeing the + // returned value to avoid a memory leak. + sqlite3_int64 isize = 0; + *buffer = sqlite3_serialize(db, "main", &isize, 0); + *size = isize; + if(*buffer == NULL) + { + log_warn("Failed to serialize in-memory database to buffer: %s", sqlite3_errmsg(db)); + sqlite3_close(db); + return false; + } + + // Close the in-memory database + sqlite3_close(db); + + return true; +} + +const char *generate_teleporter_zip(mz_zip_archive *zip, char filename[128], void *ptr, size_t *size) +{ + // Initialize ZIP archive + memset(zip, 0, sizeof(*zip)); + + // Start with 64KB allocation size (pihole.TOML is slightly larger than 32KB + // at the time of writing thjs) + if(!mz_zip_writer_init_heap(zip, 0, 64*1024)) + { + return "Failed creating heap ZIP archive"; + } + + // Add pihole.toml to the ZIP archive + const char *file_comment = "Pi-hole's configuration"; + const char *file_path = GLOBALTOMLPATH; + if(!mz_zip_writer_add_file(zip, file_path+1, file_path, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION)) + { + mz_zip_writer_end(zip); + return "Failed to add "GLOBALTOMLPATH" to heap ZIP archive!"; + } + + // Add /etc/hosts to the ZIP archive + file_comment = "System's HOSTS file"; + file_path = "/etc/hosts"; + if(!mz_zip_writer_add_file(zip, file_path+1, file_path, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION)) + { + mz_zip_writer_end(zip); + return "Failed to add /etc/hosts to heap ZIP archive!"; + } + + // Add /etc/pihole/dhcp.lease to the ZIP archive if it exists + file_comment = "DHCP leases file"; + file_path = "/etc/pihole/dhcp.leases"; + if(file_exists(file_path) && !mz_zip_writer_add_file(zip, file_path+1, file_path, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION)) + { + mz_zip_writer_end(zip); + return "Failed to add /etc/hosts to heap ZIP archive!"; + } + + const char *directory = "/etc/dnsmasq.d"; + if(directory_exists(directory)) + { + // Loop over all files and add them to the ZIP archive + DIR *dir; + struct dirent *ent; + if((dir = opendir(directory)) != NULL) + { + // Loop over all files in the directory + while((ent = readdir(dir)) != NULL) + { + // Skip "." and ".." + if(strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + + // Construct full path to file + char fullpath[128] = ""; + snprintf(fullpath, 128, "%s/%s", directory, ent->d_name); + + // Add file to ZIP archive + file_comment = "dnsmasq configuration file"; + file_path = fullpath; + + if(!mz_zip_writer_add_file(zip, file_path+1, file_path, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION)) + continue; + } + closedir(dir); + } + } + + // Add (a reduced version of) the gravity database to the ZIP archive + void *dbbuf = NULL; + size_t dbsize = 0u; + if(create_teleporter_database(config.files.gravity.v.s, gravity_tables, ArraySize(gravity_tables), &dbbuf, &dbsize)) + { + // Add gravity database to ZIP archive + file_comment = "Pi-hole's gravity database"; + file_path = config.files.gravity.v.s; + if(file_path[0] == '/') + file_path++; + if(!mz_zip_writer_add_mem_ex(zip, file_path, dbbuf, dbsize, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION, 0, 0)) + { + sqlite3_free(dbbuf); + mz_zip_writer_end(zip); + return "Failed to add gravity database to heap ZIP archive!"; + } + sqlite3_free(dbbuf); + } + else + { + mz_zip_writer_end(zip); + return "Failed to create gravity database for heap ZIP archive!"; + } + + if(create_teleporter_database(config.files.database.v.s, ftl_tables, ArraySize(ftl_tables), &dbbuf, &dbsize)) + { + // Add FTL database to ZIP archive + file_comment = "Pi-hole's FTL database"; + file_path = config.files.database.v.s; + if(file_path[0] == '/') + file_path++; + if(!mz_zip_writer_add_mem_ex(zip, file_path, dbbuf, dbsize, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION, 0, 0)) + { + sqlite3_free(dbbuf); + mz_zip_writer_end(zip); + return "Failed to add FTL database to heap ZIP archive!"; + } + sqlite3_free(dbbuf); + } + else + { + mz_zip_writer_end(zip); + return "Failed to create FTL database for heap ZIP archive!"; + } + + // Get the heap data so we can send it to the requesting client + if(!mz_zip_writer_finalize_heap_archive(zip, ptr, size)) + { + mz_zip_writer_end(zip); + return "Failed to finalize heap ZIP archive!"; + } + + // Generate filename for ZIP archive (it has both the hostname and the + // current datetime) + char timestr[TIMESTR_SIZE] = ""; + get_timestr(timestr, time(NULL), false, true); + snprintf(filename, 128, "pi-hole_%s_teleporter_%s.zip", hostname(), timestr); + + // Everything worked well + return NULL; +} + +static const char *test_and_import_pihole_toml(void *ptr, size_t size, char * const hint) +{ + // Check if the file is empty + if(size == 0) + return "File etc/pihole/pihole.toml in ZIP archive is empty"; + + // Create a memory copy that is null-terminated + char *buffer = calloc(size+1, sizeof(char)); + if(buffer == NULL) + return "Failed to allocate memory for null-terminated copy of etc/pihole/pihole.toml in ZIP archive"; + memcpy(buffer, ptr, size); + buffer[size] = '\0'; + + // Check if the file is a valid TOML file + toml_table_t *toml = toml_parse(buffer, hint, ERRBUF_SIZE); + if(toml == NULL) + { + free(buffer); + return "File etc/pihole/pihole.toml in ZIP archive is not a valid TOML file"; + } + free(buffer); + + // Check if the file contains a valid configuration for Pi-hole by parsing it into + // a temporary config struct (teleporter_config) + struct config teleporter_config = { 0 }; + duplicate_config(&teleporter_config, &config); + if(!readFTLtoml(&teleporter_config, toml, true)) + return "File etc/pihole/pihole.toml in ZIP archive contains invalid TOML configuration"; + + // Test dnsmasq config in the imported configuration + // The dnsmasq configuration will be overwritten if the test succeeds + if(!write_dnsmasq_config(&teleporter_config, true, hint)) + return "File etc/pihole/pihole.toml in ZIP archive contains invalid dnsmasq configuration"; + + // When we reach this point, we know that the file is a valid TOML file and contains + // a valid configuration for Pi-hole. We can now safely overwrite the current + // configuration with the one from the ZIP archive + + // Install new configuration + replace_config(&teleporter_config); + + // Write new pihole.toml to disk, the dnsmaq config was already written above + // Also write the custom list to disk + rotate_files(GLOBALTOMLPATH); + writeFTLtoml(true); + write_custom_list(); + + return NULL; +} + +static const char *import_dhcp_leases(void *ptr, size_t size, char * const hint) +{ + // We do not check if the file is empty here, as an empty dhcp.leases file is valid + + // When we reach this point, we know that the file is a valid dhcp.leases file. + // We can now safely overwrite the current dhcp.leases file with the one from the ZIP archive + // Nevertheless, we rotate the current dhcp.leases file to keep a backup of the previous version + + // Rotate current dhcp.leases file + rotate_files(DHCPLEASESFILE); + + // Write new dhcp.leases file to disk + FILE *fp = fopen(DHCPLEASESFILE, "w"); + if(fp == NULL) + { + strncpy(hint, strerror(errno), ERRBUF_SIZE); + return "Failed to open dhcp.leases file for writing"; + } + if(fwrite(ptr, 1, size, fp) != size) + { + strncpy(hint, strerror(errno), ERRBUF_SIZE); + fclose(fp); + return "Failed to write to dhcp.leases file"; + } + fclose(fp); + + return NULL; +} + +static const char *test_and_import_database(void *ptr, size_t size, const char *destination, + const char **tables, const unsigned int num_tables, + char * const hint) +{ + // Check if the file is empty + // The first 100 bytes of the database file comprise the database file header. + // See https://www.sqlite.org/fileformat.html, section 1.3 + if(size < 100) + { + return "File etc/pihole/gravity.db in ZIP archive is empty"; + } + + // Check file header to see if this is a SQLite3 database file + // Every valid SQLite database file begins with the following 16 bytes (in + // hex): 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00. This byte sequence + // corresponds to the UTF-8 string "SQLite format 3" including the nul + // terminator character at the end. The nul terminator character is not + // included in the 16 bytes of the header. + // See https://www.sqlite.org/fileformat.html, section 1.3.1 + if(memcmp(ptr, "SQLite format 3", 15) != 0) + { + return "File etc/pihole/gravity.db in ZIP archive is not a SQLite3 database file (no header)"; + } + + // Check if the file is a valid SQlite3 database + // We do this by trying to deserialize the file into a SQLite3 database + // object. If this fails, the file is not a valid SQlite3 database. + sqlite3 *database = NULL; + if(sqlite3_open_v2(":memory:", &database, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK) + { + strncpy(hint, sqlite3_errmsg(database), ERRBUF_SIZE); + return "Failed to open temporary SQLite3 database"; + } + if(sqlite3_deserialize(database, "main", ptr, size, size, SQLITE_DESERIALIZE_READONLY) != SQLITE_OK) + { + strncpy(hint, sqlite3_errmsg(database), ERRBUF_SIZE); + return "File etc/pihole/gravity.db in ZIP archive is not a valid SQLite3 database file"; + } + + // Run PRAGMA integrity_check on the database to check if the database is + // valid. If the database is valid, the result of the PRAGMA integrity_check + // is "ok". If the database is invalid, the result of the PRAGMA + // integrity_check is a string describing the error. + // See https://www.sqlite.org/pragma.html#pragma_integrity_check + sqlite3_stmt *statement = NULL; + if(sqlite3_prepare_v2(database, "PRAGMA integrity_check;", -1, &statement, NULL) != SQLITE_OK) + { + strncpy(hint, sqlite3_errmsg(database), ERRBUF_SIZE); + sqlite3_finalize(statement); + sqlite3_close(database); + return "Failed to prepare PRAGMA integrity_check statement"; + } + if(sqlite3_step(statement) != SQLITE_ROW) + { + strncpy(hint, sqlite3_errmsg(database), ERRBUF_SIZE); + sqlite3_finalize(statement); + sqlite3_close(database); + return "Failed to execute PRAGMA integrity_check statement"; + } + if(strcmp((const char *)sqlite3_column_text(statement, 0), "ok") != 0) + { + strncpy(hint, (const char *)sqlite3_column_text(statement, 0), ERRBUF_SIZE); + sqlite3_finalize(statement); + sqlite3_close(database); + return "Database file in ZIP archive is not a valid SQLite3 database (integrity check failed)"; + } + // Finalize statement + sqlite3_finalize(statement); + + // When we reach this point, we know that the file is a valid SQLite3 database + + // ATTACH the database file to the in-memory database + char *err = NULL; + char attach_stmt[128] = ""; + snprintf(attach_stmt, sizeof(attach_stmt), "ATTACH DATABASE '%s' AS disk;", destination); + if(sqlite3_exec(database, attach_stmt, NULL, NULL, &err) != SQLITE_OK) + { + sqlite3_free(err); + sqlite3_close(database); + return "Failed to attach database file to in-memory SQLite3 database"; + } + + // Disable foreign key checks for import + if(sqlite3_exec(database, "PRAGMA foreign_keys = 0;", NULL, NULL, &err) != SQLITE_OK) + { + sqlite3_free(err); + sqlite3_close(database); + return "Failed to disable foreign key checks for import"; + } + + // Start transaction + if(sqlite3_exec(database, "BEGIN TRANSACTION;", NULL, NULL, &err) != SQLITE_OK) + { + sqlite3_free(err); + sqlite3_close(database); + return "Failed to start transaction"; + } + + // Loop over the tables + for(unsigned int i = 0; i < num_tables; i++) + { + char stmt[256] = ""; + // Delete all rows in the disk table + snprintf(stmt, sizeof(stmt), "DELETE FROM disk.\"%s\";", tables[i]); + if(sqlite3_exec(database, stmt, NULL, NULL, &err) != SQLITE_OK) + { + strncpy(hint, err, ERRBUF_SIZE); + sqlite3_free(err); + sqlite3_close(database); + return "Failed to delete from disk database table"; + } + + // Store the table in the disk database + // We have to use INSERT OR REPLACE here, because the gravity database + // has several triggers, e.g., immediately recreating the default group + // on (accidental) deletion. This would cause the import to fail due to + // a unique constraint violation. + snprintf(stmt, sizeof(stmt), "INSERT OR REPLACE INTO disk.\"%s\" SELECT * FROM \"%s\";", tables[i], tables[i]); + if(sqlite3_exec(database, stmt, NULL, NULL, &err) != SQLITE_OK) + { + strncpy(hint, err, ERRBUF_SIZE); + sqlite3_free(err); + sqlite3_close(database); + return "Failed to insert into disk database table"; + } + + log_debug(DEBUG_DATABASE, "Replaced table %s in %s", tables[i], destination); + } + + // End transaction + if(sqlite3_exec(database, "END TRANSACTION;", NULL, NULL, &err) != SQLITE_OK) + { + sqlite3_free(err); + sqlite3_close(database); + return "Failed to end transaction"; + } + + // Detach the database file from the in-memory database + if(sqlite3_exec(database, "DETACH DATABASE disk;", NULL, NULL, &err) != SQLITE_OK) + { + log_warn("Failed to detach disk database: %s", err); + sqlite3_free(err); + sqlite3_close(database); + return "Failed to detach database file from in-memory SQLite3 database"; + } + + // Close the database + sqlite3_close(database); + + // Add event to reload gravity database + set_event(RELOAD_GRAVITY); + + return NULL; +} + +const char *read_teleporter_zip(char *buffer, const size_t buflen, char * const hint, cJSON *imported_files) +{ + // Initialize ZIP archive + mz_zip_archive zip = { 0 }; + memset(&zip, 0, sizeof(zip)); + + log_debug(DEBUG_CONFIG, "Reading ZIP archive from memory buffer (size %zu)", buflen); + + // Open ZIP archive from memory buffer + if(!mz_zip_reader_init_mem(&zip, buffer, buflen, 0)) + { + strncpy(hint, mz_zip_get_error_string(mz_zip_get_last_error(&zip)), ERRBUF_SIZE); + return "Failed to parse received ZIP archive"; + } + + // Loop over all files in the ZIP archive + for(mz_uint i = 0; i < mz_zip_reader_get_num_files(&zip); i++) + { + // Get file information + mz_zip_archive_file_stat file_stat; + if(!mz_zip_reader_file_stat(&zip, i, &file_stat)) + { + log_warn("Failed to get file information for file %d in ZIP archive: %s", + i, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + continue; + } + + // Check if this file is one of the files we want to extract and process + bool extract = false; + for(size_t j = 0; j < ArraySize(extract_files); j++) + { + if(strcmp(file_stat.m_filename, extract_files[j]) == 0) + { + extract = true; + break; + } + } + if(!extract) + continue; + + // Read file into its dedicated memory buffer + void *ptr = malloc(file_stat.m_uncomp_size); + if(ptr == NULL) + { + log_warn("Failed to allocate memory for file %d (%s) in ZIP archive: %s", + i, file_stat.m_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + continue; + } + if(!mz_zip_reader_extract_to_mem(&zip, i, ptr, file_stat.m_uncomp_size, 0)) + { + log_warn("Failed to read file %d (%s) in ZIP archive: %s", + i, file_stat.m_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + free(ptr); + continue; + } + + log_debug(DEBUG_CONFIG, "Processing file %d (%s) in ZIP archive (%zu/%zu bytes, comment: \"%s\", timestamp: %lu)", + i, file_stat.m_filename, file_stat.m_comp_size, file_stat.m_uncomp_size, + file_stat.m_comment, (unsigned long)file_stat.m_time); + + // Process file + // Is this "etc/pihole/pihole.toml" ? + if(strcmp(file_stat.m_filename, "etc/pihole/pihole.toml") == 0) + { + // Import Pi-hole configuration + memset(hint, 0, ERRBUF_SIZE); + const char *err = test_and_import_pihole_toml(ptr, file_stat.m_uncomp_size, hint); + if(err != NULL) + { + free(ptr); + return err; + } + log_debug(DEBUG_CONFIG, "Imported Pi-hole configuration: %s", file_stat.m_filename); + } + // Is this "etc/pihole/dhcp.leases"? + else if(strcmp(file_stat.m_filename, "etc/pihole/dhcp.leases") == 0) + { + // Import DHCP leases + memset(hint, 0, ERRBUF_SIZE); + const char *err = import_dhcp_leases(ptr, file_stat.m_uncomp_size, hint); + if(err != NULL) + { + free(ptr); + return err; + } + log_debug(DEBUG_CONFIG, "Imported DHCP leases: %s", file_stat.m_filename); + } + else if(strcmp(file_stat.m_filename, "etc/pihole/gravity.db") == 0) + { + // Import gravity database + memset(hint, 0, ERRBUF_SIZE); + const char *err = test_and_import_database(ptr, file_stat.m_uncomp_size, config.files.gravity.v.s, + gravity_tables, ArraySize(gravity_tables), hint); + if(err != NULL) + { + free(ptr); + return err; + } + log_debug(DEBUG_CONFIG, "Imported database: %s", file_stat.m_filename); + } + else + { + log_warn("Ignoring file %s in Teleporter archive", file_stat.m_filename); + free(ptr); + continue; + } + + // Add filename of processed files to JSON array + if(imported_files != NULL && !cJSON_AddItemToArray(imported_files, cJSON_CreateString(file_stat.m_filename))) + log_warn("Failed to add file %s to JSON array", file_stat.m_filename); + + // Free allocated memory + free(ptr); + } + + // Close ZIP archive + mz_zip_reader_end(&zip); + + // Everything worked well + return NULL; +} + +bool free_teleporter_zip(mz_zip_archive *zip) +{ + return mz_zip_writer_end(zip); +} + +bool write_teleporter_zip_to_disk(void) +{ + // Generate in-memory ZIP file + mz_zip_archive zip = { 0 }; + void *ptr = NULL; + size_t size = 0u; + char filename[128] = ""; + const char *error = generate_teleporter_zip(&zip, filename, &ptr, &size); + if(error != NULL) + { + log_err("Failed to create Teleporter ZIP file: %s", error); + return false; + } + + // Write file to disk + FILE *fp = fopen(filename, "w"); + if(fp == NULL) + { + log_err("Failed to open %s for writing: %s", filename, strerror(errno)); + free_teleporter_zip(&zip); + return false; + } + if(fwrite(ptr, 1, size, fp) != size) + { + log_err("Failed to write %zu bytes to %s: %s", size, filename, strerror(errno)); + free_teleporter_zip(&zip); + return false; + } + fclose(fp); + + // Free allocated ZIP memory + free_teleporter_zip(&zip); + + /* Output filename on successful creation */ + log_info("%s", filename); + + return true; +} diff --git a/src/miniz/teleporter.h b/src/compression/teleporter.h similarity index 79% rename from src/miniz/teleporter.h rename to src/compression/teleporter.h index e2c67baf..66b7c2a6 100644 --- a/src/miniz/teleporter.h +++ b/src/compression/teleporter.h @@ -11,8 +11,11 @@ #define TELEPORTER_H #include "miniz/miniz.h" +#include "cJSON/cJSON.h" + const char *generate_teleporter_zip(mz_zip_archive *zip, char filename[128], void *ptr, size_t *size); bool free_teleporter_zip(mz_zip_archive *zip); bool write_teleporter_zip_to_disk(void); +const char *read_teleporter_zip(char *buffer, const size_t buflen, char *hint, cJSON *json_files); -#endif // TELEPORTER_H \ No newline at end of file +#endif // TELEPORTER_H diff --git a/src/config/cli.c b/src/config/cli.c index bfc5be98..b3386f1d 100644 --- a/src/config/cli.c +++ b/src/config/cli.c @@ -258,13 +258,13 @@ static bool readStringValue(struct conf_item *conf_item, const char *value) if(!cJSON_IsString(item)) { log_err("Config setting %s is invalid: element with index %d is not a string", conf_item->k, i); - cJSON_free(elem); + cJSON_Delete(elem); return false; } } // If we reach this point, all elements are valid // Free previously allocated JSON array and replace with new - cJSON_free(conf_item->v.json); + cJSON_Delete(conf_item->v.json); conf_item->v.json = elem; break; } @@ -276,20 +276,20 @@ static bool readStringValue(struct conf_item *conf_item, const char *value) bool set_config_from_CLI(const char *key, const char *value) { // Identify config option - struct config conf_copy; - duplicate_config(&conf_copy); + struct config newconf; + duplicate_config(&newconf, &config); struct conf_item *conf_item = NULL; - struct conf_item *copy_item = NULL; + struct conf_item *new_item = NULL; for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { // Get pointer to (copied) memory location of this conf_item - struct conf_item *item = get_conf_item(&conf_copy, i); + struct conf_item *item = get_conf_item(&newconf, i); if(strcmp(item->k, key) != 0) continue; // This is the config option we are looking for - copy_item = item; + new_item = item; // Also get pointer to memory location of this conf_item conf_item = get_conf_item(&config, i); @@ -299,18 +299,18 @@ bool set_config_from_CLI(const char *key, const char *value) } // Check if we found the config option - if(copy_item == NULL) + if(new_item == NULL) { log_err("Unknown config option: %s", key); return false; } // Parse value - if(!readStringValue(copy_item, value)) + if(!readStringValue(new_item, value)) return false; // Check if value changed compared to current value - if(!compare_config_item(copy_item, conf_item)) + if(!compare_config_item(new_item, conf_item)) { // Config item changed @@ -318,7 +318,7 @@ bool set_config_from_CLI(const char *key, const char *value) if(conf_item->f & FLAG_RESTART_DNSMASQ) { char errbuf[ERRBUF_SIZE] = { 0 }; - if(!write_dnsmasq_config(&conf_copy, true, errbuf)) + if(!write_dnsmasq_config(&newconf, true, errbuf)) { // Test failed log_debug(DEBUG_CONFIG, "Config item %s: dnsmasq config test failed", conf_item->k); @@ -335,22 +335,16 @@ bool set_config_from_CLI(const char *key, const char *value) } // Install new configuration - // Backup old config struct (so we can free it) - struct config old_conf; - memcpy(&old_conf, &config, sizeof(struct config)); - // Replace old config struct by changed one - memcpy(&config, &conf_copy, sizeof(struct config)); - // Free old backup struct - free_config(&old_conf); + replace_config(&newconf); // Print value - writeTOMLvalue(stdout, -1, copy_item->t, ©_item->v); + writeTOMLvalue(stdout, -1, new_item->t, &new_item->v); } else { // No change log_debug(DEBUG_CONFIG, "Config item %s: Unchanged", conf_item->k); - free_config(&conf_copy); + free_config(&newconf); // Print value writeTOMLvalue(stdout, -1, conf_item->t, &conf_item->v); diff --git a/src/config/config.c b/src/config/config.c index 1e1b0356..b4e81400 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -21,8 +21,11 @@ #include "files.h" // write_dnsmasq_config() #include "config/dnsmasq_config.h" +// lock_shm(), unlock_shm() +#include "shmem.h" struct config config = { 0 }; +static bool config_initialized = false; void set_all_debug(const bool status) { @@ -170,17 +173,17 @@ unsigned int __attribute__ ((pure)) config_path_depth(char **paths) } -void duplicate_config(struct config *conf) +void duplicate_config(struct config *dst, struct config *src) { // Post-processing: // Initialize and verify config data for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { // Get pointer to memory location of this conf_item (original) - struct conf_item *conf_item = get_conf_item(&config, i); + struct conf_item *conf_item = get_conf_item(src, i); // Get pointer to memory location of this conf_item (copy) - struct conf_item *copy_item = get_conf_item(conf, i); + struct conf_item *copy_item = get_conf_item(dst, i); // Copy constant/static fields memcpy(copy_item, conf_item, sizeof(*conf_item)); @@ -306,54 +309,58 @@ void free_config(struct config *conf) } } -void initConfig(void) +void initConfig(struct config *conf) { + if(config_initialized) + return; + config_initialized = true; + // struct dns - config.dns.upstreams.k = "dns.upstreams"; - config.dns.upstreams.h = "Array of upstream DNS servers used by Pi-hole\n Example: [ \"8.8.8.8\", \"127.0.0.1#5353\", \"docker-resolver\" ]"; - config.dns.upstreams.a = cJSON_CreateStringReference("array of IP addresses and/or hostnames, optionally with a port (#...)"); - config.dns.upstreams.t = CONF_JSON_STRING_ARRAY; - config.dns.upstreams.d.json = cJSON_CreateArray(); - config.dns.upstreams.f = FLAG_RESTART_DNSMASQ; + conf->dns.upstreams.k = "dns.upstreams"; + conf->dns.upstreams.h = "Array of upstream DNS servers used by Pi-hole\n Example: [ \"8.8.8.8\", \"127.0.0.1#5353\", \"docker-resolver\" ]"; + conf->dns.upstreams.a = cJSON_CreateStringReference("array of IP addresses and/or hostnames, optionally with a port (#...)"); + conf->dns.upstreams.t = CONF_JSON_STRING_ARRAY; + conf->dns.upstreams.d.json = cJSON_CreateArray(); + conf->dns.upstreams.f = FLAG_RESTART_DNSMASQ; - config.dns.CNAMEdeepInspect.k = "dns.CNAMEdeepInspect"; - config.dns.CNAMEdeepInspect.h = "Use this option to control deep CNAME inspection. Disabling it might be beneficial for very low-end devices"; - config.dns.CNAMEdeepInspect.t = CONF_BOOL; - config.dns.CNAMEdeepInspect.f = FLAG_ADVANCED_SETTING; - config.dns.CNAMEdeepInspect.d.b = true; + conf->dns.CNAMEdeepInspect.k = "dns.CNAMEdeepInspect"; + conf->dns.CNAMEdeepInspect.h = "Use this option to control deep CNAME inspection. Disabling it might be beneficial for very low-end devices"; + conf->dns.CNAMEdeepInspect.t = CONF_BOOL; + conf->dns.CNAMEdeepInspect.f = FLAG_ADVANCED_SETTING; + conf->dns.CNAMEdeepInspect.d.b = true; - config.dns.blockESNI.k = "dns.blockESNI"; - config.dns.blockESNI.h = "Should _esni. subdomains be blocked by default? Encrypted Server Name Indication (ESNI) is certainly a good step into the right direction to enhance privacy on the web. It prevents on-path observers, including ISPs, coffee shop owners and firewalls, from intercepting the TLS Server Name Indication (SNI) extension by encrypting it. This prevents the SNI from being used to determine which websites users are visiting.\n ESNI will obviously cause issues for pixelserv-tls which will be unable to generate matching certificates on-the-fly when it cannot read the SNI. Cloudflare and Firefox are already enabling ESNI. According to the IEFT draft (link above), we can easily restore piselserv-tls's operation by replying NXDOMAIN to _esni. subdomains of blocked domains as this mimics a \"not configured for this domain\" behavior."; - config.dns.blockESNI.t = CONF_BOOL; - config.dns.blockESNI.f = FLAG_ADVANCED_SETTING; - config.dns.blockESNI.d.b = true; + conf->dns.blockESNI.k = "dns.blockESNI"; + conf->dns.blockESNI.h = "Should _esni. subdomains be blocked by default? Encrypted Server Name Indication (ESNI) is certainly a good step into the right direction to enhance privacy on the web. It prevents on-path observers, including ISPs, coffee shop owners and firewalls, from intercepting the TLS Server Name Indication (SNI) extension by encrypting it. This prevents the SNI from being used to determine which websites users are visiting.\n ESNI will obviously cause issues for pixelserv-tls which will be unable to generate matching certificates on-the-fly when it cannot read the SNI. Cloudflare and Firefox are already enabling ESNI. According to the IEFT draft (link above), we can easily restore piselserv-tls's operation by replying NXDOMAIN to _esni. subdomains of blocked domains as this mimics a \"not configured for this domain\" behavior."; + conf->dns.blockESNI.t = CONF_BOOL; + conf->dns.blockESNI.f = FLAG_ADVANCED_SETTING; + conf->dns.blockESNI.d.b = true; - config.dns.EDNS0ECS.k = "dns.EDNS0ECS"; - config.dns.EDNS0ECS.h = "Should we overwrite the query source when client information is provided through EDNS0 client subnet (ECS) information? This allows Pi-hole to obtain client IPs even if they are hidden behind the NAT of a router. This feature has been requested and discussed on Discourse where further information how to use it can be found: https://discourse.pi-hole.net/t/support-for-add-subnet-option-from-dnsmasq-ecs-edns0-client-subnet/35940"; - config.dns.EDNS0ECS.t = CONF_BOOL; - config.dns.EDNS0ECS.f = FLAG_ADVANCED_SETTING; - config.dns.EDNS0ECS.d.b = true; + conf->dns.EDNS0ECS.k = "dns.EDNS0ECS"; + conf->dns.EDNS0ECS.h = "Should we overwrite the query source when client information is provided through EDNS0 client subnet (ECS) information? This allows Pi-hole to obtain client IPs even if they are hidden behind the NAT of a router. This feature has been requested and discussed on Discourse where further information how to use it can be found: https://discourse.pi-hole.net/t/support-for-add-subnet-option-from-dnsmasq-ecs-edns0-client-subnet/35940"; + conf->dns.EDNS0ECS.t = CONF_BOOL; + conf->dns.EDNS0ECS.f = FLAG_ADVANCED_SETTING; + conf->dns.EDNS0ECS.d.b = true; - config.dns.ignoreLocalhost.k = "dns.ignoreLocalhost"; - config.dns.ignoreLocalhost.h = "Should FTL hide queries made by localhost?"; - config.dns.ignoreLocalhost.t = CONF_BOOL; - config.dns.ignoreLocalhost.f = FLAG_ADVANCED_SETTING; - config.dns.ignoreLocalhost.d.b = false; + conf->dns.ignoreLocalhost.k = "dns.ignoreLocalhost"; + conf->dns.ignoreLocalhost.h = "Should FTL hide queries made by localhost?"; + conf->dns.ignoreLocalhost.t = CONF_BOOL; + conf->dns.ignoreLocalhost.f = FLAG_ADVANCED_SETTING; + conf->dns.ignoreLocalhost.d.b = false; - config.dns.showDNSSEC.k = "dns.showDNSSEC"; - config.dns.showDNSSEC.h = "Should FTL should analyze and show internally generated DNSSEC queries?"; - config.dns.showDNSSEC.t = CONF_BOOL; - config.dns.showDNSSEC.f = FLAG_ADVANCED_SETTING; - config.dns.showDNSSEC.d.b = true; + conf->dns.showDNSSEC.k = "dns.showDNSSEC"; + conf->dns.showDNSSEC.h = "Should FTL should analyze and show internally generated DNSSEC queries?"; + conf->dns.showDNSSEC.t = CONF_BOOL; + conf->dns.showDNSSEC.f = FLAG_ADVANCED_SETTING; + conf->dns.showDNSSEC.d.b = true; - config.dns.analyzeOnlyAandAAAA.k = "dns.analyzeOnlyAandAAAA"; - config.dns.analyzeOnlyAandAAAA.h = "Should FTL analyze *only* A and AAAA queries?"; - config.dns.analyzeOnlyAandAAAA.t = CONF_BOOL; - config.dns.analyzeOnlyAandAAAA.f = FLAG_ADVANCED_SETTING; - config.dns.analyzeOnlyAandAAAA.d.b = false; + conf->dns.analyzeOnlyAandAAAA.k = "dns.analyzeOnlyAandAAAA"; + conf->dns.analyzeOnlyAandAAAA.h = "Should FTL analyze *only* A and AAAA queries?"; + conf->dns.analyzeOnlyAandAAAA.t = CONF_BOOL; + conf->dns.analyzeOnlyAandAAAA.f = FLAG_ADVANCED_SETTING; + conf->dns.analyzeOnlyAandAAAA.d.b = false; - config.dns.piholePTR.k = "dns.piholePTR"; - config.dns.piholePTR.h = "Controls whether and how FTL will reply with for address for which a local interface exists."; + conf->dns.piholePTR.k = "dns.piholePTR"; + conf->dns.piholePTR.h = "Controls whether and how FTL will reply with for address for which a local interface exists."; { struct enum_options piholePTR[] = { @@ -362,14 +369,14 @@ void initConfig(void) { "HOSTNAMEFQDN", "Serve the machine's global hostname as fully qualified domain by adding the local suffix. If no local suffix has been defined, FTL appends the local domain .no_fqdn_available. In this case you should either add domain=whatever.com to a custom config file inside /etc/dnsmasq.d/ (to set whatever.com as local domain) or use domain=# which will try to derive the local domain from /etc/resolv.conf (or whatever is set with resolv-file, when multiple search directives exist, the first one is used)." }, { "PI.HOLE", "Respond with \"pi.hole\"." } }; - CONFIG_ADD_ENUM_OPTIONS(config.dns.piholePTR.a, piholePTR); + CONFIG_ADD_ENUM_OPTIONS(conf->dns.piholePTR.a, piholePTR); } - config.dns.piholePTR.t = CONF_ENUM_PTR_TYPE; - config.dns.piholePTR.f = FLAG_ADVANCED_SETTING; - config.dns.piholePTR.d.ptr_type = PTR_PIHOLE; + conf->dns.piholePTR.t = CONF_ENUM_PTR_TYPE; + conf->dns.piholePTR.f = FLAG_ADVANCED_SETTING; + conf->dns.piholePTR.d.ptr_type = PTR_PIHOLE; - config.dns.replyWhenBusy.k = "dns.replyWhenBusy"; - config.dns.replyWhenBusy.h = "How should FTL handle queries when the gravity database is not available?"; + conf->dns.replyWhenBusy.k = "dns.replyWhenBusy"; + conf->dns.replyWhenBusy.h = "How should FTL handle queries when the gravity database is not available?"; { struct enum_options replyWhenBusy[] = { @@ -378,72 +385,72 @@ void initConfig(void) { "REFUSE", "Refuse all queries which arrive while the database is busy." }, { "DROP", "Just drop the queries, i.e., never reply to them at all. Despite \"REFUSE\" sounding similar to \"DROP\", it turned out that many clients will just immediately retry, causing up to several thousands of queries per second. This does not happen in \"DROP\" mode." } }; - CONFIG_ADD_ENUM_OPTIONS(config.dns.replyWhenBusy.a, replyWhenBusy); + CONFIG_ADD_ENUM_OPTIONS(conf->dns.replyWhenBusy.a, replyWhenBusy); } - config.dns.replyWhenBusy.t = CONF_ENUM_BUSY_TYPE; - config.dns.replyWhenBusy.f = FLAG_ADVANCED_SETTING; - config.dns.replyWhenBusy.d.busy_reply = BUSY_ALLOW; + conf->dns.replyWhenBusy.t = CONF_ENUM_BUSY_TYPE; + conf->dns.replyWhenBusy.f = FLAG_ADVANCED_SETTING; + conf->dns.replyWhenBusy.d.busy_reply = BUSY_ALLOW; - config.dns.blockTTL.k = "dns.blockTTL"; - config.dns.blockTTL.h = "FTL's internal TTL to be handed out for blocked queries in seconds. This settings allows users to select a value different from the dnsmasq config option local-ttl. This is useful in context of locally used hostnames that are known to stay constant over long times (printers, etc.).\n Note that large values may render whitelisting ineffective due to client-side caching of blocked queries."; - config.dns.blockTTL.t = CONF_UINT; - config.dns.blockTTL.f = FLAG_ADVANCED_SETTING; - config.dns.blockTTL.d.ui = 2; + conf->dns.blockTTL.k = "dns.blockTTL"; + conf->dns.blockTTL.h = "FTL's internal TTL to be handed out for blocked queries in seconds. This settings allows users to select a value different from the dnsmasq config option local-ttl. This is useful in context of locally used hostnames that are known to stay constant over long times (printers, etc.).\n Note that large values may render whitelisting ineffective due to client-side caching of blocked queries."; + conf->dns.blockTTL.t = CONF_UINT; + conf->dns.blockTTL.f = FLAG_ADVANCED_SETTING; + conf->dns.blockTTL.d.ui = 2; - config.dns.hosts.k = "dns.hosts"; - config.dns.hosts.h = "Array of custom DNS records\n Example: hosts = [ \"127.0.0.1 mylocal\", \"192.168.0.1 therouter\" ]"; - config.dns.hosts.a = cJSON_CreateStringReference("Array of custom DNS records each one in HOSTS form: \"IP HOSTNAME\""); - config.dns.hosts.t = CONF_JSON_STRING_ARRAY; - config.dns.hosts.f = FLAG_ADVANCED_SETTING; - config.dns.hosts.d.json = cJSON_CreateArray(); + conf->dns.hosts.k = "dns.hosts"; + conf->dns.hosts.h = "Array of custom DNS records\n Example: hosts = [ \"127.0.0.1 mylocal\", \"192.168.0.1 therouter\" ]"; + conf->dns.hosts.a = cJSON_CreateStringReference("Array of custom DNS records each one in HOSTS form: \"IP HOSTNAME\""); + conf->dns.hosts.t = CONF_JSON_STRING_ARRAY; + conf->dns.hosts.f = FLAG_ADVANCED_SETTING; + conf->dns.hosts.d.json = cJSON_CreateArray(); - config.dns.domain.k = "dns.domain"; - config.dns.domain.h = "The DNS domain used by your Pi-hole"; - config.dns.domain.a = cJSON_CreateStringReference(""); - config.dns.domain.t = CONF_STRING; - config.dns.domain.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dns.domain.d.s = (char*)"lan"; + conf->dns.domain.k = "dns.domain"; + conf->dns.domain.h = "The DNS domain used by your Pi-hole"; + conf->dns.domain.a = cJSON_CreateStringReference(""); + conf->dns.domain.t = CONF_STRING; + conf->dns.domain.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dns.domain.d.s = (char*)"lan"; - config.dns.domain_needed.k = "dns.domain_needed"; - config.dns.domain_needed.h = "If set, A and AAAA queries for plain names, without dots or domain parts, are never forwarded to upstream nameservers"; - config.dns.domain_needed.t = CONF_BOOL; - config.dns.domain_needed.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dns.domain_needed.d.b = false; + conf->dns.domain_needed.k = "dns.domain_needed"; + conf->dns.domain_needed.h = "If set, A and AAAA queries for plain names, without dots or domain parts, are never forwarded to upstream nameservers"; + conf->dns.domain_needed.t = CONF_BOOL; + conf->dns.domain_needed.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dns.domain_needed.d.b = false; - config.dns.expand_hosts.k = "dns.expand_hosts"; - config.dns.expand_hosts.h = "If set, the domain is added to simple names (without a period) in /etc/hosts in the same way as for DHCP-derived names"; - config.dns.expand_hosts.t = CONF_BOOL; - config.dns.expand_hosts.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dns.expand_hosts.d.b = false; + conf->dns.expand_hosts.k = "dns.expand_hosts"; + conf->dns.expand_hosts.h = "If set, the domain is added to simple names (without a period) in /etc/hosts in the same way as for DHCP-derived names"; + conf->dns.expand_hosts.t = CONF_BOOL; + conf->dns.expand_hosts.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dns.expand_hosts.d.b = false; - config.dns.bogus_priv.k = "dns.bogus_priv"; - config.dns.bogus_priv.h = "Should all reverse lookups for private IP ranges (i.e., 192.168.x.y, etc) which are not found in /etc/hosts or the DHCP leases file be answered with \"no such domain\" rather than being forwarded upstream?"; - config.dns.bogus_priv.t = CONF_BOOL; - config.dns.bogus_priv.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dns.bogus_priv.d.b = true; + conf->dns.bogus_priv.k = "dns.bogus_priv"; + conf->dns.bogus_priv.h = "Should all reverse lookups for private IP ranges (i.e., 192.168.x.y, etc) which are not found in /etc/hosts or the DHCP leases file be answered with \"no such domain\" rather than being forwarded upstream?"; + conf->dns.bogus_priv.t = CONF_BOOL; + conf->dns.bogus_priv.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dns.bogus_priv.d.b = true; - config.dns.dnssec.k = "dns.dnssec"; - config.dns.dnssec.h = "Validate DNS replies using DNSSEC?"; - config.dns.dnssec.t = CONF_BOOL; - config.dns.dnssec.f = FLAG_RESTART_DNSMASQ; - config.dns.dnssec.d.b = true; + conf->dns.dnssec.k = "dns.dnssec"; + conf->dns.dnssec.h = "Validate DNS replies using DNSSEC?"; + conf->dns.dnssec.t = CONF_BOOL; + conf->dns.dnssec.f = FLAG_RESTART_DNSMASQ; + conf->dns.dnssec.d.b = true; - config.dns.interface.k = "dns.interface"; - config.dns.interface.h = "Interface to use for DNS (see also dnsmasq.listening.mode) and DHCP (if enabled)"; - config.dns.interface.a = cJSON_CreateStringReference("a valid interface name"); - config.dns.interface.t = CONF_STRING; - config.dns.interface.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dns.interface.d.s = (char*)""; + conf->dns.interface.k = "dns.interface"; + conf->dns.interface.h = "Interface to use for DNS (see also dnsmasq.listening.mode) and DHCP (if enabled)"; + conf->dns.interface.a = cJSON_CreateStringReference("a valid interface name"); + conf->dns.interface.t = CONF_STRING; + conf->dns.interface.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dns.interface.d.s = (char*)""; - config.dns.host_record.k = "dns.host_record"; - config.dns.host_record.h = "Add A, AAAA and PTR records to the DNS. This adds one or more names to the DNS with associated IPv4 (A) and IPv6 (AAAA) records"; - config.dns.host_record.a = cJSON_CreateStringReference("[,....],[],[][,]"); - config.dns.host_record.t = CONF_STRING; - config.dns.host_record.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dns.host_record.d.s = (char*)""; + conf->dns.host_record.k = "dns.host_record"; + conf->dns.host_record.h = "Add A, AAAA and PTR records to the DNS. This adds one or more names to the DNS with associated IPv4 (A) and IPv6 (AAAA) records"; + conf->dns.host_record.a = cJSON_CreateStringReference("[,....],[],[][,]"); + conf->dns.host_record.t = CONF_STRING; + conf->dns.host_record.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dns.host_record.d.s = (char*)""; - config.dns.listening_mode.k = "dns.listening_mode"; - config.dns.listening_mode.h = "Pi-hole interface listening modes"; + conf->dns.listening_mode.k = "dns.listening_mode"; + conf->dns.listening_mode.h = "Pi-hole interface listening modes"; { struct enum_options listening_mode[] = { @@ -452,46 +459,45 @@ void initConfig(void) { "BIND", "By default, FTL binds the wildcard address. If this is not what you want, you can use this option as it forces FTL to really bind only the interfaces it is listening on. Note that this may result in issues when the interface may go down (cable unplugged, etc.). About the only time when this is useful is when running another nameserver on the same port on the same machine. This may also happen if you run a virtualization API such as libvirt. When this option is used, IP alias interface labels (e.g. enp2s0:0) are checked rather than interface names." }, { "ALL", "Permit all origins, accept on all interfaces. Make sure your Pi-hole is properly firewalled! This truly allows any traffic to be replied to and is a dangerous thing to do as your Pi-hole could become an open resolver. You should always ask yourself if the first option doesn't work for you as well." } }; - CONFIG_ADD_ENUM_OPTIONS(config.dns.listening_mode.a, listening_mode); + CONFIG_ADD_ENUM_OPTIONS(conf->dns.listening_mode.a, listening_mode); } - config.dns.listening_mode.t = CONF_ENUM_LISTENING_MODE; - config.dns.listening_mode.f = FLAG_RESTART_DNSMASQ; - config.dns.listening_mode.d.listening_mode = LISTEN_LOCAL; + conf->dns.listening_mode.t = CONF_ENUM_LISTENING_MODE; + conf->dns.listening_mode.f = FLAG_RESTART_DNSMASQ; + conf->dns.listening_mode.d.listening_mode = LISTEN_LOCAL; - config.dns.cache_size.k = "dns.cache_size"; - config.dns.cache_size.h = "Cache size of the DNS server. Note that expiring cache entries naturally make room for new insertions over time. Setting this number too high will have an adverse effect as not only more space is needed, but also lookup speed gets degraded in the 10,000+ range. dnsmasq may issue a warning when you go beyond 10,000+ cache entries."; - config.dns.cache_size.t = CONF_UINT; - config.dns.cache_size.f = FLAG_RESTART_DNSMASQ; - config.dns.cache_size.d.ui = 2000u; + conf->dns.cache_size.k = "dns.cache_size"; + conf->dns.cache_size.h = "Cache size of the DNS server. Note that expiring cache entries naturally make room for new insertions over time. Setting this number too high will have an adverse effect as not only more space is needed, but also lookup speed gets degraded in the 10,000+ range. dnsmasq may issue a warning when you go beyond 10,000+ cache entries."; + conf->dns.cache_size.t = CONF_UINT; + conf->dns.cache_size.f = FLAG_RESTART_DNSMASQ; + conf->dns.cache_size.d.ui = 2000u; - config.dns.query_logging.k = "dns.query_logging"; - config.dns.query_logging.h = "Log DNS queries and replies to pihole.log"; - config.dns.query_logging.t = CONF_BOOL; - config.dns.query_logging.f = FLAG_RESTART_DNSMASQ; - config.dns.query_logging.d.b = true; + conf->dns.query_logging.k = "dns.query_logging"; + conf->dns.query_logging.h = "Log DNS queries and replies to pihole.log"; + conf->dns.query_logging.t = CONF_BOOL; + conf->dns.query_logging.f = FLAG_RESTART_DNSMASQ; + conf->dns.query_logging.d.b = true; - config.dns.cnames.k = "dns.cnames"; - config.dns.cnames.h = "List of CNAME records which indicate that is really . If the is given, it overwrites the value of local-ttl"; - config.dns.cnames.a = cJSON_CreateStringReference("Array of static leases each on in one of the following forms: \",[,]\""); - config.dns.cnames.t = CONF_JSON_STRING_ARRAY; - config.dns.cnames.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dns.cnames.d.json = cJSON_CreateArray(); + conf->dns.cnames.k = "dns.cnames"; + conf->dns.cnames.h = "List of CNAME records which indicate that is really . If the is given, it overwrites the value of local-ttl"; + conf->dns.cnames.a = cJSON_CreateStringReference("Array of static leases each on in one of the following forms: \",[,]\""); + conf->dns.cnames.t = CONF_JSON_STRING_ARRAY; + conf->dns.cnames.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dns.cnames.d.json = cJSON_CreateArray(); - config.dns.port.k = "dns.port"; - config.dns.port.h = "Port used by the DNS server"; - config.dns.port.t = CONF_UINT16; - config.dns.port.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dns.port.d.ui = 53u; + conf->dns.port.k = "dns.port"; + conf->dns.port.h = "Port used by the DNS server"; + conf->dns.port.t = CONF_UINT16; + conf->dns.port.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dns.port.d.ui = 53u; // sub-struct dns.blocking - config.dns.blocking.active.k = "dns.blocking.active"; - config.dns.blocking.active.h = "Should FTL block queries?"; - config.dns.blocking.active.t = CONF_BOOL; - config.dns.blocking.active.d.b = true; + conf->dns.blocking.active.k = "dns.blocking.active"; + conf->dns.blocking.active.h = "Should FTL block queries?"; + conf->dns.blocking.active.t = CONF_BOOL; + conf->dns.blocking.active.d.b = true; - config.dns.blocking.mode.k = "dns.blocking.mode"; - config.dns.blocking.mode.h = "How should FTL reply to blocked queries?"; - config.dns.blocking.mode.a = cJSON_CreateStringReference("[ \"NULL\", \"IP-NODATA-AAAA\", \"IP\", \"NXDOMAIN\", \"NODATA\" ]"); + conf->dns.blocking.mode.k = "dns.blocking.mode"; + conf->dns.blocking.mode.h = "How should FTL reply to blocked queries?"; { struct enum_options blockingmode[] = { @@ -501,188 +507,188 @@ void initConfig(void) { "NXDOMAIN", "In NXDOMAIN mode, blocked queries will be answered with an empty response (i.e., there won't be an answer section) and status NXDOMAIN. A NXDOMAIN response should indicate that there is no such domain to the client making the query." }, { "NODATA", "In NODATA mode, blocked queries will be answered with an empty response (no answer section) and status NODATA. A NODATA response indicates that the domain exists, but there is no record for the requested query type." } }; - CONFIG_ADD_ENUM_OPTIONS(config.dns.blocking.mode.a, blockingmode); + CONFIG_ADD_ENUM_OPTIONS(conf->dns.blocking.mode.a, blockingmode); } - config.dns.blocking.mode.t = CONF_ENUM_BLOCKING_MODE; - config.dns.blocking.mode.d.blocking_mode = MODE_NULL; + conf->dns.blocking.mode.t = CONF_ENUM_BLOCKING_MODE; + conf->dns.blocking.mode.d.blocking_mode = MODE_NULL; // sub-struct dns.rate_limit - config.dns.rateLimit.count.k = "dns.rateLimit.count"; - config.dns.rateLimit.count.h = "Rate-limited queries are answered with a REFUSED reply and not further processed by FTL.\nThe default settings for FTL's rate-limiting are to permit no more than 1000 queries in 60 seconds. Both numbers can be customized independently. It is important to note that rate-limiting is happening on a per-client basis. Other clients can continue to use FTL while rate-limited clients are short-circuited at the same time.\n For this setting, both numbers, the maximum number of queries within a given time, and the length of the time interval (seconds) have to be specified. For instance, if you want to set a rate limit of 1 query per hour, the option should look like RATE_LIMIT=1/3600. The time interval is relative to when FTL has finished starting (start of the daemon + possible delay by DELAY_STARTUP) then it will advance in steps of the rate-limiting interval. If a client reaches the maximum number of queries it will be blocked until the end of the current interval. This will be logged to /var/log/pihole/FTL.log, e.g. Rate-limiting 10.0.1.39 for at least 44 seconds. If the client continues to send queries while being blocked already and this number of queries during the blocking exceeds the limit the client will continue to be blocked until the end of the next interval (FTL.log will contain lines like Still rate-limiting 10.0.1.39 as it made additional 5007 queries). As soon as the client requests less than the set limit, it will be unblocked (Ending rate-limitation of 10.0.1.39).\n Rate-limiting may be disabled altogether by setting both values to zero (this results in the same behavior as before FTL v5.7).\n How many queries are permitted..."; - config.dns.rateLimit.count.t = CONF_UINT; - config.dns.rateLimit.count.d.ui = 1000; + conf->dns.rateLimit.count.k = "dns.rateLimit.count"; + conf->dns.rateLimit.count.h = "Rate-limited queries are answered with a REFUSED reply and not further processed by FTL.\nThe default settings for FTL's rate-limiting are to permit no more than 1000 queries in 60 seconds. Both numbers can be customized independently. It is important to note that rate-limiting is happening on a per-client basis. Other clients can continue to use FTL while rate-limited clients are short-circuited at the same time.\n For this setting, both numbers, the maximum number of queries within a given time, and the length of the time interval (seconds) have to be specified. For instance, if you want to set a rate limit of 1 query per hour, the option should look like RATE_LIMIT=1/3600. The time interval is relative to when FTL has finished starting (start of the daemon + possible delay by DELAY_STARTUP) then it will advance in steps of the rate-limiting interval. If a client reaches the maximum number of queries it will be blocked until the end of the current interval. This will be logged to /var/log/pihole/FTL.log, e.g. Rate-limiting 10.0.1.39 for at least 44 seconds. If the client continues to send queries while being blocked already and this number of queries during the blocking exceeds the limit the client will continue to be blocked until the end of the next interval (FTL.log will contain lines like Still rate-limiting 10.0.1.39 as it made additional 5007 queries). As soon as the client requests less than the set limit, it will be unblocked (Ending rate-limitation of 10.0.1.39).\n Rate-limiting may be disabled altogether by setting both values to zero (this results in the same behavior as before FTL v5.7).\n How many queries are permitted..."; + conf->dns.rateLimit.count.t = CONF_UINT; + conf->dns.rateLimit.count.d.ui = 1000; - config.dns.rateLimit.interval.k = "dns.rateLimit.interval"; - config.dns.rateLimit.interval.h = "... in the set interval before rate-limiting?"; - config.dns.rateLimit.interval.t = CONF_UINT; - config.dns.rateLimit.interval.d.ui = 60; + conf->dns.rateLimit.interval.k = "dns.rateLimit.interval"; + conf->dns.rateLimit.interval.h = "... in the set interval before rate-limiting?"; + conf->dns.rateLimit.interval.t = CONF_UINT; + conf->dns.rateLimit.interval.d.ui = 60; // sub-struct dns.special_domains - config.dns.specialDomains.mozillaCanary.k = "dns.specialDomains.mozillaCanary"; - config.dns.specialDomains.mozillaCanary.h = "Should Pi-hole always replies with NXDOMAIN to A and AAAA queries of use-application-dns.net to disable Firefox automatic DNS-over-HTTP? This is following the recommendation on https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https"; - config.dns.specialDomains.mozillaCanary.t = CONF_BOOL; - config.dns.specialDomains.mozillaCanary.d.b = true; + conf->dns.specialDomains.mozillaCanary.k = "dns.specialDomains.mozillaCanary"; + conf->dns.specialDomains.mozillaCanary.h = "Should Pi-hole always replies with NXDOMAIN to A and AAAA queries of use-application-dns.net to disable Firefox automatic DNS-over-HTTP? This is following the recommendation on https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https"; + conf->dns.specialDomains.mozillaCanary.t = CONF_BOOL; + conf->dns.specialDomains.mozillaCanary.d.b = true; - config.dns.specialDomains.iCloudPrivateRelay.k = "dns.specialDomains.iCloudPrivateRelay"; - config.dns.specialDomains.iCloudPrivateRelay.h = "Should Pi-hole always replies with NXDOMAIN to A and AAAA queries of mask.icloud.com and mask-h2.icloud.com to disable Apple's iCloud Private Relay to prevent Apple devices from bypassing Pi-hole? This is following the recommendation on https://developer.apple.com/support/prepare-your-network-for-icloud-private-relay"; - config.dns.specialDomains.iCloudPrivateRelay.t = CONF_BOOL; - config.dns.specialDomains.iCloudPrivateRelay.d.b = true; + conf->dns.specialDomains.iCloudPrivateRelay.k = "dns.specialDomains.iCloudPrivateRelay"; + conf->dns.specialDomains.iCloudPrivateRelay.h = "Should Pi-hole always replies with NXDOMAIN to A and AAAA queries of mask.icloud.com and mask-h2.icloud.com to disable Apple's iCloud Private Relay to prevent Apple devices from bypassing Pi-hole? This is following the recommendation on https://developer.apple.com/support/prepare-your-network-for-icloud-private-relay"; + conf->dns.specialDomains.iCloudPrivateRelay.t = CONF_BOOL; + conf->dns.specialDomains.iCloudPrivateRelay.d.b = true; // sub-struct dns.reply_addr - config.dns.reply.host.overwrite_v4.k = "dns.reply.host.overwrite_v4"; - config.dns.reply.host.overwrite_v4.h = "Use a specific IPv4 address for the Pi-hole host? By default, FTL determines the address of the interface a query arrived on and uses this address for replying to A queries with the most suitable address for the requesting client. This setting can be used to use a fixed, rather than the dynamically obtained, address when Pi-hole responds to the following names: [ \"pi.hole\", \"\", \"pi.hole.\", \".\" ]"; - config.dns.reply.host.overwrite_v4.t = CONF_BOOL; - config.dns.reply.host.overwrite_v4.f = FLAG_ADVANCED_SETTING; - config.dns.reply.host.overwrite_v4.d.b = false; + conf->dns.reply.host.overwrite_v4.k = "dns.reply.host.overwrite_v4"; + conf->dns.reply.host.overwrite_v4.h = "Use a specific IPv4 address for the Pi-hole host? By default, FTL determines the address of the interface a query arrived on and uses this address for replying to A queries with the most suitable address for the requesting client. This setting can be used to use a fixed, rather than the dynamically obtained, address when Pi-hole responds to the following names: [ \"pi.hole\", \"\", \"pi.hole.\", \".\" ]"; + conf->dns.reply.host.overwrite_v4.t = CONF_BOOL; + conf->dns.reply.host.overwrite_v4.f = FLAG_ADVANCED_SETTING; + conf->dns.reply.host.overwrite_v4.d.b = false; - config.dns.reply.host.v4.k = "dns.reply.host.IPv4"; - config.dns.reply.host.v4.h = "Custom IPv4 address for the Pi-hole host"; - config.dns.reply.host.v4.a = cJSON_CreateStringReference(" or empty string (\"\")"); - config.dns.reply.host.v4.t = CONF_STRUCT_IN_ADDR; - config.dns.reply.host.v4.f = FLAG_ADVANCED_SETTING; - memset(&config.dns.reply.host.v4.d.in_addr, 0, sizeof(struct in_addr)); + conf->dns.reply.host.v4.k = "dns.reply.host.IPv4"; + conf->dns.reply.host.v4.h = "Custom IPv4 address for the Pi-hole host"; + conf->dns.reply.host.v4.a = cJSON_CreateStringReference(" or empty string (\"\")"); + conf->dns.reply.host.v4.t = CONF_STRUCT_IN_ADDR; + conf->dns.reply.host.v4.f = FLAG_ADVANCED_SETTING; + memset(&conf->dns.reply.host.v4.d.in_addr, 0, sizeof(struct in_addr)); - config.dns.reply.host.overwrite_v6.k = "dns.reply.host.overwrite_v6"; - config.dns.reply.host.overwrite_v6.h = "Use a specific IPv6 address for the Pi-hole host? See description for the IPv4 variant above for further details."; - config.dns.reply.host.overwrite_v6.t = CONF_BOOL; - config.dns.reply.host.overwrite_v6.f = FLAG_ADVANCED_SETTING; - config.dns.reply.host.overwrite_v6.d.b = false; + conf->dns.reply.host.overwrite_v6.k = "dns.reply.host.overwrite_v6"; + conf->dns.reply.host.overwrite_v6.h = "Use a specific IPv6 address for the Pi-hole host? See description for the IPv4 variant above for further details."; + conf->dns.reply.host.overwrite_v6.t = CONF_BOOL; + conf->dns.reply.host.overwrite_v6.f = FLAG_ADVANCED_SETTING; + conf->dns.reply.host.overwrite_v6.d.b = false; - config.dns.reply.host.v6.k = "dns.reply.host.IPv6"; - config.dns.reply.host.v6.h = "Custom IPv6 address for the Pi-hole host"; - config.dns.reply.host.v6.a = cJSON_CreateStringReference(" or empty string (\"\")"); - config.dns.reply.host.v6.t = CONF_STRUCT_IN6_ADDR; - config.dns.reply.host.v6.f = FLAG_ADVANCED_SETTING; - memset(&config.dns.reply.host.v6.d.in6_addr, 0, sizeof(struct in6_addr)); + conf->dns.reply.host.v6.k = "dns.reply.host.IPv6"; + conf->dns.reply.host.v6.h = "Custom IPv6 address for the Pi-hole host"; + conf->dns.reply.host.v6.a = cJSON_CreateStringReference(" or empty string (\"\")"); + conf->dns.reply.host.v6.t = CONF_STRUCT_IN6_ADDR; + conf->dns.reply.host.v6.f = FLAG_ADVANCED_SETTING; + memset(&conf->dns.reply.host.v6.d.in6_addr, 0, sizeof(struct in6_addr)); - config.dns.reply.blocking.overwrite_v4.k = "dns.reply.blocking.overwrite_v4"; - config.dns.reply.blocking.overwrite_v4.h = "Use a specific IPv4 address in IP blocking mode? By default, FTL determines the address of the interface a query arrived on and uses this address for replying to A queries with the most suitable address for the requesting client. This setting can be used to use a fixed, rather than the dynamically obtained, address when Pi-hole responds in the following cases: IP blocking mode is used and this query is to be blocked, regular expressions with the ;reply=IP regex extension."; - config.dns.reply.blocking.overwrite_v4.t = CONF_BOOL; - config.dns.reply.blocking.overwrite_v4.f = FLAG_ADVANCED_SETTING; - config.dns.reply.blocking.overwrite_v4.d.b = false; + conf->dns.reply.blocking.overwrite_v4.k = "dns.reply.blocking.overwrite_v4"; + conf->dns.reply.blocking.overwrite_v4.h = "Use a specific IPv4 address in IP blocking mode? By default, FTL determines the address of the interface a query arrived on and uses this address for replying to A queries with the most suitable address for the requesting client. This setting can be used to use a fixed, rather than the dynamically obtained, address when Pi-hole responds in the following cases: IP blocking mode is used and this query is to be blocked, regular expressions with the ;reply=IP regex extension."; + conf->dns.reply.blocking.overwrite_v4.t = CONF_BOOL; + conf->dns.reply.blocking.overwrite_v4.f = FLAG_ADVANCED_SETTING; + conf->dns.reply.blocking.overwrite_v4.d.b = false; - config.dns.reply.blocking.v4.k = "dns.reply.blocking.IPv4"; - config.dns.reply.blocking.v4.h = "Custom IPv4 address for IP blocking mode"; - config.dns.reply.blocking.v4.a = cJSON_CreateStringReference(" or empty string (\"\")"); - config.dns.reply.blocking.v4.t = CONF_STRUCT_IN_ADDR; - config.dns.reply.blocking.v4.f = FLAG_ADVANCED_SETTING; - memset(&config.dns.reply.blocking.v4.d.in_addr, 0, sizeof(struct in_addr)); + conf->dns.reply.blocking.v4.k = "dns.reply.blocking.IPv4"; + conf->dns.reply.blocking.v4.h = "Custom IPv4 address for IP blocking mode"; + conf->dns.reply.blocking.v4.a = cJSON_CreateStringReference(" or empty string (\"\")"); + conf->dns.reply.blocking.v4.t = CONF_STRUCT_IN_ADDR; + conf->dns.reply.blocking.v4.f = FLAG_ADVANCED_SETTING; + memset(&conf->dns.reply.blocking.v4.d.in_addr, 0, sizeof(struct in_addr)); - config.dns.reply.blocking.overwrite_v6.k = "dns.reply.blocking.overwrite_v6"; - config.dns.reply.blocking.overwrite_v6.h = "Use a specific IPv6 address in IP blocking mode? See description for the IPv4 variant above for further details."; - config.dns.reply.blocking.overwrite_v6.t = CONF_BOOL; - config.dns.reply.blocking.overwrite_v6.f = FLAG_ADVANCED_SETTING; - config.dns.reply.blocking.overwrite_v6.d.b = false; + conf->dns.reply.blocking.overwrite_v6.k = "dns.reply.blocking.overwrite_v6"; + conf->dns.reply.blocking.overwrite_v6.h = "Use a specific IPv6 address in IP blocking mode? See description for the IPv4 variant above for further details."; + conf->dns.reply.blocking.overwrite_v6.t = CONF_BOOL; + conf->dns.reply.blocking.overwrite_v6.f = FLAG_ADVANCED_SETTING; + conf->dns.reply.blocking.overwrite_v6.d.b = false; - config.dns.reply.blocking.v6.k = "dns.reply.blocking.IPv6"; - config.dns.reply.blocking.v6.h = "Custom IPv6 address for IP blocking mode"; - config.dns.reply.blocking.v6.a = cJSON_CreateStringReference(" or empty string (\"\")"); - config.dns.reply.blocking.v6.t = CONF_STRUCT_IN6_ADDR; - config.dns.reply.blocking.v6.f = FLAG_ADVANCED_SETTING; - memset(&config.dns.reply.blocking.v6.d.in6_addr, 0, sizeof(struct in6_addr)); + conf->dns.reply.blocking.v6.k = "dns.reply.blocking.IPv6"; + conf->dns.reply.blocking.v6.h = "Custom IPv6 address for IP blocking mode"; + conf->dns.reply.blocking.v6.a = cJSON_CreateStringReference(" or empty string (\"\")"); + conf->dns.reply.blocking.v6.t = CONF_STRUCT_IN6_ADDR; + conf->dns.reply.blocking.v6.f = FLAG_ADVANCED_SETTING; + memset(&conf->dns.reply.blocking.v6.d.in6_addr, 0, sizeof(struct in6_addr)); // sub-struct rev_server - config.dns.rev_server.active.k = "dns.rev_server.active"; - config.dns.rev_server.active.h = "Is the reverse server (former also called \"conditional forwarding\") feature enabled?"; - config.dns.rev_server.active.t = CONF_BOOL; - config.dns.rev_server.active.d.b = false; - config.dns.rev_server.active.f = FLAG_RESTART_DNSMASQ; + conf->dns.rev_server.active.k = "dns.rev_server.active"; + conf->dns.rev_server.active.h = "Is the reverse server (former also called \"conditional forwarding\") feature enabled?"; + conf->dns.rev_server.active.t = CONF_BOOL; + conf->dns.rev_server.active.d.b = false; + conf->dns.rev_server.active.f = FLAG_RESTART_DNSMASQ; - config.dns.rev_server.cidr.k = "dns.rev_server.cidr"; - config.dns.rev_server.cidr.h = "Address range for the reverse server feature in CIDR notation. If the prefix length is omitted, either 32 (IPv4) or 128 (IPv6) are substitutet (exact address match). This is almost certainly not what you want here."; - config.dns.rev_server.cidr.a = cJSON_CreateStringReference("[/], e.g., \"192.168.0.0/24\" for the range 192.168.0.1 - 192.168.0.255"); - config.dns.rev_server.cidr.t = CONF_STRING; - config.dns.rev_server.cidr.d.s = (char*)""; - config.dns.rev_server.cidr.f = FLAG_RESTART_DNSMASQ; + conf->dns.rev_server.cidr.k = "dns.rev_server.cidr"; + conf->dns.rev_server.cidr.h = "Address range for the reverse server feature in CIDR notation. If the prefix length is omitted, either 32 (IPv4) or 128 (IPv6) are substitutet (exact address match). This is almost certainly not what you want here."; + conf->dns.rev_server.cidr.a = cJSON_CreateStringReference("[/], e.g., \"192.168.0.0/24\" for the range 192.168.0.1 - 192.168.0.255"); + conf->dns.rev_server.cidr.t = CONF_STRING; + conf->dns.rev_server.cidr.d.s = (char*)""; + conf->dns.rev_server.cidr.f = FLAG_RESTART_DNSMASQ; - config.dns.rev_server.target.k = "dns.rev_server.target"; - config.dns.rev_server.target.h = "Target server tp be used for the reverse server feature"; - config.dns.rev_server.target.a = cJSON_CreateStringReference("[#], e.g., \"192.168.0.1\""); - config.dns.rev_server.target.t = CONF_STRING; - config.dns.rev_server.target.d.s = (char*)""; - config.dns.rev_server.target.f = FLAG_RESTART_DNSMASQ; + conf->dns.rev_server.target.k = "dns.rev_server.target"; + conf->dns.rev_server.target.h = "Target server tp be used for the reverse server feature"; + conf->dns.rev_server.target.a = cJSON_CreateStringReference("[#], e.g., \"192.168.0.1\""); + conf->dns.rev_server.target.t = CONF_STRING; + conf->dns.rev_server.target.d.s = (char*)""; + conf->dns.rev_server.target.f = FLAG_RESTART_DNSMASQ; - config.dns.rev_server.domain.k = "dns.rev_server.domain"; - config.dns.rev_server.domain.h = "Domain used for the reverse server feature"; - config.dns.rev_server.domain.a = cJSON_CreateStringReference(", typically set to the same value as dns.domain"); - config.dns.rev_server.domain.t = CONF_STRING; - config.dns.rev_server.domain.d.s = (char*)""; - config.dns.rev_server.domain.f = FLAG_RESTART_DNSMASQ; + conf->dns.rev_server.domain.k = "dns.rev_server.domain"; + conf->dns.rev_server.domain.h = "Domain used for the reverse server feature"; + conf->dns.rev_server.domain.a = cJSON_CreateStringReference(", typically set to the same value as dns.domain"); + conf->dns.rev_server.domain.t = CONF_STRING; + conf->dns.rev_server.domain.d.s = (char*)""; + conf->dns.rev_server.domain.f = FLAG_RESTART_DNSMASQ; // sub-struct dhcp - config.dhcp.active.k = "dhcp.active"; - config.dhcp.active.h = "Is the embedded DHCP server enabled?"; - config.dhcp.active.t = CONF_BOOL; - config.dhcp.active.f = FLAG_RESTART_DNSMASQ; - config.dhcp.active.d.b = false; + conf->dhcp.active.k = "dhcp.active"; + conf->dhcp.active.h = "Is the embedded DHCP server enabled?"; + conf->dhcp.active.t = CONF_BOOL; + conf->dhcp.active.f = FLAG_RESTART_DNSMASQ; + conf->dhcp.active.d.b = false; - config.dhcp.start.k = "dhcp.start"; - config.dhcp.start.h = "Start address of the DHCP address pool"; - config.dhcp.start.a = cJSON_CreateStringReference(", e.g., \"192.168.0.10\""); - config.dhcp.start.t = CONF_STRING; - config.dhcp.start.f = FLAG_RESTART_DNSMASQ; - config.dhcp.start.d.s = (char*)""; + conf->dhcp.start.k = "dhcp.start"; + conf->dhcp.start.h = "Start address of the DHCP address pool"; + conf->dhcp.start.a = cJSON_CreateStringReference(", e.g., \"192.168.0.10\""); + conf->dhcp.start.t = CONF_STRING; + conf->dhcp.start.f = FLAG_RESTART_DNSMASQ; + conf->dhcp.start.d.s = (char*)""; - config.dhcp.end.k = "dhcp.end"; - config.dhcp.end.h = "End address of the DHCP address pool"; - config.dhcp.end.a = cJSON_CreateStringReference(", e.g., \"192.168.0.250\""); - config.dhcp.end.t = CONF_STRING; - config.dhcp.end.f = FLAG_RESTART_DNSMASQ; - config.dhcp.end.d.s = (char*)""; + conf->dhcp.end.k = "dhcp.end"; + conf->dhcp.end.h = "End address of the DHCP address pool"; + conf->dhcp.end.a = cJSON_CreateStringReference(", e.g., \"192.168.0.250\""); + conf->dhcp.end.t = CONF_STRING; + conf->dhcp.end.f = FLAG_RESTART_DNSMASQ; + conf->dhcp.end.d.s = (char*)""; - config.dhcp.router.k = "dhcp.router"; - config.dhcp.router.h = "Address of the gateway to be used (typicaly the address of your router in a home installation)"; - config.dhcp.router.a = cJSON_CreateStringReference(", e.g., \"192.168.0.1\""); - config.dhcp.router.t = CONF_STRING; - config.dhcp.router.f = FLAG_RESTART_DNSMASQ; - config.dhcp.router.d.s = (char*)""; + conf->dhcp.router.k = "dhcp.router"; + conf->dhcp.router.h = "Address of the gateway to be used (typicaly the address of your router in a home installation)"; + conf->dhcp.router.a = cJSON_CreateStringReference(", e.g., \"192.168.0.1\""); + conf->dhcp.router.t = CONF_STRING; + conf->dhcp.router.f = FLAG_RESTART_DNSMASQ; + conf->dhcp.router.d.s = (char*)""; - config.dhcp.leasetime.k = "dhcp.leasetime"; - config.dhcp.leasetime.h = "If the lease time is given, then leases will be given for that length of time. If not given, the default lease time is one hour for IPv4 and one day for IPv6."; - config.dhcp.leasetime.a = cJSON_CreateStringReference("The lease time can be in seconds, or minutes (e.g., \"45m\") or hours (e.g., \"1h\") or days (like \"2d\") or even weeks (\"1w\"). You may also use \"infinite\" as string but be aware of the drawbacks"); - config.dhcp.leasetime.t = CONF_STRING; - config.dhcp.leasetime.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dhcp.leasetime.d.s = (char*)""; + conf->dhcp.leasetime.k = "dhcp.leasetime"; + conf->dhcp.leasetime.h = "If the lease time is given, then leases will be given for that length of time. If not given, the default lease time is one hour for IPv4 and one day for IPv6."; + conf->dhcp.leasetime.a = cJSON_CreateStringReference("The lease time can be in seconds, or minutes (e.g., \"45m\") or hours (e.g., \"1h\") or days (like \"2d\") or even weeks (\"1w\"). You may also use \"infinite\" as string but be aware of the drawbacks"); + conf->dhcp.leasetime.t = CONF_STRING; + conf->dhcp.leasetime.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dhcp.leasetime.d.s = (char*)""; - config.dhcp.ipv6.k = "dhcp.ipv6"; - config.dhcp.ipv6.h = "Should Pi-hole make an attempt to also satisfy IPv6 address requests (be aware that IPv6 works a whole lot different than IPv4)"; - config.dhcp.ipv6.t = CONF_BOOL; - config.dhcp.ipv6.f = FLAG_RESTART_DNSMASQ; - config.dhcp.ipv6.d.b = false; + conf->dhcp.ipv6.k = "dhcp.ipv6"; + conf->dhcp.ipv6.h = "Should Pi-hole make an attempt to also satisfy IPv6 address requests (be aware that IPv6 works a whole lot different than IPv4)"; + conf->dhcp.ipv6.t = CONF_BOOL; + conf->dhcp.ipv6.f = FLAG_RESTART_DNSMASQ; + conf->dhcp.ipv6.d.b = false; - config.dhcp.rapid_commit.k = "dhcp.rapid_commit"; - config.dhcp.rapid_commit.h = "Enable DHCPv4 Rapid Commit Option specified in RFC 4039. Should only be enabled if either the server is the only server for the subnet to avoid conflicts"; - config.dhcp.rapid_commit.t = CONF_BOOL; - config.dhcp.rapid_commit.f = FLAG_RESTART_DNSMASQ; - config.dhcp.rapid_commit.d.b = false; + conf->dhcp.rapid_commit.k = "dhcp.rapid_commit"; + conf->dhcp.rapid_commit.h = "Enable DHCPv4 Rapid Commit Option specified in RFC 4039. Should only be enabled if either the server is the only server for the subnet to avoid conflicts"; + conf->dhcp.rapid_commit.t = CONF_BOOL; + conf->dhcp.rapid_commit.f = FLAG_RESTART_DNSMASQ; + conf->dhcp.rapid_commit.d.b = false; - config.dhcp.hosts.k = "dhcp.hosts"; - config.dhcp.hosts.h = "Per host parameters for the DHCP server. This allows a machine with a particular hardware address to be always allocated the same hostname, IP address and lease time or to specify static DHCP leases"; - config.dhcp.hosts.a = cJSON_CreateStringReference("Array of static leases each on in one of the following forms: \"[][,id:|*][,set:][,tag:][,][,][,][,ignore]\""); - config.dhcp.hosts.t = CONF_JSON_STRING_ARRAY; - config.dhcp.hosts.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; - config.dhcp.hosts.d.json = cJSON_CreateArray(); + conf->dhcp.hosts.k = "dhcp.hosts"; + conf->dhcp.hosts.h = "Per host parameters for the DHCP server. This allows a machine with a particular hardware address to be always allocated the same hostname, IP address and lease time or to specify static DHCP leases"; + conf->dhcp.hosts.a = cJSON_CreateStringReference("Array of static leases each on in one of the following forms: \"[][,id:|*][,set:][,tag:][,][,][,][,ignore]\""); + conf->dhcp.hosts.t = CONF_JSON_STRING_ARRAY; + conf->dhcp.hosts.f = FLAG_RESTART_DNSMASQ | FLAG_ADVANCED_SETTING; + conf->dhcp.hosts.d.json = cJSON_CreateArray(); // struct resolver - config.resolver.resolveIPv6.k = "resolver.resolveIPv6"; - config.resolver.resolveIPv6.h = "Should FTL try to resolve IPv6 addresses to hostnames?"; - config.resolver.resolveIPv6.t = CONF_BOOL; - config.resolver.resolveIPv6.d.b = true; + conf->resolver.resolveIPv6.k = "resolver.resolveIPv6"; + conf->resolver.resolveIPv6.h = "Should FTL try to resolve IPv6 addresses to hostnames?"; + conf->resolver.resolveIPv6.t = CONF_BOOL; + conf->resolver.resolveIPv6.d.b = true; - config.resolver.resolveIPv4.k = "resolver.resolveIPv4"; - config.resolver.resolveIPv4.h = "Should FTL try to resolve IPv4 addresses to hostnames?"; - config.resolver.resolveIPv4.t = CONF_BOOL; - config.resolver.resolveIPv4.d.b = true; + conf->resolver.resolveIPv4.k = "resolver.resolveIPv4"; + conf->resolver.resolveIPv4.h = "Should FTL try to resolve IPv4 addresses to hostnames?"; + conf->resolver.resolveIPv4.t = CONF_BOOL; + conf->resolver.resolveIPv4.d.b = true; - config.resolver.networkNames.k = "resolver.networkNames"; - config.resolver.networkNames.h = "Control whether FTL should use the fallback option to try to obtain client names from checking the network table. This behavior can be disabled with this option.\nAssume an IPv6 client without a host names. However, the network table knows - though the client's MAC address - that this is the same device where we have a host name for another IP address (e.g., a DHCP server managed IPv4 address). In this case, we use the host name associated to the other address as this is the same device."; - config.resolver.networkNames.t = CONF_BOOL; - config.resolver.networkNames.f = FLAG_ADVANCED_SETTING; - config.resolver.networkNames.d.b = true; + conf->resolver.networkNames.k = "resolver.networkNames"; + conf->resolver.networkNames.h = "Control whether FTL should use the fallback option to try to obtain client names from checking the network table. This behavior can be disabled with this option.\nAssume an IPv6 client without a host names. However, the network table knows - though the client's MAC address - that this is the same device where we have a host name for another IP address (e.g., a DHCP server managed IPv4 address). In this case, we use the host name associated to the other address as this is the same device."; + conf->resolver.networkNames.t = CONF_BOOL; + conf->resolver.networkNames.f = FLAG_ADVANCED_SETTING; + conf->resolver.networkNames.d.b = true; - config.resolver.refreshNames.k = "resolver.refreshNames"; - config.resolver.refreshNames.h = "With this option, you can change how (and if) hourly PTR requests are made to check for changes in client and upstream server hostnames."; + conf->resolver.refreshNames.k = "resolver.refreshNames"; + conf->resolver.refreshNames.h = "With this option, you can change how (and if) hourly PTR requests are made to check for changes in client and upstream server hostnames."; { struct enum_options refreshNames[] = { @@ -691,143 +697,143 @@ void initConfig(void) { "UNKNOWN", "Only resolve unknown hostnames. Already existing hostnames are never refreshed, i.e., there will be no PTR queries made for clients where hostnames are known. This also means that known hostnames will not be updated once known." }, { "NONE", "Don't do any hourly PTR lookups. This means we look host names up exactly once (when we first see a client) and never again. You may miss future changes of host names." } }; - CONFIG_ADD_ENUM_OPTIONS(config.resolver.refreshNames.a, refreshNames); + CONFIG_ADD_ENUM_OPTIONS(conf->resolver.refreshNames.a, refreshNames); } - config.resolver.refreshNames.t = CONF_ENUM_REFRESH_HOSTNAMES; - config.resolver.refreshNames.f = FLAG_ADVANCED_SETTING; - config.resolver.refreshNames.d.refresh_hostnames = REFRESH_IPV4_ONLY; + conf->resolver.refreshNames.t = CONF_ENUM_REFRESH_HOSTNAMES; + conf->resolver.refreshNames.f = FLAG_ADVANCED_SETTING; + conf->resolver.refreshNames.d.refresh_hostnames = REFRESH_IPV4_ONLY; // struct database - config.database.DBimport.k = "database.DBimport"; - config.database.DBimport.h = "Should FTL load information from the database on startup to be aware of the most recent history?"; - config.database.DBimport.t = CONF_BOOL; - config.database.DBimport.d.b = true; + conf->database.DBimport.k = "database.DBimport"; + conf->database.DBimport.h = "Should FTL load information from the database on startup to be aware of the most recent history?"; + conf->database.DBimport.t = CONF_BOOL; + conf->database.DBimport.d.b = true; - config.database.DBexport.k = "database.DBexport"; - config.database.DBexport.h = "Should FTL store queries in the long-term database?"; - config.database.DBexport.t = CONF_BOOL; - config.database.DBexport.d.b = true; + conf->database.DBexport.k = "database.DBexport"; + conf->database.DBexport.h = "Should FTL store queries in the long-term database?"; + conf->database.DBexport.t = CONF_BOOL; + conf->database.DBexport.d.b = true; - config.database.maxDBdays.k = "database.maxDBdays"; - config.database.maxDBdays.h = "How long should queries be stored in the database [days]?"; - config.database.maxDBdays.t = CONF_INT; - config.database.maxDBdays.d.i = 365; + conf->database.maxDBdays.k = "database.maxDBdays"; + conf->database.maxDBdays.h = "How long should queries be stored in the database [days]?"; + conf->database.maxDBdays.t = CONF_INT; + conf->database.maxDBdays.d.i = 365; - config.database.maxHistory.k = "database.maxHistory"; - config.database.maxHistory.h = "How much history should be imported from the database [seconds]? (max 24*60*60 = 86400)"; - config.database.maxHistory.t = CONF_UINT; - config.database.maxHistory.d.ui = MAXLOGAGE*3600; + conf->database.maxHistory.k = "database.maxHistory"; + conf->database.maxHistory.h = "How much history should be imported from the database [seconds]? (max 24*60*60 = 86400)"; + conf->database.maxHistory.t = CONF_UINT; + conf->database.maxHistory.d.ui = MAXLOGAGE*3600; - config.database.DBinterval.k = "database.DBinterval"; - config.database.DBinterval.h = "How often do we store queries in FTL's database [seconds]?"; - config.database.DBinterval.t = CONF_UINT; - config.database.DBinterval.d.ui = 60; + conf->database.DBinterval.k = "database.DBinterval"; + conf->database.DBinterval.h = "How often do we store queries in FTL's database [seconds]?"; + conf->database.DBinterval.t = CONF_UINT; + conf->database.DBinterval.d.ui = 60; // sub-struct database.network - config.database.network.parseARPcache.k = "database.network.parseARPcache"; - config.database.network.parseARPcache.h = "Should FTL anaylze the local ARP cache? When disabled, client identification and the network table will stop working reliably."; - config.database.network.parseARPcache.t = CONF_BOOL; - config.database.network.parseARPcache.f = FLAG_ADVANCED_SETTING; - config.database.network.parseARPcache.d.b = true; + conf->database.network.parseARPcache.k = "database.network.parseARPcache"; + conf->database.network.parseARPcache.h = "Should FTL anaylze the local ARP cache? When disabled, client identification and the network table will stop working reliably."; + conf->database.network.parseARPcache.t = CONF_BOOL; + conf->database.network.parseARPcache.f = FLAG_ADVANCED_SETTING; + conf->database.network.parseARPcache.d.b = true; - config.database.network.expire.k = "database.network.expire"; - config.database.network.expire.h = "How long should IP addresses be kept in the network_addresses table [days]? IP addresses (and associated host names) older than the specified number of days are removed to avoid dead entries in the network overview table."; - config.database.network.expire.t = CONF_UINT; - config.database.network.expire.f = FLAG_ADVANCED_SETTING; - config.database.network.expire.d.ui = config.database.maxDBdays.d.ui; + conf->database.network.expire.k = "database.network.expire"; + conf->database.network.expire.h = "How long should IP addresses be kept in the network_addresses table [days]? IP addresses (and associated host names) older than the specified number of days are removed to avoid dead entries in the network overview table."; + conf->database.network.expire.t = CONF_UINT; + conf->database.network.expire.f = FLAG_ADVANCED_SETTING; + conf->database.network.expire.d.ui = conf->database.maxDBdays.d.ui; // struct http - config.webserver.domain.k = "webserver.domain"; - config.webserver.domain.h = "On which domain is the web interface served?"; - config.webserver.domain.a = cJSON_CreateStringReference(""); - config.webserver.domain.t = CONF_STRING; - config.webserver.domain.d.s = (char*)"pi.hole"; + conf->webserver.domain.k = "webserver.domain"; + conf->webserver.domain.h = "On which domain is the web interface served?"; + conf->webserver.domain.a = cJSON_CreateStringReference(""); + conf->webserver.domain.t = CONF_STRING; + conf->webserver.domain.d.s = (char*)"pi.hole"; - config.webserver.acl.k = "webserver.acl"; - config.webserver.acl.h = "Webserver access control list (ACL) allowing for restrictions to be put on the list of IP addresses which have access to the web server. The ACL is a comma separated list of IP subnets, where each subnet is prepended by either a - or a + sign. A plus sign means allow, where a minus sign means deny. If a subnet mask is omitted, such as -1.2.3.4, this means to deny only that single IP address. If this value is not set (empty string), all accesses are allowed. Otherwise, the default setting is to deny all accesses. On each request the full list is traversed, and the last (!) match wins. IPv6 addresses may be specified in CIDR-form [a:b::c]/64.\n\n Example 1: acl = \"+127.0.0.1,+[::1]\"\n ---> deny all access, except from 127.0.0.1 and ::1,\n Example 2: acl = \"+192.168.0.0/16\"\n ---> deny all accesses, except from the 192.168.0.0/16 subnet,\n Example 3: acl = \"+[::]/0\" ---> allow only IPv6 access."; - config.webserver.acl.a = cJSON_CreateStringReference(""); - config.webserver.acl.f = FLAG_ADVANCED_SETTING; - config.webserver.acl.t = CONF_STRING; - config.webserver.acl.d.s = (char*)""; + conf->webserver.acl.k = "webserver.acl"; + conf->webserver.acl.h = "Webserver access control list (ACL) allowing for restrictions to be put on the list of IP addresses which have access to the web server. The ACL is a comma separated list of IP subnets, where each subnet is prepended by either a - or a + sign. A plus sign means allow, where a minus sign means deny. If a subnet mask is omitted, such as -1.2.3.4, this means to deny only that single IP address. If this value is not set (empty string), all accesses are allowed. Otherwise, the default setting is to deny all accesses. On each request the full list is traversed, and the last (!) match wins. IPv6 addresses may be specified in CIDR-form [a:b::c]/64.\n\n Example 1: acl = \"+127.0.0.1,+[::1]\"\n ---> deny all access, except from 127.0.0.1 and ::1,\n Example 2: acl = \"+192.168.0.0/16\"\n ---> deny all accesses, except from the 192.168.0.0/16 subnet,\n Example 3: acl = \"+[::]/0\" ---> allow only IPv6 access."; + conf->webserver.acl.a = cJSON_CreateStringReference(""); + conf->webserver.acl.f = FLAG_ADVANCED_SETTING; + conf->webserver.acl.t = CONF_STRING; + conf->webserver.acl.d.s = (char*)""; - config.webserver.port.k = "webserver.port"; - config.webserver.port.h = "Ports to be used by the webserver. Comma-separated list of ports to listen on. It is possible to specify an IP address to bind to. In this case, an IP address and a colon must be prepended to the port number. For example, to bind to the loopback interface on port 80 (IPv4) and to all interfaces port 8080 (IPv4), use \"127.0.0.1:80,8080\". \"[::]:8080\" can be used to listen to IPv6 connections to port 8080. IPv6 addresses of network interfaces can be specified as well, e.g. \"[::1]:8080\" for the IPv6 loopback interface. [::]:80 will bind to port 80 IPv6 only.\n In order to use port 8080 for all interfaces, both IPv4 and IPv6, use either the configuration \"8080,[::]:8080\" (create one socket for IPv4 and one for IPv6 only), or \"+8080\" (create one socket for both, IPv4 and IPv6). The + notation to use IPv4 and IPv6 will only work if no network interface is specified. Depending on your operating system version and IPv6 network environment, some configurations might not work as expected, so you have to test to find the configuration most suitable for your needs. In case \"+8080\" does not work for your environment, you need to use \"8080,[::]:8080\"."; - config.webserver.port.a = cJSON_CreateStringReference("comma-separated list of <[ip_address:]port>"); - config.webserver.port.t = CONF_STRING; - config.webserver.port.d.s = (char*)"8080,[::]:8080"; + conf->webserver.port.k = "webserver.port"; + conf->webserver.port.h = "Ports to be used by the webserver. Comma-separated list of ports to listen on. It is possible to specify an IP address to bind to. In this case, an IP address and a colon must be prepended to the port number. For example, to bind to the loopback interface on port 80 (IPv4) and to all interfaces port 8080 (IPv4), use \"127.0.0.1:80,8080\". \"[::]:8080\" can be used to listen to IPv6 connections to port 8080. IPv6 addresses of network interfaces can be specified as well, e.g. \"[::1]:8080\" for the IPv6 loopback interface. [::]:80 will bind to port 80 IPv6 only.\n In order to use port 8080 for all interfaces, both IPv4 and IPv6, use either the configuration \"8080,[::]:8080\" (create one socket for IPv4 and one for IPv6 only), or \"+8080\" (create one socket for both, IPv4 and IPv6). The + notation to use IPv4 and IPv6 will only work if no network interface is specified. Depending on your operating system version and IPv6 network environment, some configurations might not work as expected, so you have to test to find the configuration most suitable for your needs. In case \"+8080\" does not work for your environment, you need to use \"8080,[::]:8080\"."; + conf->webserver.port.a = cJSON_CreateStringReference("comma-separated list of <[ip_address:]port>"); + conf->webserver.port.t = CONF_STRING; + conf->webserver.port.d.s = (char*)"8080,[::]:8080"; // sub-struct paths - config.webserver.paths.webroot.k = "webserver.paths.webroot"; - config.webserver.paths.webroot.h = "Server root on the host"; - config.webserver.paths.webroot.a = cJSON_CreateStringReference(""); - config.webserver.paths.webroot.t = CONF_STRING; - config.webserver.paths.webroot.f = FLAG_ADVANCED_SETTING; - config.webserver.paths.webroot.d.s = (char*)"/var/www/html"; + conf->webserver.paths.webroot.k = "webserver.paths.webroot"; + conf->webserver.paths.webroot.h = "Server root on the host"; + conf->webserver.paths.webroot.a = cJSON_CreateStringReference(""); + conf->webserver.paths.webroot.t = CONF_STRING; + conf->webserver.paths.webroot.f = FLAG_ADVANCED_SETTING; + conf->webserver.paths.webroot.d.s = (char*)"/var/www/html"; - config.webserver.paths.webhome.k = "webserver.paths.webhome"; - config.webserver.paths.webhome.h = "Sub-directory of the root containing the web interface"; - config.webserver.paths.webhome.a = cJSON_CreateStringReference(", both slashes are needed!"); - config.webserver.paths.webhome.t = CONF_STRING; - config.webserver.paths.webhome.f = FLAG_ADVANCED_SETTING; - config.webserver.paths.webhome.d.s = (char*)"/admin/"; + conf->webserver.paths.webhome.k = "webserver.paths.webhome"; + conf->webserver.paths.webhome.h = "Sub-directory of the root containing the web interface"; + conf->webserver.paths.webhome.a = cJSON_CreateStringReference(", both slashes are needed!"); + conf->webserver.paths.webhome.t = CONF_STRING; + conf->webserver.paths.webhome.f = FLAG_ADVANCED_SETTING; + conf->webserver.paths.webhome.d.s = (char*)"/admin/"; // sub-struct interface - config.webserver.interface.boxed.k = "webserver.interface.boxed"; - config.webserver.interface.boxed.h = "Should the web interface use the boxed layout?"; - config.webserver.interface.boxed.t = CONF_BOOL; - config.webserver.interface.boxed.d.b = true; + conf->webserver.interface.boxed.k = "webserver.interface.boxed"; + conf->webserver.interface.boxed.h = "Should the web interface use the boxed layout?"; + conf->webserver.interface.boxed.t = CONF_BOOL; + conf->webserver.interface.boxed.d.b = true; - config.webserver.interface.theme.k = "webserver.interface.theme"; - config.webserver.interface.theme.h = "Theme used by the Pi-hole web interface"; - config.webserver.interface.theme.a = cJSON_CreateStringReference(""); - config.webserver.interface.theme.t = CONF_STRING; - config.webserver.interface.theme.d.s = (char*)"default"; + conf->webserver.interface.theme.k = "webserver.interface.theme"; + conf->webserver.interface.theme.h = "Theme used by the Pi-hole web interface"; + conf->webserver.interface.theme.a = cJSON_CreateStringReference(""); + conf->webserver.interface.theme.t = CONF_STRING; + conf->webserver.interface.theme.d.s = (char*)"default"; // sub-struct api - config.webserver.api.localAPIauth.k = "webserver.api.localAPIauth"; - config.webserver.api.localAPIauth.h = "Does local clients need to authenticate to access the API?"; - config.webserver.api.localAPIauth.t = CONF_BOOL; - config.webserver.api.localAPIauth.d.b = true; + conf->webserver.api.localAPIauth.k = "webserver.api.localAPIauth"; + conf->webserver.api.localAPIauth.h = "Does local clients need to authenticate to access the API?"; + conf->webserver.api.localAPIauth.t = CONF_BOOL; + conf->webserver.api.localAPIauth.d.b = true; - config.webserver.api.prettyJSON.k = "webserver.api.prettyJSON"; - config.webserver.api.prettyJSON.h = "Should FTL prettify the API output (add extra spaces, newlines and indentation)?"; - config.webserver.api.prettyJSON.t = CONF_BOOL; - config.webserver.api.prettyJSON.f = FLAG_ADVANCED_SETTING; - config.webserver.api.prettyJSON.d.b = false; + conf->webserver.api.prettyJSON.k = "webserver.api.prettyJSON"; + conf->webserver.api.prettyJSON.h = "Should FTL prettify the API output (add extra spaces, newlines and indentation)?"; + conf->webserver.api.prettyJSON.t = CONF_BOOL; + conf->webserver.api.prettyJSON.f = FLAG_ADVANCED_SETTING; + conf->webserver.api.prettyJSON.d.b = false; - config.webserver.api.sessionTimeout.k = "webserver.api.sessionTimeout"; - config.webserver.api.sessionTimeout.h = "How long should a session be considered valid after login [seconds]?"; - config.webserver.api.sessionTimeout.t = CONF_UINT; - config.webserver.api.sessionTimeout.d.ui = 300; + conf->webserver.api.sessionTimeout.k = "webserver.api.sessionTimeout"; + conf->webserver.api.sessionTimeout.h = "How long should a session be considered valid after login [seconds]?"; + conf->webserver.api.sessionTimeout.t = CONF_UINT; + conf->webserver.api.sessionTimeout.d.ui = 300; - config.webserver.api.pwhash.k = "webserver.api.pwhash"; - config.webserver.api.pwhash.h = "API password hash"; - config.webserver.api.pwhash.a = cJSON_CreateStringReference(""); - config.webserver.api.pwhash.t = CONF_STRING; - config.webserver.api.pwhash.d.s = (char*)""; + conf->webserver.api.pwhash.k = "webserver.api.pwhash"; + conf->webserver.api.pwhash.h = "API password hash"; + conf->webserver.api.pwhash.a = cJSON_CreateStringReference(""); + conf->webserver.api.pwhash.t = CONF_STRING; + conf->webserver.api.pwhash.d.s = (char*)""; - config.webserver.api.exclude_clients.k = "webserver.api.exclude_clients"; - config.webserver.api.exclude_clients.h = "Array of clients to be excluded from certain API responses\n Example: [ \"192.168.2.56\", \"fe80::341\", \"localhost\" ]"; - config.webserver.api.exclude_clients.a = cJSON_CreateStringReference("array of IP addresses and/or hostnames"); - config.webserver.api.exclude_clients.t = CONF_JSON_STRING_ARRAY; - config.webserver.api.exclude_clients.d.json = cJSON_CreateArray(); + conf->webserver.api.exclude_clients.k = "webserver.api.exclude_clients"; + conf->webserver.api.exclude_clients.h = "Array of clients to be excluded from certain API responses\n Example: [ \"192.168.2.56\", \"fe80::341\", \"localhost\" ]"; + conf->webserver.api.exclude_clients.a = cJSON_CreateStringReference("array of IP addresses and/or hostnames"); + conf->webserver.api.exclude_clients.t = CONF_JSON_STRING_ARRAY; + conf->webserver.api.exclude_clients.d.json = cJSON_CreateArray(); - config.webserver.api.exclude_domains.k = "webserver.api.exclude_domains"; - config.webserver.api.exclude_domains.h = "Array of domains to be excluded from certain API responses\n Example: [ \"google.de\", \"pi-hole.net\" ]"; - config.webserver.api.exclude_domains.a = cJSON_CreateStringReference("array of IP addresses and/or hostnames"); - config.webserver.api.exclude_domains.t = CONF_JSON_STRING_ARRAY; - config.webserver.api.exclude_domains.d.json = cJSON_CreateArray(); + conf->webserver.api.exclude_domains.k = "webserver.api.exclude_domains"; + conf->webserver.api.exclude_domains.h = "Array of domains to be excluded from certain API responses\n Example: [ \"google.de\", \"pi-hole.net\" ]"; + conf->webserver.api.exclude_domains.a = cJSON_CreateStringReference("array of IP addresses and/or hostnames"); + conf->webserver.api.exclude_domains.t = CONF_JSON_STRING_ARRAY; + conf->webserver.api.exclude_domains.d.json = cJSON_CreateArray(); // sub-struct webserver.api.temp - config.webserver.api.temp.limit.k = "webserver.api.temp.limit"; - config.webserver.api.temp.limit.h = "Which upper temperature limit should be used by Pi-hole? Temperatures above this limit will be shown as \"hot\". The number specified here is in the unit defined below"; - config.webserver.api.temp.limit.t = CONF_DOUBLE; - config.webserver.api.temp.limit.d.d = 60.0; // °C + conf->webserver.api.temp.limit.k = "webserver.api.temp.limit"; + conf->webserver.api.temp.limit.h = "Which upper temperature limit should be used by Pi-hole? Temperatures above this limit will be shown as \"hot\". The number specified here is in the unit defined below"; + conf->webserver.api.temp.limit.t = CONF_DOUBLE; + conf->webserver.api.temp.limit.d.d = 60.0; // °C - config.webserver.api.temp.unit.k = "webserver.api.temp.unit"; - config.webserver.api.temp.unit.h = "Which temperature unit should be used for temperatures processed by FTL?"; + conf->webserver.api.temp.unit.k = "webserver.api.temp.unit"; + conf->webserver.api.temp.unit.h = "Which temperature unit should be used for temperatures processed by FTL?"; { struct enum_options temp_unit[] = { @@ -835,76 +841,76 @@ void initConfig(void) { "F", "Fahrenheit" }, { "K", "Kelvin" }, }; - CONFIG_ADD_ENUM_OPTIONS(config.webserver.api.temp.unit.a, temp_unit); + CONFIG_ADD_ENUM_OPTIONS(conf->webserver.api.temp.unit.a, temp_unit); } - config.webserver.api.temp.unit.t = CONF_STRING; - config.webserver.api.temp.unit.d.s = (char*)"C"; + conf->webserver.api.temp.unit.t = CONF_STRING; + conf->webserver.api.temp.unit.d.s = (char*)"C"; // struct files - config.files.pid.k = "files.pid"; - config.files.pid.h = "The file which contains the PID of FTL's main process."; - config.files.pid.a = cJSON_CreateStringReference(""); - config.files.pid.t = CONF_STRING; - config.files.pid.f = FLAG_ADVANCED_SETTING; - config.files.pid.d.s = (char*)"/run/pihole-FTL.pid"; + conf->files.pid.k = "files.pid"; + conf->files.pid.h = "The file which contains the PID of FTL's main process."; + conf->files.pid.a = cJSON_CreateStringReference(""); + conf->files.pid.t = CONF_STRING; + conf->files.pid.f = FLAG_ADVANCED_SETTING; + conf->files.pid.d.s = (char*)"/run/pihole-FTL.pid"; - config.files.database.k = "files.database"; - config.files.database.h = "The location of FTL's long-term database"; - config.files.database.a = cJSON_CreateStringReference(""); - config.files.database.t = CONF_STRING; - config.files.database.f = FLAG_ADVANCED_SETTING; - config.files.database.d.s = (char*)"/etc/pihole/pihole-FTL.db"; + conf->files.database.k = "files.database"; + conf->files.database.h = "The location of FTL's long-term database"; + conf->files.database.a = cJSON_CreateStringReference(""); + conf->files.database.t = CONF_STRING; + conf->files.database.f = FLAG_ADVANCED_SETTING; + conf->files.database.d.s = (char*)"/etc/pihole/pihole-FTL.db"; - config.files.gravity.k = "files.gravity"; - config.files.gravity.h = "The location of Pi-hole's gravity database"; - config.files.gravity.a = cJSON_CreateStringReference(""); - config.files.gravity.t = CONF_STRING; - config.files.gravity.f = FLAG_ADVANCED_SETTING; - config.files.gravity.d.s = (char*)"/etc/pihole/gravity.db"; + conf->files.gravity.k = "files.gravity"; + conf->files.gravity.h = "The location of Pi-hole's gravity database"; + conf->files.gravity.a = cJSON_CreateStringReference(""); + conf->files.gravity.t = CONF_STRING; + conf->files.gravity.f = FLAG_ADVANCED_SETTING; + conf->files.gravity.d.s = (char*)"/etc/pihole/gravity.db"; - config.files.macvendor.k = "files.macvendor"; - config.files.macvendor.h = "The database containing MAC -> Vendor information for the network table"; - config.files.macvendor.a = cJSON_CreateStringReference(""); - config.files.macvendor.t = CONF_STRING; - config.files.macvendor.f = FLAG_ADVANCED_SETTING; - config.files.macvendor.d.s = (char*)"/etc/pihole/macvendor.db"; + conf->files.macvendor.k = "files.macvendor"; + conf->files.macvendor.h = "The database containing MAC -> Vendor information for the network table"; + conf->files.macvendor.a = cJSON_CreateStringReference(""); + conf->files.macvendor.t = CONF_STRING; + conf->files.macvendor.f = FLAG_ADVANCED_SETTING; + conf->files.macvendor.d.s = (char*)"/etc/pihole/macvendor.db"; - config.files.setupVars.k = "files.setupVars"; - config.files.setupVars.h = "The config file of Pi-hole"; - config.files.setupVars.a = cJSON_CreateStringReference(""); - config.files.setupVars.t = CONF_STRING; - config.files.setupVars.f = FLAG_ADVANCED_SETTING; - config.files.setupVars.d.s = (char*)"/etc/pihole/setupVars.conf"; + conf->files.setupVars.k = "files.setupVars"; + conf->files.setupVars.h = "The config file of Pi-hole"; + conf->files.setupVars.a = cJSON_CreateStringReference(""); + conf->files.setupVars.t = CONF_STRING; + conf->files.setupVars.f = FLAG_ADVANCED_SETTING; + conf->files.setupVars.d.s = (char*)"/etc/pihole/setupVars.conf"; - config.files.http_info.k = "files.http_info"; - config.files.http_info.h = "The log file used by the webserver"; - config.files.http_info.a = cJSON_CreateStringReference(""); - config.files.http_info.t = CONF_STRING; - config.files.http_info.f = FLAG_ADVANCED_SETTING; - config.files.http_info.d.s = (char*)"/var/log/pihole/HTTP_info.log"; + conf->files.http_info.k = "files.http_info"; + conf->files.http_info.h = "The log file used by the webserver"; + conf->files.http_info.a = cJSON_CreateStringReference(""); + conf->files.http_info.t = CONF_STRING; + conf->files.http_info.f = FLAG_ADVANCED_SETTING; + conf->files.http_info.d.s = (char*)"/var/log/pihole/HTTP_info.log"; - config.files.ph7_error.k = "files.ph7_error"; - config.files.ph7_error.h = "The log file used by the dynamic interpreter PH7"; - config.files.ph7_error.a = cJSON_CreateStringReference(""); - config.files.ph7_error.t = CONF_STRING; - config.files.ph7_error.f = FLAG_ADVANCED_SETTING; - config.files.ph7_error.d.s = (char*)"/var/log/pihole/PH7.log"; + conf->files.ph7_error.k = "files.ph7_error"; + conf->files.ph7_error.h = "The log file used by the dynamic interpreter PH7"; + conf->files.ph7_error.a = cJSON_CreateStringReference(""); + conf->files.ph7_error.t = CONF_STRING; + conf->files.ph7_error.f = FLAG_ADVANCED_SETTING; + conf->files.ph7_error.d.s = (char*)"/var/log/pihole/PH7.log"; // sub-struct files.log - // config.files.log.ftl is set in a separate function + // conf->files.log.ftl is set in a separate function - config.files.log.dnsmasq.k = "files.log.dnsmasq"; - config.files.log.dnsmasq.h = "The log file used by the embedded dnsmasq DNS server"; - config.files.log.dnsmasq.a = cJSON_CreateStringReference(""); - config.files.log.dnsmasq.t = CONF_STRING; - config.files.log.dnsmasq.f = FLAG_ADVANCED_SETTING; - config.files.log.dnsmasq.d.s = (char*)"/var/log/pihole/pihole.log"; + conf->files.log.dnsmasq.k = "files.log.dnsmasq"; + conf->files.log.dnsmasq.h = "The log file used by the embedded dnsmasq DNS server"; + conf->files.log.dnsmasq.a = cJSON_CreateStringReference(""); + conf->files.log.dnsmasq.t = CONF_STRING; + conf->files.log.dnsmasq.f = FLAG_ADVANCED_SETTING; + conf->files.log.dnsmasq.d.s = (char*)"/var/log/pihole/pihole.log"; // struct misc - config.misc.privacylevel.k = "misc.privacylevel"; - config.misc.privacylevel.h = "Using privacy levels you can specify which level of detail you want to see in your Pi-hole statistics."; + conf->misc.privacylevel.k = "misc.privacylevel"; + conf->misc.privacylevel.h = "Using privacy levels you can specify which level of detail you want to see in your Pi-hole statistics."; { struct enum_options privacylevel[] = { @@ -913,189 +919,189 @@ void initConfig(void) { "2", "Hide domains and clients. This setting disables Top Domains, Top Ads, Top Clients and Clients over time." }, { "3", "Anonymize everything. This setting disabled almost any statistics and query analysis. There will be no long-term database logging and no Query Log. You will also loose most regex features." } }; - CONFIG_ADD_ENUM_OPTIONS(config.misc.privacylevel.a, privacylevel); + CONFIG_ADD_ENUM_OPTIONS(conf->misc.privacylevel.a, privacylevel); } - config.misc.privacylevel.t = CONF_ENUM_PRIVACY_LEVEL; - config.misc.privacylevel.d.privacy_level = PRIVACY_SHOW_ALL; + conf->misc.privacylevel.t = CONF_ENUM_PRIVACY_LEVEL; + conf->misc.privacylevel.d.privacy_level = PRIVACY_SHOW_ALL; - config.misc.delay_startup.k = "misc.delay_startup"; - config.misc.delay_startup.h = "During startup, in some configurations, network interfaces appear only late during system startup and are not ready when FTL tries to bind to them. Therefore, you may want FTL to wait a given amount of time before trying to start the DNS revolver. This setting takes any integer value between 0 and 300 seconds. To prevent delayed startup while the system is already running and FTL is restarted, the delay only takes place within the first 180 seconds (hard-coded) after booting."; - config.misc.delay_startup.t = CONF_UINT; - config.misc.delay_startup.d.ui = 0; + conf->misc.delay_startup.k = "misc.delay_startup"; + conf->misc.delay_startup.h = "During startup, in some configurations, network interfaces appear only late during system startup and are not ready when FTL tries to bind to them. Therefore, you may want FTL to wait a given amount of time before trying to start the DNS revolver. This setting takes any integer value between 0 and 300 seconds. To prevent delayed startup while the system is already running and FTL is restarted, the delay only takes place within the first 180 seconds (hard-coded) after booting."; + conf->misc.delay_startup.t = CONF_UINT; + conf->misc.delay_startup.d.ui = 0; - config.misc.nice.k = "misc.nice"; - config.misc.nice.h = "Set niceness of pihole-FTL. Defaults to -10 and can be disabled altogether by setting a value of -999. The nice value is an attribute that can be used to influence the CPU scheduler to favor or disfavor a process in scheduling decisions. The range of the nice value varies across UNIX systems. On modern Linux, the range is -20 (high priority = not very nice to other processes) to +19 (low priority)."; - config.misc.nice.t = CONF_INT; - config.misc.nice.f = FLAG_ADVANCED_SETTING; - config.misc.nice.d.i = -10; + conf->misc.nice.k = "misc.nice"; + conf->misc.nice.h = "Set niceness of pihole-FTL. Defaults to -10 and can be disabled altogether by setting a value of -999. The nice value is an attribute that can be used to influence the CPU scheduler to favor or disfavor a process in scheduling decisions. The range of the nice value varies across UNIX systems. On modern Linux, the range is -20 (high priority = not very nice to other processes) to +19 (low priority)."; + conf->misc.nice.t = CONF_INT; + conf->misc.nice.f = FLAG_ADVANCED_SETTING; + conf->misc.nice.d.i = -10; - config.misc.addr2line.k = "misc.addr2line"; - config.misc.addr2line.h = "Should FTL translate its own stack addresses into code lines during the bug backtrace? This improves the analysis of crashed significantly. It is recommended to leave the option enabled. This option should only be disabled when addr2line is known to not be working correctly on the machine because, in this case, the malfunctioning addr2line can prevent from generating any backtrace at all."; - config.misc.addr2line.t = CONF_BOOL; - config.misc.addr2line.f = FLAG_ADVANCED_SETTING; - config.misc.addr2line.d.b = true; + conf->misc.addr2line.k = "misc.addr2line"; + conf->misc.addr2line.h = "Should FTL translate its own stack addresses into code lines during the bug backtrace? This improves the analysis of crashed significantly. It is recommended to leave the option enabled. This option should only be disabled when addr2line is known to not be working correctly on the machine because, in this case, the malfunctioning addr2line can prevent from generating any backtrace at all."; + conf->misc.addr2line.t = CONF_BOOL; + conf->misc.addr2line.f = FLAG_ADVANCED_SETTING; + conf->misc.addr2line.d.b = true; // sub-struct misc.check - config.misc.check.load.k = "misc.check.load"; - config.misc.check.load.h = "Pi-hole is very lightweight on resources. Nevertheless, this does not mean that you should run Pi-hole on a server that is otherwise extremely busy as queuing on the system can lead to unnecessary delays in DNS operation as the system becomes less and less usable as the system load increases because all resources are permanently in use. To account for this, FTL regularly checks the system load. To bring this to your attention, FTL warns about excessive load when the 15 minute system load average exceeds the number of cores.\n This check can be disabled with this setting."; - config.misc.check.load.t = CONF_BOOL; - config.misc.check.load.d.b = true; + conf->misc.check.load.k = "misc.check.load"; + conf->misc.check.load.h = "Pi-hole is very lightweight on resources. Nevertheless, this does not mean that you should run Pi-hole on a server that is otherwise extremely busy as queuing on the system can lead to unnecessary delays in DNS operation as the system becomes less and less usable as the system load increases because all resources are permanently in use. To account for this, FTL regularly checks the system load. To bring this to your attention, FTL warns about excessive load when the 15 minute system load average exceeds the number of cores.\n This check can be disabled with this setting."; + conf->misc.check.load.t = CONF_BOOL; + conf->misc.check.load.d.b = true; - config.misc.check.disk.k = "misc.check.disk"; - config.misc.check.disk.h = "FTL stores its long-term history in a database file on disk. Furthermore, FTL stores log files. By default, FTL warns if usage of the disk holding any crucial file exceeds 90%. You can set any integer limit between 0 to 100 (interpreted as percentages) where 0 means that checking of disk usage is disabled."; - config.misc.check.disk.t = CONF_UINT; - config.misc.check.disk.d.ui = 90; + conf->misc.check.disk.k = "misc.check.disk"; + conf->misc.check.disk.h = "FTL stores its long-term history in a database file on disk. Furthermore, FTL stores log files. By default, FTL warns if usage of the disk holding any crucial file exceeds 90%. You can set any integer limit between 0 to 100 (interpreted as percentages) where 0 means that checking of disk usage is disabled."; + conf->misc.check.disk.t = CONF_UINT; + conf->misc.check.disk.d.ui = 90; - config.misc.check.shmem.k = "misc.check.shmem"; - config.misc.check.shmem.h = "FTL stores history in shared memory to allow inter-process communication with forked dedicated TCP workers. If FTL runs out of memory, it cannot continue to work as queries cannot be analyzed any further. Hence, FTL checks if enough shared memory is available on your system and warns you if this is not the case.\n By default, FTL warns if the shared-memory usage exceeds 90%. You can set any integer limit between 0 to 100 (interpreted as percentages) where 0 means that checking of shared-memory usage is disabled."; - config.misc.check.shmem.t = CONF_UINT; - config.misc.check.shmem.d.ui = 90; + conf->misc.check.shmem.k = "misc.check.shmem"; + conf->misc.check.shmem.h = "FTL stores history in shared memory to allow inter-process communication with forked dedicated TCP workers. If FTL runs out of memory, it cannot continue to work as queries cannot be analyzed any further. Hence, FTL checks if enough shared memory is available on your system and warns you if this is not the case.\n By default, FTL warns if the shared-memory usage exceeds 90%. You can set any integer limit between 0 to 100 (interpreted as percentages) where 0 means that checking of shared-memory usage is disabled."; + conf->misc.check.shmem.t = CONF_UINT; + conf->misc.check.shmem.d.ui = 90; // struct debug - config.debug.database.k = "debug.database"; - config.debug.database.h = "Print debugging information about database actions. This prints performed SQL statements as well as some general information such as the time it took to store the queries and how many have been saved to the database."; - config.debug.database.t = CONF_BOOL; - config.debug.database.f = FLAG_ADVANCED_SETTING; - config.debug.database.d.b = false; + conf->debug.database.k = "debug.database"; + conf->debug.database.h = "Print debugging information about database actions. This prints performed SQL statements as well as some general information such as the time it took to store the queries and how many have been saved to the database."; + conf->debug.database.t = CONF_BOOL; + conf->debug.database.f = FLAG_ADVANCED_SETTING; + conf->debug.database.d.b = false; - config.debug.networking.k = "debug.networking"; - config.debug.networking.h = "Prints a list of the detected interfaces on the startup of pihole-FTL. Also, prints whether these interfaces are IPv4 or IPv6 interfaces."; - config.debug.networking.t = CONF_BOOL; - config.debug.networking.f = FLAG_ADVANCED_SETTING; - config.debug.networking.d.b = false; + conf->debug.networking.k = "debug.networking"; + conf->debug.networking.h = "Prints a list of the detected interfaces on the startup of pihole-FTL. Also, prints whether these interfaces are IPv4 or IPv6 interfaces."; + conf->debug.networking.t = CONF_BOOL; + conf->debug.networking.f = FLAG_ADVANCED_SETTING; + conf->debug.networking.d.b = false; - config.debug.locks.k = "debug.locks"; - config.debug.locks.h = "Print information about shared memory locks. Messages will be generated when waiting, obtaining, and releasing a lock."; - config.debug.locks.t = CONF_BOOL; - config.debug.locks.f = FLAG_ADVANCED_SETTING; - config.debug.locks.d.b = false; + conf->debug.locks.k = "debug.locks"; + conf->debug.locks.h = "Print information about shared memory locks. Messages will be generated when waiting, obtaining, and releasing a lock."; + conf->debug.locks.t = CONF_BOOL; + conf->debug.locks.f = FLAG_ADVANCED_SETTING; + conf->debug.locks.d.b = false; - config.debug.queries.k = "debug.queries"; - config.debug.queries.h = "Print extensive query information (domains, types, replies, etc.). This has always been part of the legacy debug mode of pihole-FTL."; - config.debug.queries.t = CONF_BOOL; - config.debug.queries.f = FLAG_ADVANCED_SETTING; - config.debug.queries.d.b = false; + conf->debug.queries.k = "debug.queries"; + conf->debug.queries.h = "Print extensive query information (domains, types, replies, etc.). This has always been part of the legacy debug mode of pihole-FTL."; + conf->debug.queries.t = CONF_BOOL; + conf->debug.queries.f = FLAG_ADVANCED_SETTING; + conf->debug.queries.d.b = false; - config.debug.flags.k = "debug.flags"; - config.debug.flags.h = "Print flags of queries received by the DNS hooks. Only effective when DEBUG_QUERIES is enabled as well."; - config.debug.flags.t = CONF_BOOL; - config.debug.flags.f = FLAG_ADVANCED_SETTING; - config.debug.flags.d.b = false; + conf->debug.flags.k = "debug.flags"; + conf->debug.flags.h = "Print flags of queries received by the DNS hooks. Only effective when DEBUG_QUERIES is enabled as well."; + conf->debug.flags.t = CONF_BOOL; + conf->debug.flags.f = FLAG_ADVANCED_SETTING; + conf->debug.flags.d.b = false; - config.debug.shmem.k = "debug.shmem"; - config.debug.shmem.h = "Print information about shared memory buffers. Messages are either about creating or enlarging shmem objects or string injections."; - config.debug.shmem.t = CONF_BOOL; - config.debug.shmem.f = FLAG_ADVANCED_SETTING; - config.debug.shmem.d.b = false; + conf->debug.shmem.k = "debug.shmem"; + conf->debug.shmem.h = "Print information about shared memory buffers. Messages are either about creating or enlarging shmem objects or string injections."; + conf->debug.shmem.t = CONF_BOOL; + conf->debug.shmem.f = FLAG_ADVANCED_SETTING; + conf->debug.shmem.d.b = false; - config.debug.gc.k = "debug.gc"; - config.debug.gc.h = "Print information about garbage collection (GC): What is to be removed, how many have been removed and how long did GC take."; - config.debug.gc.t = CONF_BOOL; - config.debug.gc.f = FLAG_ADVANCED_SETTING; - config.debug.gc.d.b = false; + conf->debug.gc.k = "debug.gc"; + conf->debug.gc.h = "Print information about garbage collection (GC): What is to be removed, how many have been removed and how long did GC take."; + conf->debug.gc.t = CONF_BOOL; + conf->debug.gc.f = FLAG_ADVANCED_SETTING; + conf->debug.gc.d.b = false; - config.debug.arp.k = "debug.arp"; - config.debug.arp.h = "Print information about ARP table processing: How long did parsing take, whether read MAC addresses are valid, and if the macvendor.db file exists."; - config.debug.arp.t = CONF_BOOL; - config.debug.arp.f = FLAG_ADVANCED_SETTING; - config.debug.arp.d.b = false; + conf->debug.arp.k = "debug.arp"; + conf->debug.arp.h = "Print information about ARP table processing: How long did parsing take, whether read MAC addresses are valid, and if the macvendor.db file exists."; + conf->debug.arp.t = CONF_BOOL; + conf->debug.arp.f = FLAG_ADVANCED_SETTING; + conf->debug.arp.d.b = false; - config.debug.regex.k = "debug.regex"; - config.debug.regex.h = "Controls if FTLDNS should print extended details about regex matching into FTL.log."; - config.debug.regex.t = CONF_BOOL; - config.debug.regex.f = FLAG_ADVANCED_SETTING; - config.debug.regex.d.b = false; + conf->debug.regex.k = "debug.regex"; + conf->debug.regex.h = "Controls if FTLDNS should print extended details about regex matching into FTL.log."; + conf->debug.regex.t = CONF_BOOL; + conf->debug.regex.f = FLAG_ADVANCED_SETTING; + conf->debug.regex.d.b = false; - config.debug.api.k = "debug.api"; - config.debug.api.h = "Print extra debugging information during telnet API calls. Currently only used to send extra information when getting all queries."; - config.debug.api.t = CONF_BOOL; - config.debug.api.f = FLAG_ADVANCED_SETTING; - config.debug.api.d.b = false; + conf->debug.api.k = "debug.api"; + conf->debug.api.h = "Print extra debugging information during telnet API calls. Currently only used to send extra information when getting all queries."; + conf->debug.api.t = CONF_BOOL; + conf->debug.api.f = FLAG_ADVANCED_SETTING; + conf->debug.api.d.b = false; - config.debug.overtime.k = "debug.overtime"; - config.debug.overtime.h = "Print information about overTime memory operations, such as initializing or moving overTime slots."; - config.debug.overtime.t = CONF_BOOL; - config.debug.overtime.f = FLAG_ADVANCED_SETTING; - config.debug.overtime.d.b = false; + conf->debug.overtime.k = "debug.overtime"; + conf->debug.overtime.h = "Print information about overTime memory operations, such as initializing or moving overTime slots."; + conf->debug.overtime.t = CONF_BOOL; + conf->debug.overtime.f = FLAG_ADVANCED_SETTING; + conf->debug.overtime.d.b = false; - config.debug.status.k = "debug.status"; - config.debug.status.h = "Print information about status changes for individual queries. This can be useful to identify unexpected unknown queries."; - config.debug.status.t = CONF_BOOL; - config.debug.status.f = FLAG_ADVANCED_SETTING; - config.debug.status.d.b = false; + conf->debug.status.k = "debug.status"; + conf->debug.status.h = "Print information about status changes for individual queries. This can be useful to identify unexpected unknown queries."; + conf->debug.status.t = CONF_BOOL; + conf->debug.status.f = FLAG_ADVANCED_SETTING; + conf->debug.status.d.b = false; - config.debug.caps.k = "debug.caps"; - config.debug.caps.h = "Print information about capabilities granted to the pihole-FTL process. The current capabilities are printed on receipt of SIGHUP, i.e., the current set of capabilities can be queried without restarting pihole-FTL (by setting DEBUG_CAPS=true and thereafter sending killall -HUP pihole-FTL)."; - config.debug.caps.t = CONF_BOOL; - config.debug.caps.f = FLAG_ADVANCED_SETTING; - config.debug.caps.d.b = false; + conf->debug.caps.k = "debug.caps"; + conf->debug.caps.h = "Print information about capabilities granted to the pihole-FTL process. The current capabilities are printed on receipt of SIGHUP, i.e., the current set of capabilities can be queried without restarting pihole-FTL (by setting DEBUG_CAPS=true and thereafter sending killall -HUP pihole-FTL)."; + conf->debug.caps.t = CONF_BOOL; + conf->debug.caps.f = FLAG_ADVANCED_SETTING; + conf->debug.caps.d.b = false; - config.debug.dnssec.k = "debug.dnssec"; - config.debug.dnssec.h = "Print information about DNSSEC activity"; - config.debug.dnssec.t = CONF_BOOL; - config.debug.dnssec.f = FLAG_ADVANCED_SETTING; - config.debug.dnssec.d.b = false; + conf->debug.dnssec.k = "debug.dnssec"; + conf->debug.dnssec.h = "Print information about DNSSEC activity"; + conf->debug.dnssec.t = CONF_BOOL; + conf->debug.dnssec.f = FLAG_ADVANCED_SETTING; + conf->debug.dnssec.d.b = false; - config.debug.vectors.k = "debug.vectors"; - config.debug.vectors.h = "FTL uses dynamically allocated vectors for various tasks. This config option enables extensive debugging information such as information about allocation, referencing, deletion, and appending."; - config.debug.vectors.t = CONF_BOOL; - config.debug.vectors.f = FLAG_ADVANCED_SETTING; - config.debug.vectors.d.b = false; + conf->debug.vectors.k = "debug.vectors"; + conf->debug.vectors.h = "FTL uses dynamically allocated vectors for various tasks. This config option enables extensive debugging information such as information about allocation, referencing, deletion, and appending."; + conf->debug.vectors.t = CONF_BOOL; + conf->debug.vectors.f = FLAG_ADVANCED_SETTING; + conf->debug.vectors.d.b = false; - config.debug.resolver.k = "debug.resolver"; - config.debug.resolver.h = "Extensive information about hostname resolution like which DNS servers are used in the first and second hostname resolving tries (only affecting internally generated PTR queries)."; - config.debug.resolver.t = CONF_BOOL; - config.debug.resolver.f = FLAG_ADVANCED_SETTING; - config.debug.resolver.d.b = false; + conf->debug.resolver.k = "debug.resolver"; + conf->debug.resolver.h = "Extensive information about hostname resolution like which DNS servers are used in the first and second hostname resolving tries (only affecting internally generated PTR queries)."; + conf->debug.resolver.t = CONF_BOOL; + conf->debug.resolver.f = FLAG_ADVANCED_SETTING; + conf->debug.resolver.d.b = false; - config.debug.edns0.k = "debug.edns0"; - config.debug.edns0.h = "Print debugging information about received EDNS(0) data."; - config.debug.edns0.t = CONF_BOOL; - config.debug.edns0.f = FLAG_ADVANCED_SETTING; - config.debug.edns0.d.b = false; + conf->debug.edns0.k = "debug.edns0"; + conf->debug.edns0.h = "Print debugging information about received EDNS(0) data."; + conf->debug.edns0.t = CONF_BOOL; + conf->debug.edns0.f = FLAG_ADVANCED_SETTING; + conf->debug.edns0.d.b = false; - config.debug.clients.k = "debug.clients"; - config.debug.clients.h = "Log various important client events such as change of interface (e.g., client switching from WiFi to wired or VPN connection), as well as extensive reporting about how clients were assigned to its groups."; - config.debug.clients.t = CONF_BOOL; - config.debug.clients.f = FLAG_ADVANCED_SETTING; - config.debug.clients.d.b = false; + conf->debug.clients.k = "debug.clients"; + conf->debug.clients.h = "Log various important client events such as change of interface (e.g., client switching from WiFi to wired or VPN connection), as well as extensive reporting about how clients were assigned to its groups."; + conf->debug.clients.t = CONF_BOOL; + conf->debug.clients.f = FLAG_ADVANCED_SETTING; + conf->debug.clients.d.b = false; - config.debug.aliasclients.k = "debug.aliasclients"; - config.debug.aliasclients.h = "Log information related to alias-client processing."; - config.debug.aliasclients.t = CONF_BOOL; - config.debug.aliasclients.f = FLAG_ADVANCED_SETTING; - config.debug.aliasclients.d.b = false; + conf->debug.aliasclients.k = "debug.aliasclients"; + conf->debug.aliasclients.h = "Log information related to alias-client processing."; + conf->debug.aliasclients.t = CONF_BOOL; + conf->debug.aliasclients.f = FLAG_ADVANCED_SETTING; + conf->debug.aliasclients.d.b = false; - config.debug.events.k = "debug.events"; - config.debug.events.h = "Log information regarding FTL's embedded event handling queue."; - config.debug.events.t = CONF_BOOL; - config.debug.events.f = FLAG_ADVANCED_SETTING; - config.debug.events.d.b = false; + conf->debug.events.k = "debug.events"; + conf->debug.events.h = "Log information regarding FTL's embedded event handling queue."; + conf->debug.events.t = CONF_BOOL; + conf->debug.events.f = FLAG_ADVANCED_SETTING; + conf->debug.events.d.b = false; - config.debug.helper.k = "debug.helper"; - config.debug.helper.h = "Log information about script helpers, e.g., due to dhcp-script."; - config.debug.helper.t = CONF_BOOL; - config.debug.helper.f = FLAG_ADVANCED_SETTING; - config.debug.helper.d.b = false; + conf->debug.helper.k = "debug.helper"; + conf->debug.helper.h = "Log information about script helpers, e.g., due to dhcp-script."; + conf->debug.helper.t = CONF_BOOL; + conf->debug.helper.f = FLAG_ADVANCED_SETTING; + conf->debug.helper.d.b = false; - config.debug.config.k = "debug.config"; - config.debug.config.h = "Print config parsing details"; - config.debug.config.t = CONF_BOOL; - config.debug.config.f = FLAG_ADVANCED_SETTING; - config.debug.config.d.b = false; + conf->debug.config.k = "debug.config"; + conf->debug.config.h = "Print config parsing details"; + conf->debug.config.t = CONF_BOOL; + conf->debug.config.f = FLAG_ADVANCED_SETTING; + conf->debug.config.d.b = false; - config.debug.extra.k = "debug.extra"; - config.debug.extra.h = "Temporary flag that may print additional information. This debug flag is meant to be used whenever needed for temporary investigations. The logged content may change without further notice at any time."; - config.debug.extra.t = CONF_BOOL; - config.debug.extra.f = FLAG_ADVANCED_SETTING; - config.debug.extra.d.b = false; + conf->debug.extra.k = "debug.extra"; + conf->debug.extra.h = "Temporary flag that may print additional information. This debug flag is meant to be used whenever needed for temporary investigations. The logged content may change without further notice at any time."; + conf->debug.extra.t = CONF_BOOL; + conf->debug.extra.f = FLAG_ADVANCED_SETTING; + conf->debug.extra.d.b = false; - config.debug.reserved.k = "debug.reserved"; - config.debug.reserved.h = "Reserved debug flag"; - config.debug.reserved.t = CONF_BOOL; - config.debug.reserved.f = FLAG_ADVANCED_SETTING; - config.debug.reserved.d.b = false; + conf->debug.reserved.k = "debug.reserved"; + conf->debug.reserved.h = "Reserved debug flag"; + conf->debug.reserved.t = CONF_BOOL; + conf->debug.reserved.f = FLAG_ADVANCED_SETTING; + conf->debug.reserved.d.b = false; // Post-processing: // Initialize and verify config data @@ -1105,7 +1111,7 @@ void initConfig(void) struct conf_item *conf_item = get_conf_item(&config, i); // Initialize config value with default one for all *except* the log file path - if(conf_item != &config.files.log.ftl) + if(conf_item != &conf->files.log.ftl) { if(conf_item->t == CONF_JSON_STRING_ARRAY) // JSON objects really need to be duplicated as the config @@ -1138,10 +1144,10 @@ void initConfig(void) } } -void readFTLconf(const bool rewrite) +void readFTLconf(struct config *conf, const bool rewrite) { // First try to read TOML config file - if(readFTLtoml(rewrite)) + if(readFTLtoml(conf, NULL, rewrite)) { // If successful, we write the config file back to disk // to ensure that all options are present and comments @@ -1149,7 +1155,7 @@ void readFTLconf(const bool rewrite) if(rewrite) { writeFTLtoml(true); - write_dnsmasq_config(&config, false, NULL); + write_dnsmasq_config(conf, false, NULL); write_custom_list(); } return; @@ -1158,7 +1164,7 @@ void readFTLconf(const bool rewrite) // On error, try to read legacy (pre-v6.0) config file. If successful, // we move the legacy config file out of our way const char *path = ""; - if((path = readFTLlegacy()) != NULL) + if((path = readFTLlegacy(conf)) != NULL) { const char *target = "/etc/pihole/pihole-FTL.conf.bck"; log_info("Moving %s to %s", path, target); @@ -1187,7 +1193,7 @@ void readFTLconf(const bool rewrite) // Initialize the TOML config file writeFTLtoml(true); - write_dnsmasq_config(&config, false, NULL); + write_dnsmasq_config(conf, false, NULL); write_custom_list(); } @@ -1207,7 +1213,7 @@ bool getLogFilePath(void) // Check if the config file contains a different path if(!getLogFilePathTOML()) - return getLogFilePathLegacy(NULL); + return getLogFilePathLegacy(&config, NULL); return true; } @@ -1261,4 +1267,54 @@ const char * __attribute__ ((const)) get_conf_type_str(const enum conf_type type default: return "unknown"; } -} \ No newline at end of file +} + +void replace_config(struct config *newconf) +{ + // Lock shared memory + lock_shm(); + + // Backup old config struct (so we can free it) + struct config old_conf; + memcpy(&old_conf, &config, sizeof(struct config)); + + // Replace old config struct by changed one atomically + memcpy(&config, newconf, sizeof(struct config)); + + // Free old backup struct + free_config(&old_conf); + + // Unlock shared memory + unlock_shm(); +} + +void reread_config(void) +{ + struct config conf_copy; + duplicate_config(&conf_copy, &config); + + // Read TOML config file + if(readFTLtoml(&conf_copy, NULL, true)) + { + // Install new configuration + log_debug(DEBUG_CONFIG, "Loaded configuration is valid, installing it"); + replace_config(&conf_copy); + } + else + { + // New configuration is invalid, restore old one + log_debug(DEBUG_CONFIG, "Loaded configuration is invalid, restoring old one"); + free_config(&conf_copy); + } + + // Write the config file back to disk to ensure that all options and + // comments about options deviating from the default are present + writeFTLtoml(true); + + // We do not write the dnsmasq config file here as this is done on every + // restart and changes would have no effect here + + // However, we do need to write the custom.list file as this file can change + // at any time and is automatically reloaded by dnsmasq + write_custom_list(); +} diff --git a/src/config/config.h b/src/config/config.h index fa962c53..fc01ddcc 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -276,19 +276,21 @@ extern struct config config; // Defined in config.c void set_all_debug(const bool status); -void initConfig(void); -void readFTLconf(const bool rewrite); +void initConfig(struct config *conf); +void readFTLconf(struct config *conf, const bool rewrite); bool getLogFilePath(void); struct conf_item *get_conf_item(struct config *conf, const unsigned int n); struct conf_item *get_debug_item(const enum debug_flag debug); unsigned int config_path_depth(char **paths) __attribute__ ((pure)); -void duplicate_config(struct config *conf); +void duplicate_config(struct config *dst, struct config *src); void free_config(struct config *conf); bool compare_config_item(const struct conf_item *conf_item1, const struct conf_item *conf_item2); char **gen_config_path(const char *pathin, const char delim); void free_config_path(char **paths); bool check_paths_equal(char **paths1, char **paths2, unsigned int max_level) __attribute__ ((pure)); const char *get_conf_type_str(const enum conf_type type) __attribute__ ((const)); +void replace_config(struct config *newconf); +void reread_config(void); // Defined in toml_reader.c bool getPrivacyLevel(void); @@ -301,7 +303,7 @@ void set_blockingstatus(bool enabled); // Add enum items with descriptions #define CONFIG_ADD_ENUM_OPTIONS(json, opts)({ \ json = cJSON_CreateArray(); \ - for(unsigned int i = 0; i < sizeof(opts)/sizeof(*opts); i++) \ + for(unsigned int i = 0; i < ArraySize(opts); i++) \ { \ cJSON *jopt = cJSON_CreateObject(); \ if(opts[i].item[0] >= '0' && opts[i].item[0] <= '9') \ @@ -327,7 +329,7 @@ void set_blockingstatus(bool enabled); cJSON_AddItemToArray(array, cJSON_Duplicate(item, true)); \ } \ output = cJSON_PrintUnformatted(array); \ - cJSON_free(array); \ + cJSON_Delete(array); \ } \ else if(cJSON_IsString(json)) \ { \ diff --git a/src/config/dnsmasq_config.c b/src/config/dnsmasq_config.c index 410c43ee..4764faff 100644 --- a/src/config/dnsmasq_config.c +++ b/src/config/dnsmasq_config.c @@ -401,7 +401,7 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_ { fputs("# DHCP server setting\n", pihole_conf); fputs("dhcp-authoritative\n", pihole_conf); - fputs("dhcp-leasefile=/etc/pihole/dhcp.leases\n", pihole_conf); + fputs("dhcp-leasefile="DHCPLEASESFILE"\n", pihole_conf); fprintf(pihole_conf, "dhcp-range=%s,%s,%s\n", conf->dhcp.start.v.s, conf->dhcp.end.v.s, diff --git a/src/config/dnsmasq_config.h b/src/config/dnsmasq_config.h index 33e435dc..8552169c 100644 --- a/src/config/dnsmasq_config.h +++ b/src/config/dnsmasq_config.h @@ -27,5 +27,6 @@ bool write_custom_list(void); #define DNSMASQ_STATIC_LEASES "/etc/pihole/04-pihole-static-dhcp.conf" #define DNSMASQ_CNAMES "/etc/pihole/05-pihole-custom-cname.conf" #define DNSMASQ_CUSTOM_LIST "/etc/pihole/custom.list" +#define DHCPLEASESFILE "/etc/pihole/dhcp.leases" #endif //DNSMASQ_CONFIG_H diff --git a/src/config/legacy_reader.c b/src/config/legacy_reader.c index e02d2bc8..d1103401 100644 --- a/src/config/legacy_reader.c +++ b/src/config/legacy_reader.c @@ -49,7 +49,7 @@ static FILE * __attribute__((nonnull(1), malloc, warn_unused_result)) openFTLcon return fp; } -bool getLogFilePathLegacy(FILE *fp) +bool getLogFilePathLegacy(struct config *conf, FILE *fp) { const char *path = NULL; if(fp == NULL) @@ -67,13 +67,13 @@ bool getLogFilePathLegacy(FILE *fp) if(buffer == NULL) { // Use standard path if no custom path was obtained from the config file - config.files.log.ftl.v.s = strdup("/var/log/pihole/FTL.log"); - config.files.log.ftl.t = CONF_STRING_ALLOCATED; + conf->files.log.ftl.v.s = strdup("/var/log/pihole/FTL.log"); + conf->files.log.ftl.t = CONF_STRING_ALLOCATED; // Test if memory allocation was successful - if(config.files.log.ftl.v.s == NULL) + if(conf->files.log.ftl.v.s == NULL) { - printf("FATAL: Allocating memory for config.files.log.ftl.v.s failed (%s, %i). Exiting.", + printf("FATAL: Allocating memory for conf->files.log.ftl.v.s failed (%s, %i). Exiting.", strerror(errno), errno); exit(EXIT_FAILURE); } @@ -82,24 +82,24 @@ bool getLogFilePathLegacy(FILE *fp) else if(sscanf(buffer, "%127ms", &val_buffer) == 0) { // Free previously allocated memory (if any) - if(config.files.log.ftl.t == CONF_STRING_ALLOCATED) - free(config.files.log.ftl.v.s); + if(conf->files.log.ftl.t == CONF_STRING_ALLOCATED) + free(conf->files.log.ftl.v.s); // Set empty file string - config.files.log.ftl.v.s = NULL; - config.files.log.ftl.t = CONF_STRING; + conf->files.log.ftl.v.s = NULL; + conf->files.log.ftl.t = CONF_STRING; log_info("Using syslog facility"); } if(val_buffer) { // Free previously allocated memory (if any) - if(config.files.log.ftl.t == CONF_STRING_ALLOCATED) - free(config.files.log.ftl.v.s); + if(conf->files.log.ftl.t == CONF_STRING_ALLOCATED) + free(conf->files.log.ftl.v.s); // Set string - config.files.log.ftl.v.s = val_buffer; - config.files.log.ftl.t = CONF_STRING_ALLOCATED; + conf->files.log.ftl.v.s = val_buffer; + conf->files.log.ftl.t = CONF_STRING_ALLOCATED; } fclose(fp); @@ -107,7 +107,7 @@ bool getLogFilePathLegacy(FILE *fp) } // Returns which file was read -const char *readFTLlegacy(void) +const char *readFTLlegacy(struct config *conf) { char *buffer; const char *path = NULL; @@ -131,18 +131,18 @@ const char *readFTLlegacy(void) // Only use valid values if(value == -1 || value >= 0) - config.database.maxDBdays.v.i = value; + conf->database.maxDBdays.v.i = value; } // RESOLVE_IPV6 // defaults to: Yes buffer = parseFTLconf(fp, "RESOLVE_IPV6"); - parseBool(buffer, &config.resolver.resolveIPv6.v.b); + parseBool(buffer, &conf->resolver.resolveIPv6.v.b); // RESOLVE_IPV4 // defaults to: Yes buffer = parseFTLconf(fp, "RESOLVE_IPV4"); - parseBool(buffer, &config.resolver.resolveIPv4.v.b); + parseBool(buffer, &conf->resolver.resolveIPv4.v.b); // DBINTERVAL // How often do we store queries in FTL's database [minutes]? @@ -156,25 +156,25 @@ const char *readFTLlegacy(void) // - larger than 0.1min (6sec), and // - smaller than 1440.0min (once a day) if(fvalue >= 0.1f && fvalue <= 1440.0f) - config.database.DBinterval.v.ui = (int)(fvalue * 60); + conf->database.DBinterval.v.ui = (int)(fvalue * 60); // DBFILE // defaults to: "/etc/pihole/pihole-FTL.db" buffer = parseFTLconf(fp, "DBFILE"); // Use sscanf() to obtain filename from config file parameter only if buffer != NULL - if(!(buffer != NULL && sscanf(buffer, "%127ms", &config.files.database.v.s))) + if(!(buffer != NULL && sscanf(buffer, "%127ms", &conf->files.database.v.s))) { // Use standard path if no custom path was obtained from the config file - config.files.database.v.s = config.files.database.d.s; + conf->files.database.v.s = conf->files.database.d.s; } - if(config.files.database.v.s == NULL || strlen(config.files.database.v.s) == 0) + if(conf->files.database.v.s == NULL || strlen(conf->files.database.v.s) == 0) { // Use standard path if path was set to zero but override // MAXDBDAYS=0 to ensure no queries are stored in the database - config.files.database.v.s = config.files.database.d.s; - config.database.maxDBdays.v.i = 0; + conf->files.database.v.s = conf->files.database.d.s; + conf->database.maxDBdays.v.i = 0; } // MAXLOGAGE @@ -186,7 +186,7 @@ const char *readFTLlegacy(void) if(buffer != NULL && sscanf(buffer, "%f", &fvalue)) { if(fvalue >= 0.0f && fvalue <= 1.0f*MAXLOGAGE) - config.database.maxHistory.v.ui = (int)(fvalue * 3600); + conf->database.maxHistory.v.ui = (int)(fvalue * 3600); } // PRIVACYLEVEL @@ -204,10 +204,10 @@ const char *readFTLlegacy(void) // ignoreLocalhost // defaults to: false buffer = parseFTLconf(fp, "IGNORE_LOCALHOST"); - parseBool(buffer, &config.dns.ignoreLocalhost.v.b); + parseBool(buffer, &conf->dns.ignoreLocalhost.v.b); if(buffer != NULL && strcasecmp(buffer, "yes") == 0) - config.dns.ignoreLocalhost.v.b = true; + conf->dns.ignoreLocalhost.v.b = true; // BLOCKINGMODE // defaults to: MODE_IP @@ -216,37 +216,37 @@ const char *readFTLlegacy(void) // ANALYZE_ONLY_A_AND_AAAA // defaults to: false buffer = parseFTLconf(fp, "ANALYZE_ONLY_A_AND_AAAA"); - parseBool(buffer, &config.dns.analyzeOnlyAandAAAA.v.b); + parseBool(buffer, &conf->dns.analyzeOnlyAandAAAA.v.b); if(buffer != NULL && strcasecmp(buffer, "true") == 0) - config.dns.analyzeOnlyAandAAAA.v.b = true; + conf->dns.analyzeOnlyAandAAAA.v.b = true; // DBIMPORT // defaults to: Yes buffer = parseFTLconf(fp, "DBIMPORT"); - parseBool(buffer, &config.database.DBimport.v.b); + parseBool(buffer, &conf->database.DBimport.v.b); // PIDFILE - config.files.pid.v.s = getPath(fp, "PIDFILE", config.files.pid.v.s); + conf->files.pid.v.s = getPath(fp, "PIDFILE", conf->files.pid.v.s); // SETUPVARSFILE - config.files.setupVars.v.s = getPath(fp, "SETUPVARSFILE", config.files.setupVars.v.s); + conf->files.setupVars.v.s = getPath(fp, "SETUPVARSFILE", conf->files.setupVars.v.s); // MACVENDORDB - config.files.macvendor.v.s = getPath(fp, "MACVENDORDB", config.files.macvendor.v.s); + conf->files.macvendor.v.s = getPath(fp, "MACVENDORDB", conf->files.macvendor.v.s); // GRAVITYDB - config.files.gravity.v.s = getPath(fp, "GRAVITYDB", config.files.gravity.v.s); + conf->files.gravity.v.s = getPath(fp, "GRAVITYDB", conf->files.gravity.v.s); // PARSE_ARP_CACHE // defaults to: true buffer = parseFTLconf(fp, "PARSE_ARP_CACHE"); - parseBool(buffer, &config.database.network.parseARPcache.v.b); + parseBool(buffer, &conf->database.network.parseARPcache.v.b); // CNAME_DEEP_INSPECT // defaults to: true buffer = parseFTLconf(fp, "CNAME_DEEP_INSPECT"); - parseBool(buffer, &config.dns.CNAMEdeepInspect.v.b); + parseBool(buffer, &conf->dns.CNAMEdeepInspect.v.b); // DELAY_STARTUP // defaults to: zero (seconds) @@ -254,15 +254,15 @@ const char *readFTLlegacy(void) unsigned int unum; if(buffer != NULL && sscanf(buffer, "%u", &unum) && unum > 0 && unum <= 300) - config.misc.delay_startup.v.ui = unum; + conf->misc.delay_startup.v.ui = unum; // BLOCK_ESNI // defaults to: true buffer = parseFTLconf(fp, "BLOCK_ESNI"); - parseBool(buffer, &config.dns.blockESNI.v.b); + parseBool(buffer, &conf->dns.blockESNI.v.b); // WEBROOT - config.webserver.paths.webroot.v.s = getPath(fp, "WEBROOT", config.webserver.paths.webroot.v.s); + conf->webserver.paths.webroot.v.s = getPath(fp, "WEBROOT", conf->webserver.paths.webroot.v.s); // WEBPORT // On which port should FTL's API be listening? @@ -271,12 +271,12 @@ const char *readFTLlegacy(void) value = 0; if(buffer != NULL && strlen(buffer) > 0) - config.webserver.port.v.s = strdup(buffer); + conf->webserver.port.v.s = strdup(buffer); // WEBHOME // From which sub-directory is the web interface served from? // Defaults to: /admin/ (both slashes are needed!) - config.webserver.paths.webhome.v.s = getPath(fp, "WEBHOME", config.webserver.paths.webhome.v.s); + conf->webserver.paths.webhome.v.s = getPath(fp, "WEBHOME", conf->webserver.paths.webhome.v.s); // WEBACL // Default: allow all access @@ -300,12 +300,12 @@ const char *readFTLlegacy(void) // buffer = parseFTLconf(fp, "WEBACL"); if(buffer != NULL) - config.webserver.acl.v.s = strdup(buffer); + conf->webserver.acl.v.s = strdup(buffer); // API_AUTH_FOR_LOCALHOST // defaults to: true buffer = parseFTLconf(fp, "API_AUTH_FOR_LOCALHOST"); - parseBool(buffer, &config.webserver.api.localAPIauth.v.b); + parseBool(buffer, &conf->webserver.api.localAPIauth.v.b); // API_SESSION_TIMEOUT // How long should a session be considered valid after login? @@ -314,18 +314,18 @@ const char *readFTLlegacy(void) value = 0; if(buffer != NULL && sscanf(buffer, "%i", &value) && value > 0) - config.webserver.api.sessionTimeout.v.ui = value; + conf->webserver.api.sessionTimeout.v.ui = value; // API_PRETTY_JSON // defaults to: false buffer = parseFTLconf(fp, "API_PRETTY_JSON"); - parseBool(buffer, &config.webserver.api.prettyJSON.v.b); + parseBool(buffer, &conf->webserver.api.prettyJSON.v.b); // API_ERROR_LOG - config.files.ph7_error.v.s = getPath(fp, "API_ERROR_LOG", config.files.ph7_error.v.s); + conf->files.ph7_error.v.s = getPath(fp, "API_ERROR_LOG", conf->files.ph7_error.v.s); // API_INFO_LOG - config.files.http_info.v.s = getPath(fp, "API_INFO_LOG", config.files.http_info.v.s); + conf->files.http_info.v.s = getPath(fp, "API_INFO_LOG", conf->files.http_info.v.s); // NICE // Shall we change the nice of the current process? @@ -350,7 +350,7 @@ const char *readFTLlegacy(void) if(buffer != NULL && sscanf(buffer, "%i", &ivalue) && ivalue > 0 && ivalue <= 8760) // 8760 days = 24 years - config.database.network.expire.v.ui = ivalue; + conf->database.network.expire.v.ui = ivalue; // NAMES_FROM_NETDB // Should we use the fallback option to try to obtain client names from @@ -361,30 +361,30 @@ const char *readFTLlegacy(void) // device. This behavior can be disabled using NAMES_FROM_NETDB=false // defaults to: true buffer = parseFTLconf(fp, "NAMES_FROM_NETDB"); - parseBool(buffer, &config.resolver.networkNames.v.b); + parseBool(buffer, &conf->resolver.networkNames.v.b); // EDNS0_ECS // Should we overwrite the query source when client information is // provided through EDNS0 client subnet (ECS) information? // defaults to: true buffer = parseFTLconf(fp, "EDNS0_ECS"); - parseBool(buffer, &config.dns.EDNS0ECS.v.b); + parseBool(buffer, &conf->dns.EDNS0ECS.v.b); // REFRESH_HOSTNAMES // defaults to: IPV4 buffer = parseFTLconf(fp, "REFRESH_HOSTNAMES"); if(buffer != NULL && strcasecmp(buffer, "ALL") == 0) - config.resolver.refreshNames.v.refresh_hostnames = REFRESH_ALL; + conf->resolver.refreshNames.v.refresh_hostnames = REFRESH_ALL; else if(buffer != NULL && strcasecmp(buffer, "NONE") == 0) - config.resolver.refreshNames.v.refresh_hostnames = REFRESH_NONE; + conf->resolver.refreshNames.v.refresh_hostnames = REFRESH_NONE; else if(buffer != NULL && strcasecmp(buffer, "UNKNOWN") == 0) - config.resolver.refreshNames.v.refresh_hostnames = REFRESH_UNKNOWN; + conf->resolver.refreshNames.v.refresh_hostnames = REFRESH_UNKNOWN; else - config.resolver.refreshNames.v.refresh_hostnames = REFRESH_IPV4_ONLY; + conf->resolver.refreshNames.v.refresh_hostnames = REFRESH_IPV4_ONLY; // WEBDOMAIN - config.webserver.domain.v.s = getPath(fp, "WEBDOMAIN", config.webserver.domain.v.s); + conf->webserver.domain.v.s = getPath(fp, "WEBDOMAIN", conf->webserver.domain.v.s); // RATE_LIMIT // defaults to: 1000 queries / 60 seconds @@ -393,47 +393,47 @@ const char *readFTLlegacy(void) unsigned int count = 0, interval = 0; if(buffer != NULL && sscanf(buffer, "%u/%u", &count, &interval) == 2) { - config.dns.rateLimit.count.v.ui = count; - config.dns.rateLimit.interval.v.ui = interval; + conf->dns.rateLimit.count.v.ui = count; + conf->dns.rateLimit.interval.v.ui = interval; } // LOCAL_IPV4 // Use a specific IP address instead of automatically detecting the // IPv4 interface address a query arrived on for A hostname queries // defaults to: not set - config.dns.reply.host.overwrite_v4.v.b = false; - config.dns.reply.host.v4.v.in_addr.s_addr = 0; + conf->dns.reply.host.overwrite_v4.v.b = false; + conf->dns.reply.host.v4.v.in_addr.s_addr = 0; buffer = parseFTLconf(fp, "LOCAL_IPV4"); - if(buffer != NULL && inet_pton(AF_INET, buffer, &config.dns.reply.host.v4.v.in_addr)) - config.dns.reply.host.overwrite_v4.v.b = true; + if(buffer != NULL && inet_pton(AF_INET, buffer, &conf->dns.reply.host.v4.v.in_addr)) + conf->dns.reply.host.overwrite_v4.v.b = true; // LOCAL_IPV6 // Use a specific IP address instead of automatically detecting the // IPv6 interface address a query arrived on for AAAA hostname queries // defaults to: not set - config.dns.reply.host.overwrite_v6.v.b = false; - memset(&config.dns.reply.host.v6.v.in6_addr, 0, sizeof(config.dns.reply.host.v6.v.in6_addr)); + conf->dns.reply.host.overwrite_v6.v.b = false; + memset(&conf->dns.reply.host.v6.v.in6_addr, 0, sizeof(conf->dns.reply.host.v6.v.in6_addr)); buffer = parseFTLconf(fp, "LOCAL_IPV6"); - if(buffer != NULL && inet_pton(AF_INET6, buffer, &config.dns.reply.host.v6.v.in6_addr)) - config.dns.reply.host.overwrite_v6.v.b = true; + if(buffer != NULL && inet_pton(AF_INET6, buffer, &conf->dns.reply.host.v6.v.in6_addr)) + conf->dns.reply.host.overwrite_v6.v.b = true; // BLOCK_IPV4 // Use a specific IPv4 address for IP blocking mode replies // defaults to: REPLY_ADDR4 setting - config.dns.reply.blocking.overwrite_v4.v.b = false; - config.dns.reply.blocking.v4.v.in_addr.s_addr = 0; + conf->dns.reply.blocking.overwrite_v4.v.b = false; + conf->dns.reply.blocking.v4.v.in_addr.s_addr = 0; buffer = parseFTLconf(fp, "BLOCK_IPV4"); - if(buffer != NULL && inet_pton(AF_INET, buffer, &config.dns.reply.blocking.v4.v.in_addr)) - config.dns.reply.blocking.overwrite_v4.v.b = true; + if(buffer != NULL && inet_pton(AF_INET, buffer, &conf->dns.reply.blocking.v4.v.in_addr)) + conf->dns.reply.blocking.overwrite_v4.v.b = true; // BLOCK_IPV6 // Use a specific IPv6 address for IP blocking mode replies // defaults to: REPLY_ADDR6 setting - config.dns.reply.blocking.overwrite_v6.v.b = false; - memset(&config.dns.reply.blocking.v6.v.in6_addr, 0, sizeof(config.dns.reply.host.v6.v.in6_addr)); + conf->dns.reply.blocking.overwrite_v6.v.b = false; + memset(&conf->dns.reply.blocking.v6.v.in6_addr, 0, sizeof(conf->dns.reply.host.v6.v.in6_addr)); buffer = parseFTLconf(fp, "BLOCK_IPV6"); - if(buffer != NULL && inet_pton(AF_INET6, buffer, &config.dns.reply.blocking.v6.v.in6_addr)) - config.dns.reply.blocking.overwrite_v6.v.b = true; + if(buffer != NULL && inet_pton(AF_INET6, buffer, &conf->dns.reply.blocking.v6.v.in6_addr)) + conf->dns.reply.blocking.overwrite_v6.v.b = true; // REPLY_ADDR4 (deprecated setting) // Use a specific IP address instead of automatically detecting the @@ -443,16 +443,16 @@ const char *readFTLlegacy(void) buffer = parseFTLconf(fp, "REPLY_ADDR4"); if(buffer != NULL && inet_pton(AF_INET, buffer, &reply_addr4)) { - if(config.dns.reply.host.overwrite_v4.v.b || config.dns.reply.blocking.overwrite_v4.v.b) + if(conf->dns.reply.host.overwrite_v4.v.b || conf->dns.reply.blocking.overwrite_v4.v.b) { log_warn("Ignoring REPLY_ADDR4 as LOCAL_IPV4 or BLOCK_IPV4 has been specified."); } else { - config.dns.reply.host.overwrite_v4.v.b = true; - memcpy(&config.dns.reply.host.v4.v.in_addr, &reply_addr4, sizeof(reply_addr4)); - config.dns.reply.blocking.overwrite_v4.v.b = true; - memcpy(&config.dns.reply.blocking.v4.v.in_addr, &reply_addr4, sizeof(reply_addr4)); + conf->dns.reply.host.overwrite_v4.v.b = true; + memcpy(&conf->dns.reply.host.v4.v.in_addr, &reply_addr4, sizeof(reply_addr4)); + conf->dns.reply.blocking.overwrite_v4.v.b = true; + memcpy(&conf->dns.reply.blocking.v4.v.in_addr, &reply_addr4, sizeof(reply_addr4)); } } @@ -464,16 +464,16 @@ const char *readFTLlegacy(void) buffer = parseFTLconf(fp, "REPLY_ADDR6"); if(buffer != NULL && inet_pton(AF_INET, buffer, &reply_addr6)) { - if(config.dns.reply.host.overwrite_v6.v.b || config.dns.reply.blocking.overwrite_v6.v.b) + if(conf->dns.reply.host.overwrite_v6.v.b || conf->dns.reply.blocking.overwrite_v6.v.b) { log_warn("Ignoring REPLY_ADDR6 as LOCAL_IPV6 or BLOCK_IPV6 has been specified."); } else { - config.dns.reply.host.overwrite_v6.v.b = true; - memcpy(&config.dns.reply.host.v6.v.in6_addr, &reply_addr6, sizeof(reply_addr6)); - config.dns.reply.blocking.overwrite_v6.v.b = true; - memcpy(&config.dns.reply.blocking.v6.v.in6_addr, &reply_addr6, sizeof(reply_addr6)); + conf->dns.reply.host.overwrite_v6.v.b = true; + memcpy(&conf->dns.reply.host.v6.v.in6_addr, &reply_addr6, sizeof(reply_addr6)); + conf->dns.reply.blocking.overwrite_v6.v.b = true; + memcpy(&conf->dns.reply.blocking.v6.v.in6_addr, &reply_addr6, sizeof(reply_addr6)); } } @@ -481,13 +481,13 @@ const char *readFTLlegacy(void) // Should FTL analyze and include automatically generated DNSSEC queries in the Query Log? // defaults to: true buffer = parseFTLconf(fp, "SHOW_DNSSEC"); - parseBool(buffer, &config.dns.showDNSSEC.v.b); + parseBool(buffer, &conf->dns.showDNSSEC.v.b); // MOZILLA_CANARY // Should FTL handle use-application-dns.net specifically and always return NXDOMAIN? // defaults to: true buffer = parseFTLconf(fp, "MOZILLA_CANARY"); - parseBool(buffer, &config.dns.specialDomains.mozillaCanary.v.b); + parseBool(buffer, &conf->dns.specialDomains.mozillaCanary.v.b); // PIHOLE_PTR // Should FTL return "pi.hole" as name for PTR requests to local IP addresses? @@ -498,18 +498,18 @@ const char *readFTLlegacy(void) { if(strcasecmp(buffer, "none") == 0 || strcasecmp(buffer, "false") == 0) - config.dns.piholePTR.v.ptr_type = PTR_NONE; + conf->dns.piholePTR.v.ptr_type = PTR_NONE; else if(strcasecmp(buffer, "hostname") == 0) - config.dns.piholePTR.v.ptr_type = PTR_HOSTNAME; + conf->dns.piholePTR.v.ptr_type = PTR_HOSTNAME; else if(strcasecmp(buffer, "hostnamefqdn") == 0) - config.dns.piholePTR.v.ptr_type = PTR_HOSTNAMEFQDN; + conf->dns.piholePTR.v.ptr_type = PTR_HOSTNAMEFQDN; } // ADDR2LINE // Should FTL try to call addr2line when generating backtraces? // defaults to: true buffer = parseFTLconf(fp, "ADDR2LINE"); - parseBool(buffer, &config.misc.addr2line.v.b); + parseBool(buffer, &conf->misc.addr2line.v.b); // REPLY_WHEN_BUSY // How should FTL handle queries when the gravity database is not available? @@ -519,55 +519,55 @@ const char *readFTLlegacy(void) if(buffer != NULL) { if(strcasecmp(buffer, "DROP") == 0) - config.dns.replyWhenBusy.v.busy_reply = BUSY_DROP; + conf->dns.replyWhenBusy.v.busy_reply = BUSY_DROP; else if(strcasecmp(buffer, "REFUSE") == 0) - config.dns.replyWhenBusy.v.busy_reply = BUSY_REFUSE; + conf->dns.replyWhenBusy.v.busy_reply = BUSY_REFUSE; else if(strcasecmp(buffer, "BLOCK") == 0) - config.dns.replyWhenBusy.v.busy_reply = BUSY_BLOCK; + conf->dns.replyWhenBusy.v.busy_reply = BUSY_BLOCK; } // BLOCK_TTL // defaults to: 2 seconds - config.dns.blockTTL.v.ui = 2; + conf->dns.blockTTL.v.ui = 2; buffer = parseFTLconf(fp, "BLOCK_TTL"); unsigned int uval = 0; if(buffer != NULL && sscanf(buffer, "%u", &uval)) - config.dns.blockTTL.v.ui = uval; + conf->dns.blockTTL.v.ui = uval; // BLOCK_ICLOUD_PR // Should FTL handle the iCloud privacy relay domains specifically and // always return NXDOMAIN?? // defaults to: true buffer = parseFTLconf(fp, "BLOCK_ICLOUD_PR"); - parseBool(buffer, &config.dns.specialDomains.iCloudPrivateRelay.v.b); + parseBool(buffer, &conf->dns.specialDomains.iCloudPrivateRelay.v.b); // CHECK_LOAD // Should FTL check the 15 min average of CPU load and complain if the // load is larger than the number of available CPU cores? // defaults to: true buffer = parseFTLconf(fp, "CHECK_LOAD"); - parseBool(buffer, &config.misc.check.load.v.b); + parseBool(buffer, &conf->misc.check.load.v.b); // CHECK_SHMEM // Limit above which FTL should complain about a shared-memory shortage // defaults to: 90% - config.misc.check.shmem.v.ui = 90; + conf->misc.check.shmem.v.ui = 90; buffer = parseFTLconf(fp, "CHECK_SHMEM"); if(buffer != NULL && sscanf(buffer, "%i", &ivalue) && ivalue >= 0 && ivalue <= 100) - config.misc.check.shmem.v.ui = ivalue; + conf->misc.check.shmem.v.ui = ivalue; // CHECK_DISK // Limit above which FTL should complain about disk shortage for checked files // defaults to: 90% - config.misc.check.disk.v.ui = 90; + conf->misc.check.disk.v.ui = 90; buffer = parseFTLconf(fp, "CHECK_DISK"); if(buffer != NULL && sscanf(buffer, "%i", &ivalue) && ivalue >= 0 && ivalue <= 100) - config.misc.check.disk.v.ui = ivalue; + conf->misc.check.disk.v.ui = ivalue; // Read DEBUG_... setting from pihole-FTL.conf // This option should be the last one as it causes diff --git a/src/config/legacy_reader.h b/src/config/legacy_reader.h index 6b6dac1b..e460d1af 100644 --- a/src/config/legacy_reader.h +++ b/src/config/legacy_reader.h @@ -10,7 +10,9 @@ #ifndef LEGACY_READER_H #define LEGACY_READER_H -bool getLogFilePathLegacy(FILE *fp); -const char *readFTLlegacy(void); +#include "config/config.h" + +bool getLogFilePathLegacy(struct config *conf, FILE *fp); +const char *readFTLlegacy(struct config *conf); #endif //LEGACY_READER_H diff --git a/src/config/toml_helper.c b/src/config/toml_helper.c index 6e718a41..e2eb240d 100644 --- a/src/config/toml_helper.c +++ b/src/config/toml_helper.c @@ -578,7 +578,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t case CONF_JSON_STRING_ARRAY: { // Free previously allocated JSON array - cJSON_free(conf_item->v.json); + cJSON_Delete(conf_item->v.json); conf_item->v.json = cJSON_CreateArray(); // Parse TOML array and generate a JSON array const toml_array_t *array = toml_array_in(toml, key); diff --git a/src/config/toml_reader.c b/src/config/toml_reader.c index 3277eb4a..a7b504b4 100644 --- a/src/config/toml_reader.c +++ b/src/config/toml_reader.c @@ -10,7 +10,6 @@ #include "FTL.h" #include "toml_reader.h" -#include "config.h" #include "setupVars.h" #include "log.h" // getprio(), setprio() @@ -20,7 +19,6 @@ // INT_MAX #include -#include "tomlc99/toml.h" #include "../datastructure.h" // openFTLtoml() #include "toml_helper.h" @@ -29,30 +27,37 @@ static toml_table_t *parseTOML(void); static void reportDebugConfig(void); -bool readFTLtoml(const bool verbose) +bool readFTLtoml(struct config *conf, toml_table_t *toml, const bool verbose) { - // Parse lines in the config file - toml_table_t *conf = parseTOML(); - if(!conf) - return false; + // Parse lines in the config file if we did not receive a pointer to a TOML + // table (e.g. from an imported Teleporter file) + bool external = true; + if(toml == NULL) + { + external = false; + toml = parseTOML(); + if(!toml) + return false; - // Initialize config with default values - initConfig(); + // Initialize config with default values + initConfig(conf); + } // Try to read debug config. This is done before the full config // parsing to allow for debug output further down - toml_table_t *conf_debug = toml_table_in(conf, "debug"); + toml_table_t *conf_debug = toml_table_in(toml, "debug"); if(conf_debug) readTOMLvalue(&config.debug.config, "config", conf_debug); set_debug_flags(); - log_debug(DEBUG_CONFIG, "Reading TOML config file: full config"); + log_debug(DEBUG_CONFIG, "Reading %s TOML config file: full config", + external ? "external" : "default"); // Read all known config items for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { // Get pointer to memory location of this conf_item - struct conf_item *conf_item = get_conf_item(&config, i); + struct conf_item *conf_item = get_conf_item(conf, i); // Get config path depth unsigned int level = config_path_depth(conf_item->p); @@ -63,7 +68,7 @@ bool readFTLtoml(const bool verbose) for(unsigned int j = 0; j < level-1; j++) { // Get table at this level - table[j] = toml_table_in(j > 0 ? table[j-1] : conf, conf_item->p[j]); + table[j] = toml_table_in(j > 0 ? table[j-1] : toml, conf_item->p[j]); if(!table[j]) { log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST", conf_item->k); @@ -86,7 +91,7 @@ bool readFTLtoml(const bool verbose) reportDebugConfig(); // Free memory allocated by the TOML parser and return success - toml_free(conf); + toml_free(toml); return true; } @@ -257,4 +262,4 @@ static void reportDebugConfig(void) log_debug(DEBUG_ANY, "* %s:%*s %s *", name+6, spaces, "", debug_flags[debug_flag] ? "YES" : "NO "); } log_debug(DEBUG_ANY, "************************"); -} \ No newline at end of file +} diff --git a/src/config/toml_reader.h b/src/config/toml_reader.h index 8338e49c..88699367 100644 --- a/src/config/toml_reader.h +++ b/src/config/toml_reader.h @@ -10,7 +10,10 @@ #ifndef TOML_READER_H #define TOML_READER_H -bool readFTLtoml(const bool verbose); +#include "config/config.h" +#include "tomlc99/toml.h" + +bool readFTLtoml(struct config *conf, toml_table_t *toml, const bool verbose); bool getLogFilePathTOML(void); #endif //TOML_READER_H diff --git a/src/config/toml_writer.c b/src/config/toml_writer.c index e996bc4f..12b37509 100644 --- a/src/config/toml_writer.c +++ b/src/config/toml_writer.c @@ -105,4 +105,4 @@ bool writeFTLtoml(const bool verbose) closeFTLtoml(fp); return true; -} \ No newline at end of file +} diff --git a/src/daemon.c b/src/daemon.c index df573331..52885741 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -242,6 +242,10 @@ static void terminate_threads(void) log_info("Waiting for threads to join"); for(int i = 0; i < THREADS_MAX; i++) { + // Skip threads that have never been started or which are already stopped + if(!thread_running[i]) + continue; + // Cancel thread if it is idle if(thread_cancellable[i]) { @@ -326,7 +330,7 @@ void cleanup(const int ret) // Remove shared memory objects // Important: This invalidated all objects such as // counters-> ... etc. - // This should be the last action when cleaning up + // This should be the last action when c destroy_shmem(); char buffer[42] = { 0 }; diff --git a/src/database/database-thread.c b/src/database/database-thread.c index 13cc8b81..964c335c 100644 --- a/src/database/database-thread.c +++ b/src/database/database-thread.c @@ -55,6 +55,7 @@ void *DB_thread(void *val) { // Set thread name thread_names[DB] = "database"; + thread_running[DB] = true; prctl(PR_SET_NAME, thread_names[DB], 0, 0, 0); // Save timestamp as we do not want to store immediately @@ -183,5 +184,6 @@ void *DB_thread(void *val) dbclose(&db); log_info("Terminating database thread"); + thread_running[DB] = false; return NULL; } diff --git a/src/dhcp-discover.c b/src/dhcp-discover.c index a294a18f..c6ee8139 100644 --- a/src/dhcp-discover.c +++ b/src/dhcp-discover.c @@ -697,7 +697,7 @@ int run_dhcp_discover(void) // Disable terminal output during config config file parsing log_ctrl(false, false); // Process pihole-FTL.conf to get gravity.db - readFTLconf(false); + readFTLconf(&config, false); // Only print to terminal, disable log file log_ctrl(false, true); diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index ce777244..06b725bf 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -56,6 +56,8 @@ #include "struct_size.h" // query_to_database() #include "database/query-table.h" +// reread_config() +#include "config/config.h" // Private prototypes static void print_flags(const unsigned int flags); @@ -188,9 +190,9 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len // Debug logging if(*ede != EDE_UNSET) - log_debug(DEBUG_FLAGS, "Preparing reply for \"%s\", EDE: %s (%d)", name, edestr(*ede), *ede); + log_debug(DEBUG_QUERIES, "Preparing reply for \"%s\", EDE: %s (%d)", name, edestr(*ede), *ede); else - log_debug(DEBUG_FLAGS, "Preparing reply for \"%s\", EDE: N/A", name); + log_debug(DEBUG_QUERIES, "Preparing reply for \"%s\", EDE: N/A", name); // Get question type int qtype, flags = 0; @@ -218,7 +220,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len force_next_DNS_reply = REPLY_UNKNOWN; // Debug logging - log_debug(DEBUG_FLAGS, "Forced DNS reply to NXDOMAIN"); + log_debug(DEBUG_QUERIES, "Forced DNS reply to NXDOMAIN"); } else if(force_next_DNS_reply == REPLY_NODATA) { @@ -227,7 +229,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len force_next_DNS_reply = REPLY_UNKNOWN; // Debug logging - log_debug(DEBUG_FLAGS, "Forced DNS reply to NODATA"); + log_debug(DEBUG_QUERIES, "Forced DNS reply to NODATA"); } else if(force_next_DNS_reply == REPLY_REFUSED) { @@ -237,7 +239,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len force_next_DNS_reply = REPLY_UNKNOWN; // Debug logging - log_debug(DEBUG_FLAGS, "Forced DNS reply to REFUSED"); + log_debug(DEBUG_QUERIES, "Forced DNS reply to REFUSED"); // Set EDE code to blocked *ede = EDE_BLOCKED; @@ -252,7 +254,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len force_next_DNS_reply = REPLY_UNKNOWN; // Debug logging - log_debug(DEBUG_FLAGS, "Forced DNS reply to IP"); + log_debug(DEBUG_QUERIES, "Forced DNS reply to IP"); } else if(force_next_DNS_reply == REPLY_NONE) { @@ -260,7 +262,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len force_next_DNS_reply = REPLY_UNKNOWN; // Debug logging - log_debug(DEBUG_FLAGS, "Forced DNS reply to NONE - dropping this query"); + log_debug(DEBUG_QUERIES, "Forced DNS reply to NONE - dropping this query"); return 0; } @@ -272,7 +274,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len // If we block in NXDOMAIN mode, we set flags to NXDOMAIN // (NEG will be added after setup_reply() below) flags = F_NXDOMAIN; - log_debug(DEBUG_FLAGS, "Configured blocking mode is NXDOMAIN"); + log_debug(DEBUG_QUERIES, "Configured blocking mode is NXDOMAIN"); } else if(config.dns.blocking.mode.v.blocking_mode == MODE_NODATA || (config.dns.blocking.mode.v.blocking_mode == MODE_IP_NODATA_AAAA && (flags & F_IPV6))) @@ -280,7 +282,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len // If we block in NODATA mode or NODATA for AAAA queries, we apply // the NOERROR response flag. This ensures we're sending an empty response flags = F_NOERR; - log_debug(DEBUG_FLAGS, "Configured blocking mode is NODATA%s", + log_debug(DEBUG_QUERIES, "Configured blocking mode is NODATA%s", config.dns.blocking.mode.v.blocking_mode == MODE_IP_NODATA_AAAA ? "-IPv6" : ""); } } @@ -295,7 +297,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len last_regex_idx = -1; // Debug logging - log_debug(DEBUG_FLAGS, "Regex match is %sredirected", redirecting ? "" : "NOT "); + log_debug(DEBUG_QUERIES, "Regex match is %sredirected", redirecting ? "" : "NOT "); } // Debug logging @@ -1723,10 +1725,13 @@ static void FTL_forwarded(const unsigned int flags, const char *name, const unio unlock_shm(); } +static unsigned int reload = 0u; void FTL_dnsmasq_reload(void) { // This function is called by the dnsmasq code on receive of SIGHUP // *before* clearing the cache and re-reading the lists + if(reload++ > 0) + log_info("Received SIGHUP, flushing chache and re-reading config"); // Gravity database updates // - (Re-)open gravity database connection @@ -1740,8 +1745,10 @@ void FTL_dnsmasq_reload(void) if(config.debug.caps.v.b) check_capabilities(); - // Re-read configuration (incl. rewriting) - readFTLconf(true); + // Re-read pihole.toml (incl. rewriting) on every but the first reload + // (which is happening right after the start of dnsmasq) + if(reload > 1) + reread_config(); // Report blocking mode log_info("Blocking status is %s", config.dns.blocking.active.v.b ? "enabled" : "disabled"); @@ -2551,7 +2558,7 @@ void print_flags(const unsigned int flags) return; char *flagstr = calloc(sizeof(flagnames) + 1, sizeof(char)); - for (unsigned int i = 0; i < (sizeof(flagnames) / sizeof(*flagnames)); i++) + for (unsigned int i = 0; i < ArraySize(flagnames); i++) if (flags & (1u << i)) strcat(flagstr, flagnames[i]); log_debug(DEBUG_FLAGS, " Flags: %s", flagstr); diff --git a/src/files.c b/src/files.c index 30c9c04c..59c965d0 100644 --- a/src/files.c +++ b/src/files.c @@ -27,7 +27,7 @@ // dirname() #include // compression functions -#include "miniz/compression.h" +#include "compression/gzip.h" #define BACKUP_DIR "/etc/pihole/config_backups" diff --git a/src/gc.c b/src/gc.c index 2329b5d6..72cda50d 100644 --- a/src/gc.c +++ b/src/gc.c @@ -122,6 +122,7 @@ void *GC_thread(void *val) { // Set thread name thread_names[GC] = "housekeeper"; + thread_running[GC] = true; prctl(PR_SET_NAME, thread_names[GC], 0, 0, 0); // Remember when we last ran the actions @@ -255,7 +256,7 @@ void *GC_thread(void *val) break; } - // Update reply counters + // Update reply countersthread_running[GC] = false; counters->reply[query->reply]--; // Update type counters @@ -322,5 +323,6 @@ void *GC_thread(void *val) } log_info("Terminating GC thread"); + thread_running[GC] = false; return NULL; } diff --git a/src/main.c b/src/main.c index 71065cc3..04ccd667 100644 --- a/src/main.c +++ b/src/main.c @@ -69,7 +69,7 @@ int main (int argc, char *argv[]) // Process pihole.toml configuration file // The file is rewritten after parsing to ensure that all // settings are present and have a valid value - readFTLconf(true); + readFTLconf(&config, true); // Set process priority set_nice(); @@ -132,6 +132,8 @@ int main (int argc, char *argv[]) // Start the resolver startup = false; + // Stop writing to STDOUT + log_ctrl(true, false); main_dnsmasq(argc_dnsmasq, argv_dnsmasq); log_info("Shutting down... // exit code %d", exit_code); diff --git a/src/miniz/CMakeLists.txt b/src/miniz/CMakeLists.txt index 965b4513..c6e7de96 100644 --- a/src/miniz/CMakeLists.txt +++ b/src/miniz/CMakeLists.txt @@ -9,13 +9,9 @@ # Please see LICENSE file for your rights under this license. set(sources - compression.c - compression.h miniz.c miniz.h - teleporter.c - teleporter.h - ) +) add_library(miniz OBJECT ${sources}) target_compile_options(miniz PRIVATE) diff --git a/src/miniz/teleporter.c b/src/miniz/teleporter.c deleted file mode 100644 index 9f6a5ebb..00000000 --- a/src/miniz/teleporter.c +++ /dev/null @@ -1,297 +0,0 @@ -/* Pi-hole: A black hole for Internet advertisements -* (c) 2023 Pi-hole, LLC (https://pi-hole.net) -* Network-wide ad blocking via your own hardware. -* -* FTL Engine -* Teleporter un-/compression routines -* -* This file is copyright under the latest version of the EUPL. -* Please see LICENSE file for your rights under this license. */ - -#include "FTL.h" -#include "miniz/teleporter.h" -#include "config/config.h" -// hostname() -#include "daemon.h" -// get_timestr(), TIMESTR_SIZE -#include "log.h" -// directory_exists() -#include "files.h" -// DIR, dirent, opendir(), readdir(), closedir() -#include -// sqlite3 -#include "database/sqlite3.h" - -// Tables to copy from the gravity database to the Teleporter database -static const char *gravity_tables[] = { - "info", - "group", - "adlist", - "adlist_by_group", - "domainlist", - "domainlist_by_group", - "client", - "client_by_group", - "domain_audit" -}; - -// Tables to copy from the FTL database to the Teleporter database -static const char *ftl_tables[] = { - "message", - "aliasclient", - "network", - "network_addresses" -}; - -// Create database in memory, copy selected tables to it, serialize and return a memory pointer to it -static bool create_teleporter_database(const char *filename, const char **tables, const unsigned int num_tables, - void **buffer, size_t *size) -{ - // Open in-memory sqlite3 database - sqlite3 *db; - if(sqlite3_open_v2(":memory:", &db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK) - { - log_warn("Failed to open in-memory database: %s", sqlite3_errmsg(db)); - return false; - } - // Attach the FTL database to the in-memory database - char *err = NULL; - char attach_stmt[128] = ""; - - snprintf(attach_stmt, sizeof(attach_stmt), "ATTACH DATABASE '%s' AS \"disk\";", filename); - - if(sqlite3_exec(db, attach_stmt, NULL, NULL, &err) != SQLITE_OK) - { - log_warn("Failed to attach database \"%s\" to in-memory database: %s", filename, err); - sqlite3_free(err); - sqlite3_close(db); - return false; - } - - // Loop over the tables and copy them to the in-memory database - for(unsigned int i = 0; i < num_tables; i++) - { - char *err = NULL; - char create_stmt[128] = ""; - - // Create in-memory table copy - snprintf(create_stmt, sizeof(create_stmt), "CREATE TABLE \"%s\" AS SELECT * FROM disk.\"%s\";", tables[i], tables[i]); - if(sqlite3_exec(db, create_stmt, NULL, NULL, &err) != SQLITE_OK) - { - log_warn("Failed to create %s in in-memory database: %s", tables[i], err); - sqlite3_free(err); - sqlite3_close(db); - return false; - } - } - - // Detach the FTL database from the in-memory database - if(sqlite3_exec(db, "DETACH DATABASE 'disk';", NULL, NULL, &err) != SQLITE_OK) - { - log_warn("Failed to detach FTL database from in-memory database: %s", err); - sqlite3_free(err); - sqlite3_close(db); - return false; - } - - // Serialize the in-memory database to a buffer - // The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory that - // is a serialization of the S database on database connection D. If P is - // not a NULL pointer, then the size of the database in bytes is written - // into *P. - // For an ordinary on-disk database file, the serialization is just a copy - // of the disk file. For an in-memory database or a "TEMP" database, the - // serialization is the same sequence of bytes which would be written to - // disk if that database where backed up to disk. - // The usual case is that sqlite3_serialize() copies the serialization of - // the database into memory obtained from sqlite3_malloc64() and returns a - // pointer to that memory. The caller is responsible for freeing the - // returned value to avoid a memory leak. - sqlite3_int64 isize = 0; - *buffer = sqlite3_serialize(db, "main", &isize, 0); - *size = isize; - if(*buffer == NULL) - { - log_warn("Failed to serialize in-memory database to buffer: %s", sqlite3_errmsg(db)); - sqlite3_close(db); - return false; - } - - // Close the in-memory database - sqlite3_close(db); - - return true; -} - -const char *generate_teleporter_zip(mz_zip_archive *zip, char filename[128], void *ptr, size_t *size) -{ - // Initialize ZIP archive - memset(zip, 0, sizeof(*zip)); - - // Start with 64KB allocation size (pihole.TOML is slightly larger than 32KB - // at the time of writing thjs) - if(!mz_zip_writer_init_heap(zip, 0, 64*1024)) - { - return "Failed creating heap ZIP archive"; - } - - // Add pihole.toml to the ZIP archive - const char *file_comment = "Pi-hole's configuration"; - const char *file_path = GLOBALTOMLPATH; - if(!mz_zip_writer_add_file(zip, file_path+1, file_path, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION)) - { - mz_zip_writer_end(zip); - return "Failed to add "GLOBALTOMLPATH" to heap ZIP archive!"; - } - - // Add /etc/hosts to the ZIP archive - file_comment = "System's HOSTS file"; - file_path = "/etc/hosts"; - if(!mz_zip_writer_add_file(zip, file_path+1, file_path, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION)) - { - mz_zip_writer_end(zip); - return "Failed to add /etc/hosts to heap ZIP archive!"; - } - - // Add /etc/pihole/dhcp.lease to the ZIP archive if it exists - file_comment = "DHCP leases file"; - file_path = "/etc/pihole/dhcp.leases"; - if(file_exists(file_path) && !mz_zip_writer_add_file(zip, file_path+1, file_path, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION)) - { - mz_zip_writer_end(zip); - return "Failed to add /etc/hosts to heap ZIP archive!"; - } - - const char *directory = "/etc/dnsmasq.d"; - if(directory_exists(directory)) - { - // Loop over all files and add them to the ZIP archive - DIR *dir; - struct dirent *ent; - if((dir = opendir(directory)) != NULL) - { - // Loop over all files in the directory - while((ent = readdir(dir)) != NULL) - { - // Skip "." and ".." - if(strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) - continue; - - // Construct full path to file - char fullpath[128] = ""; - snprintf(fullpath, 128, "%s/%s", directory, ent->d_name); - - // Add file to ZIP archive - file_comment = "dnsmasq configuration file"; - file_path = fullpath; - - if(!mz_zip_writer_add_file(zip, file_path+1, file_path, file_comment, (uint16_t)strlen(file_comment), MZ_BEST_COMPRESSION)) - continue; - } - closedir(dir); - } - } - - // Add (a reduced version of) the gravity database to the ZIP archive - void *dbbuf = NULL; - size_t dbsize = 0u; - if(create_teleporter_database(config.files.gravity.v.s, gravity_tables, ArraySize(gravity_tables), &dbbuf, &dbsize)) - { - // Add gravity database to ZIP archive - file_comment = "Pi-hole's gravity database"; - file_path = config.files.gravity.v.s; - if(file_path[0] == '/') - file_path++; - if(!mz_zip_writer_add_mem(zip, file_path, dbbuf, dbsize, MZ_BEST_COMPRESSION)) - { - sqlite3_free(dbbuf); - mz_zip_writer_end(zip); - return "Failed to add gravity database to heap ZIP archive!"; - } - sqlite3_free(dbbuf); - } - else - { - mz_zip_writer_end(zip); - return "Failed to create gravity database for heap ZIP archive!"; - } - - if(create_teleporter_database(config.files.database.v.s, ftl_tables, ArraySize(ftl_tables), &dbbuf, &dbsize)) - { - // Add FTL database to ZIP archive - file_comment = "Pi-hole's FTL database"; - file_path = config.files.database.v.s; - if(file_path[0] == '/') - file_path++; - if(!mz_zip_writer_add_mem(zip, file_path, dbbuf, dbsize, MZ_BEST_COMPRESSION)) - { - sqlite3_free(dbbuf); - mz_zip_writer_end(zip); - return "Failed to add FTL database to heap ZIP archive!"; - } - sqlite3_free(dbbuf); - } - else - { - mz_zip_writer_end(zip); - return "Failed to create FTL database for heap ZIP archive!"; - } - - // Get the heap data so we can send it to the requesting client - if(!mz_zip_writer_finalize_heap_archive(zip, ptr, size)) - { - mz_zip_writer_end(zip); - return "Failed to finalize heap ZIP archive!"; - } - - char timestr[TIMESTR_SIZE] = ""; - get_timestr(timestr, time(NULL), false, true); - snprintf(filename, 128, "pi-hole_%s_teleporter_%s.zip", hostname(), timestr); - - // Everything worked well - return NULL; -} - -bool free_teleporter_zip(mz_zip_archive *zip) -{ - return mz_zip_writer_end(zip); -} - -bool write_teleporter_zip_to_disk(void) -{ - // Generate in-memory ZIP file - mz_zip_archive zip = { 0 }; - void *ptr = NULL; - size_t size = 0u; - char filename[128] = ""; - const char *error = generate_teleporter_zip(&zip, filename, &ptr, &size); - if(error != NULL) - { - log_err("Failed to create Teleporter ZIP file: %s", error); - return false; - } - - // Write file to disk - FILE *fp = fopen(filename, "w"); - if(fp == NULL) - { - log_err("Failed to open %s for writing: %s", filename, strerror(errno)); - free_teleporter_zip(&zip); - return false; - } - if(fwrite(ptr, 1, size, fp) != size) - { - log_err("Failed to write %zu bytes to %s: %s", size, filename, strerror(errno)); - free_teleporter_zip(&zip); - return false; - } - fclose(fp); - - // Free allocated ZIP memory - free_teleporter_zip(&zip); - - /* Output filename on successful creation */ - log_info("%s", filename); - - return true; -} \ No newline at end of file diff --git a/src/regex.c b/src/regex.c index 551f21c9..1349bbf0 100644 --- a/src/regex.c +++ b/src/regex.c @@ -701,7 +701,7 @@ int regex_test(const bool debug_mode, const bool quiet, const char *domainin, co // Process pihole-FTL.conf to get gravity.db path // Do not overwrite the file after reading it - readFTLconf(false); + readFTLconf(&config, false); // Disable all debugging output if not explicitly in debug mode (CLI argument "d") if(!debug_mode) diff --git a/src/resolve.c b/src/resolve.c index 6681cd00..055e312a 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -603,6 +603,7 @@ void *DNSclient_thread(void *val) { // Set thread name thread_names[DNSclient] = "DNS client"; + thread_running[DNSclient] = true; prctl(PR_SET_NAME, thread_names[DNSclient], 0, 0, 0); // Initial delay until we first try to resolve anything @@ -662,5 +663,6 @@ void *DNSclient_thread(void *val) } log_info("Terminating resolver thread"); + thread_running[DNSclient] = false; return NULL; } diff --git a/src/shmem.c b/src/shmem.c index 2b30d6aa..1a82a040 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -89,7 +89,6 @@ static SharedMemory *sharedMemories[] = { &shm_lock, &shm_dns_cache, &shm_per_client_regex, &shm_fifo_log }; -#define NUM_SHMEM (sizeof(sharedMemories)/sizeof(SharedMemory*)) // Variable size array structs static queriesData *queries = NULL; @@ -399,12 +398,14 @@ static void remap_shm(void) // Obtain SHMEM lock void _lock_shm(const char *func, const int line, const char *file) { - log_debug(DEBUG_LOCKS, "Waiting for SHM lock in %s() (%s:%i)", func, file, line); - log_debug(DEBUG_LOCKS, "SHM lock: %p", shmLock); - + // There is no need to lock if we are the only thread + // (e.g., when running pihole-FTL --config a.b.c def) if(shmLock == NULL) return; + log_debug(DEBUG_LOCKS, "Waiting for SHM lock in %s() (%s:%i)", func, file, line); + log_debug(DEBUG_LOCKS, "SHM lock: %p", shmLock); + int result = pthread_mutex_lock(&shmLock->lock.outer); if(result != 0) @@ -457,15 +458,17 @@ void _lock_shm(const char *func, const int line, const char *file) // Release SHM lock void _unlock_shm(const char* func, const int line, const char * file) { + // There is no need to unlock if we are the only thread + // (e.g., when running pihole-FTL --config a.b.c def) + if(shmLock == NULL) + return; + if(config.debug.locks.v.b && !is_our_lock()) { log_err("Tried to unlock but lock is owned by %li/%li", (long int)shmLock->owner.pid, (long int)shmLock->owner.tid); } - if(shmLock == NULL) - return; - // Unlock mutex int result = pthread_mutex_unlock(&shmLock->lock.inner); shmLock->owner.pid = 0; @@ -617,7 +620,7 @@ bool init_shmem() // CHOWN all shared memory objects to supplied user/group void chown_all_shmem(struct passwd *ent_pw) { - for(unsigned int i = 0; i < NUM_SHMEM; i++) + for(unsigned int i = 0; i < ArraySize(sharedMemories); i++) chown_shmem(sharedMemories[i], ent_pw); } @@ -633,7 +636,7 @@ void destroy_shmem(void) shmLock = NULL; // Then, we delete the shared memory objects - for(unsigned int i = 0; i < NUM_SHMEM; i++) + for(unsigned int i = 0; i < ArraySize(sharedMemories); i++) delete_shm(sharedMemories[i]); } diff --git a/src/signals.c b/src/signals.c index 7b717933..83ad1e24 100644 --- a/src/signals.c +++ b/src/signals.c @@ -33,7 +33,8 @@ static volatile pid_t mpid = -1; static time_t FTLstarttime = 0; volatile int exit_code = EXIT_SUCCESS; -volatile sig_atomic_t thread_cancellable[THREADS_MAX] = { true }; +volatile sig_atomic_t thread_cancellable[THREADS_MAX] = { false }; +volatile sig_atomic_t thread_running[THREADS_MAX] = { false }; const char *thread_names[THREADS_MAX] = { "" }; // Return the (null-terminated) name of the calling thread @@ -321,7 +322,7 @@ void handle_signals(void) struct sigaction old_action; const int signals[] = { SIGSEGV, SIGBUS, SIGILL, SIGFPE }; - for(unsigned int i = 0; i < sizeof(signals)/sizeof(signals[0]); i++) + for(unsigned int i = 0; i < ArraySize(signals); i++) { // Catch this signal sigaction (signals[i], NULL, &old_action); diff --git a/src/signals.h b/src/signals.h index 99fde1b0..38f82129 100644 --- a/src/signals.h +++ b/src/signals.h @@ -24,6 +24,7 @@ extern volatile sig_atomic_t want_to_reimport_aliasclients; extern volatile sig_atomic_t want_to_reload_lists; extern volatile sig_atomic_t thread_cancellable[THREADS_MAX]; +extern volatile sig_atomic_t thread_running[THREADS_MAX]; extern const char *thread_names[THREADS_MAX]; #endif //SIGNALS_H diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 3c06f867..05225bd2 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -35,6 +35,7 @@ enum http_method { struct api_options { bool domains :1; + bool parse_json :1; enum fifo_logs which; }; diff --git a/src/webserver/json_macros.h b/src/webserver/json_macros.h index c054fd09..27421521 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -8,9 +8,9 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "../cJSON/cJSON.h" +#include "cJSON/cJSON.h" // logging routines -#include "../log.h" +#include "log.h" #define JSON_NEW_OBJECT() cJSON_CreateObject(); #define JSON_NEW_ARRAY() cJSON_CreateArray(); @@ -243,4 +243,4 @@ free(additional_headers); \ return code; \ }) -*/ \ No newline at end of file +*/ diff --git a/src/webserver/ph7.c b/src/webserver/ph7.c index 06b4dd54..0808b9bf 100644 --- a/src/webserver/ph7.c +++ b/src/webserver/ph7.c @@ -8,17 +8,19 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ +// ArraySize() +#include "FTL.h" #include #include #include // strncpy() #include -#include "../log.h" -#include "../ph7/ph7.h" -#include "../civetweb/civetweb.h" +#include "log.h" +#include "ph7/ph7.h" +#include "civetweb/civetweb.h" #include "ph7.h" // struct config.http -#include "../config/config.h" +#include "config/config.h" // mmap #include // stat @@ -130,7 +132,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, webroot_with_home_and_scripts); // Register Pi-hole's PH7 extensions (defined in subdirectory "ph7_ext/") - for(unsigned int i = 0; i < sizeof(aFunc)/sizeof(aFunc[0]); i++ ) + for(unsigned int i = 0; i < ArraySize(aFunc); i++ ) { rc = ph7_create_function(pVm, aFunc[i].zName, aFunc[i].xProc, NULL /* NULL: No private data */); if( rc != PH7_OK ){ diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index b8e34db1..7e7dbc74 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -159,11 +159,11 @@ void http_init(void) // Add access control list if configured (last two options) if(strlen(config.webserver.acl.v.s) > 0) { - options[(sizeof(options)/sizeof(*options)) - 3] = "access_control_list"; + options[ArraySize(options) - 3] = "access_control_list"; // Note: The string is duplicated by CivetWeb, so it doesn't matter if // the original string is freed (config changes) after mg_start() // returns below. - options[(sizeof(options)/sizeof(*options)) - 2] = config.webserver.acl.v.s; + options[ArraySize(options) - 2] = config.webserver.acl.v.s; } // Configure logging handlers diff --git a/test/api/checkAPI.py b/test/api/checkAPI.py index 64d1788e..b081e6d4 100644 --- a/test/api/checkAPI.py +++ b/test/api/checkAPI.py @@ -58,9 +58,9 @@ if __name__ == "__main__": verifyer = ResponseVerifyer(ftl, openapi) errors = verifyer.verify_endpoint(path) if len(errors) == 0: - print(" " + path + ": OK") + print(" " + path + " (" + verifyer.auth_method + " auth): OK") else: - print(" " + path + ":") + print(" " + path + " (" + verifyer.auth_method + " auth):") for error in errors: print(" - " + error) errs[2] += len(errors) @@ -79,6 +79,5 @@ if __name__ == "__main__": exit(1) # If there are no errors, exit with success - # (this is important for the CI) print("Everything okay!") exit(0) diff --git a/test/api/libs/FTLAPI.py b/test/api/libs/FTLAPI.py index 11803328..d603c668 100644 --- a/test/api/libs/FTLAPI.py +++ b/test/api/libs/FTLAPI.py @@ -9,6 +9,7 @@ # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. +from enum import Enum import requests from typing import List import json @@ -25,6 +26,11 @@ valid = session["session"]["valid"] # True / False sid = session["session"]["sid"] # SID string if succesful, null otherwise """ +class AuthenticationMethods(Enum): + HEADER = 1 + BODY = 2 + COOKIE = 3 + # Class to query the FTL API class FTLAPI(): def __init__(self, api_url: str): @@ -95,7 +101,7 @@ class FTLAPI(): self.session = response["session"] # Query the FTL API (GET) and return the response - def GET(self, uri: str, params: List[str] = [], expected_mimetype: str = "application/json"): + def GET(self, uri: str, params: List[str] = [], expected_mimetype: str = "application/json", authenticate: AuthenticationMethods = AuthenticationMethods.BODY): self.errors = [] try: # Add parameters to the URI (if any) @@ -104,13 +110,20 @@ class FTLAPI(): # Add session ID to the request (if any) data = None + headers = None + cookies = None if self.session is not None and 'sid' in self.session: - data = {"sid": self.session['sid'] } + if authenticate == AuthenticationMethods.HEADER: + headers = {"X-FTL-SID": self.session['sid']} + elif authenticate == AuthenticationMethods.BODY: + data = {"sid": self.session['sid'] } + elif authenticate == AuthenticationMethods.COOKIE: + cookies = {"sid": self.session['sid'] } # Query the API if self.verbose: print("GET " + self.api_url + uri + " with data: " + json.dumps(data)) - with requests.get(url = self.api_url + uri, json = data) as response: + with requests.get(url = self.api_url + uri, json = data, headers=headers, cookies=cookies) as response: if self.verbose: print(json.dumps(response.json(), indent=4)) if expected_mimetype == "application/json": @@ -149,4 +162,4 @@ class FTLAPI(): print("Exception when pre-processing endpoints from FTL: " + str(e)) exit(1) - return self.endpoints \ No newline at end of file + return self.endpoints diff --git a/test/api/libs/responseVerifyer.py b/test/api/libs/responseVerifyer.py index 8f92e53e..e8d2a49b 100644 --- a/test/api/libs/responseVerifyer.py +++ b/test/api/libs/responseVerifyer.py @@ -10,10 +10,11 @@ # Please see LICENSE file for your rights under this license. import io +import random import zipfile from libs.openAPI import openApi import urllib.request, urllib.parse -from libs.FTLAPI import FTLAPI +from libs.FTLAPI import FTLAPI, AuthenticationMethods from collections.abc import MutableMapping class ResponseVerifyer(): @@ -22,6 +23,8 @@ class ResponseVerifyer(): YAML_TYPES = { "string": [str], "integer": [int], "number": [int, float], "boolean": [bool], "array": [list] } TELEPORTER_FILES = ["etc/pihole/gravity.db", "etc/pihole/pihole.toml", "etc/pihole/pihole-FTL.db", "etc/hosts"] + auth_method = "?" + def __init__(self, ftl: FTLAPI, openapi: openApi): self.ftl = ftl self.openapi = openapi @@ -62,6 +65,10 @@ class ResponseVerifyer(): # Get YAML response schema and examples (if applicable) expected_mimetype = True + # Assign random authentication method so we can test them all + authentication_method = random.choice([a for a in AuthenticationMethods]) + self.auth_method = authentication_method.name + # Check if the expected response is defined in the API specs response_rcode = self.openapi.paths[endpoint][method]['responses'][str(rcode)] if 'content' in response_rcode: content = response_rcode['content'] @@ -73,6 +80,8 @@ class ResponseVerifyer(): elif 'application/zip' in content: expected_mimetype = 'application/zip' jsonData = content[expected_mimetype] + # Thie endpoint requires HEADER authentication + authentication_method = AuthenticationMethods.HEADER YAMLresponseSchema = None YAMLresponseExamples = None else: @@ -93,7 +102,7 @@ class ResponseVerifyer(): FTLparameters.append(param['name'] + "=" + urllib.parse.quote_plus(str(param['example']))) # Get FTL response - FTLresponse = self.ftl.GET("/api" + endpoint, FTLparameters, expected_mimetype) + FTLresponse = self.ftl.GET("/api" + endpoint, FTLparameters, expected_mimetype, authentication_method) if FTLresponse is None: return self.ftl.errors diff --git a/test/test_suite.bats b/test/test_suite.bats index c06e4127..f65f704c 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -1165,13 +1165,18 @@ @test "Pi-hole uses dns.reply.blocking.IPv4/6 for blocked domain" { run bash -c 'grep "mode = \"NULL\"" /etc/pihole/pihole.toml' + printf "grep output: %s\n" "${lines[@]}" [[ "${lines[0]}" == ' mode = "NULL"' ]] run bash -c './pihole-FTL --config dns.blocking.mode IP' printf "setting config: %s\n" "${lines[@]}" + run bash -c 'grep "mode = \"IP" /etc/pihole/pihole.toml' + printf "grep output (before reload): %s\n" "${lines[@]}" + [[ "${lines[0]}" == *'mode = "IP" ### CHANGED, default = "NULL"' ]] run bash -c "kill -HUP $(cat /run/pihole-FTL.pid)" - sleep 2 - run bash -c 'grep "mode = \"IP\"" /etc/pihole/pihole.toml' - [[ "${lines[0]}" == ' mode = "IP" ### CHANGED, default = "NULL"' ]] + sleep 1 + run bash -c 'grep "mode = \"IP" /etc/pihole/pihole.toml' + printf "grep output (after reload): %s\n" "${lines[@]}" + [[ "${lines[0]}" == *'mode = "IP" ### CHANGED, default = "NULL"' ]] run bash -c "dig A denied.ftl +short @127.0.0.1" printf "A: %s\n" "${lines[@]}" [[ "${lines[0]}" == "10.100.0.11" ]] @@ -1225,12 +1230,12 @@ printf "Compression output:\n" printf "%s\n" "${lines[@]}" [[ $status == 0 ]] - [[ ${lines[0]} == "Compressed test/pihole-FTL.db.sql ("* ]] + [[ ${lines[0]} == "Compressed test/pihole-FTL.db.sql (2.0KB) to test/pihole-FTL.db.sql.gz (689.0B), 66.0% size reduction" ]] printf "Uncompress (FTL) output:\n" run bash -c './pihole-FTL gzip test/pihole-FTL.db.sql.gz test/pihole-FTL.db.sql.1' printf "%s\n" "${lines[@]}" [[ $status == 0 ]] - [[ ${lines[0]} == "Uncompressed test/pihole-FTL.db.sql.gz ("* ]] + [[ ${lines[0]} == "Uncompressed test/pihole-FTL.db.sql.gz (677.0B) to test/pihole-FTL.db.sql.1 (2.0KB), 199.3% size increase" ]] printf "Uncompress (gzip) output:\n" run bash -c 'gzip -dkc test/pihole-FTL.db.sql.gz > test/pihole-FTL.db.sql.2' printf "%s\n" "${lines[@]}"