From e145d20d28e8cc74ade8e474b928112ec319aef6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 5 Jan 2023 21:30:13 +0100 Subject: [PATCH] Rewrite the entire config-related code to allow for changing data without having to restart. Hereby, we greatly reduce code duplication in the TOML routines so we won't have to touch tme in the future when adding additional options. Signed-off-by: DL6ER --- src/FTL.h | 2 +- src/api/auth.c | 12 +- src/api/config.c | 268 +++++----- src/api/dns.c | 19 +- src/api/docs/content/specs/config.yaml | 216 +++++--- src/api/docs/content/specs/dns.yaml | 94 +++- src/api/docs/content/specs/stats.yaml | 8 + src/api/ftl.c | 2 +- src/api/history.c | 2 +- src/api/queries.c | 2 +- src/api/stats.c | 23 +- src/config/config.c | 667 +++++++++++++++++++++--- src/config/config.h | 225 +++++--- src/config/legacy_reader.c | 272 +++++----- src/config/toml_helper.c | 295 ++++++++--- src/config/toml_helper.h | 15 +- src/config/toml_reader.c | 677 ++----------------------- src/config/toml_writer.c | 216 ++------ src/daemon.c | 38 +- src/daemon.h | 1 + src/database/common.c | 18 +- src/database/database-thread.c | 18 +- src/database/gravity-db.c | 24 +- src/database/message-table.c | 2 +- src/database/network-table.c | 28 +- src/database/query-table.c | 16 +- src/database/sqlite3-ext.c | 2 +- src/datastructure.c | 67 ++- src/datastructure.h | 4 + src/dnsmasq_interface.c | 156 +++--- src/edns0.c | 10 +- src/enums.h | 50 +- src/events.c | 4 +- src/files.c | 4 +- src/gc.c | 24 +- src/log.c | 57 +-- src/log.h | 2 +- src/main.c | 23 +- src/overTime.c | 4 +- src/regex.c | 6 +- src/resolve.c | 34 +- src/setupVars.c | 4 +- src/shmem.c | 39 +- src/signals.c | 2 +- src/webserver/http-common.c | 2 +- src/webserver/json_macros.h | 8 +- src/webserver/ph7.c | 14 +- src/webserver/webserver.c | 18 +- test/api/checkAPI.py | 10 +- test/api/libs/FTLAPI.py | 3 + test/api/libs/openAPI.py | 8 +- test/api/libs/responseVerifyer.py | 93 +++- test/test_suite.bats | 2 +- 53 files changed, 2075 insertions(+), 1735 deletions(-) diff --git a/src/FTL.h b/src/FTL.h index 5e332e97..f2fb3617 100644 --- a/src/FTL.h +++ b/src/FTL.h @@ -170,7 +170,7 @@ #include "syscalls/syscalls.h" // Preprocessor help functions -#define str(x) # x +#define str(x) #x #define xstr(x) str(x) // Intentionally ignore result of function declared warn_unused_result diff --git a/src/api/auth.c b/src/api/auth.c index b081e4d8..d5b1b5bc 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -86,7 +86,7 @@ static void sha256_hex(uint8_t *data, char *buffer) int check_client_auth(struct ftl_conn *api) { // Is the user requesting from localhost? - if(!config.http.localAPIauth && (strcmp(api->request->remote_addr, LOCALHOSTv4) == 0 || + if(!config.http.localAPIauth.v.b && (strcmp(api->request->remote_addr, LOCALHOSTv4) == 0 || strcmp(api->request->remote_addr, LOCALHOSTv6) == 0)) { return API_AUTH_LOCALHOST; @@ -179,17 +179,17 @@ int check_client_auth(struct ftl_conn *api) // Update timestamp of this client to extend // the validity of their API authentication - auth_data[user_id].valid_until = now + config.http.sessionTimeout; + auth_data[user_id].valid_until = now + config.http.sessionTimeout.v.ui; // Update user cookie if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), FTL_SET_COOKIE, - auth_data[user_id].sid, config.http.sessionTimeout) < 0) + auth_data[user_id].sid, config.http.sessionTimeout.v.ui) < 0) { return send_json_error(api, 500, "internal_error", "Internal server error", NULL); } - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until, false); @@ -488,7 +488,7 @@ int api_auth(struct ftl_conn *api) if(!auth_data[i].used) { auth_data[i].used = true; - auth_data[i].valid_until = now + config.http.sessionTimeout; + auth_data[i].valid_until = now + config.http.sessionTimeout.v.ui; strncpy(auth_data[i].remote_addr, api->request->remote_addr, sizeof(auth_data[i].remote_addr)); auth_data[i].remote_addr[sizeof(auth_data[i].remote_addr)-1] = '\0'; generateSID(auth_data[i].sid); @@ -499,7 +499,7 @@ int api_auth(struct ftl_conn *api) } // Debug logging - if(config.debug & DEBUG_API && user_id > API_AUTH_UNAUTHORIZED) + if(config.debug.api.v.b && user_id > API_AUTH_UNAUTHORIZED) { char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until, false); diff --git a/src/api/config.c b/src/api/config.c index 94fdc8b8..baab3446 100644 --- a/src/api/config.c +++ b/src/api/config.c @@ -37,142 +37,172 @@ // Interate through directories #include -int api_config(struct ftl_conn *api) +// The following functions are used to create the JSON output +// of the /api/config endpoint. + +// This function is used to build the object architecture. It is called +// recursively to build the tree of objects. +static cJSON *get_or_create_object(cJSON *parent, const char *path_element) +{ + // Check if this object already exists + cJSON *object = cJSON_GetObjectItem(parent, path_element); + + // If not, create and append it to the parent + if(object == NULL) + { + object = JSON_NEW_OBJECT(); + JSON_ADD_ITEM_TO_OBJECT(parent, path_element, object); + } + + // Return the object + return object; +} + +// This function is used to add a property to the JSON output using the +// appropriate type of the config item to add. +static cJSON *add_property(const enum conf_type conf_type, union conf_value *val) +{ + switch(conf_type) + { + case CONF_BOOL: + return cJSON_CreateBool(val->b); + case CONF_INT: + return cJSON_CreateNumber(val->i); + case CONF_UINT: + case CONF_ENUM_PRIVACY_LEVEL: + return cJSON_CreateNumber(val->ui); + case CONF_LONG: + return cJSON_CreateNumber(val->l); + case CONF_ULONG: + return cJSON_CreateNumber(val->ul); + case CONF_STRING: + return val->s ? cJSON_CreateStringReference(val->s) : cJSON_CreateNull(); + case CONF_ENUM_PTR_TYPE: + return cJSON_CreateStringReference(get_ptr_type_str(val->ptr_type)); + case CONF_ENUM_BUSY_TYPE: + return cJSON_CreateStringReference(get_busy_reply_str(val->busy_reply)); + case CONF_ENUM_BLOCKING_MODE: + return cJSON_CreateStringReference(get_blocking_mode_str(val->blocking_mode)); + case CONF_ENUM_REFRESH_HOSTNAMES: + return cJSON_CreateStringReference(get_refresh_hostnames_str(val->refresh_hostnames)); + case CONF_STRUCT_IN_ADDR: + { + char addr4[INET_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET, &val->in_addr, addr4, INET_ADDRSTRLEN); + return cJSON_CreateString(addr4); // Performs a copy + } + case CONF_STRUCT_IN6_ADDR: + { + char addr6[INET6_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET6, &val->in6_addr, addr6, INET6_ADDRSTRLEN); + return cJSON_CreateString(addr6); // Performs a copy + } + default: + return NULL; + } +} + +static int api_config_get(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) return send_json_unauthorized(api); + // Parse query string parameters + bool detailed = false; + if(api->request->query_string != NULL) + { + // Check if we should return detailed config information + get_bool_var(api->request->query_string, "detailed", &detailed); + } + + // Create root JSON object cJSON *config_j = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(config_j, "debug",config.debug); /* TODO: Split into individual fields */ - cJSON *dns = JSON_NEW_OBJECT(); - JSON_ADD_BOOL_TO_OBJECT(dns, "CNAMEdeepInspect", config.dns.CNAMEdeepInspect); - JSON_ADD_BOOL_TO_OBJECT(dns, "blockESNI", config.dns.blockESNI); - JSON_ADD_BOOL_TO_OBJECT(dns, "EDNS0ECS", config.dns.EDNS0ECS); - JSON_ADD_BOOL_TO_OBJECT(dns, "ignoreLocalhost", config.dns.ignoreLocalhost); - JSON_ADD_BOOL_TO_OBJECT(dns, "showDNSSEC", config.dns.showDNSSEC); - JSON_ADD_BOOL_TO_OBJECT(dns, "analyzeAAAA", config.dns.analyzeAAAA); - JSON_ADD_BOOL_TO_OBJECT(dns, "analyzeOnlyAandAAAA", config.dns.analyzeOnlyAandAAAA); - const char *piholePTR = get_ptr_type_str(config.dns.piholePTR); - JSON_REF_STR_IN_OBJECT(dns, "piholePTR", piholePTR); - const char *replyWhenBusy = get_busy_reply_str(config.dns.replyWhenBusy); - JSON_REF_STR_IN_OBJECT(dns, "replyWhenBusy", replyWhenBusy); - JSON_ADD_NUMBER_TO_OBJECT(dns, "blockTTL", config.dns.blockTTL); - const char *blockingmode = get_blocking_mode_str(config.dns.blockingmode); - JSON_REF_STR_IN_OBJECT(dns, "blockingmode", blockingmode); - JSON_ADD_NUMBER_TO_OBJECT(dns, "port", config.dns.port); - cJSON *specialDomains = JSON_NEW_OBJECT(); - JSON_ADD_BOOL_TO_OBJECT(specialDomains, "mozillaCanary", config.dns.specialDomains.mozillaCanary); - JSON_ADD_BOOL_TO_OBJECT(specialDomains, "iCloudPrivateRelay", config.dns.specialDomains.iCloudPrivateRelay); - JSON_ADD_ITEM_TO_OBJECT(dns, "specialDomains", specialDomains); - cJSON *reply = JSON_NEW_OBJECT(); - cJSON *host = JSON_NEW_OBJECT(); + // Iterate over all known config elements and create appropriate JSON + // objects + items for each of them + for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { - if(config.dns.reply.host.overwrite_v4) + // Get pointer to memory location of this conf_item + struct conf_item *conf_item = get_conf_item(i); + + // Get path depth + unsigned int level = config_path_depth(conf_item); + + cJSON *parent = config_j; + // Parse tree of properties and create JSON objects for each + // path element if they do not exist yet. We do not create the + // leaf object itself here (level - 1) as we want to add the + // actual value of the config item to it. + for(unsigned int j = 0; j < level - 1; j++) + parent = get_or_create_object(parent, conf_item->p[j]); + + // Create the config item leaf object + if(detailed) { - JSON_COPY_STR_TO_OBJECT(host, "IPv4", inet_ntoa(config.dns.reply.host.v4)); + cJSON *leaf = JSON_NEW_OBJECT(); + JSON_REF_STR_IN_OBJECT(leaf, "description", conf_item->h); + JSON_REF_STR_IN_OBJECT(leaf, "hints", conf_item->a); + // Create the config item leaf object + cJSON *val = add_property(conf_item->t, &conf_item->v); + if(val == NULL) + { + log_warn("Cannot format config item type %s of type %i", + conf_item->k, conf_item->t); + continue; + } + cJSON *dval = add_property(conf_item->t, &conf_item->d); + if(dval == NULL) + { + log_warn("Cannot format config item type %s of type %i", + conf_item->k, conf_item->t); + continue; + } + const bool changed = memcmp(&conf_item->v, &conf_item->d, sizeof(conf_item->v)) != 0; + JSON_ADD_ITEM_TO_OBJECT(leaf, "value", val); + JSON_ADD_ITEM_TO_OBJECT(leaf, "default", dval); + JSON_ADD_BOOL_TO_OBJECT(leaf, "changed", changed); + JSON_ADD_ITEM_TO_OBJECT(parent, conf_item->p[level - 1], leaf); } else { - JSON_REF_STR_IN_OBJECT(host, "IPv4", ""); - } - char ip6[INET6_ADDRSTRLEN] = { 0 }; - if(config.dns.reply.host.overwrite_v6) - { - JSON_COPY_STR_TO_OBJECT(host, "IPv6", inet_ntop(AF_INET6, &config.dns.reply.host.v6, ip6, INET6_ADDRSTRLEN)); - } - else - { - JSON_REF_STR_IN_OBJECT(host, "IPv6", ""); + // Create the config item leaf object + cJSON *leaf = add_property(conf_item->t, &conf_item->v); + if(leaf == NULL) + { + log_warn("Cannot format config item type %s of type %i", + conf_item->k, conf_item->t); + continue; + } + JSON_ADD_ITEM_TO_OBJECT(parent, conf_item->p[level - 1], leaf); } } - JSON_ADD_ITEM_TO_OBJECT(reply, "host", host); - cJSON *blocking = JSON_NEW_OBJECT(); - { - if(config.dns.reply.blocking.overwrite_v4) - { - JSON_COPY_STR_TO_OBJECT(blocking, "IPv4", inet_ntoa(config.dns.reply.blocking.v4)); - } - else - { - JSON_REF_STR_IN_OBJECT(blocking, "IPv4", ""); - } - char ip6[INET6_ADDRSTRLEN] = { 0 }; - if(config.dns.reply.blocking.overwrite_v6) - { - JSON_COPY_STR_TO_OBJECT(blocking, "IPv6", inet_ntop(AF_INET6, &config.dns.reply.blocking.v6, ip6, INET6_ADDRSTRLEN)); - } - else - { - JSON_REF_STR_IN_OBJECT(blocking, "IPv6", ""); - } - } - JSON_ADD_ITEM_TO_OBJECT(reply, "blocking", blocking); - JSON_ADD_ITEM_TO_OBJECT(dns, "reply", reply); - cJSON *rateLimit = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(rateLimit, "count", config.dns.rateLimit.count); - JSON_ADD_NUMBER_TO_OBJECT(rateLimit, "interval", config.dns.rateLimit.interval); - JSON_ADD_ITEM_TO_OBJECT(dns, "rateLimit", rateLimit); - JSON_ADD_ITEM_TO_OBJECT(config_j, "dns", dns); - cJSON *resolver = JSON_NEW_OBJECT(); - JSON_ADD_BOOL_TO_OBJECT(resolver, "resolveIPv4", config.resolver.resolveIPv4); - JSON_ADD_BOOL_TO_OBJECT(resolver, "resolveIPv6", config.resolver.resolveIPv6); - JSON_ADD_BOOL_TO_OBJECT(resolver, "networkNames", config.resolver.networkNames); - const char *refreshstr = get_refresh_hostnames_str(config.resolver.refreshNames); - JSON_REF_STR_IN_OBJECT(resolver, "refreshNames", refreshstr); - JSON_ADD_ITEM_TO_OBJECT(config_j, "resolver", resolver); - - cJSON *database = JSON_NEW_OBJECT(); - JSON_ADD_BOOL_TO_OBJECT(database, "DBimport", config.database.DBimport); - JSON_ADD_BOOL_TO_OBJECT(database, "DBexport", config.database.DBexport); - JSON_ADD_NUMBER_TO_OBJECT(database, "maxHistory", config.database.maxHistory); - JSON_ADD_NUMBER_TO_OBJECT(database, "maxDBdays", config.database.maxDBdays); - JSON_ADD_NUMBER_TO_OBJECT(database, "DBinterval", config.database.DBinterval); - cJSON *network = JSON_NEW_OBJECT(); - JSON_ADD_BOOL_TO_OBJECT(network, "parseARPcache", config.database.network.parseARPcache); - JSON_ADD_NUMBER_TO_OBJECT(network, "expire", config.database.network.expire); - JSON_ADD_ITEM_TO_OBJECT(database, "network", network); - JSON_ADD_ITEM_TO_OBJECT(config_j, "database", database); - - cJSON *http = JSON_NEW_OBJECT(); - JSON_ADD_BOOL_TO_OBJECT(http, "localAPIauth", config.http.localAPIauth); - JSON_ADD_BOOL_TO_OBJECT(http, "prettyJSON", config.http.prettyJSON); - JSON_ADD_NUMBER_TO_OBJECT(http, "sessionTimeout", config.http.sessionTimeout); - JSON_REF_STR_IN_OBJECT(http, "domain", config.http.domain); - JSON_REF_STR_IN_OBJECT(http, "acl", config.http.acl); - JSON_REF_STR_IN_OBJECT(http, "port", config.http.port); - cJSON *paths = JSON_NEW_OBJECT(); - JSON_REF_STR_IN_OBJECT(paths, "webroot", config.http.paths.webroot); - JSON_REF_STR_IN_OBJECT(paths, "webhome", config.http.paths.webhome); - JSON_ADD_ITEM_TO_OBJECT(http, "paths", paths); - JSON_ADD_ITEM_TO_OBJECT(config_j, "http", http); - - cJSON *files = JSON_NEW_OBJECT(); - JSON_REF_STR_IN_OBJECT(files, "log", config.files.log); - JSON_REF_STR_IN_OBJECT(files, "pid", config.files.pid); - JSON_REF_STR_IN_OBJECT(files, "database", config.files.database); - JSON_REF_STR_IN_OBJECT(files, "gravity", config.files.gravity); - JSON_REF_STR_IN_OBJECT(files, "macvendor", config.files.macvendor); - JSON_REF_STR_IN_OBJECT(files, "setupVars", config.files.setupVars); - JSON_REF_STR_IN_OBJECT(files, "http_info", config.files.http_info); - JSON_REF_STR_IN_OBJECT(files, "ph7_error", config.files.ph7_error); - JSON_ADD_ITEM_TO_OBJECT(config_j, "files", files); - - cJSON *misc = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(misc, "nice", config.misc.nice); - JSON_ADD_NUMBER_TO_OBJECT(misc, "delay_startup", config.misc.delay_startup); - JSON_ADD_BOOL_TO_OBJECT(misc, "addr2line", config.misc.addr2line); - JSON_ADD_NUMBER_TO_OBJECT(misc, "privacylevel", config.misc.privacylevel); - cJSON *check = JSON_NEW_OBJECT(); - JSON_ADD_BOOL_TO_OBJECT(check, "load", config.misc.check.load); - JSON_ADD_NUMBER_TO_OBJECT(check, "shmem", config.misc.check.shmem); - JSON_ADD_NUMBER_TO_OBJECT(check, "disk", config.misc.check.disk); - JSON_ADD_ITEM_TO_OBJECT(misc, "check", check); - JSON_ADD_ITEM_TO_OBJECT(config_j, "misc", misc); + // Add special item DNS port + cJSON *dns = get_or_create_object(config_j, "dns"); + JSON_ADD_NUMBER_TO_OBJECT(dns, "port", dns_port); + // Build and return JSON response cJSON *json = JSON_NEW_OBJECT(); JSON_ADD_ITEM_TO_OBJECT(json, "config", config_j); JSON_SEND_OBJECT(json); } + +// Endpoint /api/config router +int api_config(struct ftl_conn *api) +{ + if(api->method == HTTP_GET) + return api_config_get(api); + + // POST: Create a new config (not supported) + // PATCH: Replace parts of the the config with the provided one + // PUT: Replaces the entire config with the provided one (not supported + // but PATCH with a full config is the same) +// else if(api->method == HTTP_PATCH) +// return api_config_patch(api); + else + return send_json_error(api, 405, "method_error", + "Method not allowed", + "Use GET to retrieve the current config and " + "PATCH to change it (either partially or fully)"); +} diff --git a/src/api/dns.c b/src/api/dns.c index 61e80997..7266450e 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -126,10 +126,10 @@ int api_dns_cache(struct ftl_conn *api) struct cache_info ci = { 0 }; get_dnsmasq_cache_info(&ci); - cJSON *json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(json, "size", ci.cache_size); - JSON_ADD_NUMBER_TO_OBJECT(json, "inserted", ci.cache_inserted); - JSON_ADD_NUMBER_TO_OBJECT(json, "evicted", ci.cache_live_freed); + cJSON *cache = JSON_NEW_OBJECT(); + JSON_ADD_NUMBER_TO_OBJECT(cache, "size", ci.cache_size); + JSON_ADD_NUMBER_TO_OBJECT(cache, "inserted", ci.cache_inserted); + JSON_ADD_NUMBER_TO_OBJECT(cache, "evicted", ci.cache_live_freed); cJSON *valid = JSON_NEW_OBJECT(); JSON_ADD_NUMBER_TO_OBJECT(valid, "ipv4", ci.valid.ipv4); JSON_ADD_NUMBER_TO_OBJECT(valid, "ipv6", ci.valid.ipv6); @@ -138,15 +138,18 @@ int api_dns_cache(struct ftl_conn *api) JSON_ADD_NUMBER_TO_OBJECT(valid, "ds", ci.valid.ds); JSON_ADD_NUMBER_TO_OBJECT(valid, "dnskey", ci.valid.dnskey); JSON_ADD_NUMBER_TO_OBJECT(valid, "other", ci.valid.other); - JSON_ADD_ITEM_TO_OBJECT(json, "valid", valid); - JSON_ADD_NUMBER_TO_OBJECT(json, "expired", ci.expired); - JSON_ADD_NUMBER_TO_OBJECT(json, "immortal", ci.immortal); + JSON_ADD_ITEM_TO_OBJECT(cache, "valid", valid); + JSON_ADD_NUMBER_TO_OBJECT(cache, "expired", ci.expired); + JSON_ADD_NUMBER_TO_OBJECT(cache, "immortal", ci.immortal); + + cJSON *json = JSON_NEW_OBJECT(); + JSON_ADD_ITEM_TO_OBJECT(json, "cache", cache); JSON_SEND_OBJECT(json); } int api_dns_port(struct ftl_conn *api) { cJSON *json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(json, "dns_port", config.dns.port); + JSON_ADD_NUMBER_TO_OBJECT(json, "dns_port", dns_port); JSON_SEND_OBJECT(json); } diff --git a/src/api/docs/content/specs/config.yaml b/src/api/docs/content/specs/config.yaml index 3e5df38e..62fe51f6 100644 --- a/src/api/docs/content/specs/config.yaml +++ b/src/api/docs/content/specs/config.yaml @@ -29,210 +29,312 @@ components: config: type: object properties: - debug: - type: integer - example: 0 dns: type: object properties: CNAMEdeepInspect: type: boolean - example: true blockESNI: type: boolean - example: true EDNS0ECS: type: boolean - example: true ignoreLocalhost: type: boolean - example: false showDNSSEC: type: boolean - example: true analyzeAAAA: type: boolean - example: true analyzeOnlyAandAAAA: type: boolean - example: false piholePTR: type: string - example: "PI.HOLE" replyWhenBusy: type: string - example: "ALLOW" blockTTL: type: integer - example: 2 blockingmode: type: string - example: "NULL" port: type: integer - example: 53 specialDomains: type: object properties: mozillaCanary: type: boolean - example: true iCloudPrivateRelay: type: boolean - example: true reply: type: object properties: host: type: object properties: + overwrite_v4: + type: boolean + overwrite_v6: + type: boolean IPv4: type: string - example: "192.168.2.4" IPv6: type: string - example: "fd08:4582::1425" blocking: type: object properties: + overwrite_v4: + type: boolean + overwrite_v6: + type: boolean IPv4: type: string - example: "" IPv6: type: string - example: "" rateLimit: type: object properties: count: type: integer - example: 1000 interval: type: integer - example: 60 resolver: type: object properties: resolveIPv4: type: boolean - example: true resolveIPv6: type: boolean - example: true networkNames: type: boolean - example: true refreshNames: type: string - example: "IPV4_ONLY" database: type: object properties: DBimport: type: boolean - example: true DBexport: type: boolean - example: true maxHistory: type: integer - example: 86400 maxDBdays: type: integer - example: 365 DBinterval: type: integer - example: 60 network: type: object properties: parseARPcache: type: boolean - example: true expire: type: integer - example: 365 http: type: object properties: localAPIauth: type: boolean - example: true prettyJSON: type: boolean - example: false sessionTimeout: type: integer - example: 300 domain: type: string - example: "pi.hole" acl: type: string - example: "+0.0.0.0/0" port: type: string - example: "8080,[::]:8080" paths: type: object properties: webroot: type: string - example: "/var/www/html" webhome: type: string - example: "/admin/" files: type: object properties: log: type: string - example: "/var/log/pihole/FTL.log" pid: type: string - example: "/run/pihole-FTL.pid" database: type: string - example: "/etc/pihole/pihole-FTL.db" gravity: type: string - example: "/etc/pihole/gravity.db" macvendor: type: string - example: "/etc/pihole/macvendor.db" setupVars: type: string - example: "/etc/pihole/setupVars.conf" http_info: type: string - example: "/var/log/pihole/HTTP_info.log" ph7_error: type: string - example: "/var/log/pihole/PH7_error.log" misc: type: object properties: nice: type: integer - example: -10 delay_startup: type: integer - example: 0 addr2line: type: boolean - example: true privacylevel: type: integer - example: 0 check: type: object properties: load: type: boolean - example: true shmem: type: integer - example: 90 disk: type: integer - example: 90 + debug: + type: object + properties: + database: + type: boolean + networking: + type: boolean + locks: + type: boolean + queries: + type: boolean + flags: + type: boolean + shmem: + type: boolean + gc: + type: boolean + arp: + type: boolean + regex: + type: boolean + api: + type: boolean + overtime: + type: boolean + status: + type: boolean + caps: + type: boolean + dnssec: + type: boolean + vectors: + type: boolean + resolver: + type: boolean + edns0: + type: boolean + clients: + type: boolean + aliasclients: + type: boolean + events: + type: boolean + helper: + type: boolean + config: + type: boolean + extra: + type: boolean + reserved: + type: boolean + examples: + config: + config: + dns: + CNAMEdeepInspect: true + blockESNI: true + EDNS0ECS: true + ignoreLocalhost: false + showDNSSEC: true + analyzeAAAA: true + analyzeOnlyAandAAAA: false + piholePTR: PI.HOLE + replyWhenBusy: ALLOW + blockTTL: 2 + blockingmode: 'NULL' + specialDomains: + mozillaCanary: true + iCloudPrivateRelay: true + reply: + host: + overwrite_v4: false + overwrite_v6: false + IPv4: 0.0.0.0 + IPv6: "::" + blocking: + overwrite_v4: false + overwrite_v6: false + IPv4: 0.0.0.0 + IPv6: "::" + rateLimit: + count: 0 + interval: 0 + port: 53 + resolver: + resolveIPv4: true + resolveIPv6: true + networkNames: true + refreshNames: IPV4_ONLY + database: + DBimport: true + DBexport: true + maxHistory: 86400 + maxDBdays: 365 + DBinterval: 60 + network: + parseARPcache: true + expire: 365 + http: + localAPIauth: false + prettyJSON: false + sessionTimeout: 300 + domain: pi.hole + acl: "+0.0.0.0/0" + port: 8080,[::]:8080 + paths: + webroot: "/var/www/html" + webhome: "/admin/" + files: + log: "/var/log/pihole/FTL.log" + pid: "/run/pihole-FTL.pid" + database: "/etc/pihole/pihole-FTL.db" + gravity: "/etc/pihole/gravity.db" + macvendor: "/etc/pihole/macvendor.db" + setupVars: "/etc/pihole/setupVars.conf" + http_info: "/var/log/pihole/HTTP_info.log" + ph7_error: "/var/log/pihole/PH7.log" + misc: + nice: -10 + delay_startup: 10 + addr2line: true + privacylevel: 0 + check: + load: true + shmem: 90 + disk: 90 + debug: + database: false + networking: false + locks: false + queries: false + flags: false + shmem: false + gc: false + arp: false + regex: false + api: false + overtime: false + status: false + caps: false + dnssec: false + vectors: false + resolver: false + edns0: false + clients: false + aliasclients: false + events: false + helper: false + config: false + extra: false + reserved: false diff --git a/src/api/docs/content/specs/dns.yaml b/src/api/docs/content/specs/dns.yaml index d4fe42f7..75eb771e 100644 --- a/src/api/docs/content/specs/dns.yaml +++ b/src/api/docs/content/specs/dns.yaml @@ -79,10 +79,10 @@ components: content: application/json: schema: - allOf: - - $ref: 'dns.yaml#/components/schemas/cache_size' - - $ref: 'dns.yaml#/components/schemas/cache_inserted' - - $ref: 'dns.yaml#/components/schemas/cache_evicted' + $ref: 'dns.yaml#/components/schemas/cache' + examples: + cache: + $ref: 'dns.yaml#/components/examples/cache' '401': description: Unauthorized content: @@ -120,27 +120,6 @@ components: description: Remaining seconds until blocking mode is automatically changed nullable: true example: 15 - cache_size: - type: object - properties: - size: - type: integer - description: Size of the DNS domain cache - example: 10000 - cache_inserted: - type: object - properties: - inserted: - type: integer - description: Number of total insertions into the cache - example: 100 - cache_evicted: - type: object - properties: - evicted: - type: integer - description: The number of cache entries that had to be removed although the corresponding entries were **not** expired - example: 0 dns_port: type: object properties: @@ -148,6 +127,53 @@ components: type: integer description: DNS port example: 53 + cache: + type: object + properties: + cache: + type: object + description: Cache information + properties: + size: + type: integer + description: Cache size + inserted: + type: integer + description: Number of inserted entries + evicted: + type: integer + description: Number of evicted entries + expired: + type: integer + description: Number of expired entries + immortal: + type: integer + description: Number of immortal entries + valid: + type: object + description: Number of valid entries + properties: + ipv4: + type: integer + description: Number of valid IPv4 entries + ipv6: + type: integer + description: Number of valid IPv6 entries + cname: + type: integer + description: Number of valid CNAME entries + srv: + type: integer + description: Number of valid SRV entries + ds: + type: integer + description: Number of valid DS entries + dnskey: + type: integer + description: Number of valid DNSKEY entries + other: + type: integer + description: Number of valid other entries errors: item_missing: type: object @@ -189,3 +215,21 @@ components: nullable: true description: "Additional data (if available)" example: null + examples: + cache: + summary: Cache info + value: + cache: + size: 10000 + inserted: 4060 + evicted: 0 + expired: 0 + immortal: 0 + valid: + ipv4: 212 + ipv6: 61 + cname: 14 + srv: 0 + ds: 60 + dnskey: 35 + other: 14 diff --git a/src/api/docs/content/specs/stats.yaml b/src/api/docs/content/specs/stats.yaml index 04a67bef..a18a23c5 100644 --- a/src/api/docs/content/specs/stats.yaml +++ b/src/api/docs/content/specs/stats.yaml @@ -489,6 +489,10 @@ components: type: integer description: Total number of queries example: 29160 + blocked_queries: + type: integer + description: Number of blocked queries + example: 6379 top_clients: type: object properties: @@ -514,6 +518,10 @@ components: type: integer description: Total number of queries example: 29160 + blocked_queries: + type: integer + description: Number of blocked queries + example: 6379 query_types: type: object properties: diff --git a/src/api/ftl.c b/src/api/ftl.c index cd4987fb..0f9cd67b 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -423,7 +423,7 @@ int get_ftl_obj(struct ftl_conn *api, cJSON *ftl, const bool is_locked) const int db_allowed = counters->database.domains.allowed; const int db_denied = counters->database.domains.denied; const int clients_total = counters->clients; - const int privacylevel = config.misc.privacylevel; + const int privacylevel = config.misc.privacylevel.v.privacy_level; // unique_clients: count only clients that have been active within the most recent 24 hours int activeclients = 0; diff --git a/src/api/history.c b/src/api/history.c index c0c1626d..623a9631 100644 --- a/src/api/history.c +++ b/src/api/history.c @@ -103,7 +103,7 @@ int api_history_clients(struct ftl_conn *api) } // Exit before processing any data if requested via config setting - if(config.misc.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS || sendit < 0) + if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS_CLIENTS || sendit < 0) { // Minimum structure is // {"history":[], "clients":[]} diff --git a/src/api/queries.c b/src/api/queries.c index b473f059..8f4c2c51 100644 --- a/src/api/queries.c +++ b/src/api/queries.c @@ -217,7 +217,7 @@ static void querystr_finish(char *querystr) int api_queries(struct ftl_conn *api) { // Exit before processing any data if requested via config setting - if(config.misc.privacylevel >= PRIVACY_MAXIMUM) + if(config.misc.privacylevel.v.privacy_level >= PRIVACY_MAXIMUM) { // Minimum structure is // {"queries":[], "cursor": null} diff --git a/src/api/stats.c b/src/api/stats.c index c9184d1f..62ccf225 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -124,10 +124,10 @@ int api_stats_top_domains(struct ftl_conn *api) return send_json_unauthorized(api); // Exit before processing any data if requested via config setting - if(config.misc.privacylevel >= PRIVACY_HIDE_DOMAINS) + if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS) { log_debug(DEBUG_API, "Not returning top domains: Privacy level is set to %i", - config.misc.privacylevel); + config.misc.privacylevel.v.privacy_level); // Minimum structure is // {"top_domains":[]} @@ -274,10 +274,10 @@ int api_stats_top_clients(struct ftl_conn *api) return send_json_unauthorized(api); // Exit before processing any data if requested via config setting - if(config.misc.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) + if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS_CLIENTS) { log_debug(DEBUG_API, "Not returning top clients: Privacy level is set to %i", - config.misc.privacylevel); + config.misc.privacylevel.v.privacy_level); // Minimum structure is // {"top_clients":[]} @@ -376,16 +376,9 @@ int api_stats_top_clients(struct ftl_conn *api) cJSON *json = JSON_NEW_OBJECT(); JSON_ADD_ITEM_TO_OBJECT(json, "clients", top_clients); - if(blocked) - { - const int blocked_queries = get_blocked_count(); - JSON_ADD_NUMBER_TO_OBJECT(json, "blocked_queries", blocked_queries); - } - else - { - JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", counters->queries); - } - + const int blocked_queries = get_blocked_count(); + JSON_ADD_NUMBER_TO_OBJECT(json, "blocked_queries", blocked_queries); + JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", counters->queries); JSON_SEND_OBJECT_UNLOCK(json); } @@ -534,7 +527,7 @@ int api_stats_recentblocked(struct ftl_conn *api) return send_json_unauthorized(api); // Exit before processing any data if requested via config setting - if(config.misc.privacylevel >= PRIVACY_HIDE_DOMAINS) + if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS) { // Minimum structure is // {"blocked":[]} diff --git a/src/config/config.c b/src/config/config.c index 3dff4e58..5e28e7a6 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -20,97 +20,615 @@ // file_exists() #include "../files.h" -ConfigStruct config; -ConfigStruct defaults; +struct config config = { 0 }; +bool debug_any = false; +int dns_port = -1; -void setDefaults(void) +void set_all_debug(const bool status) { - // top-level properties - defaults.debug = 0; + 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(i); + // Skip config entries whose path's are not starting in "debug." + if(strcmp("debug", conf_item->p[0]) != 0) + continue; + + // Set status + conf_item->v.b = status; + } +} + +// Extract and store key from full path +static char **gen_config_path(const char *pathin) +{ + char *path = (char*)pathin; + char *saveptr = path; + + // Sanity check + if(!pathin) + { + log_err("Config path is empty"); + return NULL; + } + + // Allocate memory for the path elements + char **paths = calloc(MAX_CONFIG_PATH_DEPTH, sizeof(char*)); + //char *token; + + size_t pathlen = 0; + // Extract all path elements + while(*path != '\0') + { + // Advance to either the next delimiter + // But only until the end of the string + while(*path != '.' && *path != '\0') + path++; + + // Get length of the extracted string + size_t len = path - saveptr; + // Create a private copy of this element in the chain of elements + paths[pathlen] = calloc(len + 1, sizeof(char)); + // No need to NULL-terminate, strncpy does this for us + strncpy(paths[pathlen], saveptr, len); + + // Did we reach the end of the string? + if(*path == '\0') + break; + + // Advance to next character + saveptr = ++path; + // Advance to next path element + pathlen++; + + // Safetly measure: Exit if this path is too deep + if(pathlen > MAX_CONFIG_PATH_DEPTH-1) + break; + } + + return paths; +} + +struct conf_item *get_conf_item(const unsigned int n) +{ + // Sanity check + if(n > CONFIG_ELEMENTS-1) + { + log_err("Config item with index %u requested but we have only %lu elements", n, CONFIG_ELEMENTS-1); + return NULL; + } + + // Return n-th config element + return (void*)&config + n*sizeof(struct conf_item); +} + +struct conf_item *get_debug_item(const enum debug_flag debug) +{ + // Sanity check + if(debug > DEBUG_MAX-1) + { + log_err("Debug config item with index %u requested but we have only %u debug elements", debug, DEBUG_MAX-1); + return NULL; + } + + // Return n-th config element + return (void*)&config.debug + debug*sizeof(struct conf_item); +} + +unsigned int __attribute__ ((pure)) config_path_depth(struct conf_item *conf_item) +{ + // Determine depth of this config path + for(unsigned int i = 0; i < MAX_CONFIG_PATH_DEPTH; i++) + if(conf_item->p[i] == NULL) + return i; + + // This should never happen as we have a maximum depth of + // MAX_CONFIG_PATH_DEPTH + return MAX_CONFIG_PATH_DEPTH; + +} + +void initConfig(void) +{ // struct dns - defaults.dns.CNAMEdeepInspect = true; - defaults.dns.blockESNI = true; - defaults.dns.EDNS0ECS = true; - defaults.dns.ignoreLocalhost = false; + config.dns.CNAMEdeepInspect.k = "dns.CNAMEdeepInspect"; + config.dns.CNAMEdeepInspect.h = "Should FTL walk CNAME paths?"; + config.dns.CNAMEdeepInspect.t = CONF_BOOL; + config.dns.CNAMEdeepInspect.d.b = true; + + config.dns.blockESNI.k = "dns.blockESNI"; + config.dns.blockESNI.h = "Should _esni. subdomains be blocked by default?"; + config.dns.blockESNI.t = CONF_BOOL; + config.dns.blockESNI.d.b = true; + + config.dns.EDNS0ECS.k = "dns.EDNS0ECS"; + config.dns.EDNS0ECS.h = "Should _esni. subdomains be blocked by default?"; + config.dns.EDNS0ECS.t = CONF_BOOL; + config.dns.EDNS0ECS.d.b = true; + + config.dns.ignoreLocalhost.k = "dns.ignoreLocalhost"; + config.dns.ignoreLocalhost.h = "Should FTL hide queries made by localhost?"; + config.dns.ignoreLocalhost.t = CONF_BOOL; + config.dns.ignoreLocalhost.d.b = false; + + config.dns.showDNSSEC.k = "dns.showDNSSEC"; + config.dns.showDNSSEC.h = "Should FTL should internally generated DNSSEC queries?"; + config.dns.showDNSSEC.t = CONF_BOOL; + config.dns.showDNSSEC.d.b = true; + + config.dns.analyzeAAAA.k = "dns.analyzeAAAA"; + config.dns.analyzeAAAA.h = "Should FTL analyze AAAA queries?"; + config.dns.analyzeAAAA.t = CONF_BOOL; + config.dns.analyzeAAAA.d.b = true; + + config.dns.analyzeOnlyAandAAAA.k = "dns.analyzeOnlyAandAAAA"; + config.dns.analyzeOnlyAandAAAA.h = "Should FTL analyze *only* A and AAAA queries?"; + config.dns.analyzeOnlyAandAAAA.t = CONF_BOOL; + config.dns.analyzeOnlyAandAAAA.d.b = false; + + config.dns.piholePTR.k = "dns.piholePTR"; + config.dns.piholePTR.h = "Should FTL return \"pi.hole\" as name for PTR requests to local IP addresses?"; + config.dns.piholePTR.a = "[ \"NONE\", \"HOSTNAME\", \"HOSTNAMEFQDN\", \"PI.HOLE\" ]"; + config.dns.piholePTR.t = CONF_ENUM_PTR_TYPE; + config.dns.piholePTR.d.ptr_type = PTR_PIHOLE; + + config.dns.replyWhenBusy.k = "dns.replyWhenBusy"; + config.dns.replyWhenBusy.h = "How should FTL handle queries when the gravity database is not available?"; + config.dns.replyWhenBusy.a = "[ \"BLOCK\", \"ALLOW\", \"REFUSE\", \"DROP\" ]"; + config.dns.replyWhenBusy.t = CONF_ENUM_BUSY_TYPE; + config.dns.replyWhenBusy.d.busy_reply = BUSY_ALLOW; + + config.dns.blockTTL.k = "dns.blockTTL"; + config.dns.blockTTL.h = "TTL for blocked queries [seconds]"; + config.dns.blockTTL.t = CONF_UINT; + config.dns.blockTTL.d.ui = 2; + + config.dns.blockingmode.k = "dns.blockingmode"; + config.dns.blockingmode.h = "How should FTL reply to blocked queries?"; + config.dns.blockingmode.a = "[ \"NULL\", \"IP-NODATA-AAAA\", \"IP\", \"NXDOMAIN\" ]"; + config.dns.blockingmode.t = CONF_ENUM_BLOCKING_MODE; + config.dns.blockingmode.d.blocking_mode = MODE_NULL; + + // sub-struct dns.rate_limit + config.dns.rateLimit.count.k = "dns.rateLimit.count"; + config.dns.rateLimit.count.h = "How many queries are permitted..."; + config.dns.rateLimit.count.t = CONF_UINT; + config.dns.rateLimit.count.d.ui = 1000; + + config.dns.rateLimit.interval.k = "dns.rateLimit.interval"; + config.dns.rateLimit.interval.h = "... in the set interval before rate-limiting?"; + config.dns.rateLimit.interval.t = CONF_UINT; + config.dns.rateLimit.interval.d.ui = 60; + + // sub-struct dns.special_domains + config.dns.specialDomains.mozillaCanary.k = "dns.specialDomains.mozillaCanary"; + config.dns.specialDomains.mozillaCanary.h = "Should FTL handle use-application-dns.net specifically and always return NXDOMAIN?"; + config.dns.specialDomains.mozillaCanary.t = CONF_BOOL; + config.dns.specialDomains.mozillaCanary.d.b = true; + + config.dns.specialDomains.iCloudPrivateRelay.k = "dns.specialDomains.iCloudPrivateRelay"; + config.dns.specialDomains.iCloudPrivateRelay.h = "Should FTL handle the iCloud privacy relay domains specifically and always return NXDOMAIN?"; + config.dns.specialDomains.iCloudPrivateRelay.t = CONF_BOOL; + config.dns.specialDomains.iCloudPrivateRelay.d.b = true; + + // sub-struct dns.reply_addr + config.dns.reply.host.overwrite_v4.k = "dns.reply.host.overwrite_v4"; + config.dns.reply.host.overwrite_v4.h = "Use a specific IPv4 address for the Pi-hole host?"; + config.dns.reply.host.overwrite_v4.t = CONF_BOOL; + config.dns.reply.host.overwrite_v4.d.b = false; + + config.dns.reply.host.v4.k = "dns.reply.host.IPv4"; + config.dns.reply.host.v4.h = "Custom IPv4 address for the Pi-hole host"; + config.dns.reply.host.v4.a = " or empty string (\"\")"; + config.dns.reply.host.v4.t = CONF_STRUCT_IN_ADDR; + memset(&config.dns.reply.host.v4.d.in_addr, 0, sizeof(struct in_addr)); + + config.dns.reply.host.overwrite_v6.k = "dns.reply.host.overwrite_v6"; + config.dns.reply.host.overwrite_v6.h = "Use a specific IPv6 address for the Pi-hole host?"; + config.dns.reply.host.overwrite_v6.t = CONF_BOOL; + config.dns.reply.host.overwrite_v6.d.b = false; + + config.dns.reply.host.v6.k = "dns.reply.host.IPv6"; + config.dns.reply.host.v6.h = "Custom IPv6 address for the Pi-hole host"; + config.dns.reply.host.v6.a = " or empty string (\"\")"; + config.dns.reply.host.v6.t = CONF_STRUCT_IN6_ADDR; + memset(&config.dns.reply.host.v6.d.in6_addr, 0, sizeof(struct in6_addr)); + + config.dns.reply.blocking.overwrite_v4.k = "dns.reply.blocking.overwrite_v4"; + config.dns.reply.blocking.overwrite_v4.h = "Use a specific IPv4 address in IP blocking mode?"; + config.dns.reply.blocking.overwrite_v4.t = CONF_BOOL; + config.dns.reply.blocking.overwrite_v4.d.b = false; + + config.dns.reply.blocking.v4.k = "dns.reply.blocking.IPv4"; + config.dns.reply.blocking.v4.h = "Custom IPv4 address for IP blocking mode"; + config.dns.reply.blocking.v4.a = " or empty string (\"\")"; + config.dns.reply.blocking.v4.t = CONF_STRUCT_IN_ADDR; + memset(&config.dns.reply.blocking.v4.d.in_addr, 0, sizeof(struct in_addr)); + + config.dns.reply.blocking.overwrite_v6.k = "dns.reply.blocking.overwrite_v6"; + config.dns.reply.blocking.overwrite_v6.h = "Use a specific IPv6 address in IP blocking mode?"; + config.dns.reply.blocking.overwrite_v6.t = CONF_BOOL; + config.dns.reply.blocking.overwrite_v6.d.b = false; + + config.dns.reply.blocking.v6.k = "dns.reply.blocking.IPv6"; + config.dns.reply.blocking.v6.h = "Custom IPv6 address for IP blocking mode"; + config.dns.reply.blocking.v6.a = " or empty string (\"\")"; + config.dns.reply.blocking.v6.t = CONF_STRUCT_IN6_ADDR; + memset(&config.dns.reply.blocking.v6.d.in6_addr, 0, sizeof(struct in6_addr)); - defaults.dns.piholePTR = PTR_PIHOLE; - defaults.dns.replyWhenBusy = BUSY_ALLOW; - defaults.dns.showDNSSEC = true; - defaults.dns.blockTTL = 2; - defaults.dns.analyzeAAAA = true; - defaults.dns.analyzeOnlyAandAAAA = false; - defaults.dns.blockingmode = MODE_NULL; - // sub-struct rate_limit - defaults.dns.rateLimit.count = 1000; - defaults.dns.rateLimit.interval = 60; - // sub-struct special_domains - defaults.dns.specialDomains.mozillaCanary = true; - defaults.dns.specialDomains.iCloudPrivateRelay = true; - // sub-struct reply_addr - defaults.dns.reply.blocking.overwrite_v4 = false; - memset(&defaults.dns.reply.blocking.v4, 0, sizeof(config.dns.reply.blocking.v4)); - defaults.dns.reply.blocking.overwrite_v6 = false; - memset(&defaults.dns.reply.blocking.v6, 0, sizeof(config.dns.reply.blocking.v6)); - defaults.dns.reply.host.overwrite_v4 = false; - memset(&defaults.dns.reply.host.v4, 0, sizeof(config.dns.reply.host.v4)); - defaults.dns.reply.host.overwrite_v6 = false; - memset(&defaults.dns.reply.host.v6, 0, sizeof(config.dns.reply.host.v6)); // struct resolver - defaults.resolver.resolveIPv6 = true; - defaults.resolver.resolveIPv4 = true; - defaults.resolver.networkNames = true; - defaults.resolver.refreshNames = REFRESH_IPV4_ONLY; + config.resolver.resolveIPv6.k = "resolver.resolveIPv6"; + config.resolver.resolveIPv6.h = "Should FTL try to resolve IPv6 addresses to hostnames?"; + config.resolver.resolveIPv6.t = CONF_BOOL; + config.resolver.resolveIPv6.d.b = true; + + config.resolver.resolveIPv4.k = "resolver.resolveIPv4"; + config.resolver.resolveIPv4.h = "Should FTL try to resolve IPv4 addresses to hostnames?"; + config.resolver.resolveIPv4.t = CONF_BOOL; + config.resolver.resolveIPv4.d.b = true; + + config.resolver.networkNames.k = "resolver.networkNames"; + config.resolver.networkNames.h = "Try to obtain client names from the network table?"; + config.resolver.networkNames.t = CONF_BOOL; + config.resolver.networkNames.d.b = true; + + config.resolver.refreshNames.k = "resolver.refreshNames"; + config.resolver.refreshNames.h = "How (and if) hourly PTR lookups should be made"; + config.resolver.refreshNames.a = "[ \"IPV4_ONLY\", \"ALL\", \"UNKNOWN\", \"NONE\" ]"; + config.resolver.refreshNames.t = CONF_ENUM_REFRESH_HOSTNAMES; + config.resolver.refreshNames.d.refresh_hostnames = REFRESH_IPV4_ONLY; + // struct database - defaults.database.DBimport = true; - defaults.database.maxDBdays = 365; - defaults.database.maxHistory = MAXLOGAGE*3600; - defaults.database.DBinterval = 60; - // sub-struct network - defaults.database.network.parseARPcache = true; - defaults.database.network.expire = defaults.database.maxDBdays; + config.database.DBimport.k = "database.DBimport"; + config.database.DBimport.h = "Should FTL load information from the database on startup to be aware of the most recent history?"; + config.database.DBimport.t = CONF_BOOL; + config.database.DBimport.d.b = true; - // struct misc - defaults.misc.nice = -10; - defaults.misc.delay_startup = 0; - defaults.misc.addr2line = true; - defaults.misc.privacylevel = PRIVACY_SHOW_ALL; + config.database.DBexport.k = "database.DBexport"; + config.database.DBexport.h = "Should FTL store queries in the long-term database?"; + config.database.DBexport.t = CONF_BOOL; + config.database.DBexport.d.b = true; + + config.database.maxDBdays.k = "database.maxDBdays"; + config.database.maxDBdays.h = "How much history should be imported from the database [seconds]? (max 24*60*60 = 86400)"; + config.database.maxDBdays.t = CONF_INT; + config.database.maxDBdays.d.i = 365; + + config.database.maxHistory.k = "database.maxHistory"; + config.database.maxHistory.h = "How long should queries be stored in the database [days]?"; + config.database.maxHistory.t = CONF_UINT; + config.database.maxHistory.d.ui = MAXLOGAGE*3600; + + config.database.DBinterval.k = "database.DBinterval"; + config.database.DBinterval.h = "How often do we store queries in FTL's database [seconds]?"; + config.database.DBinterval.t = CONF_UINT; + config.database.DBinterval.d.ui = 60; + + // sub-struct database.network + config.database.network.parseARPcache.k = "database.network.parseARPcache"; + config.database.network.parseARPcache.h = "Should FTL anaylze the local ARP cache?"; + config.database.network.parseARPcache.t = CONF_BOOL; + config.database.network.parseARPcache.d.b = true; + + config.database.network.expire.k = "database.network.expire"; + config.database.network.expire.h = "How long should IP addresses be kept in the network_addresses table [days]?"; + config.database.network.expire.t = CONF_UINT; + config.database.network.expire.d.ui = config.database.maxDBdays.d.ui; - // sub-struct check - defaults.misc.check.load = true; - defaults.misc.check.disk = 90; - defaults.misc.check.shmem = 90; // struct http - defaults.http.localAPIauth = true; - defaults.http.prettyJSON = false; - defaults.http.sessionTimeout = 300; - defaults.http.domain = (char*)"pi.hole"; - defaults.http.acl = (char*)"+0.0.0.0/0"; - defaults.http.port = (char*)"8080,[::]:8080"; - defaults.http.paths.webroot = (char*)"/var/www/html"; - defaults.http.paths.webhome = (char*)"/admin/"; + config.http.localAPIauth.k = "http.localAPIauth"; + config.http.localAPIauth.h = "Does local clients need to authenticate to access the API?"; + config.http.localAPIauth.t = CONF_BOOL; + config.http.localAPIauth.d.b = true; + + config.http.prettyJSON.k = "http.prettyJSON"; + config.http.prettyJSON.h = "Should FTL prettify the API output?"; + config.http.prettyJSON.t = CONF_BOOL; + config.http.prettyJSON.d.b = false; + + config.http.sessionTimeout.k = "http.sessionTimeout"; + config.http.sessionTimeout.h = "How long should a session be considered valid after login [seconds]?"; + config.http.sessionTimeout.t = CONF_UINT; + config.http.sessionTimeout.d.ui = 300; + + config.http.domain.k = "http.domain"; + config.http.domain.h = "On which domain is the web interface served?"; + config.http.domain.a = ""; + config.http.domain.t = CONF_STRING; + config.http.domain.d.s = (char*)"pi.hole"; + + // Webserver access control list + // + // Allows restrictions to be put on the list of IP addresses which have + // access to our web server. The ACL is a comma separated list of IP + // subnets, where each subnet is pre-pended by either a - or a + sign. A + // plus sign means allow, where a minus sign means deny. If a subnet mask is + // omitted, such as -1.2.3.4, this means to deny only that single IP + // address. The default setting is to allow all accesses. + // + // On each request the full list is traversed, and the last (!) match wins. + // + // Example 1: acl = \"-0.0.0.0/0,+127.0.0.1\" ---> deny all accesses, except + // from 127.0.0.1 + // + // Example 2: acl = \"-0.0.0.0/0,+192.168.0.0/16\" ---> deny all accesses, + // except from the 192.168/16 subnet + // + // IPv6 addresses are specified in CIDR-form [a:b::c]/64 + config.http.acl.k = "http.acl"; + config.http.acl.h = "Webserver access control list"; + config.http.acl.a = ""; + config.http.acl.t = CONF_STRING; + config.http.acl.d.s = (char*)"+0.0.0.0/0"; + + config.http.port.k = "http.port"; + config.http.port.h = "Ports to be used by the webserver"; + config.http.port.a = "comma-separated list of <[ip_address:]port>"; + config.http.port.t = CONF_STRING; + config.http.port.d.s = (char*)"8080,[::]:8080"; + + // sub-struct paths + config.http.paths.webroot.k = "http.paths.webroot"; + config.http.paths.webroot.h = "Server root on the host"; + config.http.paths.webroot.a = ""; + config.http.paths.webroot.t = CONF_STRING; + config.http.paths.webroot.d.s = (char*)"/var/www/html"; + + config.http.paths.webhome.k = "http.paths.webhome"; + config.http.paths.webhome.h = "Sub-directory of the root containing the web interface"; + config.http.paths.webhome.a = ", both slashes are needed!"; + config.http.paths.webhome.t = CONF_STRING; + config.http.paths.webhome.d.s = (char*)"/admin/"; + // struct files - defaults.files.database = (char*)"/etc/pihole/pihole-FTL.db"; - defaults.files.pid = (char*)"/run/pihole-FTL.pid"; - defaults.files.setupVars = (char*)"/etc/pihole/setupVars.conf"; - defaults.files.macvendor = (char*)"/etc/pihole/macvendor.db"; - defaults.files.gravity = (char*)"/etc/pihole/gravity.db"; - defaults.files.http_info = (char*)"/var/log/pihole/HTTP_info.log"; - defaults.files.ph7_error = (char*)"/var/log/pihole/PH7.log"; + // config.files.log is set in a separate function + config.files.pid.k = "files.pid"; + config.files.pid.h = "The location of FTL's PID file"; + config.files.pid.a = ""; + config.files.pid.t = CONF_STRING; + config.files.pid.d.s = (char*)"/run/pihole-FTL.pid"; - // Copy default values into config struct - memcpy(&config, &defaults, sizeof(config)); + config.files.database.k = "files.database"; + config.files.database.h = "The location of FTL's long-term database"; + config.files.database.a = ""; + config.files.database.t = CONF_STRING; + config.files.database.d.s = (char*)"/etc/pihole/pihole-FTL.db"; + + config.files.gravity.k = "files.gravity"; + config.files.gravity.h = "The location of Pi-hole's gravity database"; + config.files.gravity.a = ""; + config.files.gravity.t = CONF_STRING; + config.files.gravity.d.s = (char*)"/etc/pihole/gravity.db"; + + config.files.macvendor.k = "files.macvendor"; + config.files.macvendor.h = "The database containing MAC -> Vendor information for the network table"; + config.files.macvendor.a = ""; + config.files.macvendor.t = CONF_STRING; + config.files.macvendor.d.s = (char*)"/etc/pihole/macvendor.db"; + + config.files.setupVars.k = "files.setupVars"; + config.files.setupVars.h = "The config file of Pi-hole"; + config.files.setupVars.a = ""; + config.files.setupVars.t = CONF_STRING; + config.files.setupVars.d.s = (char*)"/etc/pihole/setupVars.conf"; + + config.files.http_info.k = "files.http_info"; + config.files.http_info.h = "The log file used by the webserver"; + config.files.http_info.a = ""; + config.files.http_info.t = CONF_STRING; + config.files.http_info.d.s = (char*)"/var/log/pihole/HTTP_info.log"; + + config.files.ph7_error.k = "files.ph7_error"; + config.files.ph7_error.h = "The log file used by the dynamic interpreter PH7"; + config.files.ph7_error.a = ""; + config.files.ph7_error.t = CONF_STRING; + config.files.ph7_error.d.s = (char*)"/var/log/pihole/PH7.log"; + + + // struct misc + config.misc.nice.k = "misc.nice"; + config.misc.nice.h = "Set niceness of pihole-FTL (can be disabled by setting to -999)"; + config.misc.nice.t = CONF_INT; + config.misc.nice.d.i = -10; + + config.misc.addr2line.k = "misc.addr2line"; + config.misc.addr2line.h = "The log file used by the dynamic interpreter PH7"; + config.misc.addr2line.t = CONF_BOOL; + config.misc.addr2line.d.b = true; + + config.misc.privacylevel.k = "misc.privacylevel"; + config.misc.privacylevel.h = "Privacy level"; + config.misc.privacylevel.t = CONF_ENUM_PRIVACY_LEVEL; + config.misc.privacylevel.d.privacy_level = PRIVACY_SHOW_ALL; + + config.misc.delay_startup.k = "misc.delay_startup"; + config.misc.delay_startup.h = "Should FTL try to call addr2line when generating backtraces?"; + config.misc.delay_startup.t = CONF_UINT; + config.misc.delay_startup.d.ui = 0; + + // sub-struct misc.check + config.misc.check.load.k = "misc.check.load"; + config.misc.check.load.h = "Should FTL check the 15 min average of CPU load and complain if the load is larger than the number of available CPU cores?"; + config.misc.check.load.t = CONF_BOOL; + config.misc.check.load.d.b = true; + + config.misc.check.disk.k = "misc.check.disk"; + config.misc.check.disk.h = "Limit above which FTL should complain about a shared-memory shortage [percent]"; + config.misc.check.disk.t = CONF_UINT; + config.misc.check.disk.d.ui = 90; + + config.misc.check.shmem.k = "misc.check.shmem"; + config.misc.check.shmem.h = "Limit above which FTL should complain about disk shortage for checked files [percent]"; + config.misc.check.shmem.t = CONF_UINT; + config.misc.check.shmem.d.ui = 90; + + + // struct debug + config.debug.database.k = "debug.database"; + config.debug.database.h = "Enable extra logging of database actions"; + config.debug.database.t = CONF_BOOL; + config.debug.database.d.b = false; + + config.debug.networking.k = "debug.networking"; + config.debug.networking.h = "Enable extra logging of detected interfaces"; + config.debug.networking.t = CONF_BOOL; + config.debug.networking.d.b = false; + + config.debug.locks.k = "debug.locks"; + config.debug.locks.h = "Enable extra logging of shared memory lock actions"; + config.debug.locks.t = CONF_BOOL; + config.debug.locks.d.b = false; + + config.debug.queries.k = "debug.queries"; + config.debug.queries.h = "Print extensive query information"; + config.debug.queries.t = CONF_BOOL; + config.debug.queries.d.b = false; + + config.debug.flags.k = "debug.flags"; + config.debug.flags.h = "Print flags of queries received by the DNS hooks"; + config.debug.flags.t = CONF_BOOL; + config.debug.flags.d.b = false; + + config.debug.shmem.k = "debug.shmem"; + config.debug.shmem.h = "Print information about shared memory buffers"; + config.debug.shmem.t = CONF_BOOL; + config.debug.shmem.d.b = false; + + config.debug.gc.k = "debug.gc"; + config.debug.gc.h = "Print information about garbage collection"; + config.debug.gc.t = CONF_BOOL; + config.debug.gc.d.b = false; + + config.debug.arp.k = "debug.arp"; + config.debug.arp.h = "Print information about ARP table processing"; + config.debug.arp.t = CONF_BOOL; + config.debug.arp.d.b = false; + + config.debug.regex.k = "debug.regex"; + config.debug.regex.h = "Enable extra logging of regex matching details"; + config.debug.regex.t = CONF_BOOL; + config.debug.regex.d.b = false; + + config.debug.api.k = "debug.api"; + config.debug.api.h = "Enable extra logging of API activities"; + config.debug.api.t = CONF_BOOL; + config.debug.api.d.b = false; + + config.debug.overtime.k = "debug.overtime"; + config.debug.overtime.h = "Print information about overTime memory operations"; + config.debug.overtime.t = CONF_BOOL; + config.debug.overtime.d.b = false; + + config.debug.status.k = "debug.status"; + config.debug.status.h = "Enable extra logging of query status changes"; + config.debug.status.t = CONF_BOOL; + config.debug.status.d.b = false; + + config.debug.caps.k = "debug.caps"; + config.debug.caps.h = "Print information about capabilities granted to the pihole-FTL process"; + config.debug.caps.t = CONF_BOOL; + config.debug.caps.d.b = false; + + config.debug.dnssec.k = "debug.dnssec"; + config.debug.dnssec.h = "Print information about DNSSEC activity"; + config.debug.dnssec.t = CONF_BOOL; + config.debug.dnssec.d.b = false; + + config.debug.vectors.k = "debug.vectors"; + config.debug.vectors.h = "Print vector operation details"; + config.debug.vectors.t = CONF_BOOL; + config.debug.vectors.d.b = false; + + config.debug.resolver.k = "debug.resolver"; + config.debug.resolver.h = "Extensive information about hostname resolution like which DNS servers are used"; + config.debug.resolver.t = CONF_BOOL; + config.debug.resolver.d.b = false; + + config.debug.edns0.k = "debug.edns0"; + config.debug.edns0.h = "Print EDNS(0) debugging information"; + config.debug.edns0.t = CONF_BOOL; + config.debug.edns0.d.b = false; + + config.debug.clients.k = "debug.clients"; + config.debug.clients.h = "Enable extra client detail logging"; + config.debug.clients.t = CONF_BOOL; + config.debug.clients.d.b = false; + + config.debug.aliasclients.k = "debug.aliasclients"; + config.debug.aliasclients.h = "Print aliasclient details"; + config.debug.aliasclients.t = CONF_BOOL; + config.debug.aliasclients.d.b = false; + + config.debug.events.k = "debug.events"; + config.debug.events.h = "Log information about processed internal events"; + config.debug.events.t = CONF_BOOL; + config.debug.events.d.b = false; + + config.debug.helper.k = "debug.helper"; + config.debug.helper.h = "Enable logging of script helper activity"; + config.debug.helper.t = CONF_BOOL; + config.debug.helper.d.b = false; + + config.debug.config.k = "debug.config"; + config.debug.config.h = "Print config parsing details"; + config.debug.config.t = CONF_BOOL; + config.debug.config.d.b = false; + + config.debug.extra.k = "debug.extra"; + config.debug.extra.h = "Special debug flag that may be used for debugging specific issues"; + config.debug.extra.t = CONF_BOOL; + config.debug.extra.d.b = false; + + config.debug.reserved.k = "debug.reserved"; + config.debug.reserved.h = "Reserved debug flag"; + config.debug.reserved.t = CONF_BOOL; + config.debug.reserved.d.b = false; + + // Post-processing: + // Initialize and verify config data + for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) + { + // Get pointer to memory location of this conf_item + struct conf_item *conf_item = get_conf_item(i); + + // Initialize config value with default one for all *except* the log file path + if(conf_item != &config.files.log) + memcpy(&conf_item->v, &conf_item->d, sizeof(conf_item->d)); + + // Parse and split paths + conf_item->p = gen_config_path(conf_item->k); + + // Verify all config options are defined above + if(!conf_item->p) + log_err("Config option %u/%lu is not set!", i, CONFIG_ELEMENTS); + else + if(conf_item->p[3]) + log_debug(DEBUG_CONFIG, "Config option %u is %s.%s.%s.%s", i, conf_item->p[0], conf_item->p[1], conf_item->p[2], conf_item->p[3]); + else if(conf_item->p[2]) + log_debug(DEBUG_CONFIG, "Config option %u is %s.%s.%s", i, conf_item->p[0], conf_item->p[1], conf_item->p[2]); + else if(conf_item->p[1]) + log_debug(DEBUG_CONFIG, "Config option %u is %s.%s", i, conf_item->p[0], conf_item->p[1]); + else + log_debug(DEBUG_CONFIG, "Config option %u is %s", i, conf_item->p[0]); + } } void readFTLconf(void) { // First try to read TOML config file if(readFTLtoml()) + { + // If successful, we write the config file back to disk + // to ensure that all options are present and comments + // about options deviating from the default are present + writeFTLtoml(); return; + } // On error, try to read legacy (pre-v6.0) config file. If successful, // we move the legacy config file out of our way @@ -131,9 +649,16 @@ void readFTLconf(void) bool getLogFilePath(void) { - // Set default - defaults.files.log = (char*)"/var/log/pihole/FTL.log"; - config.files.log = defaults.files.log; + // Initialize memory + memset(&config, 0, sizeof(config)); + + // Initialize the config file path + config.files.log.k = "files.log"; + config.files.log.h = "The location of FTL's log file"; + config.files.log.a = ""; + config.files.log.t = CONF_STRING; + config.files.log.d.s = (char*)"/var/log/pihole/FTL.log"; + config.files.log.v.s = config.files.log.d.s; // Check if the config file contains a different path if(!getLogFilePathTOML()) diff --git a/src/config/config.h b/src/config/config.h index 759f6ca3..b8c54e31 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -26,9 +26,14 @@ #define GLOBALTOMLPATH "/etc/pihole/pihole-FTL.toml" -void setDefaults(void); +// Defined in config.c +void set_all_debug(const bool status); +void initConfig(void); void readFTLconf(void); bool getLogFilePath(void); +struct conf_item *get_conf_item(unsigned int n); +struct conf_item *get_debug_item(const enum debug_flag debug); +unsigned int config_path_depth(struct conf_item *conf_item) __attribute__ ((pure)); // Defined in toml_reader.c bool getPrivacyLevel(void); @@ -36,121 +41,179 @@ bool getBlockingMode(void); bool readDebugSettings(void); void init_config_mutex(void); -// We do not use bitfields in here as this struct exists only once in memory. -// Accessing bitfields may produce slightly more inefficient code on some -// architectures (such as ARM) and savng a few bit of RAM but bloating up the -// rest of the application each time these fields are accessed is bad. -typedef struct { +union conf_value { + bool b; + int i; + unsigned int ui; + long l; + unsigned long ul; + char *s; + enum ptr_type ptr_type; + enum busy_reply busy_reply; + enum blocking_mode blocking_mode; + enum refresh_hostnames refresh_hostnames; + enum privacy_level privacy_level; + enum debug_flag debug_flag; + struct in_addr in_addr; + struct in6_addr in6_addr; +}; + +enum conf_type { + CONF_BOOL, + CONF_INT, + CONF_UINT, + CONF_LONG, + CONF_ULONG, + CONF_STRING, + CONF_ENUM_PTR_TYPE, + CONF_ENUM_BUSY_TYPE, + CONF_ENUM_BLOCKING_MODE, + CONF_ENUM_REFRESH_HOSTNAMES, + CONF_ENUM_PRIVACY_LEVEL, + CONF_STRUCT_IN_ADDR, + CONF_STRUCT_IN6_ADDR +} __attribute__ ((packed)); + +#define MAX_CONFIG_PATH_DEPTH 4 + +struct conf_item { + const char *k; // item Key + char **p; // item Path + const char *h; // Help text / description + const char *a; // string of Allowed values (where applicable) + enum conf_type t; // variable Type + union conf_value v; // current Value + union conf_value d; // Default value +}; + +struct config { struct { - bool CNAMEdeepInspect; - bool blockESNI; - bool EDNS0ECS; - bool ignoreLocalhost; - bool showDNSSEC; - bool analyzeAAAA; - bool analyzeOnlyAandAAAA; - enum ptr_type piholePTR; - enum busy_reply replyWhenBusy; - unsigned int blockTTL; - unsigned int port; // set in fork_and_bind.c - enum blocking_mode blockingmode; + struct conf_item CNAMEdeepInspect; + struct conf_item blockESNI; + struct conf_item EDNS0ECS; + struct conf_item ignoreLocalhost; + struct conf_item showDNSSEC; + struct conf_item analyzeAAAA; + struct conf_item analyzeOnlyAandAAAA; + struct conf_item piholePTR; + struct conf_item replyWhenBusy; + struct conf_item blockTTL; + struct conf_item blockingmode; struct { - bool mozillaCanary; - bool iCloudPrivateRelay; + struct conf_item mozillaCanary; + struct conf_item iCloudPrivateRelay; } specialDomains; struct { struct { - bool overwrite_v4 :1; - bool overwrite_v6 :1; - struct in_addr v4; - struct in6_addr v6; + struct conf_item overwrite_v4; + struct conf_item overwrite_v6; + struct conf_item v4; + struct conf_item v6; } host; struct { - bool overwrite_v4 :1; - bool overwrite_v6 :1; - struct in_addr v4; - struct in6_addr v6; + struct conf_item overwrite_v4; + struct conf_item overwrite_v6; + struct conf_item v4; + struct conf_item v6; } blocking; } reply; struct { - unsigned int count; - unsigned int interval; + struct conf_item count; + struct conf_item interval; } rateLimit; } dns; struct { - bool resolveIPv4; - bool resolveIPv6; - bool networkNames; - enum refresh_hostnames refreshNames; + struct conf_item resolveIPv4; + struct conf_item resolveIPv6; + struct conf_item networkNames; + struct conf_item refreshNames; } resolver; struct { - bool DBimport; - bool DBexport; - unsigned int maxHistory; - int maxDBdays; - unsigned int DBinterval; + struct conf_item DBimport; + struct conf_item DBexport; + struct conf_item maxHistory; + struct conf_item maxDBdays; + struct conf_item DBinterval; struct { - bool parseARPcache; - unsigned int expire; + struct conf_item parseARPcache; + struct conf_item expire; } network; } database; struct { - bool localAPIauth; - bool prettyJSON; - unsigned int sessionTimeout; - char *domain; - char *acl; - char *port; + struct conf_item localAPIauth; + struct conf_item prettyJSON; + struct conf_item sessionTimeout; + struct conf_item domain; + struct conf_item acl; + struct conf_item port; struct { - char *webroot; - char *webhome; + struct conf_item webroot; + struct conf_item webhome; } paths; } http; struct { - char *log; - char *pid; - char *database; - char *gravity; - char *macvendor; - char *setupVars; - char *http_info; - char *ph7_error; + struct conf_item log; + struct conf_item pid; + struct conf_item database; + struct conf_item gravity; + struct conf_item macvendor; + struct conf_item setupVars; + struct conf_item http_info; + struct conf_item ph7_error; } files; struct { - int nice; - unsigned int delay_startup; - bool addr2line; - enum privacy_level privacylevel; + struct conf_item nice; + struct conf_item delay_startup; + struct conf_item addr2line; + struct conf_item privacylevel; struct { - bool load; - unsigned char shmem; - unsigned char disk; + struct conf_item load; + struct conf_item shmem; + struct conf_item disk; } check; } misc; - enum debug_flag debug; -} ConfigStruct; + struct { + // The order of items in this struct has to match the order in + // enum debug_flags due to a few simplifications made elsewhere + // in the code + struct conf_item database; + struct conf_item networking; + struct conf_item locks; + struct conf_item queries; + struct conf_item flags; + struct conf_item shmem; + struct conf_item gc; + struct conf_item arp; + struct conf_item regex; + struct conf_item api; + struct conf_item overtime; + struct conf_item status; + struct conf_item caps; + struct conf_item dnssec; + struct conf_item vectors; + struct conf_item resolver; + struct conf_item edns0; + struct conf_item clients; + struct conf_item aliasclients; + struct conf_item events; + struct conf_item helper; + struct conf_item config; + struct conf_item extra; + struct conf_item reserved; + } debug; +}; -typedef struct { - const char* conf; - const char* snapConf; - char* log; - char* pid; - char* port; - char* socketfile; - char* FTL_db; - char* gravity_db; - char* macvendor_db; - char* setupVars; - char* auditlist; -} FTLFileNamesStruct; +extern struct config config; +extern int dns_port; +extern bool debug_any; -extern ConfigStruct config; -extern ConfigStruct defaults; +#define CONFIG_ELEMENTS (sizeof(config)/sizeof(struct conf_item)) +#define DEBUG_ELEMENTS (sizeof(config.debug)/sizeof(struct conf_item)) #endif //CONFIG_H diff --git a/src/config/legacy_reader.c b/src/config/legacy_reader.c index a079cdd8..43cc5f6e 100644 --- a/src/config/legacy_reader.c +++ b/src/config/legacy_reader.c @@ -28,8 +28,7 @@ static pthread_mutex_t lock; // Private prototypes static char *parseFTLconf(FILE *fp, const char *key); static void releaseConfigMemory(void); -static void getPath(FILE* fp, const char *option, char **ptr); -static void setnice(const char *buffer, int fallback); +static char *getPath(FILE* fp, const char *option, char *ptr); static bool parseBool(const char *option, bool *ptr); static void readDebugingSettingsLegacy(FILE *fp); static void getBlockingModeLegacy(FILE *fp); @@ -67,21 +66,21 @@ bool getLogFilePathLegacy(FILE *fp) if(buffer == NULL) { // Use standard path if no custom path was obtained from the config file - config.files.log = strdup("/var/log/pihole/FTL.log"); + config.files.log.v.s = strdup("/var/log/pihole/FTL.log"); // Test if memory allocation was successful - if(config.files.log == NULL) + if(config.files.log.v.s == NULL) { - printf("FATAL: Allocating memory for config.files.log failed (%s, %i). Exiting.", + printf("FATAL: Allocating memory for config.files.log.v.s failed (%s, %i). Exiting.", strerror(errno), errno); exit(EXIT_FAILURE); } } // Use sscanf() to obtain filename from config file parameter only if buffer != NULL - else if(sscanf(buffer, "%127ms", &config.files.log) == 0) + else if(sscanf(buffer, "%127ms", &config.files.log.v.s) == 0) { // Empty file string - config.files.log = NULL; + config.files.log.v.s = NULL; log_info("Using syslog facility"); } @@ -103,7 +102,7 @@ const char *readFTLlegacy(void) // AAAA_QUERY_ANALYSIS // defaults to: Yes buffer = parseFTLconf(fp, "AAAA_QUERY_ANALYSIS"); - parseBool(buffer, &config.dns.analyzeAAAA); + parseBool(buffer, &config.dns.analyzeAAAA.v.b); // MAXDBDAYS // defaults to: 365 days @@ -119,18 +118,18 @@ const char *readFTLlegacy(void) // Only use valid values if(value == -1 || value >= 0) - config.database.maxDBdays = value; + config.database.maxDBdays.v.i = value; } // RESOLVE_IPV6 // defaults to: Yes buffer = parseFTLconf(fp, "RESOLVE_IPV6"); - parseBool(buffer, &config.resolver.resolveIPv6); + parseBool(buffer, &config.resolver.resolveIPv6.v.b); // RESOLVE_IPV4 // defaults to: Yes buffer = parseFTLconf(fp, "RESOLVE_IPV4"); - parseBool(buffer, &config.resolver.resolveIPv4); + parseBool(buffer, &config.resolver.resolveIPv4.v.b); // DBINTERVAL // How often do we store queries in FTL's database [minutes]? @@ -144,25 +143,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 = (int)(fvalue * 60); + config.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))) + if(!(buffer != NULL && sscanf(buffer, "%127ms", &config.files.database.v.s))) { // Use standard path if no custom path was obtained from the config file - config.files.database = strdup(defaults.files.database); + config.files.database.v.s = config.files.database.d.s; } - if(config.files.database == NULL || strlen(config.files.database) == 0) + if(config.files.database.v.s == NULL || strlen(config.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 = strdup(defaults.files.database); - config.database.maxDBdays = 0; + config.files.database.v.s = config.files.database.d.s; + config.database.maxDBdays.v.i = 0; } // MAXLOGAGE @@ -174,7 +173,7 @@ const char *readFTLlegacy(void) if(buffer != NULL && sscanf(buffer, "%f", &fvalue)) { if(fvalue >= 0.0f && fvalue <= 1.0f*MAXLOGAGE) - config.database.maxHistory = (int)(fvalue * 3600); + config.database.maxHistory.v.ui = (int)(fvalue * 3600); } // PRIVACYLEVEL @@ -192,10 +191,10 @@ const char *readFTLlegacy(void) // ignoreLocalhost // defaults to: false buffer = parseFTLconf(fp, "IGNORE_LOCALHOST"); - parseBool(buffer, &config.dns.ignoreLocalhost); + parseBool(buffer, &config.dns.ignoreLocalhost.v.b); if(buffer != NULL && strcasecmp(buffer, "yes") == 0) - config.dns.ignoreLocalhost = true; + config.dns.ignoreLocalhost.v.b = true; // BLOCKINGMODE // defaults to: MODE_IP @@ -204,54 +203,53 @@ 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); + parseBool(buffer, &config.dns.analyzeOnlyAandAAAA.v.b); if(buffer != NULL && strcasecmp(buffer, "true") == 0) - config.dns.analyzeOnlyAandAAAA = true; + config.dns.analyzeOnlyAandAAAA.v.b = true; // DBIMPORT // defaults to: Yes buffer = parseFTLconf(fp, "DBIMPORT"); - parseBool(buffer, &config.database.DBimport); + parseBool(buffer, &config.database.DBimport.v.b); // PIDFILE - getPath(fp, "PIDFILE", &config.files.pid); + config.files.pid.v.s = getPath(fp, "PIDFILE", config.files.pid.v.s); // SETUPVARSFILE - getPath(fp, "SETUPVARSFILE", &config.files.setupVars); + config.files.setupVars.v.s = getPath(fp, "SETUPVARSFILE", config.files.setupVars.v.s); // MACVENDORDB - getPath(fp, "MACVENDORDB", &config.files.macvendor); + config.files.macvendor.v.s = getPath(fp, "MACVENDORDB", config.files.macvendor.v.s); // GRAVITYDB - getPath(fp, "GRAVITYDB", &config.files.gravity); + config.files.gravity.v.s = getPath(fp, "GRAVITYDB", config.files.gravity.v.s); // PARSE_ARP_CACHE // defaults to: true buffer = parseFTLconf(fp, "PARSE_ARP_CACHE"); - parseBool(buffer, &config.database.network.parseARPcache); + parseBool(buffer, &config.database.network.parseARPcache.v.b); // CNAME_DEEP_INSPECT // defaults to: true buffer = parseFTLconf(fp, "CNAME_DEEP_INSPECT"); - parseBool(buffer, &config.dns.CNAMEdeepInspect); + parseBool(buffer, &config.dns.CNAMEdeepInspect.v.b); // DELAY_STARTUP // defaults to: zero (seconds) buffer = parseFTLconf(fp, "DELAY_STARTUP"); - config.misc.delay_startup = defaults.misc.delay_startup; unsigned int unum; if(buffer != NULL && sscanf(buffer, "%u", &unum) && unum > 0 && unum <= 300) - config.misc.delay_startup = unum; + config.misc.delay_startup.v.ui = unum; // BLOCK_ESNI // defaults to: true buffer = parseFTLconf(fp, "BLOCK_ESNI"); - parseBool(buffer, &config.dns.blockESNI); + parseBool(buffer, &config.dns.blockESNI.v.b); // WEBROOT - getPath(fp, "WEBROOT", &config.http.paths.webroot); + config.http.paths.webroot.v.s = getPath(fp, "WEBROOT", config.http.paths.webroot.v.s); // WEBPORT // On which port should FTL's API be listening? @@ -260,12 +258,12 @@ const char *readFTLlegacy(void) value = 0; if(buffer != NULL && strlen(buffer) > 0) - config.http.port = strdup(buffer); + config.http.port.v.s = strdup(buffer); // WEBHOME // From which sub-directory is the web interface served from? // Defaults to: /admin/ (both slashes are needed!) - getPath(fp, "WEBHOME", &config.http.paths.webhome); + config.http.paths.webhome.v.s = getPath(fp, "WEBHOME", config.http.paths.webhome.v.s); // WEBACL // Default: allow all access @@ -289,12 +287,12 @@ const char *readFTLlegacy(void) // buffer = parseFTLconf(fp, "WEBACL"); if(buffer != NULL) - config.http.acl = strdup(buffer); + config.http.acl.v.s = strdup(buffer); // API_AUTH_FOR_LOCALHOST // defaults to: true buffer = parseFTLconf(fp, "API_AUTH_FOR_LOCALHOST"); - parseBool(buffer, &config.http.localAPIauth); + parseBool(buffer, &config.http.localAPIauth.v.b); // API_SESSION_TIMEOUT // How long should a session be considered valid after login? @@ -303,18 +301,18 @@ const char *readFTLlegacy(void) value = 0; if(buffer != NULL && sscanf(buffer, "%i", &value) && value > 0) - config.http.sessionTimeout = value; + config.http.sessionTimeout.v.ui = value; // API_PRETTY_JSON // defaults to: false buffer = parseFTLconf(fp, "API_PRETTY_JSON"); - parseBool(buffer, &config.http.prettyJSON); + parseBool(buffer, &config.http.prettyJSON.v.b); // API_ERROR_LOG - getPath(fp, "API_ERROR_LOG", &config.files.ph7_error); + config.files.ph7_error.v.s = getPath(fp, "API_ERROR_LOG", config.files.ph7_error.v.s); // API_INFO_LOG - getPath(fp, "API_INFO_LOG", &config.files.http_info); + config.files.http_info.v.s = getPath(fp, "API_INFO_LOG", config.files.http_info.v.s); // NICE // Shall we change the nice of the current process? @@ -328,7 +326,6 @@ const char *readFTLlegacy(void) // systems, the range is -20..20. Very early Linux kernels (Before Linux // 2.0) had the range -infinity..15. buffer = parseFTLconf(fp, "NICE"); - setnice(buffer, defaults.misc.nice); // MAXNETAGE // IP addresses (and associated host names) older than the specified number @@ -340,7 +337,7 @@ const char *readFTLlegacy(void) if(buffer != NULL && sscanf(buffer, "%i", &ivalue) && ivalue > 0 && ivalue <= 8760) // 8760 days = 24 years - config.database.network.expire = ivalue; + config.database.network.expire.v.ui = ivalue; // NAMES_FROM_NETDB // Should we use the fallback option to try to obtain client names from @@ -351,30 +348,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); + parseBool(buffer, &config.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); + parseBool(buffer, &config.dns.EDNS0ECS.v.b); // REFRESH_HOSTNAMES // defaults to: IPV4 buffer = parseFTLconf(fp, "REFRESH_HOSTNAMES"); if(buffer != NULL && strcasecmp(buffer, "ALL") == 0) - config.resolver.refreshNames = REFRESH_ALL; + config.resolver.refreshNames.v.refresh_hostnames = REFRESH_ALL; else if(buffer != NULL && strcasecmp(buffer, "NONE") == 0) - config.resolver.refreshNames = REFRESH_NONE; + config.resolver.refreshNames.v.refresh_hostnames = REFRESH_NONE; else if(buffer != NULL && strcasecmp(buffer, "UNKNOWN") == 0) - config.resolver.refreshNames = REFRESH_UNKNOWN; + config.resolver.refreshNames.v.refresh_hostnames = REFRESH_UNKNOWN; else - config.resolver.refreshNames = REFRESH_IPV4_ONLY; + config.resolver.refreshNames.v.refresh_hostnames = REFRESH_IPV4_ONLY; // WEBDOMAIN - getPath(fp, "WEBDOMAIN", &config.http.domain); + config.http.domain.v.s = getPath(fp, "WEBDOMAIN", config.http.domain.v.s); // RATE_LIMIT // defaults to: 1000 queries / 60 seconds @@ -383,47 +380,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 = count; - config.dns.rateLimit.interval = interval; + config.dns.rateLimit.count.v.ui = count; + config.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 = false; - config.dns.reply.host.v4.s_addr = 0; + config.dns.reply.host.overwrite_v4.v.b = false; + config.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)) - config.dns.reply.host.overwrite_v4 = true; + 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; // 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 = false; - memset(&config.dns.reply.host.v6, 0, sizeof(config.dns.reply.host.v6)); + 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)); buffer = parseFTLconf(fp, "LOCAL_IPV6"); - if(buffer != NULL && inet_pton(AF_INET6, buffer, &config.dns.reply.host.v6)) - config.dns.reply.host.overwrite_v6 = true; + 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; // BLOCK_IPV4 // Use a specific IPv4 address for IP blocking mode replies // defaults to: REPLY_ADDR4 setting - config.dns.reply.blocking.overwrite_v4 = false; - config.dns.reply.blocking.v4.s_addr = 0; + config.dns.reply.blocking.overwrite_v4.v.b = false; + config.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)) - config.dns.reply.blocking.overwrite_v4 = true; + 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; // BLOCK_IPV6 // Use a specific IPv6 address for IP blocking mode replies // defaults to: REPLY_ADDR6 setting - config.dns.reply.blocking.overwrite_v6 = false; - memset(&config.dns.reply.blocking.v6, 0, sizeof(config.dns.reply.host.v6)); + 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)); buffer = parseFTLconf(fp, "BLOCK_IPV6"); - if(buffer != NULL && inet_pton(AF_INET6, buffer, &config.dns.reply.blocking.v6)) - config.dns.reply.blocking.overwrite_v6 = true; + 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; // REPLY_ADDR4 (deprecated setting) // Use a specific IP address instead of automatically detecting the @@ -433,16 +430,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 || config.dns.reply.blocking.overwrite_v4) + if(config.dns.reply.host.overwrite_v4.v.b || config.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 = true; - memcpy(&config.dns.reply.host.v4, &reply_addr4, sizeof(reply_addr4)); - config.dns.reply.blocking.overwrite_v4 = true; - memcpy(&config.dns.reply.blocking.v4, &reply_addr4, sizeof(reply_addr4)); + 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)); } } @@ -454,16 +451,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 || config.dns.reply.blocking.overwrite_v6) + if(config.dns.reply.host.overwrite_v6.v.b || config.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 = true; - memcpy(&config.dns.reply.host.v6, &reply_addr6, sizeof(reply_addr6)); - config.dns.reply.blocking.overwrite_v6 = true; - memcpy(&config.dns.reply.blocking.v6, &reply_addr6, sizeof(reply_addr6)); + 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)); } } @@ -471,13 +468,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); + parseBool(buffer, &config.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); + parseBool(buffer, &config.dns.specialDomains.mozillaCanary.v.b); // PIHOLE_PTR // Should FTL return "pi.hole" as name for PTR requests to local IP addresses? @@ -488,18 +485,18 @@ const char *readFTLlegacy(void) { if(strcasecmp(buffer, "none") == 0 || strcasecmp(buffer, "false") == 0) - config.dns.piholePTR = PTR_NONE; + config.dns.piholePTR.v.ptr_type = PTR_NONE; else if(strcasecmp(buffer, "hostname") == 0) - config.dns.piholePTR = PTR_HOSTNAME; + config.dns.piholePTR.v.ptr_type = PTR_HOSTNAME; else if(strcasecmp(buffer, "hostnamefqdn") == 0) - config.dns.piholePTR = PTR_HOSTNAMEFQDN; + config.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); + parseBool(buffer, &config.misc.addr2line.v.b); // REPLY_WHEN_BUSY // How should FTL handle queries when the gravity database is not available? @@ -509,55 +506,55 @@ const char *readFTLlegacy(void) if(buffer != NULL) { if(strcasecmp(buffer, "DROP") == 0) - config.dns.replyWhenBusy = BUSY_DROP; + config.dns.replyWhenBusy.v.busy_reply = BUSY_DROP; else if(strcasecmp(buffer, "REFUSE") == 0) - config.dns.replyWhenBusy = BUSY_REFUSE; + config.dns.replyWhenBusy.v.busy_reply = BUSY_REFUSE; else if(strcasecmp(buffer, "BLOCK") == 0) - config.dns.replyWhenBusy = BUSY_BLOCK; + config.dns.replyWhenBusy.v.busy_reply = BUSY_BLOCK; } // BLOCK_TTL // defaults to: 2 seconds - config.dns.blockTTL = 2; + config.dns.blockTTL.v.ui = 2; buffer = parseFTLconf(fp, "BLOCK_TTL"); unsigned int uval = 0; if(buffer != NULL && sscanf(buffer, "%u", &uval)) - config.dns.blockTTL = uval; + config.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); + parseBool(buffer, &config.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); + parseBool(buffer, &config.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 = 90; + config.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 = ivalue; + config.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 = 90; + config.misc.check.disk.v.b = 90; buffer = parseFTLconf(fp, "CHECK_DISK"); if(buffer != NULL && sscanf(buffer, "%i", &ivalue) && ivalue >= 0 && ivalue <= 100) - config.misc.check.disk = ivalue; + config.misc.check.disk.v.b = ivalue; // Read DEBUG_... setting from pihole-FTL.conf // This option should be the last one as it causes @@ -574,34 +571,35 @@ const char *readFTLlegacy(void) return path; } -static void getPath(FILE* fp, const char *option, char **ptr) +static char* getPath(FILE* fp, const char *option, char *ptr) { // This subroutine is used to read paths from pihole-FTL.conf // fp: File ptr to opened and readable config file // option: Option string ("key") to try to read - // defaultloc: Value used if key is not found in file // ptr: Location where read (or default) parameter is stored char *buffer = parseFTLconf(fp, option); errno = 0; // Use sscanf() to obtain filename from config file parameter only if buffer != NULL - if(buffer == NULL || sscanf(buffer, "%127ms", ptr) != 1) + if(buffer == NULL || sscanf(buffer, "%127ms", &ptr) != 1) { // Use standard path if no custom path was obtained from the config file - return; + return ptr; } // Test if memory allocation was successful - if(*ptr == NULL) + if(ptr == NULL) { log_crit("Allocating memory for %s failed (%s, %i). Exiting.", option, strerror(errno), errno); exit(EXIT_FAILURE); } - else if(strlen(*ptr) == 0) + else if(strlen(ptr) == 0) { log_info(" %s: Empty path is not possible, using default", option); } + + return ptr; } static char *parseFTLconf(FILE *fp, const char * key) @@ -718,9 +716,9 @@ static void getPrivacyLevelLegacy(FILE *fp) // Check for change and validity of privacy level (set in FTL.h) if(value >= PRIVACY_SHOW_ALL && value <= PRIVACY_MAXIMUM && - value > config.misc.privacylevel) + value > config.misc.privacylevel.v.privacy_level) { - config.misc.privacylevel = value; + config.misc.privacylevel.v.privacy_level = value; } } @@ -734,8 +732,8 @@ static void getPrivacyLevelLegacy(FILE *fp) static void getBlockingModeLegacy(FILE *fp) { - // Set default value - config.dns.blockingmode = defaults.dns.blockingmode; + // (Re-)set default value + config.dns.blockingmode.v.blocking_mode = config.dns.blockingmode.d.blocking_mode; // See if we got a file handle, if not we have to open // the config file ourselves @@ -754,15 +752,15 @@ static void getBlockingModeLegacy(FILE *fp) if(buffer != NULL) { if(strcasecmp(buffer, "NXDOMAIN") == 0) - config.dns.blockingmode = MODE_NX; + config.dns.blockingmode.v.blocking_mode = MODE_NX; else if(strcasecmp(buffer, "NULL") == 0) - config.dns.blockingmode = MODE_NULL; + config.dns.blockingmode.v.blocking_mode = MODE_NULL; else if(strcasecmp(buffer, "IP-NODATA-AAAA") == 0) - config.dns.blockingmode = MODE_IP_NODATA_AAAA; + config.dns.blockingmode.v.blocking_mode = MODE_IP_NODATA_AAAA; else if(strcasecmp(buffer, "IP") == 0) - config.dns.blockingmode = MODE_IP; + config.dns.blockingmode.v.blocking_mode = MODE_IP; else if(strcasecmp(buffer, "NODATA") == 0) - config.dns.blockingmode = MODE_NODATA; + config.dns.blockingmode.v.blocking_mode = MODE_NODATA; else log_warn("Unknown blocking mode, using NULL as fallback"); } @@ -776,7 +774,7 @@ static void getBlockingModeLegacy(FILE *fp) } // Routine for setting the debug flags in the config struct -static void setDebugOption(FILE* fp, const char* option, enum debug_flag bitmask) +static void setDebugOption(FILE* fp, const char* option, enum debug_flag flag) { const char *buffer = parseFTLconf(fp, option); @@ -784,21 +782,22 @@ static void setDebugOption(FILE* fp, const char* option, enum debug_flag bitmask if(buffer == NULL) return; + struct conf_item *debug = get_debug_item(flag); + // Set bit if value equals "true", clear bit otherwise bool bit = false; if(parseBool(buffer, &bit)) - { - if(bit) - config.debug |= bitmask; - else - config.debug &= ~bitmask; - } + debug->v.b = bit; + + // Remember if we set *any* debugging flag + if(bit) + debug_any = true; } static void readDebugingSettingsLegacy(FILE *fp) { // Set default (no debug instructions set) - config.debug = 0; + set_all_debug(false); // See if we got a file handle, if not we have to open // the config file ourselves @@ -820,12 +819,12 @@ static void readDebugingSettingsLegacy(FILE *fp) for(enum debug_flag flag = DEBUG_DATABASE; flag < DEBUG_EXTRA; flag <<= 1) { // DEBUG_DATABASE - const char *name, *desc; - debugstr(flag, &name, &desc); + const char *name; + debugstr(flag, &name); setDebugOption(fp, name, flag); } - if(config.debug != 0) + if(debug_any) { // Enable debug logging in dnsmasq (only effective before starting the resolver) argv_dnsmasq[2] = "--log-debug"; @@ -843,37 +842,6 @@ static void readDebugingSettingsLegacy(FILE *fp) } } -static void setnice(const char *buffer, const int fallback) -{ - int value, nice_set, nice_target = fallback; - - // Try to read niceness value - // Attempts to set a nice value outside the range are clamped to the range. - if(buffer != NULL && sscanf(buffer, "%i", &value) == 1) - nice_target = value; - - config.misc.nice = nice_target; - - // Skip setting niceness if set to -999 - if(nice_target == -999) - return; - - // Adjust if != -999 - errno = 0; - if((nice_set = nice(nice_target)) == -1 && - errno == EPERM) - { - // ERROR EPERM: The calling process attempted to increase its priority - // by supplying a negative value but has insufficient privileges. - // On Linux, the RLIMIT_NICE resource limit can be used to define a limit to - // which an unprivileged process's nice value can be raised. We are not - // affected by this limit when pihole-FTL is running with CAP_SYS_NICE - log_info(" NICE: Cannot change niceness to %d (permission denied)", - nice_target); - return; - } -} - // Returns true if we found a setting static bool parseBool(const char *option, bool *ptr) { diff --git a/src/config/toml_helper.c b/src/config/toml_helper.c index 1cd480b7..a398df6c 100644 --- a/src/config/toml_helper.c +++ b/src/config/toml_helper.c @@ -8,10 +8,14 @@ * 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 "FTL.h" #include "toml_helper.h" -#include "../config/config.h" +#include "log.h" +#include "config/config.h" +// get_refresh_hostnames_str() +#include "datastructure.h" +// Open the TOML file for reading or writing FILE * __attribute((malloc)) __attribute((nonnull(1))) openFTLtoml(const char *mode) { FILE *fp; @@ -26,27 +30,31 @@ FILE * __attribute((malloc)) __attribute((nonnull(1))) openFTLtoml(const char *m return fp; } -static inline void print_string(FILE *fp, const char *s) +// Print a string to a TOML file, escaping special characters as necessary +static void printTOMLstring(FILE *fp, const char *s) { // Substitute empty string if pointer is NULL if(s == NULL) s = ""; bool ok = true; + // Check if string is printable and does not contain any special characters for (const char* p = s; *p && ok; p++) { int ch = *p; ok = isprint(ch) && ch != '"' && ch != '\\'; } + // If string is printable and does not contain any special characters, we can + // print it as is without furhter escaping if (ok) { fprintf(fp, "\"%s\"", s); return; } + // Otherwise, we need to escape special characters, this is more work int len = strlen(s); - fprintf(fp, "\""); for ( ; len; len--, s++) { @@ -57,15 +65,16 @@ static inline void print_string(FILE *fp, const char *s) continue; } + // Escape special characters switch (ch) { - case 0x08: fprintf(fp, "\\b"); continue; - case 0x09: fprintf(fp, "\\t"); continue; - case 0x0a: fprintf(fp, "\\n"); continue; - case 0x0c: fprintf(fp, "\\f"); continue; - case 0x0d: fprintf(fp, "\\r"); continue; - case '"': fprintf(fp, "\\\""); continue; - case '\\': fprintf(fp, "\\\\"); continue; - default: fprintf(fp, "\\0x%02x", ch & 0xff); continue; + case 0x08: fprintf(fp, "\\b"); continue; + case 0x09: fprintf(fp, "\\t"); continue; + case 0x0a: fprintf(fp, "\\n"); continue; + case 0x0c: fprintf(fp, "\\f"); continue; + case 0x0d: fprintf(fp, "\\r"); continue; + case '"': fprintf(fp, "\\\""); continue; + case '\\': fprintf(fp, "\\\\"); continue; + default: fprintf(fp, "\\0x%02x", ch & 0xff); continue; } } fprintf(fp, "\""); @@ -73,88 +82,208 @@ static inline void print_string(FILE *fp, const char *s) // Indentation (tabs and/or spaces) is allowed but not required, we use it for // the sake of readability -static inline void indentTOML(FILE *fp, const unsigned int indent) +void indentTOML(FILE *fp, const unsigned int indent) { for (unsigned int i = 0; i < 2*indent; i++) fputc(' ', fp); } -void catTOMLsection(FILE *fp, const unsigned int indent, const char *key) +// Write a TOML value to a file depending on its type +void writeTOMLvalue(FILE * fp, const enum conf_type t, union conf_value *v) { - indentTOML(fp, indent); - fprintf(fp, "[%s]\n", key); -} - -void catTOMLextrainfo(FILE *fp, const unsigned int indent, const char *infostr) -{ - indentTOML(fp, indent); - fprintf(fp, "# %s\n", infostr); -} - -void catTOMLstring(FILE *fp, const unsigned int indent, const char *key, const char *description, const char *values, const char *val, const char *dval) -{ - indentTOML(fp, indent); - fprintf(fp, "# %s\n", description); - indentTOML(fp, indent); - fprintf(fp, "# Possible values are: %s\n", values); - indentTOML(fp, indent); - fprintf(fp, "%s = ", key); - print_string(fp, val); - - // Compare with default value and comment on difference - if(val != NULL && dval != NULL && strcmp(val, dval) != 0) + switch(t) { - fprintf(fp, " ### CHANGED, default = "); - print_string(fp, dval); + case CONF_BOOL: + fprintf(fp, "%s", v->b ? "true" : "false"); + break; + case CONF_INT: + fprintf(fp, "%i", v->i); + break; + case CONF_UINT: + case CONF_ENUM_PRIVACY_LEVEL: + fprintf(fp, "%u", v->ui); + break; + case CONF_LONG: + fprintf(fp, "%li", v->l); + break; + case CONF_ULONG: + fprintf(fp, "%lu", v->ul); + break; + case CONF_STRING: + printTOMLstring(fp, v->s); + break; + case CONF_ENUM_PTR_TYPE: + printTOMLstring(fp, get_ptr_type_str(v->ptr_type)); + break; + case CONF_ENUM_BUSY_TYPE: + printTOMLstring(fp, get_busy_reply_str(v->busy_reply)); + break; + case CONF_ENUM_BLOCKING_MODE: + printTOMLstring(fp, get_blocking_mode_str(v->blocking_mode)); + break; + case CONF_ENUM_REFRESH_HOSTNAMES: + printTOMLstring(fp, get_refresh_hostnames_str(v->refresh_hostnames)); + break; + case CONF_STRUCT_IN_ADDR: + { + char addr4[INET_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET, &v->in_addr, addr4, INET_ADDRSTRLEN); + printTOMLstring(fp, addr4); + break; + } + case CONF_STRUCT_IN6_ADDR: + { + char addr6[INET6_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET6, &v->in6_addr, addr6, INET6_ADDRSTRLEN); + printTOMLstring(fp, addr6); + break; + } } - - fputs("\n\n", fp); } -void catTOMLbool(FILE *fp, const unsigned int indent, const char *key, const char *description, const bool val, const bool dval) +// Read a TOML value from a table depending on its type +void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *toml) { - indentTOML(fp, indent); - fprintf(fp, "# %s\n", description); - indentTOML(fp, indent); - fprintf(fp, "%s = %s", key, val ? "true" : "false"); - - // Compare with default value and comment on difference - if(val != dval) + switch(conf_item->t) { - fprintf(fp, " ### CHANGED, default = %s", dval ? "true" : "false"); + case CONF_BOOL: + { + const toml_datum_t val = toml_bool_in(toml, key); + if(val.ok) + conf_item->v.b = val.u.b; + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type bool", conf_item->k); + break; + } + case CONF_INT: + { + const toml_datum_t val = toml_int_in(toml, key); + if(val.ok) + conf_item->v.i = val.u.i; + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type integer", conf_item->k); + break; + } + case CONF_UINT: + { + const toml_datum_t val = toml_int_in(toml, key); + if(val.ok && val.u.i >= 0) + conf_item->v.ui = val.u.i; + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type unsigned integer", conf_item->k); + break; + } + case CONF_LONG: + { + const toml_datum_t val = toml_int_in(toml, key); + if(val.ok) + conf_item->v.l = val.u.i; + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type long", conf_item->k); + break; + } + case CONF_ULONG: + { + const toml_datum_t val = toml_int_in(toml, key); + if(val.ok && val.u.i >= 0) + conf_item->v.ul = val.u.i; + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type unsigned long", conf_item->k); + break; + } + case CONF_STRING: + { + const toml_datum_t val = toml_string_in(toml, key); + if(val.ok) + conf_item->v.s = val.u.s; + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type string", conf_item->k); + break; + } + case CONF_ENUM_PTR_TYPE: + { + const toml_datum_t val = toml_string_in(toml, key); + if(val.ok) + { + const int ptr_type = get_ptr_type_val(val.u.s); + if(ptr_type != -1) + conf_item->v.ptr_type = ptr_type; + else + log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h); + } + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type string", conf_item->k); + break; + } + case CONF_ENUM_BUSY_TYPE: + { + const toml_datum_t val = toml_string_in(toml, key); + if(val.ok) + { + const int busy_reply = get_busy_reply_val(val.u.s); + if(busy_reply != -1) + conf_item->v.busy_reply = busy_reply; + else + log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h); + } + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type string", conf_item->k); + break; + } + case CONF_ENUM_BLOCKING_MODE: + { + const toml_datum_t val = toml_string_in(toml, key); + if(val.ok) + { + const int blocking_mode = get_blocking_mode_val(val.u.s); + if(blocking_mode != -1) + conf_item->v.blocking_mode = blocking_mode; + else + log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h); + } + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type string", conf_item->k); + break; + } + case CONF_ENUM_REFRESH_HOSTNAMES: + { + const toml_datum_t val = toml_string_in(toml, key); + if(val.ok) + { + const int refresh_hostnames = get_refresh_hostnames_val(val.u.s); + if(refresh_hostnames != -1) + conf_item->v.refresh_hostnames = refresh_hostnames; + else + log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h); + } + else + log_debug(DEBUG_CONFIG, "%s does not exist or is not of type string", conf_item->k); + break; + } + case CONF_ENUM_PRIVACY_LEVEL: + { + const toml_datum_t val = toml_int_in(toml, key); + if(val.ok && val.u.i >= PRIVACY_SHOW_ALL && val.u.i <= PRIVACY_MAXIMUM) + conf_item->v.i = val.u.i; + else + log_debug(DEBUG_CONFIG, "%s does not exist or is invalid", conf_item->k); + break; + } + case CONF_STRUCT_IN_ADDR: + { + struct in_addr addr4 = { 0 }; + const toml_datum_t val = toml_string_in(toml, key); + if(val.ok && inet_pton(AF_INET, val.u.s, &addr4)) + memcpy(&conf_item->v.in_addr, &addr4, sizeof(addr4)); + break; + } + case CONF_STRUCT_IN6_ADDR: + { + struct in6_addr addr6 = { 0 }; + const toml_datum_t val = toml_string_in(toml, key); + if(val.ok && inet_pton(AF_INET6, val.u.s, &addr6)) + memcpy(&conf_item->v.in6_addr, &addr6, sizeof(addr6)); + break; + } } - - fputs("\n\n", fp); -} - -void catTOMLint(FILE *fp, const unsigned int indent, const char *key, const char *description, const int val, const int dval) -{ - indentTOML(fp, indent); - fprintf(fp, "# %s\n", description); - indentTOML(fp, indent); - fprintf(fp, "%s = %i", key, val); - - // Compare with default value and comment on difference - if(val != dval) - { - fprintf(fp, " ### CHANGED, default = %i", dval); - } - - fputs("\n\n", fp); -} - -void catTOMLuint(FILE *fp, const unsigned int indent, const char *key, const char *description, const unsigned int val, const unsigned int dval) -{ - indentTOML(fp, indent); - fprintf(fp, "# %s\n", description); - indentTOML(fp, indent); - fprintf(fp, "%s = %u", key, val); - - // Compare with default value and comment on difference - if(val != dval) - { - fprintf(fp, " ### CHANGED, default = %u", dval); - } - - fputs("\n\n", fp); } diff --git a/src/config/toml_helper.h b/src/config/toml_helper.h index a99b457d..acd480f6 100644 --- a/src/config/toml_helper.h +++ b/src/config/toml_helper.h @@ -10,14 +10,15 @@ #ifndef CONFIG_WRITER_H #define CONFIG_WRITER_H -#include "../FTL.h" +#include "FTL.h" +// union conf_value +#include "config.h" +// type toml_table_t +#include "tomlc99/toml.h" +void indentTOML(FILE *fp, const unsigned int indent); FILE *openFTLtoml(const char *mode) __attribute((malloc)) __attribute((nonnull(1))); -void catTOMLsection(FILE *fp, const unsigned int indent, const char *key); -void catTOMLextrainfo(FILE *fp, const unsigned int indent, const char *infostr); -void catTOMLstring(FILE *fp, const unsigned int indent, const char *key, const char *description, const char *values, const char *val, const char *dptr); -void catTOMLbool(FILE *fp, const unsigned int indent, const char *key, const char *description, const bool val, const bool dval); -void catTOMLint(FILE *fp, const unsigned int indent, const char *key, const char *description, const int val, const int dval); -void catTOMLuint(FILE *fp, const unsigned int indent, const char *key, const char *description, const unsigned int val, const unsigned int dval); +void writeTOMLvalue(FILE * fp, const enum conf_type t, union conf_value *v); +void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *toml); #endif //CONFIG_WRITER_H diff --git a/src/config/toml_reader.c b/src/config/toml_reader.c index ed46eba2..efbc1741 100644 --- a/src/config/toml_reader.c +++ b/src/config/toml_reader.c @@ -32,567 +32,54 @@ static void reportDebugConfig(void); bool readFTLtoml(void) { // Initialize config with default values - setDefaults(); - - // We read the debug setting first so DEBUG_CONFIG can already - readDebugSettings(); - - log_debug(DEBUG_CONFIG, "Reading TOML config file: full config"); + initConfig(); // Parse lines in the config file toml_table_t *conf = parseTOML(); if(!conf) return false; - // Read [dns] section - toml_table_t *dns = toml_table_in(conf, "dns"); - if(dns) + // 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"); + if(conf_debug) + readTOMLvalue(&config.debug.config, "config", conf_debug); + + log_debug(DEBUG_CONFIG, "Reading TOML config file: full config"); + + // Read all known config items + for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { - getBlockingMode(); + // Get pointer to memory location of this conf_item + struct conf_item *conf_item = get_conf_item(i); + toml_table_t *table[MAX_CONFIG_PATH_DEPTH] = { 0 }; + unsigned int level = config_path_depth(conf_item); - toml_datum_t cname_deep_inspect = toml_bool_in(dns, "CNAMEdeepInspect"); - if(cname_deep_inspect.ok) - config.dns.CNAMEdeepInspect = cname_deep_inspect.u.b; - else - log_debug(DEBUG_CONFIG, "dns.CNAMEdeepInspect DOES NOT EXIST"); - - toml_datum_t block_esni = toml_bool_in(dns, "blockESNI"); - if(block_esni.ok) - config.dns.blockESNI = block_esni.u.b; - else - log_debug(DEBUG_CONFIG, "dns.blockESNI DOES NOT EXIST"); - - toml_datum_t edns0_ecs = toml_bool_in(dns, "EDNS0ECS"); - if(edns0_ecs.ok) - config.dns.EDNS0ECS = edns0_ecs.u.b; - else - log_debug(DEBUG_CONFIG, "dns.EDNS0ECS DOES NOT EXIST"); - - toml_datum_t ignoreLocalhost = toml_bool_in(dns, "ignoreLocalhost"); - if(ignoreLocalhost.ok) - config.dns.ignoreLocalhost = ignoreLocalhost.u.b; - else - log_debug(DEBUG_CONFIG, "dns.ignoreLocalhost DOES NOT EXIST"); - - toml_datum_t showDNSSEC = toml_bool_in(dns, "showDNSSEC"); - if(showDNSSEC.ok) - config.dns.showDNSSEC = showDNSSEC.u.b; - else - log_debug(DEBUG_CONFIG, "dns.showDNSSEC DOES NOT EXIST"); - - toml_datum_t piholePTR = toml_string_in(dns, "piholePTR"); - if(piholePTR.ok) + // Parse tree of properties + for(unsigned int j = 0; j < level-1; j++) { - if(strcasecmp(piholePTR.u.s, "none") == 0 || - strcasecmp(piholePTR.u.s, "false") == 0) - config.dns.piholePTR = PTR_NONE; - else if(strcasecmp(piholePTR.u.s, "hostname") == 0) - config.dns.piholePTR = PTR_HOSTNAME; - else if(strcasecmp(piholePTR.u.s, "hostnamefqdn") == 0) - config.dns.piholePTR = PTR_HOSTNAMEFQDN; - } - else - log_debug(DEBUG_CONFIG, "dns.piholePTR DOES NOT EXIST"); - - toml_datum_t replyWhenBusy = toml_string_in(dns, "replyWhenBusy"); - if(replyWhenBusy.ok) - { - if(strcasecmp(replyWhenBusy.u.s, "DROP") == 0) - config.dns.replyWhenBusy = BUSY_DROP; - else if(strcasecmp(replyWhenBusy.u.s, "REFUSE") == 0) - config.dns.replyWhenBusy = BUSY_REFUSE; - else if(strcasecmp(replyWhenBusy.u.s, "BLOCK") == 0) - config.dns.replyWhenBusy = BUSY_BLOCK; - } - else - log_debug(DEBUG_CONFIG, "dns.replyWhenBusy DOES NOT EXIST"); - - toml_datum_t blockTTL = toml_int_in(dns, "blockTTL"); - if(blockTTL.ok) - config.dns.blockTTL = blockTTL.u.i; - else - log_debug(DEBUG_CONFIG, "dns.blockTTL DOES NOT EXIST"); - - - toml_datum_t analyzeAAAA = toml_bool_in(dns, "analyzeAAAA"); - if(analyzeAAAA.ok) - config.dns.analyzeAAAA = analyzeAAAA.u.b; - else - log_debug(DEBUG_CONFIG, "dns.analyzeAAAA DOES NOT EXIST"); - - toml_datum_t analyzeOnlyAandAAAA = toml_bool_in(dns, "analyzeOnlyAandAAAA"); - if(analyzeOnlyAandAAAA.ok) - config.dns.analyzeOnlyAandAAAA = analyzeOnlyAandAAAA.u.b; - else - log_debug(DEBUG_CONFIG, "dns.analyzeOnlyAandAAAA DOES NOT EXIST"); - - // Read [dns.specialDomains] section - toml_table_t *specialDomains = toml_table_in(dns, "specialDomains"); - if(specialDomains) - { - toml_datum_t mozillaCanary = toml_bool_in(specialDomains, "mozillaCanary"); - if(mozillaCanary.ok) - config.dns.specialDomains.mozillaCanary = mozillaCanary.u.b; - else - log_debug(DEBUG_CONFIG, "dns.specialDomains.mozillaCanary DOES NOT EXIST"); - - toml_datum_t iCloudPrivateRelay = toml_bool_in(specialDomains, "iCloudPrivateRelay"); - if(iCloudPrivateRelay.ok) - config.dns.specialDomains.iCloudPrivateRelay = iCloudPrivateRelay.u.b; - else - log_debug(DEBUG_CONFIG, "dns.specialDomains.iCloudPrivateRelay DOES NOT EXIST"); - } - else - log_debug(DEBUG_CONFIG, "dns.specialDomains DOES NOT EXIST"); - - // Read [dns.reply] section - toml_table_t *reply = toml_table_in(dns, "reply"); - if(reply) - { - // Read [dns.reply.host] section - toml_table_t *host = toml_table_in(reply, "host"); - if(host) + // Get table at this level + table[j] = toml_table_in(j > 0 ? table[j-1] : conf, conf_item->p[j]); + if(!table[j]) { - toml_datum_t ipv4 = toml_string_in(host, "IPv4"); - if(ipv4.ok) - { - if(inet_pton(AF_INET, ipv4.u.s, &config.dns.reply.host.v4)) - config.dns.reply.host.overwrite_v4 = true; - else - log_warn("Invalid dns.reply.host.IPv4 setting. Ignoring."); - free(ipv4.u.s); - } - else - log_debug(DEBUG_CONFIG, "dns.reply.host.IPv4 DOES NOT EXIST"); - - toml_datum_t ipv6 = toml_string_in(host, "IPv6"); - if(ipv6.ok) - { - if(inet_pton(AF_INET6, ipv6.u.s, &config.dns.reply.host.v6)) - config.dns.reply.host.overwrite_v6 = true; - else - log_warn("Invalid dns.reply.host.IPv6 setting. Ignoring."); - free(ipv6.u.s); - } - else - log_debug(DEBUG_CONFIG, "dns.reply.host.IPv6 DOES NOT EXIST"); - } - else - { - log_debug(DEBUG_CONFIG, "dns.reply.host DOES NOT EXIST"); - } - // Read [dns.reply.blocking] section - toml_table_t *blocking = toml_table_in(reply, "blocking"); - if(blocking) - { - toml_datum_t ipv4 = toml_string_in(blocking, "IPv4"); - if(ipv4.ok) - { - if(inet_pton(AF_INET, ipv4.u.s, &config.dns.reply.blocking.v4)) - config.dns.reply.blocking.overwrite_v4 = true; - else - log_warn("Invalid dns.reply.blocking.IPv4 setting. Ignoring."); - free(ipv4.u.s); - } - else - log_debug(DEBUG_CONFIG, "dns.reply.blocking.IPv4 DOES NOT EXIST"); - - toml_datum_t ipv6 = toml_string_in(blocking, "IPv6"); - if(ipv6.ok) - { - if(inet_pton(AF_INET6, ipv6.u.s, &config.dns.reply.blocking.v6)) - config.dns.reply.blocking.overwrite_v6 = true; - else - log_warn("Invalid dns.reply.blocking.IPv6 setting. Ignoring."); - free(ipv6.u.s); - } - else - log_debug(DEBUG_CONFIG, "dns.reply.blocking.IPv6 DOES NOT EXIST"); - } - else - { - log_debug(DEBUG_CONFIG, "dns.reply.blocking DOES NOT EXIST"); + log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST", conf_item->k); + break; } } - else - log_debug(DEBUG_CONFIG, "dns.reply DOES NOT EXIST"); - // Read [dns.rateLimit] section - toml_table_t *rateLimit = toml_table_in(dns, "rateLimit"); - if(rateLimit) - { - toml_datum_t count = toml_int_in(rateLimit, "count"); - if(count.ok) - config.dns.rateLimit.count = count.u.i; - else - log_debug(DEBUG_CONFIG, "dns.rateLimit.count DOES NOT EXIST"); - - toml_datum_t interval = toml_int_in(rateLimit, "interval"); - if(interval.ok) - config.dns.rateLimit.interval = interval.u.i; - else - log_debug(DEBUG_CONFIG, "dns.rateLimit.interval DOES NOT EXIST"); - } - else - log_debug(DEBUG_CONFIG, "dns.rateLimit DOES NOT EXIST"); - } - else - log_debug(DEBUG_CONFIG, "dns DOES NOT EXIST"); - - // Read [resolver] section - toml_table_t *resolver = toml_table_in(conf, "resolver"); - if(resolver) - { - toml_datum_t resolve_ipv4 = toml_bool_in(resolver, "resolveIPv4"); - if(resolve_ipv4.ok) - config.resolver.resolveIPv4 = resolve_ipv4.u.b; - else - log_debug(DEBUG_CONFIG, "resolver.resolveIPv4 DOES NOT EXIST"); - - toml_datum_t resolve_ipv6 = toml_bool_in(resolver, "resolveIPv6"); - if(resolve_ipv6.ok) - config.resolver.resolveIPv6 = resolve_ipv6.u.b; - else - log_debug(DEBUG_CONFIG, "resolver.resolveIPv6 DOES NOT EXIST"); - - toml_datum_t network_names = toml_bool_in(resolver, "networkNames"); - if(network_names.ok) - config.resolver.networkNames = network_names.u.b; - else - log_debug(DEBUG_CONFIG, "resolver.networkNames DOES NOT EXIST"); - - toml_datum_t refreshNames = toml_string_in(resolver, "refreshNames"); - if(refreshNames.ok) - { - // Iterate over possible blocking modes and check if it applies - bool found = false; - for(enum refresh_hostnames rh = REFRESH_ALL; rh <= REFRESH_NONE; rh++) - { - const char *rhstr = get_refresh_hostnames_str(rh); - if(strcasecmp(rhstr, refreshNames.u.s) == 0) - { - config.resolver.refreshNames = rh; - found = true; - break; - } - } - if(!found) - log_warn("Unknown hostname refreshNames mode, using default"); - free(refreshNames.u.s); - } - else - log_debug(DEBUG_CONFIG, "resolver.refreshNames DOES NOT EXIST"); - } - else - log_debug(DEBUG_CONFIG, "resolver DOES NOT EXIST"); - - // Read [database] section - toml_table_t *database = toml_table_in(conf, "database"); - if(database) - { - toml_datum_t dbimport = toml_bool_in(database, "DBimport"); - if(dbimport.ok) - config.database.DBimport = dbimport.u.b; - else - log_debug(DEBUG_CONFIG, "database.DBimport DOES NOT EXIST"); - - toml_datum_t dbexport = toml_bool_in(database, "DBexport"); - if(dbexport.ok) - config.database.DBexport = dbexport.u.b; - else - log_debug(DEBUG_CONFIG, "database.DBexport DOES NOT EXIST"); - - toml_datum_t maxHistory = toml_int_in(database, "maxHistory"); - if(maxHistory.ok) - { - // Sanity check - if(maxHistory.u.i >= 0.0 && maxHistory.u.i <= MAXLOGAGE * 3600) - config.database.maxHistory = maxHistory.u.i; - else - log_warn("Invalid setting for database.maxHistory, using default"); - } - else - log_debug(DEBUG_CONFIG, "database.maxHistory DOES NOT EXIST"); - - toml_datum_t maxDBdays = toml_int_in(database, "maxDBdays"); - if(maxDBdays.ok) - { - const int maxDBdays_max = INT_MAX / 24 / 60 / 60; - // Prevent possible overflow - if(maxDBdays.u.i > maxDBdays_max) - config.database.maxDBdays = maxDBdays_max; - - // Only use valid values - else if(maxDBdays.u.i == -1 || maxDBdays.u.i >= 0) - config.database.maxDBdays = maxDBdays.u.i; - else - log_warn("Invalid setting for database.maxDBdays, using default"); - } - else - log_debug(DEBUG_CONFIG, "database.maxDBdays DOES NOT EXIST"); - - toml_datum_t dbinterval = toml_int_in(database, "DBinterval"); - if(dbinterval.ok) - { - // check if the read value is - // - larger than 10sec, and - // - smaller than 24*60*60sec (once a day) - if(dbinterval.u.i >= 10 && dbinterval.u.i <= 24*60*60) - config.database.DBinterval = dbinterval.u.i; - else - log_warn("Invalid setting for database.DBinterval, using default"); - } - else - log_debug(DEBUG_CONFIG, "database.DBinterval DOES NOT EXIST"); - - // Read [database.network] section - toml_table_t *network = toml_table_in(database, "network"); - if(network) - { - toml_datum_t parse_arp = toml_bool_in(network, "parseARPcache"); - if(parse_arp.ok) - config.database.network.parseARPcache = parse_arp.u.b; - else - log_debug(DEBUG_CONFIG, "database.network.parseARPcache DOES NOT EXIST"); - - toml_datum_t expire = toml_int_in(network, "expire"); - if(expire.ok) - { - // Only use valid values, max is one year - if(expire.u.i > 0 && expire.u.i <= 365) - config.database.network.expire = expire.u.i; - else - log_warn("Invalid setting for database.network.expire, using default"); - } - else - log_debug(DEBUG_CONFIG, "database.network.expire DOES NOT EXIST"); - } - else - log_debug(DEBUG_CONFIG, "database.network DOES NOT EXIST"); - } - else - log_debug(DEBUG_CONFIG, "database DOES NOT EXIST"); - - // Read [http] section - toml_table_t *http = toml_table_in(conf, "http"); - if(http) - { - toml_datum_t localAPIauth = toml_bool_in(http, "localAPIauth"); - if(localAPIauth.ok) - config.http.localAPIauth = localAPIauth.u.b; - else - log_debug(DEBUG_CONFIG, "http.localAPIauth DOES NOT EXIST"); - - toml_datum_t prettyJSON = toml_bool_in(http, "prettyJSON"); - if(prettyJSON.ok) - config.http.prettyJSON = prettyJSON.u.b; - else - log_debug(DEBUG_CONFIG, "http.prettyJSON DOES NOT EXIST"); - - toml_datum_t sessionTimeout = toml_int_in(http, "sessionTimeout"); - if(sessionTimeout.ok) - { - if(sessionTimeout.u.i >= 0) - config.http.sessionTimeout = sessionTimeout.u.i; - else - log_warn("Invalid setting for http.sessionTimeout, using default"); - } - else - log_debug(DEBUG_CONFIG, "http.sessionTimeout DOES NOT EXIST"); - - toml_datum_t domain = toml_string_in(http, "domain"); - if(domain.ok && strlen(domain.u.s) > 0) - config.http.domain = domain.u.s; - else - log_debug(DEBUG_CONFIG, "http.domain DOES NOT EXIST or EMPTY"); - - toml_datum_t acl = toml_string_in(http, "acl"); - if(acl.ok && strlen(acl.u.s) > 0) - config.http.acl = acl.u.s; - else - log_debug(DEBUG_CONFIG, "http.acl DOES NOT EXIST or EMPTY"); - - toml_datum_t port = toml_string_in(http, "port"); - if(port.ok && strlen(port.u.s) > 0) - { - config.http.port = port.u.s; - } - else - log_debug(DEBUG_CONFIG, "http.port DOES NOT EXIST or EMPTY"); - - // Read [http.paths] section - toml_table_t *paths = toml_table_in(http, "paths"); - if(paths) - { - toml_datum_t webroot = toml_string_in(paths, "webroot"); - if(webroot.ok && strlen(webroot.u.s) > 0) - config.http.paths.webroot = webroot.u.s; - else - log_debug(DEBUG_CONFIG, "http.paths.webroot DOES NOT EXIST or EMPTY"); - - toml_datum_t webhome = toml_string_in(paths, "webhome"); - if(webhome.ok && strlen(webhome.u.s) > 0) - config.http.paths.webhome = webhome.u.s; - else - log_debug(DEBUG_CONFIG, "http.paths.webhome DOES NOT EXIST or EMPTY"); - } - else - log_debug(DEBUG_CONFIG, "http.paths DOES NOT EXIST"); - } - else - log_debug(DEBUG_CONFIG, "http DOES NOT EXIST"); - - // Read [files] section - toml_table_t *files = toml_table_in(conf, "files"); - if(files) - { - // log file path is read earlier - - toml_datum_t pid = toml_string_in(files, "pid"); - if(pid.ok && strlen(pid.u.s) > 0) - config.files.pid = pid.u.s; - else - log_debug(DEBUG_CONFIG, "files.pid DOES NOT EXIST or EMPTY"); - - toml_datum_t fdatabase = toml_string_in(files, "database"); - if(fdatabase.ok && strlen(fdatabase.u.s) > 0) - config.files.database = fdatabase.u.s; - else - log_debug(DEBUG_CONFIG, "files.database DOES NOT EXIST or EMPTY"); - - toml_datum_t gravity = toml_string_in(files, "gravity"); - if(gravity.ok && strlen(gravity.u.s) > 0) - config.files.gravity = gravity.u.s; - else - log_debug(DEBUG_CONFIG, "files.gravity DOES NOT EXIST or EMPTY"); - - toml_datum_t macvendor = toml_string_in(files, "macvendor"); - if(macvendor.ok && strlen(macvendor.u.s) > 0) - config.files.macvendor = macvendor.u.s; - else - log_debug(DEBUG_CONFIG, "files.macvendor DOES NOT EXIST or EMPTY"); - - toml_datum_t setupVars = toml_string_in(files, "setupVars"); - if(setupVars.ok && strlen(setupVars.u.s) > 0) - config.files.setupVars = setupVars.u.s; - else - log_debug(DEBUG_CONFIG, "files.setupVars DOES NOT EXIST or EMPTY"); - - toml_datum_t http_info = toml_string_in(files, "HTTPinfo"); - if(http_info.ok && strlen(http_info.u.s) > 0) - config.files.http_info = http_info.u.s; - else - log_debug(DEBUG_CONFIG, "files.HTTPinfo DOES NOT EXIST or EMPTY"); - - toml_datum_t ph7_error = toml_string_in(files, "PH7error"); - if(ph7_error.ok && strlen(ph7_error.u.s) > 0) - config.files.ph7_error = ph7_error.u.s; - else - log_debug(DEBUG_CONFIG, "files.PH7error DOES NOT EXIST or EMPTY"); - } - else - log_debug(DEBUG_CONFIG, "files DOES NOT EXIST"); - - // Read [misc] section - toml_table_t *misc = toml_table_in(conf, "misc"); - if(misc) - { - // Load privacy level - getPrivacyLevel(); - - toml_datum_t nicey = toml_int_in(misc, "nice"); - if(nicey.ok) - { - // -999 = disabled - const int priority = nicey.u.i; - const int which = PRIO_PROCESS; - const id_t pid = getpid(); - config.misc.nice = getpriority(which, pid); - - if(priority == -999 || config.misc.nice == priority) - { - // Do not set nice value - log_debug(DEBUG_CONFIG, "Not changing process priority."); - log_debug(DEBUG_CONFIG, " Asked for %d, is %d", priority, config.misc.nice); - } - else - { - const int ret = setpriority(which, pid, priority); - if(ret == -1) - // ERROR EPERM: The calling process attempted to increase its priority - // by supplying a negative value but has insufficient privileges. - // On Linux, the RLIMIT_NICE resource limit can be used to define a limit to - // which an unprivileged process's nice value can be raised. We are not - // affected by this limit when pihole-FTL is running with CAP_SYS_NICE - log_warn("Cannot set process priority to %d: %s", - priority, strerror(errno)); - - config.misc.nice = getpriority(which, pid); - } - - if(config.misc.nice != priority) - log_info("Set process niceness to %d (instead of %d)", - config.misc.nice, priority); - } - else - log_debug(DEBUG_CONFIG, "misc.nice DOES NOT EXIST"); - - toml_datum_t delay_startup = toml_int_in(misc, "delayStartup"); - if(delay_startup.ok) - { - // Maximum is 300 seconds - if(delay_startup.u.i >= 0 && delay_startup.u.i <= 300) - config.misc.delay_startup = delay_startup.u.i; - else - log_warn("Invalid setting for misc.delayStartup, using default"); - } - else - log_debug(DEBUG_CONFIG, "misc.delayStartup DOES NOT EXIST"); - - toml_datum_t addr2line = toml_bool_in(misc, "addr2line"); - if(addr2line.ok) - config.misc.addr2line = addr2line.u.b; - else - log_debug(DEBUG_CONFIG, "misc.addr2line DOES NOT EXIST"); - - // Read [misc.check] section - toml_table_t *check = toml_table_in(misc, "check"); - if(check) - { - toml_datum_t load = toml_bool_in(check, "load"); - if(load.ok) - config.misc.check.load = load.u.b; - else - log_debug(DEBUG_CONFIG, "misc.check.load DOES NOT EXIST"); - - toml_datum_t disk = toml_int_in(check, "disk"); - if(disk.ok && disk.u.i >= 0 && disk.u.i <= 100) - config.misc.check.disk = disk.u.i; - else - log_debug(DEBUG_CONFIG, "misc.check.disk DOES NOT EXIST or is INVALID"); - - toml_datum_t shmem = toml_int_in(check, "shmem"); - if(shmem.ok && shmem.u.i >= 0 && shmem.u.i <= 100) - config.misc.check.shmem = shmem.u.i; - else - log_debug(DEBUG_CONFIG, "misc.check.shmem DOES NOT EXIST or is INVALID"); - } - else - log_debug(DEBUG_CONFIG, "misc.check DOES NOT EXIST"); - } - else - log_debug(DEBUG_CONFIG, "misc DOES NOT EXIST"); - - if(config.debug) - { - // Enable debug logging in dnsmasq (only effective before starting the resolver) - argv_dnsmasq[2] = "--log-debug"; + // Try to parse config item + readTOMLvalue(conf_item, conf_item->p[level-1], table[level-2]); } + // Report debug config if enabled + reportDebugConfig(); + + // Free memory allocated by the TOML parser and return success toml_free(conf); return true; } +// Parse TOML config file static toml_table_t *parseTOML(void) { // Try to open default config file. Use fallback if not found @@ -609,6 +96,7 @@ static toml_table_t *parseTOML(void) toml_table_t *conf = toml_parse_file(fp, errbuf, sizeof(errbuf)); fclose(fp); + // Check for errors if(conf == NULL) { log_err("Cannot parse config file: %s", errbuf); @@ -616,7 +104,6 @@ static toml_table_t *parseTOML(void) } log_debug(DEBUG_CONFIG, "TOML file parsing: OK"); - return conf; } @@ -624,10 +111,12 @@ bool getPrivacyLevel(void) { log_debug(DEBUG_CONFIG, "Reading TOML config file: privacy level"); + // Parse config file toml_table_t *conf = parseTOML(); if(!conf) return false; + // Get [misc] toml_table_t *misc = toml_table_in(conf, "misc"); if(!misc) { @@ -636,6 +125,7 @@ bool getPrivacyLevel(void) return false; } + // Get misc.privacyLevel toml_datum_t privacylevel = toml_int_in(misc, "privacyLevel"); if(!privacylevel.ok) { @@ -644,8 +134,9 @@ bool getPrivacyLevel(void) return false; } + // Check if privacy level is valid if(privacylevel.u.i >= PRIVACY_SHOW_ALL && privacylevel.u.i <= PRIVACY_MAXIMUM) - config.misc.privacylevel = privacylevel.u.i; + config.misc.privacylevel.v.privacy_level = privacylevel.u.i; else log_warn("Invalid setting for misc.privacyLevel"); @@ -657,10 +148,12 @@ bool getBlockingMode(void) { log_debug(DEBUG_CONFIG, "Reading TOML config file: DNS blocking mode"); + // Parse config file toml_table_t *conf = parseTOML(); if(!conf) return false; + // Get [dns] toml_table_t *dns = toml_table_in(conf, "dns"); if(!dns) { @@ -669,6 +162,7 @@ bool getBlockingMode(void) return false; } + // Get dns.blocking mode toml_datum_t blockingmode = toml_string_in(dns, "blockingmode"); if(!blockingmode.ok) { @@ -678,77 +172,15 @@ bool getBlockingMode(void) } // Iterate over possible blocking modes and check if it applies - bool found = false; - for(enum blocking_mode bm = MODE_IP; bm < MODE_MAX; bm++) - { - const char *bmstr = get_blocking_mode_str(bm); - if(strcasecmp(bmstr, blockingmode.u.s) == 0) - { - config.dns.blockingmode = bm; - found = true; - break; - } - } - if(!found) - log_warn("Unknown blocking mode \"%s\"", blockingmode.u.s); + const int blocking_mode = get_blocking_mode_val(blockingmode.u.s); + if(blocking_mode != -1) + config.dns.blockingmode.v.blocking_mode = blocking_mode; + else + log_warn("Config setting %s is invalid, allowed options are: %s", + config.dns.blockingmode.k, config.dns.blockingmode.h); free(blockingmode.u.s); - toml_free(conf); - return true; -} - -bool readDebugSettings(void) -{ - log_debug(DEBUG_CONFIG, "Reading TOML config file: debug settings"); - - toml_table_t *conf = parseTOML(); - if(!conf) - return false; - - // Read [debug] section - toml_table_t *debug = toml_table_in(conf, "debug"); - if(!debug) - { - log_debug(DEBUG_CONFIG, "debug DOES NOT EXIST"); - toml_free(conf); - return false; - } - - toml_datum_t all = toml_bool_in(debug, "all"); - if(all.ok && all.u.b) - config.debug = ~(enum debug_flag)0; - else if(!all.ok) - log_debug(DEBUG_CONFIG, "debug.all DOES NOT EXIST"); - else - { - // debug.all is false - char buffer[64]; - for(enum debug_flag flag = DEBUG_DATABASE; flag < DEBUG_EXTRA; flag <<= 1) - { - const char *name, *desc; - debugstr(flag, &name, &desc); - memset(buffer, 0, sizeof(buffer)); - strcpy(buffer, name+6); // offset "debug_" - strtolower(buffer); - - toml_datum_t flagstr = toml_bool_in(debug, buffer); - - // Only set debug flags that are specified - if(!flagstr.ok) - { - log_debug(DEBUG_CONFIG, "debug.%s DOES NOT EXIST", buffer); - continue; - } - - if(flagstr.u.b) - config.debug |= flag; // SET bit - else - config.debug &= ~flag; // CLR bit - } - } - - reportDebugConfig(); - + // Free memory and return success toml_free(conf); return true; } @@ -778,8 +210,8 @@ bool getLogFilePathTOML(void) } // Only replace string when it is different - if(strcmp(config.files.log,log.u.s) != 0) - config.files.log = log.u.s; // Allocated string + if(strcmp(config.files.log.v.s,log.u.s) != 0) + config.files.log.v.s = log.u.s; // Allocated string else free(log.u.s); @@ -789,17 +221,20 @@ bool getLogFilePathTOML(void) static void reportDebugConfig(void) { - if(!config.debug) + if(!debug_any) return; log_debug(DEBUG_ANY, "***********************"); log_debug(DEBUG_ANY, "* DEBUG SETTINGS *"); - for(enum debug_flag flag = DEBUG_DATABASE; flag < DEBUG_EXTRA; flag <<= 1) + + // Read all known debug config items + for(unsigned int i = 0; i < DEBUG_ELEMENTS; i++) { - const char *name, *desc; - debugstr(flag, &name, &desc); + struct conf_item *debug_item = get_debug_item(i); + const char *name; + debugstr(i, &name); unsigned int spaces = 20 - strlen(name); - log_debug(DEBUG_ANY, "* %s:%*s %s", name+6, spaces, "", config.debug & flag ? "YES *" : "NO *"); + log_debug(DEBUG_ANY, "* %s:%*s %s *", name+6, spaces, "", debug_item->v.b ? "YES" : "NO "); } log_debug(DEBUG_ANY, "***********************"); -} +} \ No newline at end of file diff --git a/src/config/toml_writer.c b/src/config/toml_writer.c index 7d4577a7..f94b19b2 100644 --- a/src/config/toml_writer.c +++ b/src/config/toml_writer.c @@ -8,15 +8,15 @@ * 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 "FTL.h" #include "config.h" // get_timestr() -#include "../log.h" +#include "log.h" #include "tomlc99/toml.h" #include "toml_writer.h" #include "toml_helper.h" // get_blocking_mode_str() -#include "../datastructure.h" +#include "datastructure.h" bool writeFTLtoml(void) { @@ -31,6 +31,7 @@ bool writeFTLtoml(void) // Store lines in the config file log_info("Writing config file"); + // Write header fputs("# This file is managed by pihole-FTL\n#\n", fp); fputs("# Do not edit the file while FTL is\n", fp); fputs("# running or your changes may be overwritten\n#\n", fp); @@ -38,169 +39,54 @@ bool writeFTLtoml(void) get_timestr(timestring, time(NULL), false); fprintf(fp, "# Last update: %s\n\n", timestring); - - - // [dns] section - catTOMLsection(fp, 0, "dns"); - - // BLOCKINGMODE=NULL|IP-NODATA-AAAA|IP|NXDOMAIN - const char *blockingmode = get_blocking_mode_str(config.dns.blockingmode); - const char *defblockingmode = get_blocking_mode_str(defaults.dns.blockingmode); - catTOMLstring(fp, 1, "blockingmode", "How should FTL reply to blocked queries?", "[ \"NULL\", \"IP-NODATA-AAAA\", \"IP\", \"NXDOMAIN\" ]", blockingmode, defblockingmode); - catTOMLbool(fp, 1, "CNAMEdeepInspect", "Should FTL walk CNAME paths?", config.dns.CNAMEdeepInspect, defaults.dns.CNAMEdeepInspect); - catTOMLbool(fp, 1, "blockESNI", "Should _esni. subdomains be blocked by default?", config.dns.blockESNI, defaults.dns.blockESNI); - catTOMLbool(fp, 1, "EDNS0ECS", "Should FTL analyze possible ECS information to obtain client IPs hidden behind NATs?", config.dns.EDNS0ECS, defaults.dns.EDNS0ECS); - catTOMLbool(fp, 1, "ignoreLocalhost", "Should FTL hide queries made by localhost?", config.dns.ignoreLocalhost, defaults.dns.ignoreLocalhost); - catTOMLbool(fp, 1, "showDNSSEC", "Should FTL should internally generated DNSSEC queries?", config.dns.showDNSSEC, defaults.dns.showDNSSEC); - - const char *ptrStr = get_ptr_type_str(config.dns.piholePTR); - catTOMLstring(fp, 1, "piholePTR", "Should FTL return \"pi.hole\" as name for PTR requests to local IP addresses?", "[ \"NONE\", \"HOSTNAME\", \"HOSTNAMEFQDN\", \"PI.HOLE\" ]", ptrStr, "PI.HOLE"); - - const char *replyWhenBusy = get_busy_reply_str(config.dns.replyWhenBusy); - catTOMLstring(fp, 1, "replyWhenBusy", "How should FTL handle queries when the gravity database is not available?", "[ \"BLOCK\", \"ALLOW\", \"REFUSE\", \"DROP\" ]", replyWhenBusy, "ALLOW"); - catTOMLuint(fp, 1, "blockTTL", "TTL for blocked queries [seconds]", config.dns.blockTTL, defaults.dns.blockTTL); - catTOMLbool(fp, 1, "analyzeAAAA", "Should FTL analyze AAAA queries?", config.dns.analyzeAAAA, defaults.dns.analyzeAAAA); - catTOMLbool(fp, 1, "analyzeOnlyAandAAAA", "Should FTL analyze only A and AAAA queries?", config.dns.analyzeOnlyAandAAAA, defaults.dns.analyzeOnlyAandAAAA); - - - - // [dns.specialDomains] subsection - catTOMLsection(fp, 1, "dns.specialDomains"); - catTOMLbool(fp, 2, "mozillaCanary", "Should FTL handle use-application-dns.net specifically and always return NXDOMAIN?", config.dns.specialDomains.mozillaCanary, defaults.dns.specialDomains.mozillaCanary); - catTOMLbool(fp, 2, "iCloudPrivateRelay", "Should FTL handle the iCloud privacy relay domains specifically and always return NXDOMAIN?", config.dns.specialDomains.iCloudPrivateRelay, defaults.dns.specialDomains.iCloudPrivateRelay); - - - - // [dns.reply] subsection - catTOMLsection(fp, 1, "dns.reply"); - char addr4[INET_ADDRSTRLEN] = { 0 }; - char addr6[INET6_ADDRSTRLEN] = { 0 }; - // [dns.reply.host] subsection - catTOMLsection(fp, 2, "dns.reply.host"); - if(config.dns.reply.host.overwrite_v4) - inet_ntop(AF_INET, &config.dns.reply.host.v4, addr4, INET_ADDRSTRLEN); - catTOMLstring(fp, 3, "IPv4", "Use a specific IPv4 address for the Pi-hole host", " or empty string (\"\")", addr4, ""); - if(config.dns.reply.host.overwrite_v6) - inet_ntop(AF_INET6, &config.dns.reply.host.v6, addr6, INET6_ADDRSTRLEN); - catTOMLstring(fp, 3, "IPv6", "Use a specific IPv6 address for the Pi-hole host", " or empty string (\"\")", addr6, ""); - - // [dns.reply.blocking] subsection - catTOMLsection(fp, 2, "dns.reply.blocking"); - memset(addr4, 0, INET_ADDRSTRLEN); - if(config.dns.reply.blocking.overwrite_v4) - inet_ntop(AF_INET, &config.dns.reply.blocking.v4, addr4, INET_ADDRSTRLEN); - catTOMLstring(fp, 3, "IPv4", "Use a specific IPv4 address in IP blocking mode", " or empty string (\"\")", addr4, ""); - memset(addr6, 0, INET6_ADDRSTRLEN); - if(config.dns.reply.blocking.overwrite_v6) - inet_ntop(AF_INET6, &config.dns.reply.blocking.v6, addr6, INET6_ADDRSTRLEN); - catTOMLstring(fp, 3, "IPv6", "Use a specific IPv6 address in IP blocking mode", " or empty string (\"\")", addr6, ""); - - - - // [dns.rateLimit] subsection - catTOMLsection(fp, 1, "dns.rateLimit"); - catTOMLuint(fp, 2, "count", "How many queries are permitted...", config.dns.rateLimit.count, defaults.dns.rateLimit.count); - catTOMLuint(fp, 2, "interval", "..in the set interval before rate-limiting?", config.dns.rateLimit.interval, defaults.dns.rateLimit.interval); - - - - // [resolver] section - catTOMLsection(fp, 0, "resolver"); - catTOMLbool(fp, 1, "resolveIPv4", "Should FTL try to resolve IPv4 addresses to hostnames?", config.resolver.resolveIPv4, defaults.resolver.resolveIPv4); - catTOMLbool(fp, 1, "resolveIPv6", "Should FTL try to resolve IPv6 addresses to hostnames?", config.resolver.resolveIPv6, defaults.resolver.resolveIPv6); - const char *refresh = get_refresh_hostnames_str(config.resolver.refreshNames); - const char *refresh_default = get_refresh_hostnames_str(defaults.resolver.refreshNames); - catTOMLbool(fp, 1, "networkNames", "Try to obtain client names from the network table", config.resolver.networkNames, defaults.resolver.networkNames); - catTOMLstring(fp, 1, "refreshNames", "How (and if) hourly PTR lookups should be made", "[ \"IPV4_ONLY\", \"ALL\", \"UNKNOWN\", \"NONE\" ]", refresh, refresh_default); - - - - // [database] section - catTOMLsection(fp, 0, "database"); - catTOMLbool(fp, 1, "DBimport", "Should FTL load information from the database on startup to be aware of the most recent history?", config.database.DBimport, defaults.database.DBimport); - catTOMLbool(fp, 1, "DBexport", "Should FTL store queries in the long-term database?", config.database.DBexport, defaults.database.DBexport); - catTOMLuint(fp, 1, "maxHistory", "How much history should be imported from the database [seconds]? (max 24*60*60 = 86400)", config.database.maxHistory, defaults.database.maxHistory); - catTOMLint(fp, 1, "maxDBdays", "How long should queries be stored in the database [days]?", config.database.maxDBdays, defaults.database.maxDBdays); - catTOMLint(fp, 1, "DBinterval", "How often do we store queries in FTL's database [seconds]?", config.database.DBinterval, defaults.database.DBinterval); - - - - // [database.network] section - catTOMLsection(fp, 1, "database.network"); - catTOMLbool(fp, 2, "parseARPcache", "Should FTL anaylze the local ARP cache?", config.database.network.parseARPcache, defaults.database.network.parseARPcache); - catTOMLint(fp, 2, "expire", "How long should IP addresses be kept in the network_addresses table [days]?", config.database.network.expire, defaults.database.network.expire); - - - - // [http] section - catTOMLsection(fp, 0, "http"); - catTOMLbool(fp, 1, "localAPIauth", "Does local clients need to authenticate to access the API?", config.http.localAPIauth, defaults.http.localAPIauth); - catTOMLbool(fp, 1, "prettyJSON", "Should FTL insert extra spaces to prettify the API output?", config.http.prettyJSON, defaults.http.prettyJSON); - catTOMLuint(fp, 1, "sessionTimeout", "How long should a session be considered valid after login [seconds]?", config.http.sessionTimeout, defaults.http.sessionTimeout); - catTOMLstring(fp, 1, "domain", "On which domain is the web interface served?", "", config.http.domain, defaults.http.domain); -// Webserver access control list -// Allows restrictions to be put on the list of IP addresses which have access to our web server. -// The ACL is a comma separated list of IP subnets, where each subnet is pre-pended by either a - or a + sign. -// A plus sign means allow, where a minus sign means deny. If a subnet mask is omitted, such as -1.2.3.4, this means -// to deny only that single IP address. The default setting is to allow all accesses. -// On each request the full list is traversed, and the last (!) match wins. -// Example 1: acl = \"-0.0.0.0/0,+127.0.0.1\" ---> deny all accesses, except from 127.0.0.1 -// Example 2: acl = \"-0.0.0.0/0,+192.168.0.0/16\" ---> deny all accesses, except from the 192.168/16 subnet -// IPv6 addresses are specified in form [a:b::c]/64 - catTOMLstring(fp, 1, "acl", "Webserver access control list.", "", config.http.acl, defaults.http.acl); - catTOMLstring(fp, 1, "port", "Ports to be used by the webserver", "list of <[ip_address:]port>", config.http.port, defaults.http.port); - - - - // [http.paths] section - catTOMLsection(fp, 1, "http.paths"); - catTOMLstring(fp, 2, "webroot", "Server root on the host", "", config.http.paths.webroot, defaults.http.paths.webroot); - catTOMLstring(fp, 2, "webhome", "Sub-directory of the root containing the web interface", ", both slashes are needed!", config.http.paths.webhome, defaults.http.paths.webhome); - - - - // [files] section - catTOMLsection(fp, 0, "files"); - catTOMLstring(fp, 1, "log", "The location of FTL's log file", "", config.files.log, defaults.files.log); - catTOMLstring(fp, 1, "pid", "The location of FTL's PID file", "", config.files.pid, defaults.files.pid); - catTOMLstring(fp, 1, "database", "The location of FTL's long-term database", "", config.files.database, defaults.files.database); - catTOMLstring(fp, 1, "gravity", "The location of Pi-hole's gravity database", "", config.files.gravity, defaults.files.gravity); - catTOMLstring(fp, 1, "macvendor", "The database containing MAC -> Vendor information for the network table", "", config.files.macvendor, defaults.files.macvendor); - catTOMLstring(fp, 1, "setupVars", "The config file of Pi-hole", "", config.files.setupVars, defaults.files.setupVars); - catTOMLstring(fp, 1, "HTTPinfo", "The log file used by the webserver", "", config.files.http_info, defaults.files.http_info); - catTOMLstring(fp, 1, "PH7error", "The log file used by the dynamic interpreter PH7", "", config.files.ph7_error, defaults.files.ph7_error); - - - - // [misc] section - catTOMLsection(fp, 0, "misc"); - catTOMLuint(fp, 1, "privacyLevel", "Privacy level", config.misc.privacylevel, defaults.misc.privacylevel); - catTOMLint(fp, 1, "nice", "Set niceness of pihole-FTL (can be disabled by setting to -999)", config.misc.nice, defaults.misc.nice); - catTOMLuint(fp, 1, "delayStartup", "Artificially delay FTL's startup (0 to 300 seconds)", config.misc.delay_startup, defaults.misc.delay_startup); - catTOMLbool(fp, 1, "addr2line", "Should FTL try to call addr2line when generating backtraces?", config.misc.addr2line, defaults.misc.addr2line); - - - - // [misc.check] subsection - catTOMLsection(fp, 1, "misc.check"); - catTOMLbool(fp, 2, "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?", config.misc.check.load, defaults.misc.check.load); - catTOMLuint(fp, 2, "shmem", "Limit above which FTL should complain about a shared-memory shortage", config.misc.check.shmem, defaults.misc.check.shmem); - catTOMLuint(fp, 2, "disk", "Limit above which FTL should complain about disk shortage for checked files", config.misc.check.disk, defaults.misc.check.disk); - - - - // [debug] section - catTOMLsection(fp, 0, "debug"); - catTOMLbool(fp, 1, "all", "Temporarily enable all debug flags", false, false); - char buffer[64]; - for(enum debug_flag flag = DEBUG_DATABASE; flag < DEBUG_EXTRA; flag <<= 1) + // Iterate over configuration and store it into the file + char *last_path = (char*)""; + for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { - const char *name, *desc; - debugstr(flag, &name, &desc); - memset(buffer, 0, sizeof(buffer)); - strcpy(buffer, name+6); // offset "debug_" - strtolower(buffer); - catTOMLbool(fp, 1, buffer, desc, config.debug & flag, false); + // Get pointer to memory location of this conf_item + struct conf_item *conf_item = get_conf_item(i); + + // Get path depth + unsigned int level = config_path_depth(conf_item); + + // Write path if it is different from the last one + if(level > 1 && strcmp(last_path, conf_item->p[level-2]) != 0) + { + indentTOML(fp, level-2); + fputc('[', fp); + // Write path elements separated by dots + for(unsigned int j = 0; j < level - 1; j++) + fprintf(fp, "%s%s", j > 0 ? "." : "", conf_item->p[j]); + fputc(']', fp); + fputc('\n', fp); + // Remember last path + last_path = conf_item->p[level-2]; + } + + // Write comment + indentTOML(fp, level-1); + fprintf(fp, "# %s\n", conf_item->h); + if(conf_item->a != NULL) + { + // Write possible values if applicable + indentTOML(fp, level-1); + fprintf(fp, "# Possible values are: %s\n", conf_item->a); + } + + // Write value + indentTOML(fp, level-1); + fprintf(fp, "%s = ", conf_item->p[level-1]); + writeTOMLvalue(fp, conf_item->t, &conf_item->v); + + // Compare with default value and add a comment on difference + if(memcmp(&conf_item->v, &conf_item->d, sizeof(conf_item->v)) != 0) + { + fprintf(fp, " ### CHANGED, default = "); + writeTOMLvalue(fp, conf_item->t, &conf_item->d); + } + + // Add newlines after each entry + fputs("\n\n", fp); } // Close and flush file diff --git a/src/daemon.c b/src/daemon.c index c18c3467..dd18a6a6 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -25,6 +25,8 @@ // sysinfo() #include #include +// getprio(), setprio() +#include pthread_t threads[THREADS_MAX] = { 0 }; bool resolver_ready = false; @@ -98,7 +100,7 @@ void savepid(void) { FILE *f; const pid_t pid = getpid(); - if((f = fopen(config.files.pid, "w+")) == NULL) + if((f = fopen(config.files.pid.v.s, "w+")) == NULL) { log_warn("Unable to write PID to file."); } @@ -113,7 +115,7 @@ void savepid(void) static void removepid(void) { FILE *f; - if((f = fopen(config.files.pid, "w")) == NULL) + if((f = fopen(config.files.pid.v.s, "w")) == NULL) { log_warn("Unable to empty PID file"); return; @@ -168,7 +170,7 @@ const char *hostname(void) void delay_startup(void) { // Exit early if not sleeping - if(config.misc.delay_startup == 0u) + if(config.misc.delay_startup.v.ui == 0u) return; // Get uptime of system @@ -187,8 +189,8 @@ void delay_startup(void) } // Sleep if requested by DELAY_STARTUP - log_info("Sleeping for %d seconds as requested by configuration ...", config.misc.delay_startup); - if(sleep(config.misc.delay_startup) != 0) + log_info("Sleeping for %d seconds as requested by configuration ...", config.misc.delay_startup.v.ui); + if(sleep(config.misc.delay_startup.v.ui) != 0) { log_crit("Sleeping was interrupted by an external signal"); cleanup(EXIT_FAILURE); @@ -252,6 +254,32 @@ static void terminate_threads(void) log_info("All threads joined"); } +void set_nice(void) +{ + const int which = PRIO_PROCESS; + const id_t pid = getpid(); + const int priority = getpriority(which, pid); + + // config value -999 => do not change niceness + if(config.misc.nice.v.i == -999) + { + // Do not set nice value + log_debug(DEBUG_CONFIG, "Not changing process priority."); + } + else + { + const int ret = setpriority(which, pid, config.misc.nice.v.i); + if(ret == -1) + // ERROR EPERM: The calling process attempted to increase its priority + // by supplying a negative value but has insufficient privileges. + // On Linux, the RLIMIT_NICE resource limit can be used to define a limit to + // which an unprivileged process's nice value can be raised. We are not + // affected by this limit when pihole-FTL is running with CAP_SYS_NICE + log_warn("Cannot set process priority to %d: %s. Process priority remains at %d", + config.misc.nice.v.i, strerror(errno), priority); + } +} + // Clean up on exit void cleanup(const int ret) { diff --git a/src/daemon.h b/src/daemon.h index 488bd5b1..41b9acc6 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -20,6 +20,7 @@ const char *hostname(void); void delay_startup(void); bool is_fork(const pid_t mpid, const pid_t pid) __attribute__ ((const)); void cleanup(const int ret); +void set_nice(void); #include #include diff --git a/src/database/common.c b/src/database/common.c index 03c3c84f..0fe5fd0b 100644 --- a/src/database/common.c +++ b/src/database/common.c @@ -41,13 +41,13 @@ bool checkFTLDBrc(const int rc) // Check if the database file is malformed if(rc == SQLITE_CORRUPT) { - log_warn("Database %s is damaged and cannot be used.", config.files.database); + log_warn("Database %s is damaged and cannot be used.", config.files.database.v.s); DBerror = true; } // Check if the database file is read-only if(rc == SQLITE_READONLY) { - log_warn("Database %s is read-only and cannot be used.", config.files.database); + log_warn("Database %s is read-only and cannot be used.", config.files.database.v.s); DBerror = true; } @@ -61,7 +61,7 @@ void _dbclose(sqlite3 **db, const char *func, const int line, const char *file) if(FTLDBerror()) return; - if(config.debug & DEBUG_DATABASE) + if(config.debug.database.v.b) log_debug(DEBUG_DATABASE, "Closing FTL database in %s() (%s:%i)", func, file, line); // Only try to close an existing database connection @@ -91,7 +91,7 @@ sqlite3* _dbopen(bool create, const char *func, const int line, const char *file flags |= SQLITE_OPEN_CREATE; sqlite3 *db = NULL; - int rc = sqlite3_open_v2(config.files.database, &db, flags, NULL); + int rc = sqlite3_open_v2(config.files.database.v.s, &db, flags, NULL); if( rc != SQLITE_OK ) { log_err("Error while trying to open database: %s", sqlite3_errstr(rc)); @@ -245,7 +245,7 @@ void db_init(void) sqlite3_auto_extension((void (*)(void))sqlite3_pihole_extensions_init); // Check if database exists, if not create empty database - if(!file_exists(config.files.database)) + if(!file_exists(config.files.database.v.s)) { log_warn("No database file found, creating new (empty) database"); if (!db_create()) @@ -258,7 +258,7 @@ void db_init(void) // Explicitly set permissions to 0644 // 644 = u+w u+r g+w g+r o+r const mode_t mode = S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP| S_IROTH; - chmod_file(config.files.database, mode); + chmod_file(config.files.database.v.s, mode); // Open database sqlite3 *db = dbopen(false); @@ -470,11 +470,11 @@ void db_init(void) // Log if users asked us to not use the long-term database for queries // We will still use it to store warnings in it - config.database.DBexport = true; - if(config.database.maxDBdays == 0) + config.database.DBexport.v.b = true; + if(config.database.maxDBdays.v.i == 0) { log_info("Not using the database for storing queries"); - config.database.DBexport = false; + config.database.DBexport.v.b = false; } log_info("Database successfully initialized"); diff --git a/src/database/database-thread.c b/src/database/database-thread.c index 8b431d81..a78606c0 100644 --- a/src/database/database-thread.c +++ b/src/database/database-thread.c @@ -35,16 +35,16 @@ static bool delete_old_queries_in_DB(sqlite3 *db) { - const time_t timestamp = time(NULL) - config.database.maxDBdays * 86400; + const time_t timestamp = time(NULL) - config.database.maxDBdays.v.i * 86400; SQL_bool(db, "DELETE FROM query_storage WHERE timestamp <= "TIME_T, timestamp); // Get how many rows have been affected (deleted) const int affected = sqlite3_changes(db); // Print final message only if there is a difference - if((config.debug & DEBUG_DATABASE) || affected) + if((config.debug.database.v.b) || affected) log_info("Size of %s is %.2f MB, deleted %i rows", - config.files.database, 1e-6*get_FTL_db_filesize(), affected); + config.files.database.v.s, 1e-6*get_FTL_db_filesize(), affected); return true; } @@ -62,7 +62,7 @@ void *DB_thread(void *val) // Save timestamp as we do not want to store immediately // to the database time_t before = time(NULL); - time_t lastDBsave = before - before%config.database.DBinterval; + time_t lastDBsave = before - before%config.database.DBinterval.v.ui; // This thread runs until shutdown of the process. We keep this thread // running when pihole-FTL.db is corrupted because reloading of privacy @@ -88,13 +88,13 @@ void *DB_thread(void *val) break; // Store queries in on-disk database - if(now - lastDBsave >= (time_t)config.database.DBinterval) + if(now - lastDBsave >= (time_t)config.database.DBinterval.v.ui) { // Update lastDBsave timer - lastDBsave = now - now%config.database.DBinterval; + lastDBsave = now - now%config.database.DBinterval.v.ui; // Save data to database (if enabled) - if(config.database.DBexport) + if(config.database.DBexport.v.b) { DBOPEN_OR_AGAIN(); lock_shm(); @@ -106,7 +106,7 @@ void *DB_thread(void *val) break; // Check if GC should be done on the database - if(DBdeleteoldqueries && config.database.maxDBdays != -1) + if(DBdeleteoldqueries && config.database.maxDBdays.v.i != -1) { // No thread locks needed delete_old_queries_in_DB(db); @@ -117,7 +117,7 @@ void *DB_thread(void *val) } // Parse neighbor cache (fill network table) if enabled - if (config.database.network.parseARPcache) + if (config.database.network.parseARPcache.v.b) set_event(PARSE_NEIGHBOR_CACHE); } diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 240cbb7b..3340af35 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -99,10 +99,10 @@ void gravityDB_forked(void) bool gravityDB_open(void) { struct stat st; - if(stat(config.files.gravity, &st) != 0) + if(stat(config.files.gravity.v.s, &st) != 0) { // File does not exist - log_warn("gravityDB_open(): %s does not exist", config.files.gravity); + log_warn("gravityDB_open(): %s does not exist", config.files.gravity.v.s); return false; } @@ -112,8 +112,8 @@ bool gravityDB_open(void) return true; } - log_debug(DEBUG_DATABASE, "gravityDB_open(): Trying to open %s in read-only mode", config.files.gravity); - int rc = sqlite3_open_v2(config.files.gravity, &gravity_db, SQLITE_OPEN_READWRITE, NULL); + log_debug(DEBUG_DATABASE, "gravityDB_open(): Trying to open %s in read-only mode", config.files.gravity.v.s); + int rc = sqlite3_open_v2(config.files.gravity.v.s, &gravity_db, SQLITE_OPEN_READWRITE, NULL); if( rc != SQLITE_OK ) { log_err("gravityDB_open() - SQL error: %s", sqlite3_errstr(rc)); @@ -720,7 +720,7 @@ static bool get_client_groupids(clientsData* client) gravityDB_finalizeTable(); // Debug logging - if(config.debug & DEBUG_CLIENTS) + if(config.debug.clients.v.b) { if(interface != NULL) { @@ -1591,7 +1591,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, sqlite3_finalize(stmt); // Debug output - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { log_debug(DEBUG_API, "SQL: %s", querystr); if(item_idx > 0) @@ -1699,7 +1699,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a } // Debug output - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { log_debug(DEBUG_API, "SQL: %s", querystr); if(arg_idx > 0) @@ -1761,7 +1761,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a } // Debug output - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { log_debug(DEBUG_API, "SQL: %s", querystr2); if(arg_idx > 0) @@ -1896,7 +1896,7 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *item } // Debug output - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { log_debug(DEBUG_API, "SQL: %s", querystr); log_debug(DEBUG_API, " :item = \"%s\"", item); @@ -2099,7 +2099,7 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, } // Debug output - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { log_debug(DEBUG_API, "SQL: %s", get_querystr); log_debug(DEBUG_API, " :item = \"%s\"", row->item); @@ -2148,7 +2148,7 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, } // Debug output - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { log_debug(DEBUG_API, "SQL: %s", del_querystr); log_debug(DEBUG_API, " :id = \"%d\"", id); @@ -2213,7 +2213,7 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, } // Debug output - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { log_debug(DEBUG_API, "INSERT: %i -> (%i,%i)", rc, id, group->valueint); log_debug(DEBUG_API, "SQL: %s", add_querystr); diff --git a/src/database/message-table.c b/src/database/message-table.c index 0b542ec7..a1c04c5a 100644 --- a/src/database/message-table.c +++ b/src/database/message-table.c @@ -368,7 +368,7 @@ void logg_rate_limit_message(const char *clientIP, const unsigned int rate_limit clientIP, turnaround, turnaround == 1 ? "" : "s"); // Log to database - add_message(RATE_LIMIT_MESSAGE, clientIP, 2, config.dns.rateLimit.count, config.dns.rateLimit.interval); + add_message(RATE_LIMIT_MESSAGE, clientIP, 2, config.dns.rateLimit.count.v.ui, config.dns.rateLimit.interval.v.ui); } void logg_warn_dnsmasq_message(char *message) diff --git a/src/database/network-table.c b/src/database/network-table.c index c6359de9..8e3ff088 100644 --- a/src/database/network-table.c +++ b/src/database/network-table.c @@ -1211,7 +1211,7 @@ void parse_neighbor_cache(sqlite3* db) } // Start ARP timer - if(config.debug & DEBUG_ARP) + if(config.debug.arp.v.b) timer_start(ARP_TIMER); // Prepare buffers @@ -1238,9 +1238,9 @@ void parse_neighbor_cache(sqlite3* db) } // Remove all but the most recent IP addresses not seen for more than a certain time - if(config.database.network.expire > 0) + if(config.database.network.expire.v.ui > 0) { - const time_t limit = time(NULL)-24*3600*config.database.network.expire; + const time_t limit = time(NULL)-24*3600*config.database.network.expire.v.ui; rc = dbquery(db, "DELETE FROM network_addresses " "WHERE lastSeen < %lu;", (unsigned long)limit); if(rc != SQLITE_OK) @@ -1634,10 +1634,10 @@ static char * __attribute__ ((malloc)) getMACVendor(const char *hwaddr) return strdup("virtual interface"); struct stat st; - if(stat(config.files.macvendor, &st) != 0) + if(stat(config.files.macvendor.v.s, &st) != 0) { // File does not exist - log_debug(DEBUG_ARP, "getMACVenor(\"%s\"): %s does not exist", hwaddr, config.files.macvendor); + log_debug(DEBUG_ARP, "getMACVenor(\"%s\"): %s does not exist", hwaddr, config.files.macvendor.v.s); return strdup(""); } else if(strlen(hwaddr) != 17 || strstr(hwaddr, "ip-") != NULL) @@ -1648,7 +1648,7 @@ static char * __attribute__ ((malloc)) getMACVendor(const char *hwaddr) } sqlite3 *macvendor_db = NULL; - int rc = sqlite3_open_v2(config.files.macvendor, &macvendor_db, SQLITE_OPEN_READONLY, NULL); + int rc = sqlite3_open_v2(config.files.macvendor.v.s, &macvendor_db, SQLITE_OPEN_READONLY, NULL); if(rc != SQLITE_OK) { log_err("getMACVendor(\"%s\") - SQL error: %s", hwaddr, sqlite3_errstr(rc)); @@ -1715,10 +1715,10 @@ void updateMACVendorRecords(sqlite3 *db) return; struct stat st; - if(stat(config.files.macvendor, &st) != 0) + if(stat(config.files.macvendor.v.s, &st) != 0) { // File does not exist - log_debug(DEBUG_ARP, "updateMACVendorRecords(): \"%s\" does not exist", config.files.macvendor); + log_debug(DEBUG_ARP, "updateMACVendorRecords(): \"%s\" does not exist", config.files.macvendor.v.s); return; } @@ -2068,15 +2068,15 @@ char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr) // Database record found (result might be empty) name = strdup((char*)sqlite3_column_text(stmt, 0)); - if(config.debug & (DEBUG_DATABASE | DEBUG_RESOLVER)) - log_debug(DEBUG_ANY, "Found database host name (same device) %s -> %s", + if(config.debug.resolver.v.b) + log_debug(DEBUG_RESOLVER, "Found database host name (same device) %s -> %s", ipaddr, name); } else if(rc == SQLITE_DONE) { // Not found - if(config.debug & (DEBUG_DATABASE | DEBUG_RESOLVER)) - log_debug(DEBUG_ANY, " ---> not found"); + if(config.debug.resolver.v.b) + log_debug(DEBUG_RESOLVER, " ---> not found"); } else { @@ -2132,9 +2132,9 @@ char *__attribute__((malloc)) getIfaceFromIP(sqlite3 *db, const char *ipaddr) return NULL; } - if(config.debug & (DEBUG_DATABASE | DEBUG_RESOLVER)) + if(config.debug.resolver.v.b) { - log_debug(DEBUG_ANY, "getDatabaseHostname(): \"%s\" with ? = \"%s\"", + log_debug(DEBUG_RESOLVER, "getDatabaseHostname(): \"%s\" with ? = \"%s\"", querystr, ipaddr); } diff --git a/src/database/query-table.c b/src/database/query-table.c index 22d54318..64705395 100644 --- a/src/database/query-table.c +++ b/src/database/query-table.c @@ -172,7 +172,7 @@ static bool get_memdb_size(sqlite3 *db, size_t *memsize, int *queries) // Log the memory usage of in-memory databases static void log_in_memory_usage(void) { - if(!(config.debug & DEBUG_DATABASE)) + if(!(config.debug.database.v.b)) return; size_t memsize = 0; @@ -205,7 +205,7 @@ bool attach_disk_database(const char **message) return false; } // Bind path to prepared index - if((rc = sqlite3_bind_text(stmt, 1, config.files.database, -1, SQLITE_STATIC)) != SQLITE_OK) + if((rc = sqlite3_bind_text(stmt, 1, config.files.database.v.s, -1, SQLITE_STATIC)) != SQLITE_OK) { log_err("attach_disk_database(): Failed to bind path: %s", sqlite3_errstr(rc)); @@ -308,7 +308,7 @@ bool import_queries_from_disk(void) // Get time stamp 24 hours (or what was configured) in the past bool okay = false; const double now = double_time(); - const double mintime = now - config.database.maxHistory; + const double mintime = now - config.database.maxHistory.v.ui; const char *querystr = "INSERT INTO query_storage SELECT * FROM disk.query_storage WHERE timestamp >= ?"; // Attach disk database @@ -691,7 +691,7 @@ void DB_read_queries(void) // Prepare request // Get time stamp 24 hours in the past const double now = double_time(); - const double mintime = now - config.database.maxHistory; + const double mintime = now - config.database.maxHistory.v.ui; const char *querystr = "SELECT id,"\ "timestamp,"\ "type,"\ @@ -753,7 +753,7 @@ void DB_read_queries(void) } // Don't import AAAA queries from database if the user set // AAAA_QUERY_ANALYSIS=no in pihole-FTL.conf - if(type == TYPE_AAAA && !config.dns.analyzeAAAA) + if(type == TYPE_AAAA && !config.dns.analyzeAAAA.v.b) { continue; } @@ -782,7 +782,7 @@ void DB_read_queries(void) } // Check if user wants to skip queries coming from localhost - if(config.dns.ignoreLocalhost && + if(config.dns.ignoreLocalhost.v.b && (strcmp(clientIP, "127.0.0.1") == 0 || strcmp(clientIP, "::1") == 0)) { continue; @@ -1063,7 +1063,7 @@ bool queries_to_database(void) // Skip, we never store nor count queries recorded while have been in // maximum privacy mode in the database - if(config.misc.privacylevel >= PRIVACY_MAXIMUM) + if(config.misc.privacylevel.v.privacy_level >= PRIVACY_MAXIMUM) { log_debug(DEBUG_DATABASE, "Not storing query in database due to privacy level settings"); return true; @@ -1383,7 +1383,7 @@ bool queries_to_database(void) return false; } - if(config.debug & DEBUG_DATABASE && updated + added > 0) + if(config.debug.database.v.b && updated + added > 0) { log_debug(DEBUG_DATABASE, "In-memory database: Added %d new, updated %d known queries", added, updated); log_in_memory_usage(); diff --git a/src/database/sqlite3-ext.c b/src/database/sqlite3-ext.c index a6071f1e..510d27a9 100644 --- a/src/database/sqlite3-ext.c +++ b/src/database/sqlite3-ext.c @@ -163,7 +163,7 @@ static void subnet_match_impl(sqlite3_context *context, int argc, sqlite3_value } // Possible debug logging - if(config.debug & DEBUG_DATABASE) + if(config.debug.database.v.b) { char subnet[INET6_ADDRSTRLEN]; inet_ntop(isIPv6_FTL ? AF_INET6 : AF_INET, &bitmask, subnet, sizeof(subnet)); diff --git a/src/datastructure.c b/src/datastructure.c index 05d4d2ba..662fbb97 100644 --- a/src/datastructure.c +++ b/src/datastructure.c @@ -698,6 +698,21 @@ const char * __attribute__ ((const)) get_refresh_hostnames_str(const enum refres } } +int __attribute__ ((const)) get_refresh_hostnames_val(const char *refresh_hostnames) +{ + if(strcasecmp(refresh_hostnames, "ALL") == 0) + return REFRESH_ALL; + else if(strcasecmp(refresh_hostnames, "IPV4_ONLY") == 0) + return REFRESH_IPV4_ONLY; + else if(strcasecmp(refresh_hostnames, "UNKNOWN") == 0) + return REFRESH_UNKNOWN; + else if(strcasecmp(refresh_hostnames, "NONE") == 0) + return REFRESH_NONE; + + // Invalid value + return -1; +} + const char * __attribute__ ((const)) get_blocking_mode_str(const enum blocking_mode mode) { switch (mode) @@ -718,6 +733,23 @@ const char * __attribute__ ((const)) get_blocking_mode_str(const enum blocking_m } } +int __attribute__ ((const)) get_blocking_mode_val(const char *blocking_mode) +{ + if(strcasecmp(blocking_mode, "IP") == 0) + return MODE_IP; + else if(strcasecmp(blocking_mode, "NX") == 0) + return MODE_NX; + else if(strcasecmp(blocking_mode, "NULL") == 0) + return MODE_NULL; + else if(strcasecmp(blocking_mode, "IP_NODATA_AAAA") == 0) + return MODE_IP_NODATA_AAAA; + else if(strcasecmp(blocking_mode, "NODATA") == 0) + return MODE_NODATA; + + // Invalid value + return -1; +} + bool __attribute__ ((const)) is_blocked(const enum query_status status) { switch (status) @@ -849,7 +881,7 @@ static const char* __attribute__ ((const)) query_status_str(const enum query_sta void _query_set_status(queriesData *query, const enum query_status new_status, const char *func, const int line, const char *file) { // Debug logging - if(config.debug & DEBUG_STATUS) + if(config.debug.status.v.b) { const char *oldstr = query->status < QUERY_STATUS_MAX ? query_status_str(query->status) : "INVALID"; if(query->status == new_status) @@ -912,6 +944,22 @@ const char * __attribute__ ((const)) get_ptr_type_str(const enum ptr_type pihole return NULL; } +int __attribute__ ((const)) get_ptr_type_val(const char *piholePTR) +{ + if(strcasecmp(piholePTR, "pi.hole") == 0) + return PTR_PIHOLE; + else if(strcasecmp(piholePTR, "hostname") == 0) + return PTR_HOSTNAME; + else if(strcasecmp(piholePTR, "hostnamefqdn") == 0) + return PTR_HOSTNAMEFQDN; + else if(strcasecmp(piholePTR, "none") == 0 || + strcasecmp(piholePTR, "false") == 0) + return PTR_NONE; + + // Invalid value + return -1; +} + const char * __attribute__ ((const)) get_busy_reply_str(const enum busy_reply replyWhenBusy) { switch(replyWhenBusy) @@ -926,4 +974,19 @@ const char * __attribute__ ((const)) get_busy_reply_str(const enum busy_reply re return "DROP"; } return NULL; -} \ No newline at end of file +} + +int __attribute__ ((const)) get_busy_reply_val(const char *replyWhenBusy) +{ + if(strcasecmp(replyWhenBusy, "BLOCK") == 0) + return BUSY_BLOCK; + else if(strcasecmp(replyWhenBusy, "ALLOW") == 0) + return BUSY_ALLOW; + else if(strcasecmp(replyWhenBusy, "REFUSE") == 0) + return BUSY_REFUSE; + else if(strcasecmp(replyWhenBusy, "DROP") == 0) + return BUSY_DROP; + + // Invalid value + return -1; +} diff --git a/src/datastructure.h b/src/datastructure.h index e14d7351..4c501734 100644 --- a/src/datastructure.h +++ b/src/datastructure.h @@ -149,9 +149,13 @@ const char *get_query_status_str(const enum query_status status) __attribute__ ( const char *get_query_dnssec_str(const enum dnssec_status dnssec) __attribute__ ((const)); const char *get_query_reply_str(const enum reply_type query) __attribute__ ((const)); const char *get_refresh_hostnames_str(const enum refresh_hostnames refresh) __attribute__ ((const)); +int get_refresh_hostnames_val(const char *refresh_hostnames) __attribute__ ((const)); const char *get_blocking_mode_str(const enum blocking_mode mode) __attribute__ ((const)); +int get_blocking_mode_val(const char *blocking_mode) __attribute__ ((const)); const char *get_ptr_type_str(const enum ptr_type piholePTR) __attribute__ ((const)); +int get_ptr_type_val(const char *piholePTR) __attribute__ ((const)); const char *get_busy_reply_str(const enum busy_reply replyWhenBusy) __attribute__ ((const)); +int get_busy_reply_val(const char *replyWhenBusy) __attribute__ ((const)); // Pointer getter functions #define getQuery(queryID, checkMagic) _getQuery(queryID, checkMagic, __LINE__, __FUNCTION__, __FILE__) diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 8eb9b2d2..1d6465b1 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -98,7 +98,7 @@ static struct { // Fork-private copy of the server data the most recent reply came from static union mysockaddr last_server = {{ 0 }}; -unsigned char* pihole_privacylevel = &config.misc.privacylevel; +unsigned char* pihole_privacylevel = &config.misc.privacylevel.v.privacy_level; const char *flagnames[] = {"F_IMMORTAL ", "F_NAMEP ", "F_REVERSE ", "F_FORWARD ", "F_DHCP ", "F_NEG ", "F_HOSTS ", "F_IPV4 ", "F_IPV6 ", "F_BIGNAME ", "F_NXDOMAIN ", "F_CNAME ", "F_DNSKEY ", "F_CONFIG ", "F_DS ", "F_DNSSECOK ", "F_UPSTREAM ", "F_RRNAME ", "F_SERVER ", "F_QUERY ", "F_NOERR ", "F_AUTH ", "F_DNSSEC ", "F_KEYTAG ", "F_SECSTAT ", "F_NO_RR ", "F_IPSET ", "F_NOEXTRA ", "F_SERVFAIL", "F_RCODE", "F_SRV", "F_STALE" }; void FTL_hook(unsigned int flags, const char *name, union all_addr *addr, char *arg, int id, unsigned short type, const char* file, const int line) @@ -126,7 +126,7 @@ void FTL_hook(unsigned int flags, const char *name, union all_addr *addr, char * else if(flags & F_NOEXTRA && flags & F_DNSSEC) { // This is a new DNSSEC query (dnssec-query[DS]) - if(!config.dns.showDNSSEC) + if(!config.dns.showDNSSEC.v.b) return; const ednsData edns = { 0 }; @@ -266,21 +266,21 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len else { // Overwrite flags only if not replying with a forced reply - if(config.dns.blockingmode == MODE_NX) + if(config.dns.blockingmode.v.blocking_mode == MODE_NX) { // 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"); } - else if(config.dns.blockingmode == MODE_NODATA || - (config.dns.blockingmode == MODE_IP_NODATA_AAAA && (flags & F_IPV6))) + else if(config.dns.blockingmode.v.blocking_mode == MODE_NODATA || + (config.dns.blockingmode.v.blocking_mode == MODE_IP_NODATA_AAAA && (flags & F_IPV6))) { // 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", - config.dns.blockingmode == MODE_IP_NODATA_AAAA ? "-IPv6" : ""); + config.dns.blockingmode.v.blocking_mode == MODE_IP_NODATA_AAAA ? "-IPv6" : ""); } } @@ -334,20 +334,20 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len // Overwrite with IP address if requested if(redirecting) memcpy(&addr, &redirect_addr4, sizeof(addr)); - else if(config.dns.blockingmode == MODE_IP || - config.dns.blockingmode == MODE_IP_NODATA_AAAA || + else if(config.dns.blockingmode.v.blocking_mode == MODE_IP || + config.dns.blockingmode.v.blocking_mode == MODE_IP_NODATA_AAAA || forced_ip) { - if(hostname && config.dns.reply.host.overwrite_v4) - memcpy(&addr, &config.dns.reply.host.v4, sizeof(addr)); - else if(!hostname && config.dns.reply.blocking.overwrite_v4) - memcpy(&addr, &config.dns.reply.blocking.v4, sizeof(addr)); + if(hostname && config.dns.reply.host.overwrite_v4.v.b) + memcpy(&addr, &config.dns.reply.host.v4.v.in_addr, sizeof(addr)); + else if(!hostname && config.dns.reply.blocking.overwrite_v4.v.b) + memcpy(&addr, &config.dns.reply.blocking.v4.v.in_addr, sizeof(addr)); else memcpy(&addr, &next_iface.addr4, sizeof(addr)); } // Debug logging - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { char ip[ADDRSTRLEN+1] = { 0 }; alladdr_extract_ip(&addr, AF_INET, ip); @@ -357,7 +357,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len // Add A resource record header->ancount = htons(ntohs(header->ancount) + 1); if(add_resource_record(header, limit, &trunc, sizeof(struct dns_header), - &p, hostname ? daemon->local_ttl : config.dns.blockTTL, + &p, hostname ? daemon->local_ttl : config.dns.blockTTL.v.ui, NULL, T_A, C_IN, (char*)"4", &addr.addr4)) log_query(flags & ~F_IPV6, name, &addr, (char*)blockingreason, 0); } @@ -370,19 +370,19 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len // Overwrite with IP address if requested if(redirecting) memcpy(&addr, &redirect_addr6, sizeof(addr)); - else if(config.dns.blockingmode == MODE_IP || + else if(config.dns.blockingmode.v.blocking_mode == MODE_IP || forced_ip) { - if(hostname && config.dns.reply.host.overwrite_v6) - memcpy(&addr, &config.dns.reply.host.v6, sizeof(addr)); - else if(!hostname && config.dns.reply.blocking.overwrite_v6) - memcpy(&addr, &config.dns.reply.blocking.v6, sizeof(addr)); + if(hostname && config.dns.reply.host.overwrite_v6.v.b) + memcpy(&addr, &config.dns.reply.host.v6.v.in6_addr, sizeof(addr)); + else if(!hostname && config.dns.reply.blocking.overwrite_v6.v.b) + memcpy(&addr, &config.dns.reply.blocking.v6.v.in6_addr, sizeof(addr)); else memcpy(&addr, &next_iface.addr6, sizeof(addr)); } // Debug logging - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { char ip[ADDRSTRLEN+1] = { 0 }; alladdr_extract_ip(&addr, AF_INET6, ip); @@ -392,7 +392,7 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len // Add AAAA resource record header->ancount = htons(ntohs(header->ancount) + 1); if(add_resource_record(header, limit, &trunc, sizeof(struct dns_header), - &p, hostname ? daemon->local_ttl : config.dns.blockTTL, + &p, hostname ? daemon->local_ttl : config.dns.blockTTL.v.ui, NULL, T_AAAA, C_IN, (char*)"6", &addr.addr6)) log_query(flags & ~F_IPV4, name, &addr, (char*)blockingreason, 0); } @@ -532,10 +532,10 @@ bool _FTL_new_query(const unsigned int flags, const char *name, // virtual interface that has only an IPv4 address if((querytype == TYPE_A && !next_iface.haveIPv4 && - !config.dns.reply.host.overwrite_v4) || + !config.dns.reply.host.overwrite_v4.v.b) || (querytype == TYPE_AAAA && !next_iface.haveIPv6 && - !config.dns.reply.host.overwrite_v6)) + !config.dns.reply.host.overwrite_v6.v.b)) force_next_DNS_reply = REPLY_NODATA; else force_next_DNS_reply = REPLY_IP; @@ -558,11 +558,11 @@ bool _FTL_new_query(const unsigned int flags, const char *name, // Check if this is a PTR request for a local interface. // If so, we inject a "pi.hole" reply here - if(querytype == TYPE_PTR && config.dns.piholePTR != PTR_NONE) + if(querytype == TYPE_PTR && config.dns.piholePTR.v.ptr_type != PTR_NONE) check_pihole_PTR((char*)name); // Skip AAAA queries if user doesn't want to have them analyzed - if(!config.dns.analyzeAAAA && querytype == TYPE_AAAA) + if(!config.dns.analyzeAAAA.v.b && querytype == TYPE_AAAA) { log_debug(DEBUG_QUERIES, "Not analyzing AAAA query"); return false; @@ -581,7 +581,7 @@ bool _FTL_new_query(const unsigned int flags, const char *name, in_port_t clientPort = daemon->port; bool internal_query = false; char clientIP[ADDRSTRLEN+1] = { 0 }; - if(config.dns.EDNS0ECS && edns && edns->client_set) + if(config.dns.EDNS0ECS.v.b && edns && edns->client_set) { // Use ECS provided client strncpy(clientIP, edns->client, ADDRSTRLEN); @@ -601,7 +601,7 @@ bool _FTL_new_query(const unsigned int flags, const char *name, } // Check if user wants to skip queries coming from localhost - if(config.dns.ignoreLocalhost && + if(config.dns.ignoreLocalhost.v.b && (strcmp(clientIP, "127.0.0.1") == 0 || strcmp(clientIP, "::1") == 0)) { free(domainString); @@ -632,8 +632,8 @@ bool _FTL_new_query(const unsigned int flags, const char *name, const char *interface = internal_query ? "-" : next_iface.name; // Check rate-limit for this client - if(!internal_query && config.dns.rateLimit.count > 0 && - (++client->rate_limit > config.dns.rateLimit.count || client->flags.rate_limited)) + if(!internal_query && config.dns.rateLimit.count.v.ui > 0 && + (++client->rate_limit > config.dns.rateLimit.count.v.ui || client->flags.rate_limited)) { if(!client->flags.rate_limited) { @@ -662,7 +662,7 @@ bool _FTL_new_query(const unsigned int flags, const char *name, } // Log new query if in debug mode - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { const char *types = querystr(arg, qtype); log_debug(DEBUG_QUERIES, "**** new %sIPv%d %s query \"%s\" from %s/%s#%d (ID %i, FTL %i, %s:%i)", @@ -677,10 +677,10 @@ bool _FTL_new_query(const unsigned int flags, const char *name, // Skip rest of the analysis if this query is not of type A or AAAA // but user wants to see only A and AAAA queries (pre-v4.1 behavior) - if(config.dns.analyzeOnlyAandAAAA && querytype != TYPE_A && querytype != TYPE_AAAA) + if(config.dns.analyzeOnlyAandAAAA.v.b && querytype != TYPE_A && querytype != TYPE_AAAA) { // Don't process this query further here, we already counted it - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { const char *types = querystr(arg, qtype); log_debug(DEBUG_QUERIES, "Skipping new query: %s (%i)", types, id); @@ -744,7 +744,7 @@ bool _FTL_new_query(const unsigned int flags, const char *name, // Check and apply possible privacy level rules // The currently set privacy level (at the time the query is // generated) is stored in the queries structure - query->privacylevel = config.misc.privacylevel; + query->privacylevel = config.misc.privacylevel.v.privacy_level; // Query extended DNS error query->ede = EDE_UNSET; @@ -786,7 +786,7 @@ bool _FTL_new_query(const unsigned int flags, const char *name, const char *oldiface = getstr(client->ifacepos); if(strcasecmp(oldiface, interface) != 0) { - if(config.debug & DEBUG_CLIENTS) + if(config.debug.clients.v.b) { const char *clientName = getstr(client->namepos); log_debug(DEBUG_CLIENTS, "Client %s (%s) changed interface: %s -> %s", @@ -799,7 +799,7 @@ bool _FTL_new_query(const unsigned int flags, const char *name, } // Set client MAC address from EDNS(0) information (if available) - if(config.dns.EDNS0ECS && edns && edns->mac_set) + if(config.dns.EDNS0ECS.v.b && edns && edns->mac_set) { memcpy(client->hwaddr, edns->mac_byte, 6); client->hwlen = 6; @@ -809,7 +809,7 @@ bool _FTL_new_query(const unsigned int flags, const char *name, if(client->hwlen < 1) { client->hwlen = find_mac(addr, client->hwaddr, 1, time(NULL)); - if(config.debug & DEBUG_ARP) + if(config.debug.arp.v.b) { if(client->hwlen == 6) log_debug(DEBUG_ARP, "find_mac(\"%s\") returned hardware address " @@ -861,7 +861,7 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam ((addrfamily == AF_INET && addr->addr4.s_addr != INADDR_ANY) || (addrfamily == AF_INET6 && !IN6_IS_ADDR_UNSPECIFIED(&addr->addr6)))) { - if(config.debug & DEBUG_NETWORKING) + if(config.debug.networking.v.b) { char addrstr[INET6_ADDRSTRLEN] = { 0 }; if(addrfamily == AF_INET) @@ -879,7 +879,7 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam const char *iname = iface->slabel ? iface->slabel : iface->name; if(iface->addr.sa.sa_family == AF_INET) { - if(config.debug & DEBUG_NETWORKING) + if(config.debug.networking.v.b) { inet_ntop(AF_INET, &iface->addr.in.sin_addr, addrstr, INET6_ADDRSTRLEN); log_debug(DEBUG_NETWORKING, " - IPv4 interface %s (%d,%d) is %s", @@ -895,7 +895,7 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam } else if(iface->addr.sa.sa_family == AF_INET6) { - if(config.debug & DEBUG_NETWORKING) + if(config.debug.networking.v.b) { inet_ntop(AF_INET6, &iface->addr.in6.sin6_addr, addrstr, INET6_ADDRSTRLEN); log_debug(DEBUG_NETWORKING, " - IPv6 interface %s (%d,%d) is %s", @@ -935,7 +935,7 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam // If this interface has no name, we skip it if(iname == NULL) { - if(config.debug & DEBUG_NETWORKING) + if(config.debug.networking.v.b) log_debug(DEBUG_NETWORKING, " - SKIP IPv%d interface (%d,%d): no name", family == AF_INET ? 4 : 6, iface->index, iface->label); continue; @@ -944,7 +944,7 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam // Check if this is the interface we want if(iface->index != recviface->index || iface->label != recviface->label) { - if(config.debug & DEBUG_NETWORKING) + if(config.debug.networking.v.b) log_debug(DEBUG_NETWORKING, " - SKIP IPv%d interface %s: (%d,%d) != (%d,%d)", family == AF_INET ? 4 : 6, iname, iface->index, iface->label, recviface->index, recviface->label); @@ -997,7 +997,7 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam } // Debug logging - if(config.debug & DEBUG_NETWORKING) + if(config.debug.networking.v.b) { char buffer[ADDRSTRLEN+1] = { 0 }; if(family == AF_INET) @@ -1128,7 +1128,7 @@ static bool check_domain_blocked(const char *domain, const int clientID, { *db_okay = false; // Handle reply to this query as configured - if(config.dns.replyWhenBusy == BUSY_ALLOW) + if(config.dns.replyWhenBusy.v.busy_reply == BUSY_ALLOW) { log_debug(DEBUG_QUERIES, "Allowing query as gravity database is not available"); @@ -1137,13 +1137,13 @@ static bool check_domain_blocked(const char *domain, const int clientID, // DNS cache so this domain will be rechecked on the next query return false; } - else if(config.dns.replyWhenBusy == BUSY_REFUSE) + else if(config.dns.replyWhenBusy.v.busy_reply == BUSY_REFUSE) { blockingreason = "to be refused (gravity database is not available)"; force_next_DNS_reply = REPLY_REFUSED; *new_status = QUERY_DBBUSY; } - else if(config.dns.replyWhenBusy == BUSY_DROP) + else if(config.dns.replyWhenBusy.v.busy_reply == BUSY_DROP) { blockingreason = "to be dropped (gravity database is not available)"; force_next_DNS_reply = REPLY_NONE; @@ -1197,7 +1197,7 @@ static bool special_domain(const queriesData *query, const char *domain) // than NOERROR, such as NXDOMAIN (non-existent domain) or SERVFAIL; or // respond with NOERROR, but return no A or AAAA records. // https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https - if(config.dns.specialDomains.mozillaCanary && + if(config.dns.specialDomains.mozillaCanary.v.b && strcasecmp(domain, "use-application-dns.net") == 0 && (query->type == TYPE_A || query->type == TYPE_AAAA)) { @@ -1221,7 +1221,7 @@ static bool special_domain(const queriesData *query, const char *domain) // > mask.icloud.com // > mask-h2.icloud.com // https://developer.apple.com/support/prepare-your-network-for-icloud-private-relay - if(config.dns.specialDomains.iCloudPrivateRelay && + if(config.dns.specialDomains.iCloudPrivateRelay.v.b && (strcasecmp(domain, "mask.icloud.com") == 0 || strcasecmp(domain, "mask-h2.icloud.com") == 0)) { @@ -1401,7 +1401,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c // Check blacklist (exact + regex) and gravity for _esni.domain if enabled // (defaulting to true) - if(config.dns.blockESNI && + if(config.dns.blockESNI.v.b && !query->flags.allowed && blockDomain == NOT_FOUND && strlen(domainstr) > 6 && strncasecmp(domainstr, "_esni.", 6u) == 0) { @@ -1429,7 +1429,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c query_blocked(query, domain, client, new_status); // Debug output - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { log_debug(DEBUG_QUERIES, "Blocking %s as %s is %s", domainstr, blockedDomain, blockingreason); if(force_next_DNS_reply != 0) @@ -1459,7 +1459,7 @@ bool _FTL_CNAME(const char *dst, const char *src, const int id, const char* file log_debug(DEBUG_QUERIES, "FTL_CNAME called with: src = %s, dst = %s, id = %d", src, dst, id); // Does the user want to skip deep CNAME inspection? - if(!config.dns.CNAMEdeepInspect) + if(!config.dns.CNAMEdeepInspect.v.b) { log_debug(DEBUG_QUERIES, "Skipping analysis as cname inspection is disabled"); return false; @@ -1750,7 +1750,7 @@ void FTL_dnsmasq_reload(void) set_event(RELOAD_GRAVITY); // Print current set of capabilities if requested via debug flag - if(config.debug & DEBUG_CAPS) + if(config.debug.caps.v.b) check_capabilities(); // Set resolver as ready @@ -1813,7 +1813,7 @@ static void update_upstream(queriesData *query, const int id) int upstreamID = findUpstreamID(ip, port); if(upstreamID != query->upstreamID) { - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { upstreamsData *upstream = getUpstream(query->upstreamID, true); if(upstream) @@ -1880,7 +1880,7 @@ static void FTL_reply(const unsigned int flags, const char *name, const union al const bool stale = flags & F_STALE; // Possible debugging output - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { // Human-readable answer may be provided by arg // (e.g. for non-cached queries such as SOA) @@ -2141,7 +2141,7 @@ static void FTL_reply(const unsigned int flags, const char *name, const union al { log_warn("Unknown REPLY"); } - else if(config.debug & DEBUG_FLAGS) + else if(config.debug.flags.v.b) { log_warn("Unknown upstream REPLY"); } @@ -2180,7 +2180,7 @@ static enum query_status detect_blocked_IP(const unsigned short flags, const uni // Check for IP block 146.112.61.104 - 146.112.61.110 if((flags & F_IPV4) && ipv4Addr >= 0x92703d68 && ipv4Addr <= 0x92703d6e) { - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { char answer[ADDRSTRLEN]; answer[0] = '\0'; inet_ntop(AF_INET, addr, answer, ADDRSTRLEN); @@ -2198,7 +2198,7 @@ static enum query_status detect_blocked_IP(const unsigned short flags, const uni addr->addr6.s6_addr32[2] == 0xffff0000 && ipv6Addr >= 0x92703d68 && ipv6Addr <= 0x92703d6e) { - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { char answer[ADDRSTRLEN]; answer[0] = '\0'; inet_ntop(AF_INET6, addr, answer, ADDRSTRLEN); @@ -2215,7 +2215,7 @@ static enum query_status detect_blocked_IP(const unsigned short flags, const uni // nothing is reachable under these addresses else if(flags & F_IPV4 && ipv4Addr == 0) { - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { log_debug(DEBUG_QUERIES, "Upstream responded with 0.0.0.0, ID %i:\n\t\"%s\" -> \"0.0.0.0\"", query->id, getstr(domain->domainpos)); @@ -2230,7 +2230,7 @@ static enum query_status detect_blocked_IP(const unsigned short flags, const uni addr->addr6.s6_addr32[2] == 0 && addr->addr6.s6_addr32[3] == 0) { - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { log_debug(DEBUG_QUERIES, "Upstream responded with ::, ID %i:\n\t\"%s\" -> \"::\"", query->id, getstr(domain->domainpos)); @@ -2312,7 +2312,7 @@ static void FTL_dnssec(const char *arg, const union all_addr *addr, const int id } // Debug logging - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { // Get domain pointer const domainsData* domain = getDomain(query->domainID, true); @@ -2407,7 +2407,7 @@ static void FTL_upstream_error(const union all_addr *addr, const unsigned int fl } // Debug logging - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { // Get domain pointer const domainsData* domain = getDomain(query->domainID, true); @@ -2493,7 +2493,7 @@ static void FTL_mark_externally_blocked(const int id, const char* file, const in } // Possible debugging information - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { // Get domain name (domain cannot be NULL here) const char *domainname = getstr(domain->domainpos); @@ -2554,7 +2554,7 @@ void print_flags(const unsigned int flags) // e.g. "Flags: F_FORWARD F_NEG F_IPV6" // Only print flags if corresponding debugging flag is set - if(!(config.debug & DEBUG_FLAGS)) + if(!(config.debug.flags.v.b)) return; char *flagstr = calloc(sizeof(flagnames) + 1, sizeof(char)); @@ -2630,7 +2630,7 @@ static void _query_set_reply(const unsigned int flags, const enum reply_type rep new_reply = REPLY_BLOB; } - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { const char *path = short_path(file); log_debug(DEBUG_QUERIES, "Set reply to %s (%d) in %s:%d", get_query_reply_str(new_reply), new_reply, path, line); @@ -2655,7 +2655,7 @@ static void init_pihole_PTR(void) { char *ptrname = NULL; // Determine name that should be replied to with on Pi-hole PTRs - switch (config.dns.piholePTR) + switch (config.dns.piholePTR.v.ptr_type) { default: case PTR_NONE: @@ -2693,7 +2693,7 @@ static void init_pihole_PTR(void) } // Obtain PTR record used for Pi-hole PTR injection (if enabled) - if(config.dns.piholePTR != PTR_NONE) + if(config.dns.piholePTR.v.ptr_type != PTR_NONE) { // Add PTR record for pi.hole, the address will be injected later pihole_ptr = calloc(1, sizeof(struct ptr_record)); @@ -2774,12 +2774,12 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw) { log_info("FTL is going to drop from root to user %s (UID %d)", ent_pw->pw_name, (int)ent_pw->pw_uid); - if(chown(config.files.log, ent_pw->pw_uid, ent_pw->pw_gid) == -1) + if(chown(config.files.log.v.s, ent_pw->pw_uid, ent_pw->pw_gid) == -1) log_warn("Setting ownership (%i:%i) of %s failed: %s (%i)", - ent_pw->pw_uid, ent_pw->pw_gid, config.files.log, strerror(errno), errno); - if(chown(config.files.database, ent_pw->pw_uid, ent_pw->pw_gid) == -1) + ent_pw->pw_uid, ent_pw->pw_gid, config.files.log.v.s, strerror(errno), errno); + if(chown(config.files.database.v.s, ent_pw->pw_uid, ent_pw->pw_gid) == -1) log_warn("Setting ownership (%i:%i) of %s failed: %s (%i)", - ent_pw->pw_uid, ent_pw->pw_gid, config.files.database, strerror(errno), errno); + ent_pw->pw_uid, ent_pw->pw_gid, config.files.database.v.s, strerror(errno), errno); chown_all_shmem(ent_pw); } else @@ -2799,7 +2799,7 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw) } // Obtain DNS port from dnsmasq daemon - config.dns.port = daemon->port; + dns_port = daemon->port; // Initialize FTL HTTP server http_init(); @@ -2812,7 +2812,7 @@ static char *get_ptrname(struct in_addr *addr) { static char *ptrname = NULL; // Determine name that should be replied to with on Pi-hole PTRs - switch (config.dns.piholePTR) + switch (config.dns.piholePTR.v.ptr_type) { default: case PTR_NONE: @@ -2989,7 +2989,7 @@ unsigned int FTL_extract_question_flags(struct dns_header *header, const size_t flags |= F_IPV4; // Debug logging if enabled - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { char *qtype_str = querystr(NULL, qtype); log_debug(DEBUG_QUERIES, "CNAME header: Question was %s %s", qtype_str, name); @@ -3024,7 +3024,7 @@ void FTL_TCP_worker_terminating(bool finished) } // Possible debug logging - if(config.debug != 0) + if(config.debug.queries.v.b) { const char *reason = finished ? "client disconnected" : "timeout"; log_debug(DEBUG_ANY, "TCP worker terminating (%s)", reason); @@ -3063,8 +3063,8 @@ void FTL_TCP_worker_created(const int confd) return; } - // Print this if any debug setting is enabled - if(config.debug != 0) + // Print this if debugging is enabled + if(config.debug.queries.v.b) { // Get peer IP address (client) char peer_ip[ADDRSTRLEN] = { 0 }; @@ -3178,7 +3178,7 @@ void FTL_query_in_progress(const int id) } // Debug logging - if(config.debug & DEBUG_QUERIES) + if(config.debug.queries.v.b) { // Get domain pointer const domainsData* domain = getDomain(query->domainID, true); @@ -3281,7 +3281,7 @@ static void _query_set_dnssec(queriesData *query, const enum dnssec_status dnsse if(!option_bool(OPT_DNSSEC_VALID)) return; - if(config.debug & DEBUG_DNSSEC) + if(config.debug.dnssec.v.b) { const char *path = short_path(file); const char *status = get_query_dnssec_str(dnssec); @@ -3303,7 +3303,7 @@ void FTL_dnsmasq_log(const char *payload, const int length) int check_struct_sizes(void) { int result = 0; - result += check_one_struct("ConfigStruct", sizeof(ConfigStruct), 248, 188); + result += check_one_struct("struct config", sizeof(struct config), 4176, 3016); result += check_one_struct("queriesData", sizeof(queriesData), 72, 64); result += check_one_struct("upstreamsData", sizeof(upstreamsData), 640, 628); result += check_one_struct("clientsData", sizeof(clientsData), 672, 652); diff --git a/src/edns0.c b/src/edns0.c index 15410ab5..4a1a97c5 100644 --- a/src/edns0.c +++ b/src/edns0.c @@ -52,7 +52,7 @@ void FTL_parse_pseudoheaders(struct dns_header *header, size_t n, union mysockad return; // Debug logging - if(config.debug & DEBUG_EDNS0) + if(config.debug.edns0.v.b) { char payload[3*plen+1]; memset(payload, 0, sizeof(payload)); @@ -176,7 +176,7 @@ void FTL_parse_pseudoheaders(struct dns_header *header, size_t n, union mysockad log_debug(DEBUG_EDNS0, "EDNS(0) code %u, optlen %u (bytes %zu - %zu of %u)", code, optlen, offset, offset + optlen, rdlen); - if (code == EDNS0_ECS && config.dns.EDNS0ECS) + if (code == EDNS0_ECS && config.dns.EDNS0ECS.v.b) { // EDNS(0) CLIENT SUBNET // RFC 7871 Client Subnet in DNS Queries 6. Option Format @@ -250,7 +250,7 @@ void FTL_parse_pseudoheaders(struct dns_header *header, size_t n, union mysockad // EDNS(0) COOKIE client unsigned char client_cookie[8]; memcpy(client_cookie, p, 8); - if(config.debug & DEBUG_EDNS0) + if(config.debug.edns0.v.b) { char pretty_client_cookie[8*2 + 1]; // client: fixed length char *pp = pretty_client_cookie; @@ -272,7 +272,7 @@ void FTL_parse_pseudoheaders(struct dns_header *header, size_t n, union mysockad unsigned short server_cookie_len = optlen - 8; unsigned char server_cookie[server_cookie_len]; memcpy(server_cookie, p + 8u, server_cookie_len); - if(config.debug & DEBUG_EDNS0) + if(config.debug.edns0.v.b) { char pretty_client_cookie[8*2 + 1]; // client: fixed length char *pp = pretty_client_cookie; @@ -338,7 +338,7 @@ void FTL_parse_pseudoheaders(struct dns_header *header, size_t n, union mysockad unsigned char payload[optlen + 1u]; // variable length memcpy(payload, p, optlen); payload[optlen] = '\0'; - if(config.debug & DEBUG_EDNS0) + if(config.debug.edns0.v.b) { char pretty_payload[optlen*5 + 1u]; char *pp = pretty_payload; diff --git a/src/enums.h b/src/enums.h index 27908830..f52524b8 100644 --- a/src/enums.h +++ b/src/enums.h @@ -132,31 +132,31 @@ enum domain_client_status { } __attribute__ ((packed)); enum debug_flag { - DEBUG_DATABASE = (1 << 0), /* 00000000 00000000 00000000 00000001 */ - DEBUG_NETWORKING = (1 << 1), /* 00000000 00000000 00000000 00000010 */ - DEBUG_LOCKS = (1 << 2), /* 00000000 00000000 00000000 00000100 */ - DEBUG_QUERIES = (1 << 3), /* 00000000 00000000 00000000 00001000 */ - DEBUG_FLAGS = (1 << 4), /* 00000000 00000000 00000000 00010000 */ - DEBUG_SHMEM = (1 << 5), /* 00000000 00000000 00000000 00100000 */ - DEBUG_GC = (1 << 6), /* 00000000 00000000 00000000 01000000 */ - DEBUG_ARP = (1 << 7), /* 00000000 00000000 00000000 10000000 */ - DEBUG_REGEX = (1 << 8), /* 00000000 00000000 00000001 00000000 */ - DEBUG_API = (1 << 9), /* 00000000 00000000 00000010 00000000 */ - DEBUG_OVERTIME = (1 << 10), /* 00000000 00000000 00000100 00000000 */ - DEBUG_STATUS = (1 << 11), /* 00000000 00000000 00001000 00000000 */ - DEBUG_CAPS = (1 << 12), /* 00000000 00000000 00010000 00000000 */ - DEBUG_DNSSEC = (1 << 13), /* 00000000 00000000 00100000 00000000 */ - DEBUG_VECTORS = (1 << 14), /* 00000000 00000000 01000000 00000000 */ - DEBUG_RESOLVER = (1 << 15), /* 00000000 00000000 10000000 00000000 */ - DEBUG_EDNS0 = (1 << 16), /* 00000000 00000001 00000000 00000000 */ - DEBUG_CLIENTS = (1 << 17), /* 00000000 00000010 00000000 00000000 */ - DEBUG_ALIASCLIENTS = (1 << 18), /* 00000000 00000100 00000000 00000000 */ - DEBUG_EVENTS = (1 << 19), /* 00000000 00001000 00000000 00000000 */ - DEBUG_HELPER = (1 << 20), /* 00000000 00010000 00000000 00000000 */ - DEBUG_CONFIG = (1 << 21), /* 00000000 00100000 00000000 00000000 */ - DEBUG_EXTRA = (1 << 22), /* 00000000 01000000 00000000 00000000 */ - DEBUG_RESERVED = (1 << 23), /* 00000000 10000000 00000000 00000000 */ - // DEBUG_EXTRA has always to be the last option + DEBUG_DATABASE = 0, + DEBUG_NETWORKING, + DEBUG_LOCKS, + DEBUG_QUERIES, + DEBUG_FLAGS, + DEBUG_SHMEM, + DEBUG_GC, + DEBUG_ARP, + DEBUG_REGEX, + DEBUG_API, + DEBUG_OVERTIME, + DEBUG_STATUS, + DEBUG_CAPS, + DEBUG_DNSSEC, + DEBUG_VECTORS, + DEBUG_RESOLVER, + DEBUG_EDNS0, + DEBUG_CLIENTS, + DEBUG_ALIASCLIENTS, + DEBUG_EVENTS, + DEBUG_HELPER, + DEBUG_CONFIG, + DEBUG_EXTRA, + DEBUG_RESERVED, + DEBUG_MAX } __attribute__ ((packed)); enum events { diff --git a/src/events.c b/src/events.c index a08fb5c8..b4f32fb1 100644 --- a/src/events.c +++ b/src/events.c @@ -37,7 +37,7 @@ void _set_event(const enum events event, int line, const char *function, const c is_set = true; // Possible debug logging - if(config.debug & DEBUG_EVENTS) + if(config.debug.events.v.b) { log_debug(DEBUG_EVENTS, "Event %s -> %s called from %s() (%s:%i)", eventtext(event), @@ -68,7 +68,7 @@ bool _get_and_clear_event(const enum events event, int line, const char *functio is_set = true; // Possible debug logging only for SET status, to avoid log file flooding with NOT SET messages - if(is_set && config.debug & DEBUG_EVENTS) + if(is_set && config.debug.events.v.b) { log_debug(DEBUG_EVENTS, "Event %s -> was SET, now CLEARED called from %s() (%s:%i)", eventtext(event), function, file, line); diff --git a/src/files.c b/src/files.c index 5b2db0f8..17920845 100644 --- a/src/files.c +++ b/src/files.c @@ -67,10 +67,10 @@ bool file_exists(const char *filename) bool get_database_stat(struct stat *st) { - if(stat(config.files.database, st) == 0) + if(stat(config.files.database.v.s, st) == 0) return true; - log_err("Cannot stat %s: %s", config.files.database, strerror(errno)); + log_err("Cannot stat %s: %s", config.files.database.v.s, strerror(errno)); return false; } diff --git a/src/gc.c b/src/gc.c index 36d0b5ae..26427e99 100644 --- a/src/gc.c +++ b/src/gc.c @@ -52,7 +52,7 @@ static void reset_rate_limiting(void) const char *clientIP = getstr(client->ippos); // Check if we want to continue rate limiting - if(client->rate_limit > config.dns.rateLimit.count) + if(client->rate_limit > config.dns.rateLimit.count.v.ui) { log_info("Still rate-limiting %s as it made additional %d queries", clientIP, client->rate_limit); } @@ -73,13 +73,13 @@ static time_t lastRateLimitCleaner = 0; // Returns how many more seconds until the current rate-limiting interval is over time_t get_rate_limit_turnaround(const unsigned int rate_limit_count) { - const unsigned int how_often = rate_limit_count/config.dns.rateLimit.count; - return (time_t)config.dns.rateLimit.interval*how_often - (time(NULL) - lastRateLimitCleaner); + const unsigned int how_often = rate_limit_count/config.dns.rateLimit.count.v.ui; + return (time_t)config.dns.rateLimit.interval.v.ui*how_often - (time(NULL) - lastRateLimitCleaner); } static int check_space(const char *file, int LastUsage) { - if(config.misc.check.disk == 0) + if(config.misc.check.disk.v.b == 0) return 0; int perc = 0; @@ -88,7 +88,7 @@ static int check_space(const char *file, int LastUsage) // exceeds the configured threshold and current usage is higher than // usage in the last run (to prevent log spam) perc = get_filepath_usage(file, buffer); - if(perc > config.misc.check.disk && perc > LastUsage ) + if(perc > config.misc.check.disk.v.b && perc > LastUsage ) log_resource_shortage(-1.0, 0, -1, perc, file, buffer); return perc; @@ -96,7 +96,7 @@ static int check_space(const char *file, int LastUsage) static void check_load(void) { - if(!config.misc.check.load) + if(!config.misc.check.load.v.b) return; // Get CPU load averages @@ -132,8 +132,8 @@ void *GC_thread(void *val) while(!killed) { const time_t now = time(NULL); - if(config.dns.rateLimit.interval > 0 && - (unsigned int)(now - lastRateLimitCleaner) >= config.dns.rateLimit.interval) + if(config.dns.rateLimit.interval.v.ui > 0 && + (unsigned int)(now - lastRateLimitCleaner) >= config.dns.rateLimit.interval.v.ui) { lastRateLimitCleaner = now; lock_shm(); @@ -149,8 +149,8 @@ void *GC_thread(void *val) if(now - lastResourceCheck >= RCinterval) { check_load(); - LastDBStorageUsage = check_space(config.files.database, LastDBStorageUsage); - LastLogStorageUsage = check_space(config.files.log, LastLogStorageUsage); + LastDBStorageUsage = check_space(config.files.database.v.s, LastDBStorageUsage); + LastLogStorageUsage = check_space(config.files.log.v.s, LastLogStorageUsage); lastResourceCheck = now; } @@ -165,13 +165,13 @@ void *GC_thread(void *val) lock_shm(); // Get minimum timestamp to keep (this can be set with MAXLOGAGE) - time_t mintime = (now - GCdelay) - config.database.maxHistory; + time_t mintime = (now - GCdelay) - config.database.maxHistory.v.ui; // Align the start time of this GC run to the GCinterval. This will also align with the // oldest overTime interval after GC is done. mintime -= mintime % GCinterval; - if(config.debug & DEBUG_GC) + if(config.debug.gc.v.b) { timer_start(GC_TIMER); char timestring[84] = ""; diff --git a/src/log.c b/src/log.c index 9d1972c5..ed00e5a3 100644 --- a/src/log.c +++ b/src/log.c @@ -40,14 +40,14 @@ void init_FTL_log(const char *name) getLogFilePath(); // Open the log file in append/create mode - if(config.files.log != NULL) + if(config.files.log.v.s != NULL) { FILE *logfile = NULL; - if((logfile = fopen(config.files.log, "a+")) == NULL) + if((logfile = fopen(config.files.log.v.s, "a+")) == NULL) { syslog(LOG_ERR, "Opening of FTL\'s log file failed, using syslog instead!"); - printf("ERR: Opening of FTL log (%s) failed!\n",config.files.log); - config.files.log = NULL; + printf("ERR: Opening of FTL log (%s) failed!\n",config.files.log.v.s); + config.files.log.v.s = NULL; } // Close log file @@ -104,7 +104,7 @@ void get_timestr(char * const timestring, const time_t timein, const bool millis static const char *priostr(const int priority, const enum debug_flag flag) { - const char *name, *desc; + const char *name; switch (priority) { // system is unusable @@ -130,7 +130,7 @@ static const char *priostr(const int priority, const enum debug_flag flag) return "INFO"; // debug-level messages case LOG_DEBUG: - debugstr(flag, &name, &desc); + debugstr(flag, &name); return name; // invalid option default: @@ -138,109 +138,84 @@ static const char *priostr(const int priority, const enum debug_flag flag) } } -void debugstr(const enum debug_flag flag, const char **name, const char **desc) +void debugstr(const enum debug_flag flag, const char **name) { switch (flag) { case DEBUG_DATABASE: *name = "DEBUG_DATABASE"; - *desc = "Enable extra logging of database actions"; return; case DEBUG_NETWORKING: *name = "DEBUG_NETWORKING"; - *desc = "Enable extra logging of detected interfaces"; return; case DEBUG_LOCKS: *name = "DEBUG_LOCKS"; - *desc = "Enable extra logging of shared memory lock actions"; return; case DEBUG_QUERIES: *name = "DEBUG_QUERIES"; - *desc = "Print extensive query information"; return; case DEBUG_FLAGS: *name = "DEBUG_FLAGS"; - *desc = "Print flags of queries received by the DNS hooks"; return; case DEBUG_SHMEM: *name = "DEBUG_SHMEM"; - *desc = "Print information about shared memory buffers"; return; case DEBUG_GC: *name = "DEBUG_GC"; - *desc = "Print information about garbage collection"; return; case DEBUG_ARP: *name = "DEBUG_ARP"; - *desc = "Print information about ARP table processing"; return; case DEBUG_REGEX: *name = "DEBUG_REGEX"; - *desc = "Enable extra logging of regex matching details"; return; case DEBUG_API: *name = "DEBUG_API"; - *desc = "Enable extra logging of API activities"; return; case DEBUG_OVERTIME: *name = "DEBUG_OVERTIME"; - *desc = "Print information about overTime memory operations"; return; case DEBUG_STATUS: *name = "DEBUG_STATUS"; - *desc = "Enable extra logging of query status changes"; return; case DEBUG_CAPS: *name = "DEBUG_CAPS"; - *desc = "Print information about capabilities granted to the pihole-FTL process"; return; case DEBUG_DNSSEC: *name = "DEBUG_DNSSEC"; - *desc = "Print information about DNSSEC activity"; return; case DEBUG_VECTORS: *name = "DEBUG_VECTORS"; - *desc = "Print vector operation details"; return; case DEBUG_RESOLVER: *name = "DEBUG_RESOLVER"; - *desc = "Extensive information about hostname resolution like which DNS servers are used"; return; case DEBUG_EDNS0: *name = "DEBUG_EDNS0"; - *desc = "Print EDNS(0) debugging information"; return; case DEBUG_CLIENTS: *name = "DEBUG_CLIENTS"; - *desc = "Enable extra client detail logging"; return; case DEBUG_ALIASCLIENTS: *name = "DEBUG_ALIASCLIENTS"; - *desc = "Print aliasclient details"; return; case DEBUG_EVENTS: *name = "DEBUG_EVENTS"; - *desc = "Log information about processed internal events"; return; case DEBUG_HELPER: *name = "DEBUG_HELPER"; - *desc = "Enable logging of script helper activity"; return; case DEBUG_EXTRA: *name = "DEBUG_EXTRA"; - *desc = "Special debug flag that may be used for debugging specific issues"; return; case DEBUG_CONFIG: *name = "DEBUG_CONFIG"; - *desc = "Print config parsing details"; return; case DEBUG_RESERVED: *name = "DEBUG_RESERVED"; - *desc = "Reserved debug flag"; return; - default: - *name = "DEBUG_ANY"; - *desc = "N/A"; + case DEBUG_MAX: + *name = "DEBUG_MAX"; return; } } @@ -255,7 +230,7 @@ void __attribute__ ((format (gnu_printf, 3, 4))) _FTL_log(const int priority, co return; // Check if this is something we should print only in debug mode - if(priority == LOG_DEBUG && (config.debug == 0 || (flag != 0 && !(config.debug & flag)))) + if(priority == LOG_DEBUG && (!debug_any || (flag != 0 && !(get_debug_item(flag)->v.b)))) return; // Get human-readable time @@ -299,10 +274,10 @@ void __attribute__ ((format (gnu_printf, 3, 4))) _FTL_log(const int priority, co // Print to log file or syslog if(print_log) { - if(config.files.log != NULL) + if(config.files.log.v.s != NULL) { // Open log file - FILE *logfile = fopen(config.files.log, "a+"); + FILE *logfile = fopen(config.files.log.v.s, "a+"); // Write to log file if(logfile != NULL) @@ -344,13 +319,13 @@ static FILE * __attribute__((malloc, warn_unused_result)) open_web_log(const enu switch (code) { case HTTP_INFO: - file = config.files.http_info; + file = config.files.http_info.v.s; break; case PH7_ERROR: - file = config.files.ph7_error; + file = config.files.ph7_error.v.s; break; default: - file = config.files.ph7_error; + file = config.files.ph7_error.v.s; break; } @@ -393,7 +368,7 @@ void __attribute__ ((format (gnu_printf, 2, 3))) logg_web(enum web_code code, co void FTL_log_helper(const unsigned char n, ...) { // Only log helper debug messages if enabled - if(!(config.debug & DEBUG_HELPER)) + if(!(config.debug.helper.v.b)) return; // Extract all variable arguments diff --git a/src/log.h b/src/log.h index f2dd334c..638102a4 100644 --- a/src/log.h +++ b/src/log.h @@ -27,7 +27,7 @@ const char *get_FTL_version(void) __attribute__ ((malloc)); void log_FTL_version(bool crashreport); double double_time(void); void get_timestr(char * const timestring, const time_t timein, const bool millis); -void debugstr(const enum debug_flag flag, const char **name, const char **desc); +void debugstr(const enum debug_flag flag, const char **name); void logg_web(enum web_code code, const char *format, ...) __attribute__ ((format (gnu_printf, 2, 3))); const char *get_ordinal_suffix(unsigned int number) __attribute__ ((const)); void print_FTL_version(void); diff --git a/src/main.c b/src/main.c index dae0a857..9130f7de 100644 --- a/src/main.c +++ b/src/main.c @@ -59,13 +59,16 @@ int main (int argc, char *argv[]) log_info("########## FTL started on %s! ##########", hostname()); log_FTL_version(false); - // Process pihole-FTL.toml configuration file - readFTLconf(); - // Catch signals not handled by dnsmasq // We configure real-time signals later (after dnsmasq has forked) handle_signals(); + // Process pihole-FTL.toml configuration file + readFTLconf(); + + // Set process priority + set_nice(); + // Initialize shared memory if(!init_shmem()) { @@ -107,7 +110,7 @@ int main (int argc, char *argv[]) flush_message_table(); // Try to import queries from long-term database if available - if(config.database.DBimport) + if(config.database.DBimport.v.b) { import_queries_from_disk(); DB_read_queries(); @@ -117,20 +120,14 @@ int main (int argc, char *argv[]) check_setupVarsconf(); // Check for availability of capabilities in debug mode - if(config.debug & DEBUG_CAPS) + if(config.debug.caps.v.b) check_capabilities(); // Initialize pseudo-random number generator srand(time(NULL)); - startup = false; - if(config.debug != 0) - { - for(int i = 0; i < argc_dnsmasq; i++) - log_debug(DEBUG_ANY, "argv[%i] = \"%s\"", i, argv_dnsmasq[i]); - } - // Start the resolver + startup = false; main_dnsmasq(argc_dnsmasq, argv_dnsmasq); log_info("Shutting down..."); @@ -139,7 +136,7 @@ int main (int argc, char *argv[]) sleepms(250); // Save new queries to database (if database is used) - if(config.database.DBexport) + if(config.database.DBexport.v.b) { export_queries_to_disk(true); log_info("Finished final database update"); diff --git a/src/overTime.c b/src/overTime.c index 446711c4..c632e0af 100644 --- a/src/overTime.c +++ b/src/overTime.c @@ -27,7 +27,7 @@ overTimeData *overTime = NULL; static void initSlot(const unsigned int index, const time_t timestamp) { // Possible debug printing - if(config.debug & DEBUG_OVERTIME) + if(config.debug.overtime.v.b) { char timestr[20]; strftime(timestr, 20, "%Y-%m-%d %H:%M:%S", localtime(×tamp)); @@ -82,7 +82,7 @@ void initOverTime(void) // Oldest timestamp is (OVERTIME_SLOTS-1) times the OVERTIME_INTERVAL in the past const time_t oldest = newest - (OVERTIME_SLOTS-1) * OVERTIME_INTERVAL; - if(config.debug & DEBUG_OVERTIME) + if(config.debug.overtime.v.b) { char first[20], last[20]; strftime(first, 20, "%Y-%m-%d %H:%M:%S", localtime(&oldest)); diff --git a/src/regex.c b/src/regex.c index 0cdbff00..46605441 100644 --- a/src/regex.c +++ b/src/regex.c @@ -191,7 +191,7 @@ bool compile_regex(const char *regexin, regexData *regex, char **message) } // Debug output - else if(config.debug & DEBUG_REGEX) + else if(config.debug.config.v.b) { const char *qtypestr = get_query_type_str(regex->ext.query_type, NULL, NULL); log_debug(DEBUG_REGEX, " This regex will %s match query type %s", @@ -336,7 +336,7 @@ static int match_regex(const char *input, DNSCacheData* dns_cache, const int cli // We allow clientID = -1 to get all regex (for testing) if(clientID >= 0 && !get_per_client_regex(clientID, regexID)) { - if(config.debug & DEBUG_REGEX) + if(config.debug.regex.v.b) { clientsData* client = getClient(clientID, true); if(client != NULL) @@ -699,7 +699,7 @@ int regex_test(const bool debug_mode, const bool quiet, const char *domainin, co // Disable all debugging output if not explicitly in debug mode (CLI argument "d") if(!debug_mode) - config.debug = 0; + set_all_debug(false); // Re-enable terminal output log_ctrl(false, !quiet); diff --git a/src/resolve.c b/src/resolve.c index 21478ffa..3d3fce67 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -78,7 +78,7 @@ static bool valid_hostname(char* name, const char* clientip) static void print_used_resolvers(const char *message) { // Print details only when debugging - if(!(config.debug & DEBUG_RESOLVER)) + if(!(config.debug.resolver.v.b)) return; log_debug(DEBUG_RESOLVER, "%s", message); @@ -127,7 +127,7 @@ static void print_used_resolvers(const char *message) // (may be disabled due to config settings) bool __attribute__((pure)) resolve_names(void) { - if(!config.resolver.resolveIPv4 && !config.resolver.resolveIPv6) + if(!config.resolver.resolveIPv4.v.b && !config.resolver.resolveIPv6.v.b) return false; return true; } @@ -135,8 +135,8 @@ bool __attribute__((pure)) resolve_names(void) // Return if we want to resolve this type of address to a name bool __attribute__((pure)) resolve_this_name(const char *ipaddr) { - if(!config.resolver.resolveIPv4 || - (!config.resolver.resolveIPv6 && strstr(ipaddr,":") != NULL)) + if(!config.resolver.resolveIPv4.v.b || + (!config.resolver.resolveIPv6.v.b && strstr(ipaddr,":") != NULL)) return false; return true; } @@ -225,7 +225,7 @@ char *resolveHostname(const char *addr) // INADDR_LOOPBACK is in host byte order, however, in_addr has to be in // network byte order, convert it here if necessary struct in_addr FTLaddr = { htonl(INADDR_LOOPBACK) }; - in_port_t FTLport = htons(config.dns.port); + in_port_t FTLport = htons(dns_port); // Set FTL as system resolver only if not already the primary resolver if(_res.nsaddr_list[0].sin_addr.s_addr != FTLaddr.s_addr || _res.nsaddr_list[0].sin_port != FTLport) @@ -245,7 +245,7 @@ char *resolveHostname(const char *addr) // Set resolver port _res.nsaddr_list[0].sin_port = FTLport; - if(config.debug & DEBUG_RESOLVER) + if(config.debug.resolver.v.b) print_used_resolvers("Setting nameservers to:"); // Try to resolve address @@ -279,7 +279,7 @@ char *resolveHostname(const char *addr) _res.nsaddr_list[i].sin_port = ns_port_bck[i]; } } - else if(config.debug & DEBUG_RESOLVER) + else if(config.debug.resolver.v.b) print_used_resolvers("FTL already primary nameserver:"); // If no host name was found before, try again with system-configured @@ -311,7 +311,7 @@ char *resolveHostname(const char *addr) // No hostname found (empty PTR) hostname = strdup(""); - if(config.debug & DEBUG_RESOLVER) + if(config.debug.resolver.v.b) log_debug(DEBUG_RESOLVER, " ---> \"\" (not found externally: %s)", gai_strerror(ret)); } } @@ -350,7 +350,7 @@ static size_t resolveAndAddHostname(size_t ippos, size_t oldnamepos) // If no hostname was found, try to obtain hostname from the network table // This may be disabled due to a user setting - if(strlen(newname) == 0 && config.resolver.networkNames) + if(strlen(newname) == 0 && config.resolver.networkNames.v.b) { free(newname); newname = getNameFromIP(NULL, ipaddr); @@ -460,18 +460,18 @@ static void resolveClients(const bool onlynew, const bool force_refreshing) // 3. We should only refresh unknown hostnames, but leave // existing ones as they are if(onlynew == false && - (config.resolver.refreshNames == REFRESH_NONE || - (config.resolver.refreshNames == REFRESH_IPV4_ONLY && IPv6) || - (config.resolver.refreshNames == REFRESH_UNKNOWN && oldnamepos != 0))) + (config.resolver.refreshNames.v.refresh_hostnames == REFRESH_NONE || + (config.resolver.refreshNames.v.refresh_hostnames == REFRESH_IPV4_ONLY && IPv6) || + (config.resolver.refreshNames.v.refresh_hostnames == REFRESH_UNKNOWN && oldnamepos != 0))) { - if(config.debug & DEBUG_RESOLVER) + if(config.debug.resolver.v.b) { const char *reason = "N/A"; - if(config.resolver.refreshNames == REFRESH_NONE) + if(config.resolver.refreshNames.v.refresh_hostnames == REFRESH_NONE) reason = "Not refreshing any hostnames"; - else if(config.resolver.refreshNames == REFRESH_IPV4_ONLY) + else if(config.resolver.refreshNames.v.refresh_hostnames == REFRESH_IPV4_ONLY) reason = "Only refreshing IPv4 names"; - else if(config.resolver.refreshNames == REFRESH_UNKNOWN) + else if(config.resolver.refreshNames.v.refresh_hostnames == REFRESH_UNKNOWN) reason = "Looking only for unknown hostnames"; lock_shm(); @@ -559,7 +559,7 @@ static void resolveUpstreams(const bool onlynew) if(onlynew && !newflag) { skipped++; - if(config.debug & DEBUG_RESOLVER) + if(config.debug.resolver.v.b) { lock_shm(); log_debug(DEBUG_RESOLVER, "Upstream %s -> \"%s\" already known", getstr(ippos), getstr(oldnamepos)); diff --git a/src/setupVars.c b/src/setupVars.c index f916d072..b3b8161a 100644 --- a/src/setupVars.c +++ b/src/setupVars.c @@ -19,7 +19,7 @@ char ** setupVarsArray = NULL; void check_setupVarsconf(void) { FILE *setupVarsfp; - if((setupVarsfp = fopen(config.files.setupVars, "r")) == NULL) + if((setupVarsfp = fopen(config.files.setupVars.v.s, "r")) == NULL) { log_warn("Opening of setupVars.conf failed: %s Make sure it exists and is readable", strerror(errno)); @@ -70,7 +70,7 @@ size_t linebuffersize = 0; char * read_setupVarsconf(const char *key) { FILE *setupVarsfp; - if((setupVarsfp = fopen(config.files.setupVars, "r")) == NULL) + if((setupVarsfp = fopen(config.files.setupVars.v.s, "r")) == NULL) { log_warn("Reading setupVars.conf failed: %s", strerror(errno)); return NULL; diff --git a/src/shmem.c b/src/shmem.c index 8e7f1755..1eb367e2 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -455,7 +455,7 @@ 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) { - if(config.debug & DEBUG_LOCKS && !is_our_lock()) + 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); @@ -641,12 +641,11 @@ void destroy_shmem(void) static SharedMemory create_shm(const char *name, const size_t size) { char df[64] = { 0 }; - const int percentage = get_dev_shm_usage(df); - if(config.debug & DEBUG_SHMEM || (config.misc.check.shmem > 0 && percentage > config.misc.check.shmem)) - { + const unsigned int percentage = get_dev_shm_usage(df); + if(config.debug.shmem.v.b || (config.misc.check.shmem.v.ui > 0 && percentage > config.misc.check.shmem.v.ui)) log_info("Creating shared memory with name \"%s\" and size %zu (%s)", name, size, df); - } - if(config.misc.check.shmem > 0 && percentage > config.misc.check.shmem) + + if(config.misc.check.shmem.v.ui > 0 && percentage > config.misc.check.shmem.v.ui) log_resource_shortage(-1.0, 0, percentage, -1, SHMEM_PATH, df); SharedMemory sharedMemory = { @@ -776,7 +775,7 @@ static bool realloc_shm(SharedMemory *sharedMemory, const size_t size1, const si // Log that we are doing something here char df[64] = { 0 }; - const int percentage = get_dev_shm_usage(df); + const unsigned int percentage = get_dev_shm_usage(df); // Log output if(resize) @@ -786,7 +785,7 @@ static bool realloc_shm(SharedMemory *sharedMemory, const size_t size1, const si log_debug(DEBUG_SHMEM, "Remapping \"%s\" from %zu to (%zu * %zu) == %zu", sharedMemory->name, sharedMemory->size, size1, size2, size); - if(config.misc.check.shmem > 0 && percentage > config.misc.check.shmem) + if(config.misc.check.shmem.v.ui > 0 && percentage > config.misc.check.shmem.v.ui) log_resource_shortage(-1.0, 0, percentage, -1, SHMEM_PATH, df); // Resize shard memory object if requested @@ -1046,7 +1045,7 @@ static inline bool check_range(int ID, int MAXID, const char* type, const char * // Check bounds if(ID < 0 || ID > MAXID) { - if(config.debug) + if(debug_any) { log_err("Trying to access %s ID %i, but maximum is %i", type, ID, MAXID); log_err("found in %s() (%s:%i)", func, short_path(file), line); @@ -1063,7 +1062,7 @@ static inline bool check_magic(int ID, bool checkMagic, unsigned char magic, con // Check magic only if requested (skipped for new entries which are uninitialized) if(checkMagic && magic != MAGICBYTE) { - if(config.debug) + if(debug_any) { log_err("Trying to access %s ID %i, but magic byte is %x", type, ID, magic); log_err("found in %s() (%s:%i)", func, short_path(file), line); @@ -1082,9 +1081,9 @@ queriesData* _getQuery(int queryID, bool checkMagic, int line, const char *func, return NULL; // We are not in a locked situation, return a NULL pointer - if(config.debug & DEBUG_LOCKS && !is_our_lock()) + if(config.debug.locks.v.b && !is_our_lock()) { - if(config.debug) + if(debug_any) { log_err("Tried to obtain query pointer without lock in %s() (%s:%i)!", func, short_path(file), line); @@ -1107,9 +1106,9 @@ clientsData* _getClient(int clientID, bool checkMagic, int line, const char *fun return NULL; // We are not in a locked situation, return a NULL pointer - if(config.debug & DEBUG_LOCKS && !is_our_lock()) + if(config.debug.locks.v.b && !is_our_lock()) { - if(config.debug) + if(debug_any) { log_err("Tried to obtain client pointer without lock in %s() (%s:%i)!", func, short_path(file), line); @@ -1132,9 +1131,9 @@ domainsData* _getDomain(int domainID, bool checkMagic, int line, const char *fun return NULL; // We are not in a locked situation, return a NULL pointer - if(config.debug & DEBUG_LOCKS && !is_our_lock()) + if(config.debug.locks.v.b && !is_our_lock()) { - if(config.debug) + if(debug_any) { log_err("Tried to obtain domain pointer without lock in %s() (%s:%i)!", func, short_path(file), line); @@ -1157,9 +1156,9 @@ upstreamsData* _getUpstream(int upstreamID, bool checkMagic, int line, const cha return NULL; // We are not in a locked situation, return a NULL pointer - if(config.debug & DEBUG_LOCKS && !is_our_lock()) + if(config.debug.locks.v.b && !is_our_lock()) { - if(config.debug) + if(debug_any) { log_err("Tried to obtain upstream pointer without lock in %s() (%s:%i)!", func, short_path(file), line); @@ -1182,9 +1181,9 @@ DNSCacheData* _getDNSCache(int cacheID, bool checkMagic, int line, const char *f return NULL; // We are not in a locked situation, return a NULL pointer - if(config.debug & DEBUG_LOCKS && !is_our_lock()) + if(config.debug.locks.v.b && !is_our_lock()) { - if(config.debug) + if(debug_any) { log_err("Tried to obtain cache pointer without lock in %s() (%s:%i)!", func, short_path(file), line); diff --git a/src/signals.c b/src/signals.c index 2a55743d..bc15e3fe 100644 --- a/src/signals.c +++ b/src/signals.c @@ -66,7 +66,7 @@ static void print_addr2line(const char *symbol, const void *address, const int j snprintf(addr2line_cmd, sizeof(addr2line_cmd), "addr2line %p -e %.*s", addr, p, symbol); FILE *addr2line = NULL; char linebuffer[512]; - if(config.misc.addr2line && + if(config.misc.addr2line.v.b && (addr2line = popen(addr2line_cmd, "r")) != NULL && fgets(linebuffer, sizeof(linebuffer), addr2line) != NULL) { diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 60b9dfe0..ce724033 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -25,7 +25,7 @@ char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN] = { 0 }; // tyoically contain a JSON explorer const char* json_formatter(const cJSON *object) { - if(config.http.prettyJSON) + if(config.http.prettyJSON.v.b) { /* Examplary output: { diff --git a/src/webserver/json_macros.h b/src/webserver/json_macros.h index 2694d7da..aaf02f4b 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -81,8 +81,8 @@ } #define JSON_ADD_BOOL_TO_OBJECT(object, key, val) {\ - const cJSON_bool value = val; \ - cJSON *bool_item = cJSON_CreateBool(value); \ + const cJSON_bool var_val = val; \ + cJSON *bool_item = cJSON_CreateBool(var_val); \ if(bool_item == NULL) \ { \ cJSON_Delete(object); \ @@ -106,8 +106,8 @@ } #define JSON_ADD_BOOL_TO_ARRAY(object, val){ \ - const cJSON_bool value = val; \ - cJSON *bool_item = cJSON_CreateBool(value); \ + const cJSON_bool var_val = val; \ + cJSON *bool_item = cJSON_CreateBool(var_val); \ cJSON_AddItemToArray(object, bool_item); \ } diff --git a/src/webserver/ph7.c b/src/webserver/ph7.c index 47f1d089..4de86681 100644 --- a/src/webserver/ph7.c +++ b/src/webserver/ph7.c @@ -47,7 +47,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) const char *local_uri = req_info->local_uri_raw + 1u; // Build full path of PHP script on our machine - const size_t webroot_len = strlen(config.http.paths.webroot); + const size_t webroot_len = strlen(config.http.paths.webroot.v.s); const size_t local_uri_len = strlen(local_uri); // +1 to skip the initial '/' size_t buffer_len = webroot_len + local_uri_len + 2; @@ -60,7 +60,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) } char full_path[buffer_len]; - memcpy(full_path, config.http.paths.webroot, webroot_len); + memcpy(full_path, config.http.paths.webroot.v.s, webroot_len); full_path[webroot_len] = '/'; memcpy(full_path + webroot_len + 1u, local_uri, local_uri_len); full_path[webroot_len + local_uri_len + 1u] = '\0'; @@ -101,7 +101,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" "PHP compilation error, check %s for further details.", - config.files.log); + config.files.log.v.s); /* Extract error log */ const char *zErrLog = NULL; @@ -198,11 +198,11 @@ void init_ph7(void) // Prepare include paths // /var/www/html/admin (may be different due to user configuration) - const size_t webroot_len = strlen(config.http.paths.webroot); - const size_t webhome_len = strlen(config.http.paths.webhome); + const size_t webroot_len = strlen(config.http.paths.webroot.v.s); + const size_t webhome_len = strlen(config.http.paths.webhome.v.s); webroot_with_home = calloc(webroot_len + webhome_len + 1u, sizeof(char)); - strcpy(webroot_with_home, config.http.paths.webroot); - strcpy(webroot_with_home + webroot_len, config.http.paths.webhome); + strcpy(webroot_with_home, config.http.paths.webroot.v.s); + strcpy(webroot_with_home + webroot_len, config.http.paths.webhome.v.s); webroot_with_home[webroot_len + webhome_len] = '\0'; // /var/www/html/admin/scripts/pi-hole/php (may be different due to user configuration) diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index 0495a97d..0e1df53a 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -59,7 +59,7 @@ static int redirect_root_handler(struct mg_connection *conn, void *input) } // API debug logging - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) { log_debug(DEBUG_API, "Host header: \"%s\", extracted host: \"%.*s\"", host, (int)host_len, host); @@ -71,9 +71,9 @@ static int redirect_root_handler(struct mg_connection *conn, void *input) } // 308 Permanent Redirect from http://pi.hole -> http://pi.hole/admin - if(host != NULL && strncmp(host, config.http.domain, host_len) == 0) + if(host != NULL && strncmp(host, config.http.domain.v.s, host_len) == 0) { - mg_send_http_redirect(conn, config.http.paths.webhome, 308); + mg_send_http_redirect(conn, config.http.paths.webhome.v.s, 308); return 1; } @@ -90,7 +90,7 @@ static int log_http_message(const struct mg_connection *conn, const char *messag static int log_http_access(const struct mg_connection *conn, const char *message) { // Only log when in API debugging mode - if(config.debug & DEBUG_API) + if(config.debug.api.v.b) logg_web(HTTP_INFO, "ACCESS: %s", message); return 1; @@ -98,7 +98,7 @@ static int log_http_access(const struct mg_connection *conn, const char *message void http_init(void) { - logg_web(HTTP_INFO, "Initializing HTTP server on port %s", config.http.port); + logg_web(HTTP_INFO, "Initializing HTTP server on port %s", config.http.port.v.s); /* Initialize the library */ unsigned int features = MG_FEATURES_FILES | @@ -134,12 +134,12 @@ void http_init(void) // send no referrer information. // The latter four headers are set as expected by https://securityheaders.io const char *options[] = { - "document_root", config.http.paths.webroot, - "listening_ports", config.http.port, + "document_root", config.http.paths.webroot.v.s, + "listening_ports", config.http.port.v.s, "decode_url", "yes", "enable_directory_listing", "no", "num_threads", "16", - "access_control_list", config.http.acl, + "access_control_list", config.http.acl.v.s, "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';\r\n" "X-Frame-Options: SAMEORIGIN\r\n" "X-Xss-Protection: 1; mode=block\r\n" @@ -161,7 +161,7 @@ void http_init(void) { log_err("Start of webserver failed!. Web interface will not be available!"); log_err(" Check webroot %s and listening ports %s", - config.http.paths.webroot, config.http.port); + config.http.paths.webroot.v.s, config.http.port.v.s); return; } diff --git a/test/api/checkAPI.py b/test/api/checkAPI.py index 70d64a7f..0a10c88f 100644 --- a/test/api/checkAPI.py +++ b/test/api/checkAPI.py @@ -45,7 +45,10 @@ if __name__ == "__main__": if errs[1] == 0: print(" No missing endpoints\n") - print("Verifying endpoints...") + # Check if endpoints that are in both FTL and OpenAPI specs match + # and have the same response format. Also verify that the examples + # matches the OpenAPI specs. + print("Verifying the individual endpoint properties...") for path in openapi.endpoints["get"]: verifyer = ResponseVerifyer(ftl, openapi) errors = verifyer.verify_endpoint(path) @@ -70,6 +73,7 @@ if __name__ == "__main__": if sum(errs) > 0: exit(1) - # If there are no missing endpoints, exit with success - print("No missing endpoints") + # If there are no errors, exit with success + # (this is important for the CI) + print("Everything okay!") exit(0) diff --git a/test/api/libs/FTLAPI.py b/test/api/libs/FTLAPI.py index 4d10c4b8..e576f20f 100644 --- a/test/api/libs/FTLAPI.py +++ b/test/api/libs/FTLAPI.py @@ -12,12 +12,14 @@ import urllib.request import json +# Class to query the FTL API class FTLAPI(): def __init__(self, api_url: str): self.api_url = api_url self.endpoints = [] self.errors = [] + # Query the FTL API and return the response def getFTLresponse(self, uri: str, params: list[str] = []): self.errors = [] try: @@ -32,6 +34,7 @@ class FTLAPI(): # Query the endpoints from FTL for comparison with the OpenAPI specs def get_endpoints(self): try: + # Get all endpoints from FTL and sort them for comparison for endpoint in self.getFTLresponse("/api/ftl/endpoints")["endpoints"]: self.endpoints.append(endpoint["uri"] + endpoint["parameters"]) self.endpoints = sorted(self.endpoints) diff --git a/test/api/libs/openAPI.py b/test/api/libs/openAPI.py index 643bdfde..d42be393 100644 --- a/test/api/libs/openAPI.py +++ b/test/api/libs/openAPI.py @@ -18,7 +18,6 @@ class openApi(): METHODS = ["get", "post", "put", "delete"] def __init__(self, base_path: str, api_root: str = "/api") -> None: - # Store arguments self.base_path = base_path self.api_root = api_root @@ -31,7 +30,7 @@ class openApi(): # Cache for already read files self.yaml_cache = {} - + # Read YAML file and add content to a cache def read_yaml_maybe_cache(self, file: str) -> dict: # Check if we have already read + parsed this file if file not in self.yaml_cache: @@ -39,6 +38,7 @@ class openApi(): try: with open(file, "r") as stream: try: + # Parse the file self.yaml_cache[file] = yaml.safe_load(stream) except Exception as e: print("Exception when trying to parse " + file + ": " + str(e)) @@ -81,9 +81,11 @@ class openApi(): def recurseRef(self, dict_in: dict, dict_key: str): # Loop over all items in this dict for a in dict_in.keys(): + # Create the next dict key next_dict_key = dict_key + "/" + a if len(dict_key) > 0 else a # If the item is a dict, we check if it is a reference if isinstance(dict_in[a], dict): + # Check if this is a reference if "$ref" in dict_in[a]: # Yes, this is a reference, replace it with the actual content and ... dict_in[a] = self.resolveSingleReference(dict_in[a]["$ref"]) @@ -94,7 +96,9 @@ class openApi(): self.recurseRef(dict_in[a], next_dict_key) # If it is not a dict, it may be a list with references (e.g., OpenAPI's "allOf/anyOf") elif isinstance(dict_in[a], list): + # Loop over all items in the list for i in range(len(dict_in[a])): + # If the item is a dict, we check if it is a reference if isinstance(dict_in[a][i], dict): if "$ref" in dict_in[a][i]: # Yes, this is a reference, replace it with the actual content and ... diff --git a/test/api/libs/responseVerifyer.py b/test/api/libs/responseVerifyer.py index aefde49a..a843d8c7 100644 --- a/test/api/libs/responseVerifyer.py +++ b/test/api/libs/responseVerifyer.py @@ -13,6 +13,7 @@ from types import NoneType from libs.openAPI import openApi import urllib.request, urllib.parse from libs.FTLAPI import FTLAPI +from collections.abc import MutableMapping class ResponseVerifyer(): @@ -24,24 +25,44 @@ class ResponseVerifyer(): self.openapi = openapi self.errors = [] - def verify_endpoint(self, endpoint: str): + def flatten_dict(self, d: MutableMapping, parent_key: str = '', sep: str ='.') -> MutableMapping: + items = [] + # Iterate over all items in the dictionary + for k, v in d.items(): + # Create a new key by appending the current key to the parent key + new_key = parent_key + sep + k if parent_key else k + # If the value is a dictionary, recursively flatten it, otherwise + # simply add it to the list of items + if isinstance(v, MutableMapping): + items.extend(self.flatten_dict(v, new_key, sep=sep).items()) + else: + items.append((new_key, v)) + return dict(items) + + + def verify_endpoint(self, endpoint: str): + # If the endpoint starts with /api, remove this part (it is not + # part of the YAML specs) if endpoint.startswith("/api"): endpoint = endpoint[4:] method = 'get' rcode = '200' + # Check if the endpoint is defined in the API specs if endpoint not in self.openapi.paths: - self.errors.append("Endpoint " + endpoint + " not found in OpenAPI specs") + self.errors.append("Endpoint " + endpoint + " not found in the API specs") return self.errors + # Check if this endpoint + method are defined in the API specs if method not in self.openapi.paths[endpoint]: - self.errors.append("Method " + method + " not found in OpenAPI specs (" + endpoint + ")") + self.errors.append("Method " + method + " not found in the API specs") return self.errors # Get YAML response schema and examples (if applicable) jsonData = self.openapi.paths[endpoint][method]['responses'][str(rcode)]['content']['application/json'] YAMLresponseSchema = jsonData['schema'] YAMLresponseExamples = jsonData['examples'] if 'examples' in jsonData else None + # Prepare required parameters (if any) FTLparameters = [] if 'parameters' in self.openapi.paths[endpoint][method]: @@ -55,73 +76,127 @@ class ResponseVerifyer(): continue FTLparameters.append(param['name'] + "=" + urllib.parse.quote_plus(str(param['example']))) + # Get FTL response FTLresponse = self.ftl.getFTLresponse("/api" + endpoint, FTLparameters) if FTLresponse is None: return self.ftl.errors + self.YAMLresponse = {} + # Check if the response is an object. If so, we have to check it + # recursively if 'type' in YAMLresponseSchema and YAMLresponseSchema['type'] == 'object': + # Loop over all properties of the object for prop in YAMLresponseSchema['properties']: self.verify_property(YAMLresponseSchema['properties'], YAMLresponseExamples, FTLresponse, [prop]) + # Check if the response is a gather-all object. If so, we have + # to check all objects in the array individually elif 'allOf' in YAMLresponseSchema and len(YAMLresponseSchema['allOf']) > 0: for i in range(len(YAMLresponseSchema['allOf'])): for prop in YAMLresponseSchema['allOf'][i]['properties']: self.verify_property(YAMLresponseSchema['allOf'][i]['properties'], YAMLresponseExamples, FTLresponse, [prop]) + + # If neither of the above is true, thie definition is invalid else: self.errors.append("Top-level response should be either an object or a non-empty allOf/anyOf/oneOf") + # Finally, we check if there are extra properties in the FTL response + # that are not defined in the API specs + + # Flatten the FTL response + FTLflat = self.flatten_dict(FTLresponse) + YAMLflat = self.YAMLresponse + + # Check for properties in FTL that are not in the API specs + for property in FTLflat.keys(): + if property not in YAMLflat.keys(): + self.errors.append("Property '" + property + "' missing in the API specs") + + # Return all errors return self.errors + # Verify a single property's type def verify_type(self, prop_type: any, yaml_type: str, yaml_nullable: bool): # None is an acceptable reply when this is specified in the API specs if prop_type == NoneType and yaml_nullable: return True + # Check if the type is correct using the YAML_TYPES translation table return prop_type in self.YAML_TYPES[yaml_type] + # Verify a single property def verify_property(self, YAMLprops: dict, YAMLexamples: dict, FTLprops: dict, props: list): all_okay = True + # Build flat path of this property + flat_path = ".".join(props) + + # Check if the property is defined in the API specs if props[-1] not in YAMLprops: - self.errors.append("Property " + props[-1] + " missing in API specs") + self.errors.append("Property '" + flat_path + "' missing in the API specs") return False YAMLprop = YAMLprops[props[-1]] + + # Check if the property is defined in the FTL response if props[-1] not in FTLprops: - self.errors.append("Property " + props[-1] + " missing in FTL's response") + self.errors.append("Property '" + flat_path + "' missing in FTL's response") return False FTLprop = FTLprops[props[-1]] # If this is another object, we have to dive deeper if YAMLprop['type'] == 'object': + # Loop over all properties of the object ... for prop in YAMLprop['properties']: + # ... and check them recursively if not self.verify_property(YAMLprop['properties'], YAMLexamples, FTLprop, props + [prop]): all_okay = False else: # Check this property - full_path = " => ".join(props) + + # Get type of this property using the YAML_TYPES translation table yaml_type = YAMLprop['type'] + + # Check if this property is nullable (can be None even + # if not defined as string, integer, etc.) yaml_nullable = 'nullable' in YAMLprop and YAMLprop['nullable'] == True + # Add this property to the YAML response + self.YAMLresponse[flat_path] = [] + # Check type of YAML example (if defined) if 'example' in YAMLprop: example_type = type(YAMLprop['example']) + # Check if the type of the example matches the + # type we defined in the API specs + self.YAMLresponse[flat_path].append(YAMLprop['example']) if not self.verify_type(example_type, yaml_type, yaml_nullable): - self.errors.append(f"API example ({str(example_type)}) does not match defined type ({yaml_type}) in {full_path}") + self.errors.append(f"API example ({str(example_type)}) does not match defined type ({yaml_type}) in {flat_path}") return False + + # Check type of externally defined YAML examples (next to schema) elif YAMLexamples is not None: for t in YAMLexamples: + if 'value' not in YAMLexamples[t]: + self.errors.append(f"Example {flat_path} does not have a 'value' property") + return False example = YAMLexamples[t]['value'] + # Dive into the example to get to the property we want for p in props: + if p not in example: + self.errors.append(f"Example {flat_path} does not have an '{p}' item") + return False example = example[p] + # Check if the type of the example matches the type we defined in the API specs example_type = type(example) + self.YAMLresponse[flat_path].append(example) if not self.verify_type(example_type, yaml_type, yaml_nullable): - self.errors.append(f"API example ({str(example_type)}) does not match defined type ({yaml_type}) in {full_path}") + self.errors.append(f"API example ({str(example_type)}) does not match defined type ({yaml_type}) in {flat_path}") return False # Compare type of FTL's reply against what we defined in the API specs ftl_type = type(FTLprop) if not self.verify_type(ftl_type, yaml_type, yaml_nullable): - self.errors.append(f"FTL's reply ({str(ftl_type)}) does not match defined type ({yaml_type}) in {full_path}") + self.errors.append(f"FTL's reply ({str(ftl_type)}) does not match defined type ({yaml_type}) in {flat_path}") return False return all_okay diff --git a/test/test_suite.bats b/test/test_suite.bats index e877078c..3f830120 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -1216,7 +1216,7 @@ } @test "API validation" { - run python3 -m pytest -v test/api/checkAPI.py + run python3 test/api/checkAPI.py printf "%s\n" "${lines[@]}" [[ $status == 0 ]] }