mirror of
https://github.com/pi-hole/FTL.git
synced 2026-07-07 15:15:05 +01:00
Add POST /api/teleporter to upload and install backed up configuration
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
@@ -180,6 +180,7 @@ add_executable(pihole-FTL
|
||||
$<TARGET_OBJECTS:civetweb>
|
||||
$<TARGET_OBJECTS:cJSON>
|
||||
$<TARGET_OBJECTS:miniz>
|
||||
$<TARGET_OBJECTS:compression>
|
||||
$<TARGET_OBJECTS:ph7>
|
||||
$<TARGET_OBJECTS:database>
|
||||
$<TARGET_OBJECTS:dnsmasq>
|
||||
@@ -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)
|
||||
|
||||
+76
-69
@@ -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)
|
||||
{
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+31
-47
@@ -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);
|
||||
|
||||
@@ -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'
|
||||
$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
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+212
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -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 *",
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
@@ -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 <string.h>
|
||||
// le32toh and friends
|
||||
#include <endian.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
@@ -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 <stdbool.h>
|
||||
|
||||
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
|
||||
@@ -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 <dirent.h>
|
||||
// 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;
|
||||
}
|
||||
@@ -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
|
||||
#endif // TELEPORTER_H
|
||||
+14
-20
@@ -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);
|
||||
|
||||
+653
-597
File diff suppressed because it is too large
Load Diff
+7
-5
@@ -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)) \
|
||||
{ \
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+99
-99
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
+20
-15
@@ -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 <limits.h>
|
||||
|
||||
#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, "************************");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -105,4 +105,4 @@ bool writeFTLtoml(const bool verbose)
|
||||
closeFTLtoml(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
|
||||
+20
-13
@@ -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);
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
// dirname()
|
||||
#include <libgen.h>
|
||||
// compression functions
|
||||
#include "miniz/compression.h"
|
||||
#include "compression/gzip.h"
|
||||
|
||||
#define BACKUP_DIR "/etc/pihole/config_backups"
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 <dirent.h>
|
||||
// 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;
|
||||
}
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+12
-9
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,6 +35,7 @@ enum http_method {
|
||||
|
||||
struct api_options {
|
||||
bool domains :1;
|
||||
bool parse_json :1;
|
||||
enum fifo_logs which;
|
||||
};
|
||||
|
||||
|
||||
@@ -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; \
|
||||
})
|
||||
*/
|
||||
*/
|
||||
|
||||
+7
-5
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
// strncpy()
|
||||
#include <string.h>
|
||||
#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 <sys/mman.h>
|
||||
// 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 ){
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+17
-4
@@ -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
|
||||
return self.endpoints
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+10
-5
@@ -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[@]}"
|
||||
|
||||
Reference in New Issue
Block a user