From 70127edd52af2dbf7bc6d8e739b1657ff58a04da Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 11 Jan 2023 20:14:46 +0100 Subject: [PATCH] Add config.dnsmasq and routines to write dnsmasq config files Signed-off-by: DL6ER --- src/api/api.c | 14 ++ src/api/config.c | 41 +++- src/api/docs/content/specs/config.yaml | 90 ++++++++- src/config/CMakeLists.txt | 2 + src/config/config.c | 188 ++++++++++++++++-- src/config/config.h | 50 ++++- src/config/dnsmasq_config.c | 254 +++++++++++++++++++++++++ src/config/dnsmasq_config.h | 15 ++ src/config/legacy_reader.c | 24 +-- src/config/toml_helper.c | 109 ++++++++++- src/config/toml_helper.h | 1 + src/config/toml_reader.c | 6 +- src/config/toml_writer.c | 6 +- src/datastructure.c | 31 +++ src/datastructure.h | 2 + src/dnsmasq/dnsmasq.c | 8 +- src/dnsmasq/dnsmasq.h | 1 + src/dnsmasq_interface.c | 4 +- src/enums.h | 7 + src/gc.c | 2 +- src/log.c | 12 +- src/main.c | 6 +- src/setupVars.c | 214 ++++++++++++++------- src/setupVars.h | 2 +- src/signals.c | 2 +- src/signals.h | 1 + src/webserver/http-common.h | 3 + src/webserver/ph7.c | 2 +- 28 files changed, 953 insertions(+), 144 deletions(-) create mode 100644 src/config/dnsmasq_config.c create mode 100644 src/config/dnsmasq_config.h diff --git a/src/api/api.c b/src/api/api.c index 8b1ca7b4..ab961e0b 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -15,6 +15,11 @@ #include "webserver/json_macros.h" #include "api.h" #include "shmem.h" +// exit_code +#include "signals.h" + +// defined in dnsmasq/dnsmasq.h +extern volatile char FTL_terminate; static int api_ftl_endpoints(struct ftl_conn *api); @@ -75,6 +80,7 @@ int api_handler(struct mg_connection *conn, void *ignored) NULL, NULL, { false, 0u, NULL, NULL }, + { false }, { false, false } }; @@ -140,6 +146,14 @@ int api_handler(struct mg_connection *conn, void *ignored) // Free raw payload bytes (always allocated) free(api.payload.raw); + // Restart FTL if requested + if(api.ftl.restart) + { + // Trigger an automatic restart by systemd + exit_code = 22; + FTL_terminate = 1; + } + return ret; } diff --git a/src/api/config.c b/src/api/config.c index 4865933f..fda9cddd 100644 --- a/src/api/config.c +++ b/src/api/config.c @@ -40,6 +40,8 @@ #include // writeFTLtoml() #include "config/toml_writer.h" +// write_dnsmasq_config() +#include "config/dnsmasq_config.h" // The following functions are used to create the JSON output // of the /api/config endpoint. @@ -92,6 +94,8 @@ static cJSON *addJSONvalue(const enum conf_type conf_type, union conf_value *val 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_ENUM_LISTENING_MODE: + return cJSON_CreateStringReference(get_listening_mode_str(val->listening_mode)); case CONF_STRUCT_IN_ADDR: { char addr4[INET_ADDRSTRLEN] = { 0 }; @@ -204,7 +208,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem) free(conf_item->v.s); // Set item conf_item->v.s = strdup(elem->valuestring); - log_debug(DEBUG_CONFIG, "Set %s to \"%s\"", conf_item->k, elem->valuestring); + log_debug(DEBUG_CONFIG, "Set %s to \"%s\"", conf_item->k, conf_item->v.s); break; } case CONF_ENUM_PTR_TYPE: @@ -217,7 +221,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem) return "invalid option"; // Set item conf_item->v.ptr_type = ptr_type; - log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, ptr_type); + log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.ptr_type); break; } case CONF_ENUM_BUSY_TYPE: @@ -230,7 +234,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem) return "invalid option"; // Set item conf_item->v.busy_reply = busy_reply; - log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, busy_reply); + log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.busy_reply); break; } case CONF_ENUM_BLOCKING_MODE: @@ -243,7 +247,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem) return "invalid option"; // Set item conf_item->v.blocking_mode = blocking_mode; - log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, blocking_mode); + log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.blocking_mode); break; } case CONF_ENUM_REFRESH_HOSTNAMES: @@ -256,7 +260,20 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem) return "invalid option"; // Set item conf_item->v.refresh_hostnames = refresh_hostnames; - log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, refresh_hostnames); + log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.refresh_hostnames ); + break; + } + case CONF_ENUM_LISTENING_MODE: + { + // Check type + if(!cJSON_IsString(elem)) + return "not of type string"; + const int listening_mode = get_listening_mode_val(elem->valuestring); + if(listening_mode == -1) + return "invalid option"; + // Set item + conf_item->v.listening_mode = listening_mode; + log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.listening_mode); break; } case CONF_ENUM_PRIVACY_LEVEL: @@ -422,6 +439,7 @@ static int api_config_patch(struct ftl_conn *api) } // Read all known config items + bool dnsmasq_changed = false; for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++) { // Get pointer to memory location of this conf_item @@ -445,12 +463,25 @@ static int api_config_patch(struct ftl_conn *api) // Try to set value and report error on failure const char *response = getJSONvalue(conf_item, elem); if(response != NULL) + { log_err("/api/config: %s invalid: %s", conf_item->k, response); + continue; + } + + // If we reach this point, a valid setting was found and changed + // Check if this item requires a config-rewrite + restart of dnsmasq + if(conf_item->restart_dnsmasq) + dnsmasq_changed = true; } // Store changed configuration to disk writeFTLtoml(); + // Request restart of FTL + if(dnsmasq_changed) + write_dnsmasq_config(true); + //api->ftl.restart = true; + // Return full config after possible changes above return api_config_get(api); } diff --git a/src/api/docs/content/specs/config.yaml b/src/api/docs/content/specs/config.yaml index c713a142..96c60b9a 100644 --- a/src/api/docs/content/specs/config.yaml +++ b/src/api/docs/content/specs/config.yaml @@ -141,6 +141,59 @@ components: type: integer interval: type: integer + dnsmasq: + type: object + properties: + upstreams: + type: array + items: + type: string + domain: + type: string + domain_needed: + type: boolean + expand_hosts: + type: boolean + bogus_priv: + type: boolean + dnssec: + type: boolean + interface: + type: string + host_record: + type: string + listening_mode: + type: string + cache_size: + type: integer + rev_server: + type: object + properties: + active: + type: boolean + cidr: + type: string + target: + type: string + domain: + type: string + dhcp: + type: object + properties: + active: + type: boolean + start: + type: string + end: + type: string + router: + type: string + leasetime: + type: string + ipv6: + type: boolean + rapid_commit: + type: boolean resolver: type: object properties: @@ -217,8 +270,6 @@ components: files: type: object properties: - log: - type: string pid: type: string database: @@ -233,6 +284,13 @@ components: type: string ph7_error: type: string + log: + type: object + properties: + ftl: + type: string + dnsmasq: + type: string misc: type: object properties: @@ -343,6 +401,30 @@ components: count: 0 interval: 0 port: 53 + dnsmasq: + upstreams: [ "127.0.0.1#5353", "8.8.8.8" ] + domain: "lan" + domain_needed: true + expand_hosts: true + bogus_priv: true + dnssec: true + interface: "eth0" + host_record: "" + listening_mode: "local" + cache_size: 10000 + rev_server: + active: false + cidr: "192.168.0.0/24" + target: "192.168.0.1" + domain: "lan" + dhcp: + active: false + start: "192.168.0.10" + end: "192.168.0.250" + router: "192.168.0.1" + leasetime: "24h" + ipv6: true + rapid_commit: true resolver: resolveIPv4: true resolveIPv6: true @@ -375,7 +457,6 @@ components: boxed: true theme: "default-darker" files: - log: "/var/log/pihole/FTL.log" pid: "/run/pihole-FTL.pid" database: "/etc/pihole/pihole-FTL.db" gravity: "/etc/pihole/gravity.db" @@ -383,6 +464,9 @@ components: setupVars: "/etc/pihole/setupVars.conf" http_info: "/var/log/pihole/HTTP_info.log" ph7_error: "/var/log/pihole/PH7.log" + log: + ftl: "/var/log/pihole/FTL.log" + dnsmasq: "/var/log/pihole/pihole.log" misc: nice: -10 delay_startup: 10 diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt index e4771269..3c4a5ac5 100644 --- a/src/config/CMakeLists.txt +++ b/src/config/CMakeLists.txt @@ -11,6 +11,8 @@ set(sources config.c config.h + dnsmasq_config.c + dnsmasq_config.h legacy_reader.c legacy_reader.h toml_writer.c diff --git a/src/config/config.c b/src/config/config.c index 5f305e30..db9319bf 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -8,17 +8,19 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "../FTL.h" -#include "config.h" -#include "toml_reader.h" -#include "toml_writer.h" -#include "../setupVars.h" -#include "../log.h" -#include "../log.h" +#include "FTL.h" +#include "config/config.h" +#include "config/toml_reader.h" +#include "config/toml_writer.h" +#include "setupVars.h" +#include "log.h" +#include "log.h" // readFTLlegacy() #include "legacy_reader.h" // file_exists() -#include "../files.h" +#include "files.h" +// write_dnsmasq_config() +#include "config/dnsmasq_config.h" struct config config = { 0 }; int dns_port = -1; @@ -265,6 +267,148 @@ void initConfig(void) memset(&config.dns.reply.blocking.v6.d.in6_addr, 0, sizeof(struct in6_addr)); + // struct dnsmasq + config.dnsmasq.upstreams.k = "dnsmasq.upstreams"; + config.dnsmasq.upstreams.h = "Array of upstream DNS servers used by Pi-hole"; + config.dnsmasq.upstreams.a = "array of IP addresses and/or hostnames, optionally with a port, e.g. [ \"8.8.8.8\", \"127.0.0.1#5353\", \"docker-resolver\" ]"; + config.dnsmasq.upstreams.t = CONF_JSON_STRING_ARRAY; + config.dnsmasq.upstreams.d.json = cJSON_CreateArray(); + config.dnsmasq.upstreams.restart_dnsmasq = true; + + config.dnsmasq.domain.k = "dnsmasq.domain"; + config.dnsmasq.domain.h = "The DNS domain used by your Pi-hole"; + config.dnsmasq.domain.a = ""; + config.dnsmasq.domain.t = CONF_STRING; + config.dnsmasq.domain.d.s = (char*)"lan"; + config.dnsmasq.domain.restart_dnsmasq = true; + + config.dnsmasq.domain_needed.k = "dnsmasq.domain_needed"; + config.dnsmasq.domain_needed.h = "If set, A and AAAA queries for plain names, without dots or domain parts, are never forwarded to upstream nameservers"; + config.dnsmasq.domain_needed.t = CONF_BOOL; + config.dnsmasq.domain_needed.d.b = false; + config.dnsmasq.domain_needed.restart_dnsmasq = true; + + config.dnsmasq.expand_hosts.k = "dnsmasq.expand_hosts"; + config.dnsmasq.expand_hosts.h = "If set, the domain is added to simple names (without a period) in /etc/hosts in the same way as for DHCP-derived names"; + config.dnsmasq.expand_hosts.t = CONF_BOOL; + config.dnsmasq.expand_hosts.d.b = false; + config.dnsmasq.expand_hosts.restart_dnsmasq = true; + + config.dnsmasq.bogus_priv.k = "dnsmasq.bogus_priv"; + config.dnsmasq.bogus_priv.h = "Should all reverse lookups for private IP ranges (i.e., 192.168.x.y, etc) which are not found in /etc/hosts or the DHCP leases file be answered with \"no such domain\" rather than being forwarded upstream?"; + config.dnsmasq.bogus_priv.t = CONF_BOOL; + config.dnsmasq.bogus_priv.d.b = true; + config.dnsmasq.bogus_priv.restart_dnsmasq = true; + + config.dnsmasq.dnssec.k = "dnsmasq.dnssec"; + config.dnsmasq.dnssec.h = "Validate DNS replies and cache DNSSEC data"; + config.dnsmasq.dnssec.t = CONF_BOOL; + config.dnsmasq.dnssec.d.b = true; + config.dnsmasq.dnssec.restart_dnsmasq = true; + + config.dnsmasq.interface.k = "dnsmasq.interface"; + config.dnsmasq.interface.h = "Interface to use for DNS (see also dnsmasq.listening.mode) and DHCP (if enabled)"; + config.dnsmasq.interface.a = "a valid interface name"; + config.dnsmasq.interface.t = CONF_STRING; + config.dnsmasq.interface.d.s = (char*)""; + config.dnsmasq.interface.restart_dnsmasq = true; + + config.dnsmasq.host_record.k = "dnsmasq.host_record"; + config.dnsmasq.host_record.h = "Add A, AAAA and PTR records to the DNS. This adds one or more names to the DNS with associated IPv4 (A) and IPv6 (AAAA) records"; + config.dnsmasq.host_record.a = "[,....],[],[][,]"; + config.dnsmasq.host_record.t = CONF_STRING; + config.dnsmasq.host_record.d.s = (char*)""; + config.dnsmasq.host_record.restart_dnsmasq = true; + + config.dnsmasq.listening_mode.k = "dnsmasq.listening_mode"; + config.dnsmasq.listening_mode.h = "Pi-hole interface listening modes"; + config.dnsmasq.listening_mode.a = "[ \"LOCAL\", \"ALL\", \"SINGLE\", \"BIND\" ]"; + config.dnsmasq.listening_mode.t = CONF_ENUM_LISTENING_MODE; + config.dnsmasq.listening_mode.d.listening_mode = LISTEN_LOCAL; + config.dnsmasq.listening_mode.restart_dnsmasq = true; + + config.dnsmasq.cache_size.k = "dnsmasq.cache_size"; + config.dnsmasq.cache_size.h = "Cache size of the DNS server. Note that expiring cache entries naturally make room for new insertions over time. Setting this number too high will have an adverse effect as not only more space is needed, but also lookup speed gets degraded in the 100,000+ range"; + config.dnsmasq.cache_size.t = CONF_UINT; + config.dnsmasq.cache_size.d.ui = 10000u; + config.dnsmasq.cache_size.restart_dnsmasq = true; + + // sub-struct rev_server + config.dnsmasq.rev_server.active.k = "dnsmasq.rev_server.active"; + config.dnsmasq.rev_server.active.h = "Is the reverse server (former also called \"conditional forwarding\") feature enabled?"; + config.dnsmasq.rev_server.active.t = CONF_BOOL; + config.dnsmasq.rev_server.active.d.b = false; + config.dnsmasq.rev_server.active.restart_dnsmasq = true; + + config.dnsmasq.rev_server.cidr.k = "dnsmasq.rev_server.cidr"; + config.dnsmasq.rev_server.cidr.h = "Address range for the reverse server feature in CIDR notation. If the prefix length is omitted, either 32 (IPv4) or 128 (IPv6) are substitutet (exact address match). This is almost certainly not what you want here."; + config.dnsmasq.rev_server.cidr.a = "[/], e.g., \"192.168.0.0/24\" for the range 192.168.0.1 - 192.168.0.255"; + config.dnsmasq.rev_server.cidr.t = CONF_STRING; + config.dnsmasq.rev_server.cidr.d.s = (char*)""; + config.dnsmasq.rev_server.cidr.restart_dnsmasq = true; + + config.dnsmasq.rev_server.target.k = "dnsmasq.rev_server.target"; + config.dnsmasq.rev_server.target.h = "Target server tp be used for the reverse server feature"; + config.dnsmasq.rev_server.target.a = "[#], e.g., \"192.168.0.1\""; + config.dnsmasq.rev_server.target.t = CONF_STRING; + config.dnsmasq.rev_server.target.d.s = (char*)""; + config.dnsmasq.rev_server.target.restart_dnsmasq = true; + + config.dnsmasq.rev_server.domain.k = "dnsmasq.rev_server.domain"; + config.dnsmasq.rev_server.domain.h = "Domain used for the reverse server feature"; + config.dnsmasq.rev_server.domain.a = ", typically set to the same value as dnsmasq.domain"; + config.dnsmasq.rev_server.domain.t = CONF_STRING; + config.dnsmasq.rev_server.domain.d.s = (char*)""; + config.dnsmasq.rev_server.domain.restart_dnsmasq = true; + + // sub-struct dhcp + config.dnsmasq.dhcp.active.k = "dnsmasq.dhcp.active"; + config.dnsmasq.dhcp.active.h = "Is the embedded DHCP server enabled?"; + config.dnsmasq.dhcp.active.t = CONF_BOOL; + config.dnsmasq.dhcp.active.d.b = false; + config.dnsmasq.dhcp.active.restart_dnsmasq = true; + + config.dnsmasq.dhcp.start.k = "dnsmasq.dhcp.start"; + config.dnsmasq.dhcp.start.h = "Start address of the DHCP address pool"; + config.dnsmasq.dhcp.start.a = ", e.g., \"192.168.0.10\""; + config.dnsmasq.dhcp.start.t = CONF_STRING; + config.dnsmasq.dhcp.start.d.s = (char*)""; + config.dnsmasq.dhcp.start.restart_dnsmasq = true; + + config.dnsmasq.dhcp.end.k = "dnsmasq.dhcp.end"; + config.dnsmasq.dhcp.end.h = "End address of the DHCP address pool"; + config.dnsmasq.dhcp.end.a = ", e.g., \"192.168.0.250\""; + config.dnsmasq.dhcp.end.t = CONF_STRING; + config.dnsmasq.dhcp.end.d.s = (char*)""; + config.dnsmasq.dhcp.end.restart_dnsmasq = true; + + config.dnsmasq.dhcp.router.k = "dnsmasq.dhcp.router"; + config.dnsmasq.dhcp.router.h = "Address of the gateway to be used (typicaly the address of your router in a home installation)"; + config.dnsmasq.dhcp.router.a = ", e.g., \"192.168.0.1\""; + config.dnsmasq.dhcp.router.t = CONF_STRING; + config.dnsmasq.dhcp.router.d.s = (char*)""; + config.dnsmasq.dhcp.router.restart_dnsmasq = true; + + config.dnsmasq.dhcp.leasetime.k = "dnsmasq.dhcp.leasetime"; + config.dnsmasq.dhcp.leasetime.h = "If the lease time is given, then leases will be given for that length of time. If not given, the default lease time is one hour for IPv4 and one day for IPv6."; + config.dnsmasq.dhcp.leasetime.a = "The lease time can be in seconds, or minutes (e.g., \"45m\") or hours (e.g., \"1h\") or days (like \"2d\") or even weeks (\"1w\"). You may also use \"infinite\" as string but be aware of the drawbacks"; + config.dnsmasq.dhcp.leasetime.t = CONF_STRING; + config.dnsmasq.dhcp.leasetime.d.s = (char*)""; + config.dnsmasq.dhcp.leasetime.restart_dnsmasq = true; + + config.dnsmasq.dhcp.ipv6.k = "dnsmasq.dhcp.ipv6"; + config.dnsmasq.dhcp.ipv6.h = "Should Pi-hole make an attempt to also satisfy IPv6 address requests (be aware that IPv6 works a whole lot different than IPv4)"; + config.dnsmasq.dhcp.ipv6.t = CONF_BOOL; + config.dnsmasq.dhcp.ipv6.d.b = false; + config.dnsmasq.dhcp.ipv6.restart_dnsmasq = true; + + config.dnsmasq.dhcp.rapid_commit.k = "dnsmasq.dhcp.rapid_commit"; + config.dnsmasq.dhcp.rapid_commit.h = "Enable DHCPv4 Rapid Commit Option specified in RFC 4039. Should only be enabled if either the server is the only server for the subnet to avoid conflicts"; + config.dnsmasq.dhcp.rapid_commit.t = CONF_BOOL; + config.dnsmasq.dhcp.rapid_commit.d.b = false; + config.dnsmasq.dhcp.rapid_commit.restart_dnsmasq = true; + + // struct resolver config.resolver.resolveIPv6.k = "resolver.resolveIPv6"; config.resolver.resolveIPv6.h = "Should FTL try to resolve IPv6 addresses to hostnames?"; @@ -425,7 +569,6 @@ void initConfig(void) // struct files - // 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 = ""; @@ -468,6 +611,15 @@ void initConfig(void) config.files.ph7_error.t = CONF_STRING; config.files.ph7_error.d.s = (char*)"/var/log/pihole/PH7.log"; + // sub-struct files.log + // config.files.log.ftl is set in a separate function + + config.files.log.dnsmasq.k = "files.log.dnsmasq"; + config.files.log.dnsmasq.h = "The log file used by the embedded dnsmasq DNS server"; + config.files.log.dnsmasq.a = ""; + config.files.log.dnsmasq.t = CONF_STRING; + config.files.log.dnsmasq.d.s = (char*)"/var/log/pihole/pihole.log"; + // struct misc config.misc.nice.k = "misc.nice"; @@ -642,7 +794,7 @@ void initConfig(void) 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) + if(conf_item != &config.files.log.ftl) { if(conf_item->t == CONF_JSON_STRING_ARRAY) // JSON objects really need to be duplicated as the config @@ -683,7 +835,10 @@ void readFTLconf(const bool rewrite) // to ensure that all options are present and comments // about options deviating from the default are present if(rewrite) + { writeFTLtoml(); + write_dnsmasq_config(false); + } return; } @@ -711,6 +866,7 @@ void readFTLconf(const bool rewrite) // Initialize the TOML config file writeFTLtoml(); + write_dnsmasq_config(false); } bool getLogFilePath(void) @@ -719,12 +875,12 @@ bool getLogFilePath(void) 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; + config.files.log.ftl.k = "files.log.ftl"; + config.files.log.ftl.h = "The location of FTL's log file"; + config.files.log.ftl.a = ""; + config.files.log.ftl.t = CONF_STRING; + config.files.log.ftl.d.s = (char*)"/var/log/pihole/FTL.log"; + config.files.log.ftl.v.s = config.files.log.ftl.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 cc327e2e..f8340142 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -59,6 +59,7 @@ union conf_value { enum refresh_hostnames refresh_hostnames; // enum refresh_hostnames value enum privacy_level privacy_level; // enum privacy_level value enum debug_flag debug_flag; // enum debug_flag value + enum listening_mode listening_mode; // enum listening_mode value struct in_addr in_addr; // struct in_addr value struct in6_addr in6_addr; // struct in6_addr value cJSON *json; // cJSON * value @@ -78,6 +79,7 @@ enum conf_type { CONF_ENUM_BLOCKING_MODE, CONF_ENUM_REFRESH_HOSTNAMES, CONF_ENUM_PRIVACY_LEVEL, + CONF_ENUM_LISTENING_MODE, CONF_STRUCT_IN_ADDR, CONF_STRUCT_IN6_ADDR, // We could theoretically use a more generic type, however, we want this @@ -88,13 +90,14 @@ enum conf_type { #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 + 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 + bool restart_dnsmasq; // De we need to restart the dnsmasq core when this changes? }; struct config { @@ -137,6 +140,34 @@ struct config { } rateLimit; } dns; + struct { + struct conf_item upstreams; + struct conf_item domain; + struct conf_item domain_needed; + struct conf_item expand_hosts; + struct conf_item bogus_priv; + struct conf_item dnssec; + struct conf_item interface; + struct conf_item host_record; + struct conf_item listening_mode; + struct conf_item cache_size; + struct { + struct conf_item active; + struct conf_item cidr; + struct conf_item target; + struct conf_item domain; + } rev_server; + struct { + struct conf_item active; + struct conf_item start; + struct conf_item end; + struct conf_item router; + struct conf_item leasetime; + struct conf_item ipv6; + struct conf_item rapid_commit; + } dhcp; + } dnsmasq; + struct { struct conf_item resolveIPv4; struct conf_item resolveIPv6; @@ -180,7 +211,6 @@ struct config { } http; struct { - struct conf_item log; struct conf_item pid; struct conf_item database; struct conf_item gravity; @@ -188,6 +218,10 @@ struct config { struct conf_item setupVars; struct conf_item http_info; struct conf_item ph7_error; + struct { + struct conf_item ftl; + struct conf_item dnsmasq; + } log; } files; struct { diff --git a/src/config/dnsmasq_config.c b/src/config/dnsmasq_config.c new file mode 100644 index 00000000..ab0c0a4d --- /dev/null +++ b/src/config/dnsmasq_config.c @@ -0,0 +1,254 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2022 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* dnsmasq config writer routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "dnsmasq_config.h" +// logging routines +#include "log.h" +// get_blocking_mode_str() +#include "datastructure.h" +// flock(), LOCK_SH +#include +// struct config +#include "config/config.h" +// JSON array functions +#include "cJSON/cJSON.h" + +#define DNSMASQ_01_PIHOLE "/tmp/etc_dnsmasq.d_01-pihole.conf" + +static void write_config_header(FILE *fp) +{ + fputs("# Pi-hole: A black hole for Internet advertisements\n", fp); + fputs("# (c) 2023 Pi-hole, LLC (https://pi-hole.net)\n", fp); + fputs("# Network-wide ad blocking via your own hardware.\n", fp); + fputs("#\n", fp); + fputs("# Dnsmasq config for Pi-hole's FTLDNS\n", fp); + fputs("#\n", fp); + fputs("# This file is copyright under the latest version of the EUPL.\n", fp); + fputs("# Please see LICENSE file for your rights under this license.\n", fp); + fputc('\n', fp); + fputs("###############################################################################\n", fp); + fputs("# FILE AUTOMATICALLY POPULATED BY PI-HOLE #\n", fp); + fputs("# ANY CHANGES MADE TO THIS FILE WILL BE LOST WHEN THE CONFIGURATION CHANGES #\n", fp); + fputs("# #\n", fp); + fputs("# IF YOU WISH TO CHANGE THE UPSTREAM SERVERS, CHANGE THEM IN: #\n", fp); + fputs("# /etc/pihole/pihole-FTL.toml #\n", fp); + fputs("# and restart pihole-FTL #\n", fp); + fputs("# #\n", fp); + fputs("# ANY OTHER CHANGES SHOULD BE MADE IN A SEPARATE CONFIG FILE #\n", fp); + fputs("# WITHIN /etc/dnsmasq.d/yourname.conf #\n", fp); + fputs("###############################################################################\n", fp); + fputc('\n', fp); +} + +bool __attribute__((const)) write_dnsmasq_config(bool test_config) +{ + FILE *pihole_conf = fopen(DNSMASQ_01_PIHOLE, "w"); + // Return early if opening failed + if(!pihole_conf) + return false; + + // Lock file, may block if the file is currently opened + if(flock(fileno(pihole_conf), LOCK_EX) != 0) + { + log_err("Cannot open dnsmasq config file "DNSMASQ_01_PIHOLE" in exclusive mode: %s", strerror(errno)); + return false; + } + + write_config_header(pihole_conf); + fputs("# Additional hosts lists\n", pihole_conf); + fputs("addn-hosts=/etc/pihole/local.list\n", pihole_conf); + fputs("addn-hosts=/etc/pihole/custom.list\n", pihole_conf); + fputs("\n", pihole_conf); + fputs("# Don't read /etc/resolv.conf. Get upstream servers only from the configuration\n", pihole_conf); + fputs("no-resolv\n", pihole_conf); + fputs("\n", pihole_conf); + if(cJSON_GetArraySize(config.dnsmasq.upstreams.v.json) > 0) + { + fputs("# List of upstream DNS server\n", pihole_conf); + const int n = cJSON_GetArraySize(config.dnsmasq.upstreams.v.json); + for(int i = 0; i < n; i++) + { + cJSON *server = cJSON_GetArrayItem(config.dnsmasq.upstreams.v.json, i); + if(server != NULL && cJSON_IsString(server)) + fprintf(pihole_conf, "server=%s\n", server->valuestring); + } + fputs("\n", pihole_conf); + } + fputs("# Set the size of dnsmasq's cache. The default is 150 names. Setting the cache\n", pihole_conf); + fputs("# size to zero disables caching. Note: huge cache size impacts performance\n", pihole_conf); + fprintf(pihole_conf, "cache-size=%u\n", config.dnsmasq.cache_size.v.ui); + fputs("\n", pihole_conf); + + fputs("# Return answers to DNS queries from /etc/hosts and interface-name and\n", pihole_conf); + fputs("# dynamic-host which depend on the interface over which the query was\n", pihole_conf); + fputs("# received. If a name has more than one address associated with it, and\n", pihole_conf); + fputs("# at least one of those addresses is on the same subnet as the interface\n", pihole_conf); + fputs("# to which the query was sent, then return only the address(es) on that\n", pihole_conf); + fputs("# subnet and return all the available addresses otherwise.\n", pihole_conf); + fputs("localise-queries\n", pihole_conf); + fputs("\n", pihole_conf); + + if(strlen(config.files.log.dnsmasq.v.s) > 0) + { + fputs("# Enable query logging\n", pihole_conf); + fputs("log-queries\n", pihole_conf); + fputs("log-async\n", pihole_conf); + fprintf(pihole_conf, "log-facility=%s\n", config.files.log.dnsmasq.v.s); + fputs("\n", pihole_conf); + } + + if(config.dnsmasq.bogus_priv.v.b) + { + fputs("# Bogus private reverse lookups. All reverse lookups for private IP\n", pihole_conf); + fputs("# ranges (ie 192.168.x.x, etc) which are not found in /etc/hosts or the\n", pihole_conf); + fputs("# DHCP leases file are answered with NXDOMAIN rather than being forwarded\n", pihole_conf); + fputs("bogus-priv\n", pihole_conf); + fputs("\n", pihole_conf); + } + + if(config.dnsmasq.domain_needed.v.b) + { + fputs("# Add the domain to simple names (without a period) in /etc/hosts in\n", pihole_conf); + fputs("# the same way as for DHCP-derived names\n", pihole_conf); + fputs("domain-needed\n", pihole_conf); + fputs("\n", pihole_conf); + } + + if(config.dnsmasq.expand_hosts.v.b) + { + fputs("# Never forward A or AAAA queries for plain names, without dots or\n", pihole_conf); + fputs("# domain parts, to upstream nameservers\n", pihole_conf); + fputs("expand-hosts\n", pihole_conf); + fputs("\n", pihole_conf); + } + + if(config.dnsmasq.dnssec.v.b) + { + fputs("# Use DNNSEC\n", pihole_conf); + fputs("dnssec\n", pihole_conf); + fputs("# 2017-02-02 root zone trust anchor\n", pihole_conf); + fputs("trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D\n", + pihole_conf); + fputs("\n", pihole_conf); + } + + if(strlen(config.dnsmasq.domain.v.s) > 0 && strcasecmp("none", config.dnsmasq.domain.v.s) != 0) + { + fputs("# DNS domain for the DNS server\n", pihole_conf); + fprintf(pihole_conf, "domain=%s\n", config.dnsmasq.domain.v.s); + fputs("\n", pihole_conf); + // When there is a Pi-hole domain set and "Never forward non-FQDNs" is + // ticked, we add `local=/domain/` to signal that this domain is purely + // local and FTL may answer queries from /etc/hosts or DHCP but should + // never forward queries on that domain to any upstream servers + if(config.dnsmasq.domain_needed.v.b) + { + fputs("# Never forward A or AAAA queries for plain names, without \n", pihole_conf); + fputs("# dots or domain parts, to upstream nameservers. If the name \n", pihole_conf); + fputs("# is not known from /etc/hosts or DHCP a NXDOMAIN is returned. \n", pihole_conf); + fprintf(pihole_conf, "local=/%s/\n", + config.dnsmasq.domain.v.s); + fputs("\n", pihole_conf); + } + } + + if(strlen(config.dnsmasq.host_record.v.s) > 0) + { + fputs("# Add A, AAAA and PTR records to the DNS\n", pihole_conf); + fprintf(pihole_conf, "host-record=%s\n", config.dnsmasq.host_record.v.s); + } + + const char *interface = config.dnsmasq.interface.v.s; + // Use eth0 as fallback interface if the interface is missing + if(strlen(interface) == 0) + interface = "eth0"; + + switch(config.dnsmasq.listening_mode.v.listening_mode) + { + case LISTEN_LOCAL: + fputs("# Only respond to queries from devices that are at most one hop away (local devices)\n", + pihole_conf); + fputs("local-service\n", pihole_conf); + break; + case LISTEN_ALL: + fputs("# Listen on all interfaces, permit all origins\n", pihole_conf); + fputs("except-interface=nonexisting\n", pihole_conf); + break; + case LISTEN_SINGLE: + fputs("# Listen on one interface\n", pihole_conf); + fprintf(pihole_conf, "interface=%s\n", interface); + break; + case LISTEN_BIND: + fputs("# Bind to one interface\n", pihole_conf); + fprintf(pihole_conf, "interface=%s\n", interface); + fputs("bind-interfaces\n", pihole_conf); + break; + } + fputs("\n", pihole_conf); + + if(config.dnsmasq.rev_server.active.v.b) + { + fputs("# Reverse server setting\n", pihole_conf); + fprintf(pihole_conf, "rev-server=%s,%s\n", + config.dnsmasq.rev_server.cidr.v.s, config.dnsmasq.rev_server.target.v.s); + + // If we have a reverse domain, we forward all queries to this domain to + // the same destination + if(strlen(config.dnsmasq.rev_server.domain.v.s) > 0) + fprintf(pihole_conf, "server=/%s/%s\n", + config.dnsmasq.rev_server.domain.v.s, config.dnsmasq.rev_server.target.v.s); + + // Forward unqualified names to the target only when the "never forward + // non-FQDN" option is NOT ticked + if(!config.dnsmasq.domain_needed.v.b) + fprintf(pihole_conf, "server=//%s\n", + config.dnsmasq.rev_server.target.v.s); + } + + if(config.dnsmasq.dhcp.active.v.b) + { + fputs("# DHCP server setting\n", pihole_conf); + fputs("dhcp-authoritative\n", pihole_conf); + fputs("dhcp-leasefile=/etc/pihole/dhcp.leases\n", pihole_conf); + fprintf(pihole_conf, "dhcp-range=%s,%s,%s\n", + config.dnsmasq.dhcp.start.v.s, + config.dnsmasq.dhcp.end.v.s, + config.dnsmasq.dhcp.leasetime.v.s); + fprintf(pihole_conf, "dhcp-option=option:router,%s\n", + config.dnsmasq.dhcp.router.v.s); + + if(config.dnsmasq.dhcp.rapid_commit.v.b) + fputs("dhcp-rapid-commit\n", pihole_conf); + + if(config.dnsmasq.dhcp.ipv6.v.b) + { + fputs("dhcp-option=option6:dns-server,[::]\n", pihole_conf); + fprintf(pihole_conf, "dhcp-range=::,constructor:%s,ra-names,ra-stateless,64\n", interface); + } + } + + // Unlock file + if(flock(fileno(pihole_conf), LOCK_UN) != 0) + { + log_err("Cannot release lock on dnsmasq config file "DNSMASQ_01_PIHOLE": %s", strerror(errno)); + fclose(pihole_conf); + return false; + } + + // Close file + if(fclose(pihole_conf) != 0) + { + log_err("Cannot close dnsmasq config file "DNSMASQ_01_PIHOLE": %s", strerror(errno)); + return false; + } + + return true; +} diff --git a/src/config/dnsmasq_config.h b/src/config/dnsmasq_config.h new file mode 100644 index 00000000..ef9222dc --- /dev/null +++ b/src/config/dnsmasq_config.h @@ -0,0 +1,15 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2023 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* dnsmasq config writer prototypes +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#ifndef DNSMASQ_CONFIG_H +#define DNSMASQ_CONFIG_H + +bool write_dnsmasq_config(bool test_config); + +#endif //DNSMASQ_CONFIG_H diff --git a/src/config/legacy_reader.c b/src/config/legacy_reader.c index 9ce1ec33..46417c16 100644 --- a/src/config/legacy_reader.c +++ b/src/config/legacy_reader.c @@ -67,13 +67,13 @@ bool getLogFilePathLegacy(FILE *fp) if(buffer == NULL) { // Use standard path if no custom path was obtained from the config file - config.files.log.v.s = strdup("/var/log/pihole/FTL.log"); - config.files.log.t = CONF_STRING_ALLOCATED; + config.files.log.ftl.v.s = strdup("/var/log/pihole/FTL.log"); + config.files.log.ftl.t = CONF_STRING_ALLOCATED; // Test if memory allocation was successful - if(config.files.log.v.s == NULL) + if(config.files.log.ftl.v.s == NULL) { - printf("FATAL: Allocating memory for config.files.log.v.s failed (%s, %i). Exiting.", + printf("FATAL: Allocating memory for config.files.log.ftl.v.s failed (%s, %i). Exiting.", strerror(errno), errno); exit(EXIT_FAILURE); } @@ -82,24 +82,24 @@ bool getLogFilePathLegacy(FILE *fp) else if(sscanf(buffer, "%127ms", &val_buffer) == 0) { // Free previously allocated memory (if any) - if(config.files.log.t == CONF_STRING_ALLOCATED) - free(config.files.log.v.s); + if(config.files.log.ftl.t == CONF_STRING_ALLOCATED) + free(config.files.log.ftl.v.s); // Set empty file string - config.files.log.v.s = NULL; - config.files.log.t = CONF_STRING; + config.files.log.ftl.v.s = NULL; + config.files.log.ftl.t = CONF_STRING; log_info("Using syslog facility"); } if(val_buffer) { // Free previously allocated memory (if any) - if(config.files.log.t == CONF_STRING_ALLOCATED) - free(config.files.log.v.s); + if(config.files.log.ftl.t == CONF_STRING_ALLOCATED) + free(config.files.log.ftl.v.s); // Set string - config.files.log.v.s = val_buffer; - config.files.log.t = CONF_STRING_ALLOCATED; + config.files.log.ftl.v.s = val_buffer; + config.files.log.ftl.t = CONF_STRING_ALLOCATED; } fclose(fp); diff --git a/src/config/toml_helper.c b/src/config/toml_helper.c index 32cdf114..9615d9fa 100644 --- a/src/config/toml_helper.c +++ b/src/config/toml_helper.c @@ -35,7 +35,11 @@ FILE * __attribute((malloc)) __attribute((nonnull(1))) openFTLtoml(const char *m // Lock file, may block if the file is currently opened if(flock(fileno(fp), LOCK_EX) != 0) + { log_err("Cannot open FTL's config file in exclusive mode: %s", strerror(errno)); + fclose(fp); + return NULL; + } return fp; } @@ -43,10 +47,11 @@ FILE * __attribute((malloc)) __attribute((nonnull(1))) openFTLtoml(const char *m // Open the TOML file for reading or writing void closeFTLtoml(FILE *fp) { - // Lock file, may block if the file is currently opened + // Release file lock if(flock(fileno(fp), LOCK_UN) != 0) log_err("Cannot release lock on FTL's config file: %s", strerror(errno)); + // Close file if(fclose(fp) != 0) log_err("Cannot close FTL's config file: %s", strerror(errno)); @@ -111,6 +116,90 @@ void indentTOML(FILE *fp, const unsigned int indent) fputc(' ', fp); } +// measure the length until either the end of the string or to the next space +// (whatever comes first) +static unsigned int __attribute__((pure)) length(const char * p) +{ + const char *p2 = p; + while(*(++p2) && *p2 != ' '); + return p2 - p; +} + +void print_comment(FILE *fp, const char *str, const char *intro, const unsigned int width, const unsigned int indent) +{ + unsigned int i = 0; + unsigned int extraspace = 0; + unsigned int ratac = strlen(intro); + + // Add intro if present + if(ratac > 0) + { + for (unsigned int j = 0; j != 2*indent; ++j) + fputc(' ', fp); + fputs("# ", fp); + fputs(intro, fp); + extraspace = ratac; + ratac = 0; + } + else + { + // Add indentation + for(unsigned int j = 0; j < 2*indent; ++j) + fputc(' ', fp); + fputs("# ", fp); + for(unsigned int j = 0; j < extraspace; ++j) + fputc(' ', fp); + } + + // Print string + while(str[i] != '\0') + { + // Wrap to next line if we already printed too much for this one + if(ratac >= width-extraspace) + { + // If this the first line? If not, add a newline + if (i > 0) + fputc('\n', fp); + // Add intendation + for (unsigned int j = 0; j != 2*indent; ++j) + fputc(' ', fp); + // Start a new line + fputc('#', fp); + for(unsigned int j = 0; j < extraspace; ++j) + fputc(' ', fp); + fputc(' ', fp); + ratac = 0; + } + + // If the text character is a space, we print it right away + // Print a word - measure the length until either the end of the + // string or to the next space (whatever comes first) and print this + // part of the string + unsigned int len = length(str + i); + + // Print word if we either have enough space to print this word or + // it is really a long word and we are at the beginning of a line + if(((ratac + len) <= width-extraspace) || (ratac == 0)) + { + // Add spaces after words but not at the beginning of new lines + if(ratac > 0 && str[i] == ' ') + fputc(' ', fp); + + // Print the next word + ratac += len; + while (len--) + if(str[i++] != ' ') + fputc(str[i-1], fp); + } + else + { + // Mark this line as full + ratac = width; + } + } + fputc('\n', fp); +} + // Write a TOML value to a file depending on its type void writeTOMLvalue(FILE * fp, const enum conf_type t, union conf_value *v) { @@ -151,6 +240,9 @@ void writeTOMLvalue(FILE * fp, const enum conf_type t, union conf_value *v) case CONF_ENUM_REFRESH_HOSTNAMES: printTOMLstring(fp, get_refresh_hostnames_str(v->refresh_hostnames)); break; + case CONF_ENUM_LISTENING_MODE: + printTOMLstring(fp, get_listening_mode_str(v->listening_mode)); + break; case CONF_STRUCT_IN_ADDR: { char addr4[INET_ADDRSTRLEN] = { 0 }; @@ -323,6 +415,21 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not of type string", conf_item->k); break; } + case CONF_ENUM_LISTENING_MODE: + { + const toml_datum_t val = toml_string_in(toml, key); + if(val.ok) + { + const int listening_mode = get_listening_mode_val(val.u.s); + if(listening_mode != -1) + conf_item->v.listening_mode = listening_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_PRIVACY_LEVEL: { const toml_datum_t val = toml_int_in(toml, key); diff --git a/src/config/toml_helper.h b/src/config/toml_helper.h index 098477e3..1c38bf63 100644 --- a/src/config/toml_helper.h +++ b/src/config/toml_helper.h @@ -19,6 +19,7 @@ void indentTOML(FILE *fp, const unsigned int indent); FILE *openFTLtoml(const char *mode) __attribute((malloc)) __attribute((nonnull(1))); void closeFTLtoml(FILE *fp); +void print_comment(FILE *fp, const char *str, const char *intro, const unsigned int width, const unsigned int indent); 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); diff --git a/src/config/toml_reader.c b/src/config/toml_reader.c index 7fd505d8..75218d97 100644 --- a/src/config/toml_reader.c +++ b/src/config/toml_reader.c @@ -223,10 +223,10 @@ bool getLogFilePathTOML(void) } // Only replace string when it is different - if(strcmp(config.files.log.v.s,log.u.s) != 0) + if(strcmp(config.files.log.ftl.v.s,log.u.s) != 0) { - config.files.log.t = CONF_STRING_ALLOCATED; - config.files.log.v.s = log.u.s; // Allocated string + config.files.log.ftl.t = CONF_STRING_ALLOCATED; + config.files.log.ftl.v.s = log.u.s; // Allocated string } else free(log.u.s); diff --git a/src/config/toml_writer.c b/src/config/toml_writer.c index da36df51..cacacf5f 100644 --- a/src/config/toml_writer.c +++ b/src/config/toml_writer.c @@ -64,13 +64,11 @@ bool writeFTLtoml(void) } // Write comment - indentTOML(fp, level-1); - fprintf(fp, "# %s\n", conf_item->h); + print_comment(fp, conf_item->h, "", 85, level-1); if(conf_item->a != NULL) { // Write possible values if applicable - indentTOML(fp, level-1); - fprintf(fp, "# Possible values are: %s\n", conf_item->a); + print_comment(fp, conf_item->a, "Possible values are: ", 85, level-1); } // Write value diff --git a/src/datastructure.c b/src/datastructure.c index 662fbb97..3b5ac44d 100644 --- a/src/datastructure.c +++ b/src/datastructure.c @@ -990,3 +990,34 @@ int __attribute__ ((const)) get_busy_reply_val(const char *replyWhenBusy) // Invalid value return -1; } + +const char * __attribute__ ((const)) get_listening_mode_str(const enum listening_mode listening_mode) +{ + switch(listening_mode) + { + case LISTEN_LOCAL: + return "LOCAL"; + case LISTEN_ALL: + return "ALL"; + case LISTEN_SINGLE: + return "SINGLE"; + case LISTEN_BIND: + return "BIND"; + } + return NULL; +} + +int __attribute__ ((const)) get_listening_mode_val(const char *listening_mode) +{ + if(strcasecmp(listening_mode, "LOCAL") == 0) + return LISTEN_LOCAL; + else if(strcasecmp(listening_mode, "ALL") == 0) + return LISTEN_ALL; + else if(strcasecmp(listening_mode, "SINGLE") == 0) + return LISTEN_SINGLE; + else if(strcasecmp(listening_mode, "BIND") == 0) + return LISTEN_BIND; + + // Invalid value + return -1; +} diff --git a/src/datastructure.h b/src/datastructure.h index 4c501734..f835cf18 100644 --- a/src/datastructure.h +++ b/src/datastructure.h @@ -156,6 +156,8 @@ const char *get_ptr_type_str(const enum ptr_type piholePTR) __attribute__ ((cons 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)); +const char * get_listening_mode_str(const enum listening_mode listening_mode) __attribute__ ((const)); +int get_listening_mode_val(const char *listening_mode) __attribute__ ((const)); // Pointer getter functions #define getQuery(queryID, checkMagic) _getQuery(queryID, checkMagic, __LINE__, __FUNCTION__, __FILE__) diff --git a/src/dnsmasq/dnsmasq.c b/src/dnsmasq/dnsmasq.c index 59bd4c15..9d403d25 100644 --- a/src/dnsmasq/dnsmasq.c +++ b/src/dnsmasq/dnsmasq.c @@ -31,7 +31,7 @@ struct daemon *daemon; static volatile pid_t pid = 0; static volatile int pipewrite; -static char terminate = 0; +volatile char FTL_terminate = 0; static void set_dns_listeners(void); static void check_dns_listeners(time_t now); @@ -1067,10 +1067,10 @@ int main_dnsmasq (int argc, char **argv) #endif /*** Pi-hole modification ***/ - terminate = killed; + FTL_terminate = killed; /****************************/ - while (!terminate) + while (!FTL_terminate) { int timeout = fast_retry(now); @@ -1653,7 +1653,7 @@ static void async_event(int pipe, time_t now) flush_log(); /*** Pi-hole modification ***/ // exit(EC_GOOD); - terminate = 1; + FTL_terminate = 1; /*** Pi-hole modification ***/ } } diff --git a/src/dnsmasq/dnsmasq.h b/src/dnsmasq/dnsmasq.h index ff02fd5c..e4c3994f 100644 --- a/src/dnsmasq/dnsmasq.h +++ b/src/dnsmasq/dnsmasq.h @@ -1883,3 +1883,4 @@ int add_update_server(int flags, // Pi-hole modification const char *edestr(int ede); +extern volatile char FTL_terminate; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index dd1c5849..2013fdae 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -2772,9 +2772,9 @@ 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.v.s, ent_pw->pw_uid, ent_pw->pw_gid) == -1) + if(chown(config.files.log.ftl.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.v.s, strerror(errno), errno); + ent_pw->pw_uid, ent_pw->pw_gid, config.files.log.ftl.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.v.s, strerror(errno), errno); diff --git a/src/enums.h b/src/enums.h index cbbc54b9..b9b9a9ef 100644 --- a/src/enums.h +++ b/src/enums.h @@ -280,4 +280,11 @@ enum addinfo_type { ADDINFO_REGEX_ID } __attribute__ ((packed)); +enum listening_mode { + LISTEN_LOCAL, + LISTEN_ALL, + LISTEN_SINGLE, + LISTEN_BIND +} __attribute__ ((packed)); + #endif // ENUMS_H diff --git a/src/gc.c b/src/gc.c index 26427e99..88b248ef 100644 --- a/src/gc.c +++ b/src/gc.c @@ -150,7 +150,7 @@ void *GC_thread(void *val) { check_load(); LastDBStorageUsage = check_space(config.files.database.v.s, LastDBStorageUsage); - LastLogStorageUsage = check_space(config.files.log.v.s, LastLogStorageUsage); + LastLogStorageUsage = check_space(config.files.log.ftl.v.s, LastLogStorageUsage); lastResourceCheck = now; } diff --git a/src/log.c b/src/log.c index 4cb9bab7..ae94d12c 100644 --- a/src/log.c +++ b/src/log.c @@ -61,14 +61,14 @@ void log_ctrl(bool plog, bool pstdout) void init_FTL_log(const char *name) { // Open the log file in append/create mode - if(config.files.log.v.s != NULL) + if(config.files.log.ftl.v.s != NULL) { FILE *logfile = NULL; - if((logfile = fopen(config.files.log.v.s, "a+")) == NULL) + if((logfile = fopen(config.files.log.ftl.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.v.s); - config.files.log.v.s = NULL; + printf("ERR: Opening of FTL log (%s) failed!\n",config.files.log.ftl.v.s); + config.files.log.ftl.v.s = NULL; } // Close log file @@ -294,10 +294,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.v.s != NULL) + if(config.files.log.ftl.v.s != NULL) { // Open log file - FILE *logfile = fopen(config.files.log.v.s, "a+"); + FILE *logfile = fopen(config.files.log.ftl.v.s, "a+"); // Write to log file if(logfile != NULL) diff --git a/src/main.c b/src/main.c index 729b9f49..d1e7d336 100644 --- a/src/main.c +++ b/src/main.c @@ -16,6 +16,7 @@ #include "config/config.h" #include "database/common.h" #include "main.h" +// exit_code #include "signals.h" #include "regex_r.h" // init_shmem() @@ -36,7 +37,6 @@ char *username; bool needGC = false; bool needDBGC = false; bool startup = true; -volatile int exit_code = EXIT_SUCCESS; int main (int argc, char *argv[]) { @@ -135,8 +135,8 @@ int main (int argc, char *argv[]) main_dnsmasq(argc_dnsmasq, argv_dnsmasq); log_info("Shutting down..."); - // Extra grace time is needed as dnsmasq script-helpers may not be - // terminating immediately + // Extra grace time is needed as dnsmasq script-helpers and the API may not + // be terminating immediately sleepms(250); // Save new queries to database (if database is used) diff --git a/src/setupVars.c b/src/setupVars.c index fabfcbf9..166cc4b7 100644 --- a/src/setupVars.c +++ b/src/setupVars.c @@ -12,79 +12,102 @@ #include "log.h" #include "config/config.h" #include "setupVars.h" +#include "datastructure.h" unsigned int setupVarsElements = 0; char ** setupVarsArray = NULL; +static void get_conf_string_from_setupVars(const char *key, struct conf_item *conf_item) +{ + const char *setupVarsValue = read_setupVarsconf(key); + if(setupVarsValue == NULL) + setupVarsValue = ""; + + // Free previously allocated memory (if applicable) + if(conf_item->t == CONF_STRING_ALLOCATED) + free(conf_item->v.s); + conf_item->v.s = strdup(setupVarsValue); + conf_item->t = CONF_STRING_ALLOCATED; + + // Free memory, harmless to call if read_setupVarsconf() didn't return a result + clearSetupVarsArray(); +} + +static void get_conf_bool_from_setupVars(const char *key, struct conf_item *conf_item) +{ + const char *boolean = read_setupVarsconf(key); + + if(boolean == NULL) + // Do not change default value, this value is not set in setupVars.conf + ; + else + // Parameter present in setupVars.conf + conf_item->v.b = getSetupVarsBool(boolean); + + // Free memory, harmless to call if read_setupVarsconf() didn't return a result + clearSetupVarsArray(); +} + +static void get_conf_string_array_from_setupVars(const char *key, struct conf_item *conf_item) +{ + // Get clients which the user doesn't want to see + const char *array = read_setupVarsconf(key); + + if(array != NULL) + { + getSetupVarsArray(array); + for (unsigned int i = 0; i < setupVarsElements; ++i) + { + log_debug(DEBUG_CONFIG, "%s: [%d] = %s\n", key, i, setupVarsArray[i]); + // Add string to our JSON array + cJSON *item = cJSON_CreateString(setupVarsArray[i]); + cJSON_AddItemToArray(conf_item->v.json, item); + } + } + + // Free memory, harmless to call if read_setupVarsconf() didn't return a result + clearSetupVarsArray(); +} + +static void get_conf_upstream_servers_from_setupVars(struct conf_item *conf_item) +{ + // Try to import up to 50 servers... + #define MAX_SERVERS 50 + for(unsigned int j = 0; j < MAX_SERVERS; j++) + { + // Get clients which the user doesn't want to see + char server_key[strlen("PIHOLE_DNS_XX") + 1]; + sprintf(server_key, "PIHOLE_DNS_%d", j); + const char *value = read_setupVarsconf(server_key); + + if(value != NULL) + { + log_debug(DEBUG_CONFIG, "%s = %s\n", server_key, value); + // Add string to our JSON array + cJSON *item = cJSON_CreateString(value); + cJSON_AddItemToArray(conf_item->v.json, item); + } + + // Free memory, harmless to call if read_setupVarsconf() didn't return a result + clearSetupVarsArray(); + } +} + void importsetupVarsConf(void) { // Try to obtain password hash from setupVars.conf - const char *pwhash = read_setupVarsconf("WEBPASSWORD"); - if(pwhash == NULL) - pwhash = ""; - - // Free previously allocated memory (if applicable) - if(config.api.pwhash.t == CONF_STRING_ALLOCATED) - free(config.api.pwhash.v.s); - config.api.pwhash.v.s = strdup(pwhash); - config.api.pwhash.t = CONF_STRING_ALLOCATED; - - // Free memory, harmless to call if read_setupVarsconf() didn't return a result - clearSetupVarsArray(); + get_conf_string_from_setupVars("WEBPASSWORD", &config.api.pwhash); // Try to obtain blocking active boolean - const char *blocking = read_setupVarsconf("BLOCKING_ENABLED"); - - if(blocking == NULL || getSetupVarsBool(blocking)) - { - // Parameter either not present in setupVars.conf - // or explicitly set to true - config.dns.blocking.active.v.b = true; - } - else - { - // Disabled - config.dns.blocking.active.v.b = false; - } - - // Free memory, harmless to call if read_setupVarsconf() didn't return a result - clearSetupVarsArray(); + get_conf_bool_from_setupVars("BLOCKING_ENABLED", &config.dns.blocking.active); // Get clients which the user doesn't want to see - const char *excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); - - if(excludeclients != NULL) - { - getSetupVarsArray(excludeclients); - for (unsigned int i = 0; i < setupVarsElements; ++i) - { - log_debug(DEBUG_CONFIG, "API_EXCLUDE_CLIENTS: [%d] = %s\n", i, setupVarsArray[i]); - // Add string to our JSON array - cJSON *item = cJSON_CreateString(setupVarsArray[i]); - cJSON_AddItemToArray(config.api.exclude_clients.v.json, item); - } - } - - // Free memory, harmless to call if read_setupVarsconf() didn't return a result - clearSetupVarsArray(); + get_conf_string_array_from_setupVars("API_EXCLUDE_CLIENTS", &config.api.exclude_clients); // Get domains which the user doesn't want to see - char *excludedomains = read_setupVarsconf("API_EXCLUDE_DOMAINS"); + get_conf_string_array_from_setupVars("API_EXCLUDE_DOMAINS", &config.api.exclude_domains); - if(excludedomains != NULL) - { - getSetupVarsArray(excludedomains); - for (unsigned int i = 0; i < setupVarsElements; ++i) - { - log_debug(DEBUG_CONFIG, "API_EXCLUDE_DOMAINS: [%d] = %s\n", i, setupVarsArray[i]); - // Add string to our JSON array - cJSON *item = cJSON_CreateString(setupVarsArray[i]); - cJSON_AddItemToArray(config.api.exclude_domains.v.json, item); - } - } - // Free memory, harmless to call if read_setupVarsconf() didn't return a result - clearSetupVarsArray(); // Try to obtain temperature hot value const char *temp_limit = read_setupVarsconf("TEMPERATURE_LIMIT"); @@ -99,6 +122,8 @@ void importsetupVarsConf(void) // Free memory, harmless to call if read_setupVarsconf() didn't return a result clearSetupVarsArray(); + + // Try to obtain boxed-layout boolean const char *boxed_layout = read_setupVarsconf("WEBUIBOXEDLAYOUT"); // If the property is set to false and different than "boxed", the property @@ -110,27 +135,73 @@ void importsetupVarsConf(void) // Free memory, harmless to call if read_setupVarsconf() didn't return a result clearSetupVarsArray(); - // Try to obtain theme string - const char *web_theme = read_setupVarsconf("WEBTHEME"); - if(web_theme == NULL) - web_theme = ""; - // Free previously allocated memory (if applicable) - if(config.http.interface.theme.t == CONF_STRING_ALLOCATED) - free(config.http.interface.theme.v.s); - config.http.interface.theme.v.s = strdup(web_theme); - config.http.interface.theme.t = CONF_STRING_ALLOCATED; + + // Try to obtain theme string + get_conf_string_from_setupVars("WEBTHEME", &config.http.interface.theme); + + // Try to obtain list of upstream servers + get_conf_upstream_servers_from_setupVars(&config.dnsmasq.upstreams); + + // Try to get Pi-hole domain + get_conf_string_from_setupVars("PIHOLE_DOMAIN", &config.dnsmasq.domain); + + // Try to get bool properties (the first two are intentionally set from the same key) + get_conf_bool_from_setupVars("DNS_FQDN_REQUIRED", &config.dnsmasq.domain_needed); + get_conf_bool_from_setupVars("DNS_FQDN_REQUIRED", &config.dnsmasq.expand_hosts); + get_conf_bool_from_setupVars("DNS_BOGUS_PRIV", &config.dnsmasq.bogus_priv); + get_conf_bool_from_setupVars("DNSSEC", &config.dnsmasq.dnssec); + get_conf_string_from_setupVars("PIHOLE_INTERFACE", &config.dnsmasq.interface); + get_conf_string_from_setupVars("HOSTRECORD", &config.dnsmasq.host_record); + + + + // Try to obtain listening mode and transfort to the enum + const char *listening_mode = read_setupVarsconf("DNSMASQ_LISTENING"); + // If the property is set to false and different than "boxed", the property + // is disabled. This is consistent with the code in AdminLTE when writing + // this code + if(listening_mode != NULL) + { + int listening_mode_enum = get_listening_mode_val(listening_mode); + if(listening_mode_enum != -1) + config.dnsmasq.listening_mode.v.listening_mode = listening_mode_enum; + } // Free memory, harmless to call if read_setupVarsconf() didn't return a result clearSetupVarsArray(); + + // Try to obtain REV_SERVER settings + get_conf_bool_from_setupVars("REV_SERVER", &config.dnsmasq.rev_server.active); + get_conf_string_from_setupVars("REV_SERVER_CIDR", &config.dnsmasq.rev_server.cidr); + get_conf_string_from_setupVars("REV_SERVER_TARGET", &config.dnsmasq.rev_server.target); + get_conf_string_from_setupVars("REV_SERVER_DOMAIN", &config.dnsmasq.rev_server.domain); + + // Try to obtain DHCP settings + get_conf_bool_from_setupVars("DHCP_ACTIVE", &config.dnsmasq.dhcp.active); + get_conf_string_from_setupVars("DHCP_START", &config.dnsmasq.dhcp.start); + get_conf_string_from_setupVars("DHCP_END", &config.dnsmasq.dhcp.end); + get_conf_string_from_setupVars("DHCP_ROUTER", &config.dnsmasq.dhcp.router); + get_conf_string_from_setupVars("DHCP_LEASETIME", &config.dnsmasq.dhcp.leasetime); + get_conf_bool_from_setupVars("DHCP_IPv6", &config.dnsmasq.dhcp.ipv6); + get_conf_bool_from_setupVars("DHCP_rapid_commit", &config.dnsmasq.dhcp.rapid_commit); } -char* __attribute__((pure)) find_equals(const char *s) +char* __attribute__((pure)) find_equals(char *s) { const char *chars = "="; + // Make s point to the first char after the "=" sign while (*s && (!chars || !strchr(chars, *s))) s++; - return (char*)s; + + // additionally, we have to check if there is a whitespace and end here + // (there may be a bash comment afterwards) + char *p = s; + while(*p && *p != ' ') + p++; + *p = '\0'; + + return s; } void trim_whitespace(char *string) @@ -264,8 +335,5 @@ void clearSetupVarsArray(void) bool __attribute__((pure)) getSetupVarsBool(const char * input) { - if((strcmp(input, "true")) == 0) - return true; - else - return false; + return (strcmp(input, "true")) == 0; } diff --git a/src/setupVars.h b/src/setupVars.h index 07c47826..466c0c8f 100644 --- a/src/setupVars.h +++ b/src/setupVars.h @@ -15,7 +15,7 @@ char *read_setupVarsconf(const char * key); void getSetupVarsArray(const char * input); void clearSetupVarsArray(void); bool getSetupVarsBool(const char * input) __attribute__((pure)); -char *find_equals(const char* s) __attribute__((pure)); +char *find_equals(char* s) __attribute__((pure)); void trim_whitespace(char *string); #endif //SETUPVARS_H diff --git a/src/signals.c b/src/signals.c index 7804b610..7b717933 100644 --- a/src/signals.c +++ b/src/signals.c @@ -31,7 +31,7 @@ volatile sig_atomic_t killed = 0; static volatile pid_t mpid = -1; static time_t FTLstarttime = 0; -extern volatile int exit_code; +volatile int exit_code = EXIT_SUCCESS; volatile sig_atomic_t thread_cancellable[THREADS_MAX] = { true }; const char *thread_names[THREADS_MAX] = { "" }; diff --git a/src/signals.h b/src/signals.h index defb7c78..99fde1b0 100644 --- a/src/signals.h +++ b/src/signals.h @@ -18,6 +18,7 @@ pid_t main_pid(void); void thread_sleepms(const enum thread_types thread, const int milliseconds); void generate_backtrace(void); +extern volatile int exit_code; extern volatile sig_atomic_t killed; extern volatile sig_atomic_t want_to_reimport_aliasclients; extern volatile sig_atomic_t want_to_reload_lists; diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 69a8632b..7ca33b21 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -34,6 +34,9 @@ struct ftl_conn { char *raw; cJSON *json; } payload; + struct { + bool restart; + } ftl; bool opts[2]; }; diff --git a/src/webserver/ph7.c b/src/webserver/ph7.c index 4de86681..c162dcb8 100644 --- a/src/webserver/ph7.c +++ b/src/webserver/ph7.c @@ -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.v.s); + config.files.log.ftl.v.s); /* Extract error log */ const char *zErrLog = NULL;