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 ]] }