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

871 lines
26 KiB
C

/* Pi-hole: A black hole for Internet advertisements
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/ftl
*
* 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 "webserver/http-common.h"
#include "webserver/json_macros.h"
#include "api/api.h"
// config struct
#include "config/config.h"
// struct clientsData
#include "datastructure.h"
// INT_MIN, INT_MAX, ...
#include <limits.h>
// writeFTLtoml()
#include "config/toml_writer.h"
// write_dnsmasq_config()
#include "config/dnsmasq_config.h"
// shm_lock()
#include "shmem.h"
static struct {
const char *name;
const char *description;
} config_topics[] =
{
{ "dns", "DNS server settings" },
{ "dhcp", "DHCP server settings" },
{ "resolver", "Resolver settings" },
{ "database", "Database settings" },
{ "webserver", "Webserver and API settings" },
{ "files", "File locations" },
{ "misc", "Miscellaneous settings" },
{ "debug", "Debug settings" }
};
static struct {
const char *name;
struct {
const char *addr1;
const char *addr2;
} v4;
struct {
const char *addr1;
const char *addr2;
} v6;
} dns_server[] =
{
{ "Google (ECS, DNSSEC)", { "8.8.8.8", "8.8.4.4" }, { "2001:4860:4860:0:0:0:0:8888", "2001:4860:4860:0:0:0:0:8844" } },
{ "OpenDNS (ECS, DNSSEC)", { "208.67.222.222", "208.67.220.220" }, {"2620:119:35::35", "2620:119:53::53"} },
{ "Level3", { "4.2.2.1", "4.2.2.2" }, { NULL, NULL } },
{ "Comodo", { "8.26.56.26", "8.20.247.20" }, { NULL, NULL} },
{ "DNS.WATCH (DNSSEC)", { "84.200.69.80", "84.200.70.40" }, { "2001:1608:10:25:0:0:1c04:b12f", "2001:1608:10:25:0:0:9249:d69b" } },
{ "Quad9 (filtered, DNSSEC)", {"9.9.9.9", "149.112.112.112" }, { "2620:fe::fe", "2620:fe::9" } },
{ "Quad9 (unfiltered, no DNSSEC)", { "9.9.9.10", "149.112.112.10" }, { "2620:fe::10", "2620:fe::fe:10" } },
{ "Quad9 (filtered, ECS, DNSSEC)", { "9.9.9.11", "149.112.112.11" }, { "2620:fe::11", "2620:fe::fe:11" } },
{ "Cloudflare (DNSSEC)", { "1.1.1.1", "1.0.0.1" }, { "2606:4700:4700::1111", "2606:4700:4700::1001" } }
};
// 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 *addJSONvalue(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_UINT16:
return cJSON_CreateNumber(val->u16);
case CONF_LONG:
return cJSON_CreateNumber(val->l);
case CONF_ULONG:
return cJSON_CreateNumber(val->ul);
case CONF_DOUBLE:
return cJSON_CreateNumber(val->d);
case CONF_STRING:
case CONF_STRING_ALLOCATED:
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_ENUM_LISTENING_MODE:
return cJSON_CreateStringReference(get_listening_mode_str(val->listening_mode));
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
}
case CONF_JSON_STRING_ARRAY:
{
// Return a duplicate to ensure our instance isn't getting freed
// after returning the reply
return cJSON_Duplicate(val->json, true);
}
default:
return NULL;
}
}
static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem)
{
if(conf_item == NULL || elem == NULL)
{
log_debug(DEBUG_CONFIG, "getJSONvalue(%p, %p) called with invalid arguments, skipping",
conf_item, elem);
return "invalid arguments";
}
switch(conf_item->t)
{
case CONF_BOOL:
{
// Check type
if(!cJSON_IsBool(elem))
return "not of type bool";
// Set item
conf_item->v.b = elem->valueint;
log_debug(DEBUG_CONFIG, "Set %s to %s", conf_item->k, conf_item->v.b ? "true" : "false");
break;
}
case CONF_INT:
{
// 1. Check it is a number
// 2. Check the number is within the allowed range for the given data type
if(!cJSON_IsNumber(elem) ||
elem->valuedouble < INT_MIN || elem->valuedouble > INT_MAX)
return "not of type integer";
// Set item
conf_item->v.i = elem->valueint;
log_debug(DEBUG_CONFIG, "Set %s to %i", conf_item->k, conf_item->v.i);
break;
}
case CONF_UINT:
{
// 1. Check it is a number
// 2. Check the number is within the allowed range for the given data type
if(!cJSON_IsNumber(elem) ||
elem->valuedouble < 0 || elem->valuedouble > UINT_MAX)
return "not of type unsigned integer";
// Set item
conf_item->v.ui = elem->valuedouble;
log_debug(DEBUG_CONFIG, "Set %s to %u", conf_item->k, conf_item->v.ui);
break;
}
case CONF_UINT16:
{
// 1. Check it is a number
// 2. Check the number is within the allowed range for the given data type
if(!cJSON_IsNumber(elem) ||
elem->valuedouble < 0 || elem->valuedouble > UINT16_MAX)
return "not of type unsigned integer (16bit)";
// Set item
conf_item->v.ui = elem->valuedouble;
log_debug(DEBUG_CONFIG, "Set %s to %u", conf_item->k, conf_item->v.ui);
break;
}
case CONF_LONG:
{
// 1. Check it is a number
// 2. Check the number is within the allowed range for the given data type
if(!cJSON_IsNumber(elem) ||
elem->valuedouble < LONG_MIN || elem->valuedouble > LONG_MAX)
return "not of type long";
// Set item
conf_item->v.l = elem->valuedouble;
log_debug(DEBUG_CONFIG, "Set %s to %li", conf_item->k, conf_item->v.l);
break;
}
case CONF_ULONG:
{
// 1. Check it is a number
// 2. Check the number is within the allowed range for the given data type
if(!cJSON_IsNumber(elem) ||
elem->valuedouble < 0 || elem->valuedouble > ULONG_MAX)
return "not of type unsigned long";
// Set item
conf_item->v.ul = elem->valuedouble;
log_debug(DEBUG_CONFIG, "Set %s to %lu", conf_item->k, conf_item->v.ul);
break;
}
case CONF_DOUBLE:
{
// Check it is a number
if(!cJSON_IsNumber(elem))
return "not of type unsigned long";
// Set item
conf_item->v.d = elem->valuedouble;
log_debug(DEBUG_CONFIG, "Set %s to %f", conf_item->k, conf_item->v.d);
break;
}
case CONF_STRING:
case CONF_STRING_ALLOCATED:
{
// Check type
if(!cJSON_IsString(elem))
return "not of type string";
// Free previously allocated memory (if applicable)
if(conf_item->t == CONF_STRING_ALLOCATED)
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, conf_item->v.s);
break;
}
case CONF_ENUM_PTR_TYPE:
{
// Check type
if(!cJSON_IsString(elem))
return "not of type string";
const int ptr_type = get_ptr_type_val(elem->valuestring);
if(ptr_type == -1)
return "invalid option";
// Set item
conf_item->v.ptr_type = ptr_type;
log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.ptr_type);
break;
}
case CONF_ENUM_BUSY_TYPE:
{
// Check type
if(!cJSON_IsString(elem))
return "not of type string";
const int busy_reply = get_busy_reply_val(elem->valuestring);
if(busy_reply == -1)
return "invalid option";
// Set item
conf_item->v.busy_reply = busy_reply;
log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.busy_reply);
break;
}
case CONF_ENUM_BLOCKING_MODE:
{
// Check type
if(!cJSON_IsString(elem))
return "not of type string";
const int blocking_mode = get_blocking_mode_val(elem->valuestring);
if(blocking_mode == -1)
return "invalid option";
// Set item
conf_item->v.blocking_mode = blocking_mode;
log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.blocking_mode);
break;
}
case CONF_ENUM_REFRESH_HOSTNAMES:
{
// Check type
if(!cJSON_IsString(elem))
return "not of type string";
const int refresh_hostnames = get_refresh_hostnames_val(elem->valuestring);
if(refresh_hostnames == -1)
return "invalid option";
// Set item
conf_item->v.refresh_hostnames = 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:
{
// Check type
if(!cJSON_IsNumber(elem))
return "not of type integer";
// Check allowed interval
if(elem->valuedouble < PRIVACY_SHOW_ALL || elem->valuedouble > PRIVACY_MAXIMUM)
return "not within valid range";
// Set item
conf_item->v.i = elem->valueint;
log_debug(DEBUG_CONFIG, "Set %s to %d", conf_item->k, conf_item->v.i);
break;
}
case CONF_STRUCT_IN_ADDR:
{
struct in_addr addr4 = { 0 };
if(!cJSON_IsString(elem))
return "not of type string";
if(!inet_pton(AF_INET, elem->valuestring, &addr4))
return "not a valid IPv4 address";
// Set item
memcpy(&conf_item->v.in_addr, &addr4, sizeof(addr4));
log_debug(DEBUG_CONFIG, "Set %s to %s", conf_item->k, elem->valuestring);
break;
}
case CONF_STRUCT_IN6_ADDR:
{
struct in6_addr addr6 = { 0 };
if(!cJSON_IsString(elem))
return "not of type string";
if(!inet_pton(AF_INET6, elem->valuestring, &addr6))
return "not a valid IPv6 address";
// Set item
memcpy(&conf_item->v.in6_addr, &addr6, sizeof(addr6));
log_debug(DEBUG_CONFIG, "Set %s to %s", conf_item->k, elem->valuestring);
break;
}
case CONF_JSON_STRING_ARRAY:
{
if(!cJSON_IsArray(elem))
return "not of type array";
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))
return "array has invalid elements";
}
// If we reach this point, all elements are valid
conf_item->v.json = cJSON_Duplicate(elem, true);
}
}
return NULL;
}
static int api_config_get(struct ftl_conn *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();
// Does the user request only a subset of /config?
char **requested_path = NULL;
unsigned int min_level = 0;
if(api->item != NULL && strlen(api->item) > 0)
{
requested_path = gen_config_path(api->item, '/');
min_level = config_path_depth(requested_path);
}
// 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++)
{
// Get pointer to memory location of this conf_item
struct conf_item *conf_item = get_conf_item(&config, i);
// Get path depth
unsigned int level = config_path_depth(conf_item->p);
// Subset checking (if requested)
if(min_level > 0)
{
// Skip entry if level is too deep (don't skip arrays here)
if(min_level > level && conf_item->t != CONF_JSON_STRING_ARRAY)
continue;
// Skip entry if level if too deep (add one level for the array itself)
if(min_level > level+1 && conf_item->t == CONF_JSON_STRING_ARRAY)
continue;
// Check equality of paths up to the requested level (if any)
// Examples:
// requested was /config/dnsmasq -> skip all entries that do not start in dnsmasq.
// requested was /config/dnsmasq/dhcp -> skip all entries that do not start in dhcp
// etc.
if(!check_paths_equal(conf_item->p, requested_path, min_level - 1))
continue;
}
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)
{
// Create the config item leaf object
cJSON *leaf = JSON_NEW_OBJECT();
// Add description
JSON_REF_STR_IN_OBJECT(leaf, "description", conf_item->h);
// Add allowed properties (if applicable)
if(conf_item->a != NULL)
JSON_ADD_ITEM_TO_OBJECT(leaf, "allowed", conf_item->a);
else
JSON_ADD_NULL_TO_OBJECT(leaf, "allowed");
// Add config item type
JSON_REF_STR_IN_OBJECT(leaf, "type", get_conf_type_str(conf_item->t));
// Add current value
cJSON *val = addJSONvalue(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;
}
JSON_ADD_ITEM_TO_OBJECT(leaf, "value", val);
// Add default value
cJSON *dval = addJSONvalue(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;
}
JSON_ADD_ITEM_TO_OBJECT(leaf, "default", dval);
// Add config item flags
cJSON *flags = JSON_NEW_OBJECT();
JSON_ADD_BOOL_TO_OBJECT(flags, "restart_dnsmasq", conf_item->f & FLAG_RESTART_DNSMASQ);
JSON_ADD_BOOL_TO_OBJECT(flags, "advanced", conf_item->f & FLAG_ADVANCED_SETTING);
JSON_ADD_ITEM_TO_OBJECT(leaf, "flags", flags);
// Attach leave object to tree of objects
JSON_ADD_ITEM_TO_OBJECT(parent, conf_item->p[level - 1], leaf);
}
else
{
// Create the config item leaf object
cJSON *leaf = addJSONvalue(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);
}
}
// Release allocated memory
if(requested_path != NULL)
free_config_path(requested_path);
// Build and return JSON response
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "config", config_j);
JSON_SEND_OBJECT(json);
}
static int api_config_patch(struct ftl_conn *api)
{
// Is there a payload with valid JSON data?
if (api->payload.json == NULL)
{
if (api->payload.json_error == NULL)
return send_json_error(api, 400,
"bad_request",
"No request body data",
NULL);
else
return send_json_error(api, 400,
"bad_request",
"Invalid request body data (no valid JSON), error before hint",
api->payload.json_error);
}
// Is there a "config" object at the root of the received JSON payload?
cJSON *conf = cJSON_GetObjectItem(api->payload.json, "config");
if (!cJSON_IsObject(conf))
{
return send_json_error(api, 400,
"body_error",
"No \"config\" object in body data",
NULL);
}
// Read all known config items
bool config_changed = false;
bool dnsmasq_changed = false;
bool rewrite_custom_list = false;
struct config newconf;
duplicate_config(&newconf, &config);
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
{
// Get pointer to memory location of this conf_item (copy)
struct conf_item *new_item = get_conf_item(&newconf, i);
// Get path depth
unsigned int level = config_path_depth(new_item->p);
cJSON *elem = conf;
// Parse tree of properties and get the individual JSON elements
for(unsigned int j = 0; j < level; j++)
elem = cJSON_GetObjectItem(elem, new_item->p[j]);
// Check if this element is present - it doesn't have to be!
if(elem == NULL)
{
log_debug(DEBUG_CONFIG, "%s not in JSON payload", new_item->k);
continue;
}
// Try to set value and report error on failure
const char *response = getJSONvalue(new_item, elem);
if(response != NULL)
{
log_err("/api/config: %s invalid: %s", new_item->k, response);
continue;
}
// Get pointer to memory location of this conf_item (global)
struct conf_item *conf_item = get_conf_item(&config, i);
// Skip processing if value didn't change compared to current value
if(compare_config_item(new_item, conf_item))
{
log_debug(DEBUG_CONFIG, "Config item %s: Unchanged", conf_item->k);
continue;
}
// Memorize that at least one config item actually changed
config_changed = true;
// If we reach this point, a valid setting was found and changed
// Check if this item requires a config-rewrite + restart of dnsmasq
if(conf_item->f & FLAG_RESTART_DNSMASQ)
dnsmasq_changed = true;
// Check if this item requires a rewrite of the custom.list file
if(conf_item == &newconf.dns.hosts)
rewrite_custom_list = true;
}
// Process new config only when at least one value changed
if(config_changed)
{
// Request restart of FTL
if(dnsmasq_changed)
{
char errbuf[ERRBUF_SIZE] = { 0 };
if(write_dnsmasq_config(&newconf, true, errbuf))
api->ftl.restart = true;
else
{
return send_json_error(api, 400,
"bad_request",
"Invalid configuration",
errbuf);
}
}
else if(rewrite_custom_list)
{
// 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);
// Store changed configuration to disk
writeFTLtoml(true);
// Reload debug levels
set_debug_flags();
}
else
{
// Nothing changed, merely release copied config memory
free_config(&newconf);
}
// Return full config after possible changes above
return api_config_get(api);
}
// Inspired by https://stackoverflow.com/a/32496721
//static void replace_char(char* str, char find, char replace)
//{
// for (char *current_pos = strchr(str, find); (current_pos = strchr(str+1, find)) != NULL; *current_pos = replace);
//}
static int api_config_put_delete(struct ftl_conn *api)
{
if(api->item == NULL || strlen(api->item) == 0)
return 0;
char **requested_path = gen_config_path(api->item, '/');
const unsigned int min_level = config_path_depth(requested_path);
const char *hint = NULL, *message = NULL;
if(api->method == HTTP_PUT)
hint = "Use, e.g., PUT /config/dnsmasq/upstreams/127.0.0.1 to add \"127.0.0.1\" to config.dns.upstreams";
else
hint = "Use, e.g., DELETE /config/dnsmasq/upstreams/127.0.0.1 to remove \"127.0.0.1\" from config.dns.upstreams";
if(min_level < 2)
{
// Release allocated memory
if(requested_path != NULL)
free_config_path(requested_path);
return send_json_error(api, 400,
"bad_request",
"Invalid path depth",
hint);
}
char *new_item_str = requested_path[min_level - 1];
// Convert path to items, e.g.,
// dnsmasq/dhcp/active -> dhcp.active
//char *item = strdup(api->item);
//replace_char(item, '/', '.');
// Read all known config items
bool dnsmasq_changed = false;
bool rewrite_custom_list = false;
bool found = false;
struct config newconf;
duplicate_config(&newconf, &config);
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
{
// Get pointer to memory location of this conf_item
struct conf_item *new_item = get_conf_item(&newconf, i);
// We support PUT only for adding to string arrays
if(new_item->t != CONF_JSON_STRING_ARRAY)
continue;
// Get path depth
const unsigned int level = config_path_depth(new_item->p);
// Check equality of paths up to the requested level (if any)
// Examples:
// requested was /config/dnsmasq -> skip all entries that do not start in dnsmasq.
// requested was /config/dnsmasq/dhcp -> skip all entries that do not start in dhcp
// etc.
if(!check_paths_equal(new_item->p, requested_path, max(min_level - 2, level - 1)))
continue;
// Check if this is a property where we want to add an item
if(min_level != level + 1)
continue;
// Check if this entry does already exist in the array
int idx = 0;
for(; idx < cJSON_GetArraySize(new_item->v.json); idx++)
{
cJSON *elem = cJSON_GetArrayItem(new_item->v.json, idx);
if(elem != NULL && elem->valuestring != NULL &&
strcmp(elem->valuestring, new_item_str) == 0)
{
found = true;
break;
}
}
if(api->method == HTTP_PUT)
{
if(found)
{
// Item already present
message = "Item already present";
hint = "Uniqueness of items is enforced";
break;
}
else
{
// Add new item to array
JSON_COPY_STR_TO_ARRAY(new_item->v.json, new_item);
found = true;
}
}
else
{
if(found)
{
// Remove item from array
cJSON_DeleteItemFromArray(new_item->v.json, idx);
}
else
{
// Item not found
message = "Item not found";
hint = "Can only delete existing items";
break;
}
}
// If we reach this point, a valid setting was found and changed
// Check if this item requires a config-rewrite + restart of dnsmasq
if(new_item->f & FLAG_RESTART_DNSMASQ)
dnsmasq_changed = true;
// Check if this item requires a rewrite of the custom.list file
if(new_item == &newconf.dns.hosts)
rewrite_custom_list = true;
break;
}
// Release allocated memory
if(requested_path != NULL)
free_config_path(requested_path);
// Error 404 if not found
if(!found || message != NULL)
{
// For any other error, a more specific message will have been added
// above
if(!message)
message = "No item specified";
return send_json_error(api, 400,
"bad_request",
message,
hint);
}
// We need to build a new config (and carefully test it!) whenever dnsmasq
// options have changed that need a restart of the resolver
if(dnsmasq_changed)
{
char errbuf[ERRBUF_SIZE] = { 0 };
// Request restart of FTL
if(write_dnsmasq_config(&newconf, true, errbuf))
api->ftl.restart = true;
else
{
// The new config did not work
return send_json_error(api, 400,
"bad_request",
"Invalid configuration",
errbuf);
}
}
else if(rewrite_custom_list)
{
// 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);
// Store changed configuration to disk
writeFTLtoml(true);
// Reload debug levels
set_debug_flags();
return api->method == HTTP_PUT ? 201 : 204; // 201 - Created or 204 - No content
}
// 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 if(api->method == HTTP_PUT || api->method == HTTP_DELETE)
return api_config_put_delete(api);
return 0;
}
int api_config_topics(struct ftl_conn *api)
{
cJSON *topics = JSON_NEW_ARRAY();
for(unsigned int i = 0; i < ArraySize(config_topics); i++)
{
cJSON *topic = JSON_NEW_OBJECT();
JSON_REF_STR_IN_OBJECT(topic, "name", config_topics[i].name);
JSON_REF_STR_IN_OBJECT(topic, "description", config_topics[i].description);
JSON_ADD_ITEM_TO_ARRAY(topics, topic);
}
// Build and return JSON response
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "topics", topics);
JSON_SEND_OBJECT(json);
}
int api_config_server(struct ftl_conn *api)
{
cJSON *servers = JSON_NEW_ARRAY();
for(unsigned int i = 0; i < ArraySize(dns_server); i++)
{
cJSON *server = JSON_NEW_OBJECT();
JSON_REF_STR_IN_OBJECT(server, "name", dns_server[i].name);
cJSON *v4 = JSON_NEW_ARRAY();
if(dns_server[i].v4.addr1 != NULL)
JSON_REF_STR_IN_ARRAY(v4, dns_server[i].v4.addr1);
if(dns_server[i].v4.addr2 != NULL)
JSON_REF_STR_IN_ARRAY(v4, dns_server[i].v4.addr2);
JSON_ADD_ITEM_TO_OBJECT(server, "v4", v4);
cJSON *v6 = JSON_NEW_ARRAY();
if(dns_server[i].v6.addr1 != NULL)
JSON_REF_STR_IN_ARRAY(v6, dns_server[i].v6.addr1);
if(dns_server[i].v6.addr2 != NULL)
JSON_REF_STR_IN_ARRAY(v6, dns_server[i].v6.addr2);
JSON_ADD_ITEM_TO_OBJECT(server, "v6", v6);
JSON_ADD_ITEM_TO_ARRAY(servers, server);
}
// Build and return JSON response
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "server", servers);
JSON_SEND_OBJECT(json);
}