Files
FTL/src/config/cli.c
T
2023-01-25 21:51:12 +01:00

388 lines
9.4 KiB
C

/* 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
* CLI config 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 "config/cli.h"
#include "config/config.h"
#include "config/toml_helper.h"
#include "config/toml_writer.h"
#include "config/dnsmasq_config.h"
#include "log.h"
#include "datastructure.h"
// toml_table_t
#include "tomlc99/toml.h"
// Read a TOML value from a table depending on its type
static bool readStringValue(struct conf_item *conf_item, const char *value)
{
if(conf_item == NULL || value == NULL)
{
log_debug(DEBUG_CONFIG, "readStringValue(%p, %p) called with invalid arguments, skipping",
conf_item, value);
return false;
}
switch(conf_item->t)
{
case CONF_BOOL:
{
if(strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0)
conf_item->v.b = true;
else if(strcasecmp(value, "false") == 0 || strcasecmp(value, "no") == 0)
conf_item->v.b = false;
else
{
log_err("Config setting %s is invalid, allowed options are: [ true, false, yes, no ]", conf_item->k);
return false;
}
break;
}
case CONF_INT:
{
int val;
if(sscanf(value, "%i", &val) == 1)
conf_item->v.i = val;
else
{
log_err("Config setting %s is invalid, allowed options are: integer", conf_item->k);
return false;
}
break;
}
case CONF_UINT:
{
unsigned int val;
if(sscanf(value, "%u", &val) == 1)
conf_item->v.ui = val;
else
{
log_err("Config setting %s is invalid, allowed options are: unsigned integer", conf_item->k);
return false;
}
break;
}
case CONF_UINT16:
{
uint16_t val;
if(sscanf(value, "%hu", &val) == 1)
conf_item->v.ui = val;
else
{
log_err("Config setting %s is invalid, allowed options are: unsigned integer (16 bit)", conf_item->k);
return false;
}
break;
}
case CONF_LONG:
{
long val;
if(sscanf(value, "%li", &val) == 1)
conf_item->v.l = val;
else
{
log_err("Config setting %s is invalid, allowed options are: long integer", conf_item->k);
return false;
}
break;
}
case CONF_ULONG:
{
unsigned long val;
if(sscanf(value, "%lu", &val) == 1)
conf_item->v.ul = val;
else
{
log_err("Config setting %s is invalid, allowed options are: unsigned long integer", conf_item->k);
return false;
}
break;
}
case CONF_DOUBLE:
{
double val;
if(sscanf(value, "%lf", &val) == 1)
conf_item->v.d = val;
else
{
log_err("Config setting %s is invalid, allowed options are: double", conf_item->k);
return false;
}
break;
}
case CONF_STRING:
case CONF_STRING_ALLOCATED:
{
if(conf_item->t == CONF_STRING_ALLOCATED)
free(conf_item->v.s);
conf_item->v.s = strdup(value);
conf_item->t = CONF_STRING_ALLOCATED;
break;
}
case CONF_ENUM_PTR_TYPE:
{
const int ptr_type = get_ptr_type_val(value);
if(ptr_type != -1)
conf_item->v.ptr_type = ptr_type;
else
{
char *allowed = NULL;
CONFIG_ITEM_ARRAY(conf_item->a, allowed);
log_err("Config setting %s is invalid, allowed options are: %s", conf_item->k, allowed);
free(allowed);
return false;
}
break;
}
case CONF_ENUM_BUSY_TYPE:
{
const int busy_reply = get_busy_reply_val(value);
if(busy_reply != -1)
conf_item->v.busy_reply = busy_reply;
else
{
char *allowed = NULL;
CONFIG_ITEM_ARRAY(conf_item->a, allowed);
log_err("Config setting %s is invalid, allowed options are: %s", conf_item->k, allowed);
free(allowed);
return false;
}
break;
}
case CONF_ENUM_BLOCKING_MODE:
{
const int blocking_mode = get_blocking_mode_val(value);
if(blocking_mode != -1)
conf_item->v.blocking_mode = blocking_mode;
else
{
char *allowed = NULL;
CONFIG_ITEM_ARRAY(conf_item->a, allowed);
log_err("Config setting %s is invalid, allowed options are: %s", conf_item->k, allowed);
free(allowed);
return false;
}
break;
}
case CONF_ENUM_REFRESH_HOSTNAMES:
{
const int refresh_hostnames = get_refresh_hostnames_val(value);
if(refresh_hostnames != -1)
conf_item->v.refresh_hostnames = refresh_hostnames;
else
{
char *allowed = NULL;
CONFIG_ITEM_ARRAY(conf_item->a, allowed);
log_err("Config setting %s is invalid, allowed options are: %s", conf_item->k, allowed);
free(allowed);
return false;
}
break;
}
case CONF_ENUM_LISTENING_MODE:
{
const int listening_mode = get_listening_mode_val(value);
if(listening_mode != -1)
conf_item->v.listening_mode = listening_mode;
else
{
char *allowed = NULL;
CONFIG_ITEM_ARRAY(conf_item->a, allowed);
log_err("Config setting %s is invalid, allowed options are: %s", conf_item->k, allowed);
free(allowed);
return false;
}
break;
}
case CONF_ENUM_PRIVACY_LEVEL:
{
int val;
if(sscanf(value, "%i", &val) == 1 && val >= PRIVACY_SHOW_ALL && val <= PRIVACY_MAXIMUM)
conf_item->v.i = val;
else
{
log_err("Config setting %s is invalid, allowed options are: integer between %d and %d", conf_item->k, PRIVACY_SHOW_ALL, PRIVACY_MAXIMUM);
return false;
}
break;
}
case CONF_STRUCT_IN_ADDR:
{
struct in_addr addr4 = { 0 };
if(inet_pton(AF_INET, value, &addr4))
memcpy(&conf_item->v.in_addr, &addr4, sizeof(addr4));
else
{
log_err("Config setting %s is invalid (%s), allowed options are: IPv4 address", conf_item->k, strerror(errno));
return false;
}
break;
}
case CONF_STRUCT_IN6_ADDR:
{
struct in6_addr addr6 = { 0 };
if(inet_pton(AF_INET6, value, &addr6))
memcpy(&conf_item->v.in6_addr, &addr6, sizeof(addr6));
else
{
log_err("Config setting %s is invalid (%s), allowed options are: IPv6 address", conf_item->k, strerror(errno));
return false;
}
break;
}
case CONF_JSON_STRING_ARRAY:
{
cJSON *elem = cJSON_Parse(value);
if(elem == NULL)
{
log_err("Config setting %s is invalid: not valid JSON, error before: %s", conf_item->k, cJSON_GetErrorPtr());
return false;
}
if(!cJSON_IsArray(elem))
{
log_err("Config setting %s is invalid: not a valid string array (example: [ \"a\", \"b\", \"c\" ])", conf_item->k);
return false;
}
const unsigned int elems = cJSON_GetArraySize(elem);
for(unsigned int i = 0; i < elems; i++)
{
const cJSON *item = cJSON_GetArrayItem(elem, i);
if(!cJSON_IsString(item))
{
log_err("Config setting %s is invalid: element with index %d is not a string", conf_item->k, i);
cJSON_Delete(elem);
return false;
}
}
// If we reach this point, all elements are valid
// Free previously allocated JSON array and replace with new
cJSON_Delete(conf_item->v.json);
conf_item->v.json = elem;
break;
}
}
return true;
}
bool set_config_from_CLI(const char *key, const char *value)
{
// Identify config option
struct config newconf;
duplicate_config(&newconf, &config);
struct conf_item *conf_item = NULL;
struct conf_item *new_item = NULL;
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
{
// Get pointer to (copied) memory location of this conf_item
struct conf_item *item = get_conf_item(&newconf, i);
if(strcmp(item->k, key) != 0)
continue;
// This is the config option we are looking for
new_item = item;
// Also get pointer to memory location of this conf_item
conf_item = get_conf_item(&config, i);
// Break early
break;
}
// Check if we found the config option
if(new_item == NULL)
{
log_err("Unknown config option: %s", key);
return false;
}
// Parse value
if(!readStringValue(new_item, value))
return false;
// Check if value changed compared to current value
if(!compare_config_item(new_item, conf_item))
{
// Config item changed
// Is this a dnsmasq option we need to check?
if(conf_item->f & FLAG_RESTART_DNSMASQ)
{
char errbuf[ERRBUF_SIZE] = { 0 };
if(!write_dnsmasq_config(&newconf, true, errbuf))
{
// Test failed
log_debug(DEBUG_CONFIG, "Config item %s: dnsmasq config test failed", conf_item->k);
return false;
}
}
else if(conf_item == &config.dns.hosts)
{
// We need to rewrite the custom.list file but do not need to
// restart dnsmasq. If dnsmasq is going to be restarted anyway,
// this is not necessary as the file will be rewritten during
// the restart
write_custom_list();
}
// Install new configuration
replace_config(&newconf);
// Print value
writeTOMLvalue(stdout, -1, new_item->t, &new_item->v);
}
else
{
// No change
log_debug(DEBUG_CONFIG, "Config item %s: Unchanged", conf_item->k);
free_config(&newconf);
// Print value
writeTOMLvalue(stdout, -1, conf_item->t, &conf_item->v);
}
putchar('\n');
writeFTLtoml(false);
return true;
}
bool get_config_from_CLI(const char *key)
{
// Identify config option
struct conf_item *conf_item = NULL;
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
{
// Get pointer to memory location of this conf_item
struct conf_item *item = get_conf_item(&config, i);
if(strcmp(item->k, key) != 0)
continue;
// This is the config option we are looking for
conf_item = item;
break;
}
// Check if we found the config option
if(conf_item == NULL)
{
log_err("Unknown config option: %s", key);
return false;
}
// Print value
writeTOMLvalue(stdout, -1, conf_item->t, &conf_item->v);
putchar('\n');
return true;
}