Newer
Older
/*
* config.c: URL filter daemon
*
* Copyright (C) 2021-2024 iopsys Software Solutions AB. All rights reserved.
*
* Author: Rahul Thakur <rahul.thakur@iopsys.eu>
* Author: Mohd Husaam Mehdi <husaam.mehdi@iopsys.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <arpa/inet.h> // For INET_ADDRSTRLEN and INET6_ADDRSTRLEN
#include <netdb.h> // For NI_MAXHOST
#include <ctype.h>
#include <uci.h>
#include <sys/stat.h>
#include "config.h"
#include "filter.h"
char bundle_path[MAX_FILE_NAME] = {0};
// history of blocked accesses
unsigned int max_block_history = 50;
unsigned int blocked_index = 0;
unsigned int blocked_count = 0;
struct blocked_url_entry *blocked_history = NULL;
// common structures for different UCI sections
Amin Ben Romdhane
committed
// thread related stuff for bundles
extern pthread_mutex_t bundles_mutex;
// This function converts day of week to index, Sunday being 0, this is in line
// with the tm structure in the time.h file and eases comparison
// @param day is day of the week
// @return nothing
static void convert_day_to_index(bool *day, struct uci_list *day_list)
unsigned j = 0;
for (j = 0; j < 7; j++)
day[j] = FILTER_INACTIVE;
struct uci_element *el;
uci_foreach_element(day_list, el) {
if (strcasecmp(el->name, "Sunday") == 0)
day[0] = FILTER_ACTIVE;
else if (strcasecmp(el->name, "Monday") == 0)
day[1] = FILTER_ACTIVE;
else if (strcasecmp(el->name, "Tuesday") == 0)
day[2] = FILTER_ACTIVE;
else if (strcasecmp(el->name, "Wednesday") == 0)
day[3] = FILTER_ACTIVE;
else if (strcasecmp(el->name, "Thursday") == 0)
day[4] = FILTER_ACTIVE;
else if (strcasecmp(el->name, "Friday") == 0)
day[5] = FILTER_ACTIVE;
else if (strcasecmp(el->name, "Saturday") == 0)
day[6] = FILTER_ACTIVE;
// Function to create and initialize a new url_list node
static struct url_list *create_url_list_node(const char *value)
{
if (value && strncmp(value, "http://", 7) == 0)
else if (value && strncmp(value, "https://", 8) == 0)
value += 8;
struct url_list *node = calloc(1, sizeof(struct url_list));
if (node) {
size_t url_len = (strlen(value) + 1) > MAX_URL_LENGTH ? MAX_URL_LENGTH : (strlen(value) + 1);
node->url = NULL;
node->url = calloc(url_len, 1);
if (node->url) {
snprintf(node->url, url_len, "%s", value);
}
INIT_LIST_HEAD(&node->list);
}
}
static int mac_exists_in_array(const char *mac, char **mac_arr, unsigned int hosts_idx)
{
size_t i;
for (i = 0; i < hosts_idx; i++) {
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
if (strcasecmp(mac_arr[i], mac) == 0) {
return 1;
}
}
return 0;
}
static int resolve_hostname_from_file(const char *input, char *resolved_mac,
size_t len,
char **mac_arr,
unsigned int hosts_idx,
const char *lease_file_path)
{
if (!lease_file_path) {
log_error("Invalid arguments provided");
return -1;
}
// Open /tmp/dhcp.leases to find the MAC
FILE *leases_file = fopen(lease_file_path, "r");
if (leases_file == NULL) {
return -1;
}
char line[BUFSIZE] = {0};
char lease_time[16] = {0};
char mac_address[MAC_LENGTH] = {0};
char ip_address[INET6_ADDRSTRLEN] = {0};
char hostname[NI_MAXHOST] = {0};
char client_id[128] = {0};
bool found_mac = false;
// Search for the hostname
while (fgets(line, sizeof(line), leases_file) != NULL) {
int fields = sscanf(line, "%15s %17s %39s %1024s %127s", lease_time, mac_address, ip_address, hostname, client_id);
if (fields != 5) {
continue;
}
if (strcasecmp(input, hostname) == 0) {
// Check if MAC already exists in mac_arr
if (mac_exists_in_array(mac_address, mac_arr, hosts_idx)) {
fclose(leases_file);
return 1; // MAC already present
}
// Validate buffer size
if (len <= strlen(mac_address)) {
fclose(leases_file);
return 1;
}
snprintf(resolved_mac, len, "%s", mac_address);
// Convert to lowercase
for (i = 0; i < len - 1 && resolved_mac[i] != '\0'; i++) {
resolved_mac[i] = (char)tolower(resolved_mac[i]);
}
found_mac = true;
break;
}
}
fclose(leases_file);
if (!found_mac) {
return -1;
}
return 0;
}
static int resolve_mac_or_hostname(const char *input,
char *resolved_mac,
size_t len,
char **mac_arr,
unsigned int hosts_idx)
if (!input || !resolved_mac || !mac_arr || !len) {
log_error("Invalid arguments provided");
return -1;
}
const char *mac_regex = "^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$";
regex_t regex;
int reti;
// Compile the regex for MAC address
reti = regcomp(®ex, mac_regex, REG_EXTENDED | REG_NOSUB);
if (reti) {
log_error("Could not compile regex");
}
// Check if the input is a MAC address
reti = regexec(®ex, input, 0, NULL, 0);
regfree(®ex);
// Check if MAC already exists in mac_arr
if (mac_exists_in_array(input, mac_arr, hosts_idx)) {
return -1; // MAC already present
}
// Validate buffer size
log_error("Resolved MAC buffer too small");
snprintf(resolved_mac, len, "%s", input);
// Convert to lowercase
for (i = 0; i < len - 1 && resolved_mac[i] != '\0'; i++) {
resolved_mac[i] = (char)tolower(resolved_mac[i]);
}
if (resolve_hostname_from_file(input, resolved_mac, len, mac_arr, hosts_idx,
"/tmp/dhcp.leases") == -1) {
if (resolve_hostname_from_file(input, resolved_mac, len, mac_arr, hosts_idx,
"/etc/parentalcontrol/dhcp.leases") == -1) {
log_error2("No MAC address found for hostname:", input);
}
static void parse_and_add_schedule_section_to_filter(struct uci_section *section, struct urlfilter *filter)
enum {
SCHEDULE_START,
SCHEDULE_DURATION,
SCHEDULE_ENABLE,
SCHEDULE_DAY,
NUM_SCHEDULE_ATTRS // Number of attributes in the schedule section
};
const struct uci_parse_option schedule_opts[] = {
[SCHEDULE_START] = { .name = "start_time", .type = UCI_TYPE_STRING },
[SCHEDULE_DURATION] = { .name = "duration", .type = UCI_TYPE_STRING },
[SCHEDULE_ENABLE] = { .name = "enable", .type = UCI_TYPE_STRING },
[SCHEDULE_DAY] = { .name = "day", .type = UCI_TYPE_LIST },
};
struct uci_option *tb[NUM_SCHEDULE_ATTRS] = {0}; // Initialize tb array to hold parsed options
struct schedule *new_schedule = calloc(1, sizeof(struct schedule));
uci_parse_section(section, schedule_opts, NUM_SCHEDULE_ATTRS, tb);
snprintf(new_schedule->name, MAX_NAME_LENGTH, "%s", section->e.name);
// Process the parsed options
if (tb[SCHEDULE_START]) {
snprintf(new_schedule->start_time, MAX_TIME_LENGTH, "%s", tb[SCHEDULE_START]->v.string);
}
if (tb[SCHEDULE_DURATION]) {
new_schedule->duration = (unsigned int)strtoul(tb[SCHEDULE_DURATION]->v.string, NULL, 10);;
}
if (tb[SCHEDULE_ENABLE]) {
new_schedule->enable = (strcmp(tb[SCHEDULE_ENABLE]->v.string, "1") == 0);
}
if (tb[SCHEDULE_DAY]) {
convert_day_to_index(&new_schedule->day[0], &tb[SCHEDULE_DAY]->v.list);
}
list_add_tail(&new_schedule->list, &filter->schedules);
}
// Helper function to find the schedule by name
static void find_and_add_schedule(const char *schedule_name, struct urlfilter *filter)
{
if (!schedule_name || strlen(schedule_name) == 0 || !filter)
return;
struct uci_context *ctx = uci_alloc_context();
struct uci_package *pkg;
struct uci_element *e;
// Load and parse 'schedules' package
if (uci_load(ctx, "schedules", &pkg) != UCI_OK) {
log_info("Failed to load schedules package");
uci_perror(ctx, "Error");
uci_free_context(ctx);
return;
}
uci_foreach_element(&pkg->sections, e) {
struct uci_section *section = uci_to_section(e);
if ((strcmp(section->type, "schedule") == 0) &&
(strcmp(section->e.name, schedule_name) == 0)) {
parse_and_add_schedule_section_to_filter(section, filter);
static void parse_globals_section(struct uci_section *section, bool *serv_enabled)
{
if (section == NULL || serv_enabled == NULL)
return;
enum {
GLOBALS_ENABLE,
GLOBALS_BUNDLE_PATH,
GLOBALS_MAX_BLOCK_HISTORY,
NUM_GLOBALS_ATTRS // Number of attributes in the profile section
};
const struct uci_parse_option profile_opts[] = {
[GLOBALS_ENABLE] = { .name = "urlfilter", .type = UCI_TYPE_STRING },
[GLOBALS_BUNDLE_PATH] = { .name = "bundle_path", .type = UCI_TYPE_STRING },
[GLOBALS_MAX_BLOCK_HISTORY] = { .name = "max_block_history", .type = UCI_TYPE_STRING },
};
struct uci_option *tb[NUM_GLOBALS_ATTRS] = {0}; // Initialize tb array to hold parsed options
uci_parse_section(section, profile_opts, NUM_GLOBALS_ATTRS, tb);
if (tb[GLOBALS_ENABLE]) {
if (strlen(tb[GLOBALS_ENABLE]->v.string) == 0)
return;
if (strcmp(tb[GLOBALS_ENABLE]->v.string, "1") == 0 ||
strcmp(tb[GLOBALS_ENABLE]->v.string, "true") == 0) {
if (tb[GLOBALS_BUNDLE_PATH]) {
if (strlen(tb[GLOBALS_BUNDLE_PATH]->v.string))
snprintf(bundle_path, MAX_FILE_NAME, "%s", tb[GLOBALS_BUNDLE_PATH]->v.string);
}
if (!strlen(bundle_path)) {
snprintf(bundle_path, MAX_FILE_NAME, "%s", "/tmp/parentalcontrol");
}
if (tb[GLOBALS_MAX_BLOCK_HISTORY]) {
max_block_history = (unsigned int)strtoul(tb[GLOBALS_MAX_BLOCK_HISTORY]->v.string, NULL, 10);
if (max_block_history > MAX_BLOCK_HISTORY_LIMIT) {
max_block_history = MAX_BLOCK_HISTORY_LIMIT;
}
}
struct blocked_url_entry *tmp = NULL;
if (blocked_history == NULL) {
tmp = (struct blocked_url_entry *)calloc(max_block_history, sizeof(struct blocked_url_entry));
} else {
tmp = (struct blocked_url_entry *)realloc(blocked_history, max_block_history * sizeof(struct blocked_url_entry));
}
if (tmp) {
blocked_history = tmp;
}
blocked_index = 0;
blocked_count = 0;
}
static bool load_stringstore_from_file(struct urlbundle *bundle)
{
if (!strlen(bundle_path)) {
log_info("no bundle_path path given, cannot load bundles");
return false;
}
char filename[BUFSIZE] = {0};
snprintf(filename, sizeof(filename), "%s/stringstore/%s.store", bundle_path, bundle->download_url.file_name);
char cmph_filename[BUFSIZE] = {0};
snprintf(cmph_filename, sizeof(cmph_filename), "%s/stringstore/%s.cmph", bundle_path, bundle->download_url.file_name);
bundle->download_url.store_file_ptr = fopen(filename, "rb");
if (!bundle->download_url.store_file_ptr) {
log_error2("could not open stringstore file", filename);
return false;
}
fread(&bundle->download_url.num_strings, sizeof(unsigned long), 1, bundle->download_url.store_file_ptr);
fread(&bundle->download_url.byte_count, sizeof(unsigned long), 1, bundle->download_url.store_file_ptr);
stringstore_init(&bundle->download_url, bundle->download_url.num_strings, bundle->download_url.byte_count, false);
// Load CMPH hash
FILE *cmph_file = fopen(cmph_filename, "r");
if (!cmph_file) {
log_error2("Error opening CMPH file for reading:", cmph_filename);
stringstore_free(&bundle->download_url);
return false;
bundle->download_url.cmph_hash = cmph_load(cmph_file);
fclose(cmph_file);
if (!bundle->download_url.cmph_hash) {
log_error("Failed to load CMPH hash from file");
stringstore_free(&bundle->download_url);
return false;
}
double mebibytes = (double)bundle->download_url.byte_count / (1024.0 * 1024.0);
syslog(LOG_INFO, "Added %.2lf MiB, %lu lines of URLs [%lu bytes] from saved stringstore\n",
mebibytes, bundle->download_url.num_strings, bundle->download_url.byte_count);
return true;
static void parse_urlbundle_section(struct uci_section *section)
BUNDLE_NAME,
BUNDLE_DOWNLOAD_URL,
BUNDLE_CUSTOM_URL,
NUM_BUNDLE_ATTRS // Number of attributes in the urlbundle section
};
const struct uci_parse_option bundle_opts[] = {
[BUNDLE_ENABLE] = { .name = "enable", .type = UCI_TYPE_STRING },
[BUNDLE_NAME] = { .name = "name", .type = UCI_TYPE_STRING },
[BUNDLE_DOWNLOAD_URL] = { .name = "download_url", .type = UCI_TYPE_STRING },
[BUNDLE_CUSTOM_URL] = { .name = "custom_url", .type = UCI_TYPE_LIST },
};
struct uci_option *tb[NUM_BUNDLE_ATTRS] = {0}; // Initialize tb array to hold parsed options
uci_parse_section(section, bundle_opts, NUM_BUNDLE_ATTRS, tb);
bool enable = false;
if (tb[BUNDLE_ENABLE]) {
enable = (strcmp(tb[BUNDLE_ENABLE]->v.string, "1") == 0);
}
if (!enable)
return;
struct urlbundle *new_bundle = calloc(1, sizeof(struct urlbundle));
INIT_LIST_HEAD(&new_bundle->custom_url);
// Process the parsed options
if (tb[BUNDLE_NAME]) {
snprintf(new_bundle->name, MAX_NAME_LENGTH, "%s", tb[BUNDLE_NAME]->v.string);
} else {
// bundle name is crucial for a bundle to exist
free(new_bundle);
return;
}
if (tb[BUNDLE_CUSTOM_URL]) {
struct uci_element *el;
uci_foreach_element(&tb[BUNDLE_CUSTOM_URL]->v.list, el) {
if (is_valid_domain(el->name)) {
struct url_list *new_custom_url = create_url_list_node(el->name);
list_add_tail(&new_custom_url->list, &new_bundle->custom_url); // Add the filter text to the list
} else {
log_error2("Invalid url in custom_url:", el->name);
if (tb[BUNDLE_DOWNLOAD_URL]) {
char *file_path = tb[BUNDLE_DOWNLOAD_URL]->v.string;
char *file_name = strrchr(file_path, '/');
// If '/' is found, move past it; otherwise, use the whole path
file_name = (file_name) ? file_name + 1 : file_path;
snprintf(new_bundle->download_url.file_name, MAX_FILE_NAME, "%s", file_name);
load_stringstore_from_file(new_bundle);
}
list_add_tail(&new_bundle->list, &bundles);
static void process_hosts(struct profile *prof, struct uci_list *host_list)
if (!prof || !host_list) {
char log_buf[16] = {0};
struct uci_element *el = NULL;
unsigned int num_hosts = 0;
uci_foreach_element(host_list, el) {
num_hosts++;
// Allocate an array to hold each line as a separate string
char **mac_arr = calloc(num_hosts, sizeof(char *));
if (!mac_arr) {
log_error("Memory allocation failed");
unsigned int idx = 0;
el = NULL;
uci_foreach_element(host_list, el) {
char resolved_mac[MAC_LENGTH] = {0}; // Buffer to store the resolved MAC address
// get resolved_mac in lower case
int ret = resolve_mac_or_hostname(el->name, resolved_mac, sizeof(resolved_mac), mac_arr, idx);
if (ret == 0 && strlen(resolved_mac)) {
mac_arr[idx] = strdup(resolved_mac);
idx++;
}
// Step 2: Use cmph_io_vector_adapter to create the MPHF
cmph_io_adapter_t *source = cmph_io_vector_adapter((char **)mac_arr, (cmph_uint32)num_hosts);
if (!source) {
log_error("Error creating vector adapter");
free(mac_arr);
return;
}
cmph_config_t *config = cmph_config_new(source);
cmph_config_set_algo(config, CMPH_BDZ);
CMPH_HASH hash_type = CMPH_HASH_JENKINS;
cmph_config_set_hashfuncs(config, &hash_type);
cmph_t *hash = cmph_new(config);
if (!hash) {
log_error("Error creating hash for hosts");
cmph_config_destroy(config);
cmph_io_vector_adapter_destroy(source);
free(mac_arr);
return;
cmph_config_destroy(config);
stringstore_init(&prof->hosts, num_hosts, (unsigned long)(num_hosts * MAC_LENGTH), true);
if (!prof->hosts.data) {
log_error("failed to assign memory to hosts");
// Step 3: Populate the urlbundle using the MPHF
for (unsigned long i = 0; i < num_hosts; i++) {
unsigned long id = cmph_search(hash, mac_arr[i], (cmph_uint32)strlen(mac_arr[i]));
if (id < num_hosts) {
stringstore_append_string(&prof->hosts, id, mac_arr[i], strlen(mac_arr[i]));
} else {
snprintf(log_buf, sizeof(log_buf), "%lu", id);
log_error2("Warning: Hash ID out of bounds", log_buf);
}
}
// Save the hash and configuration to the bundle
prof->hosts.cmph_hash = hash;
// Cleanup
cmph_io_vector_adapter_destroy(source);
free(mac_arr);
static void parse_profile_section(struct uci_section *section)
{
enum {
PROFILE_NAME,
PROFILE_HOST,
NUM_PROFILE_ATTRS // Number of attributes in the profile section
};
const struct uci_parse_option profile_opts[] = {
[PROFILE_NAME] = { .name = "name", .type = UCI_TYPE_STRING },
[PROFILE_HOST] = { .name = "host", .type = UCI_TYPE_LIST },
};
struct uci_option *tb[NUM_PROFILE_ATTRS] = {0}; // Initialize tb array to hold parsed options
struct profile *new_profile = calloc(1, sizeof(struct profile));
INIT_LIST_HEAD(&new_profile->filters);
uci_parse_section(section, profile_opts, NUM_PROFILE_ATTRS, tb);
// Process the parsed options
snprintf(new_profile->name, MAX_NAME_LENGTH, "%s", section->e.name);
process_hosts(new_profile, &tb[PROFILE_HOST]->v.list);
}
list_add_tail(&new_profile->list, &profiles);
}
static void parse_urlfilter_section(struct uci_section *section)
enum {
FILTER_ENABLE,
FILTER_ACCESS,
FILTER_DM_PARENT,
FILTER_SCHEDULE,
FILTER_BUNDLE,
FILTER_FILTERTEXT,
NUM_FILTER_ATTRS // Number of attributes in the urlfilter section
};
const struct uci_parse_option filter_opts[] = {
[FILTER_ENABLE] = { .name = "enable", .type = UCI_TYPE_STRING },
[FILTER_ACCESS] = { .name = "access", .type = UCI_TYPE_STRING },
[FILTER_DM_PARENT] = { .name = "dm_parent", .type = UCI_TYPE_STRING },
[FILTER_SCHEDULE] = { .name = "profile_urlfilter_schedule", .type = UCI_TYPE_LIST },
[FILTER_BUNDLE] = { .name = "profile_urlbundle", .type = UCI_TYPE_LIST },
[FILTER_FILTERTEXT] = { .name = "filter_text", .type = UCI_TYPE_LIST },
};
struct uci_option *tb[NUM_FILTER_ATTRS] = {0}; // Initialize tb array to hold parsed options
struct urlfilter *filter = calloc(1, sizeof(struct urlfilter));
INIT_LIST_HEAD(&filter->filter_text);
INIT_LIST_HEAD(&filter->schedules);
uci_parse_section(section, filter_opts, NUM_FILTER_ATTRS, tb);
if (tb[FILTER_ENABLE]) {
filter->enable = (strcmp(tb[FILTER_ENABLE]->v.string, "1") == 0);
}
// deny it by default
filter->access_policy = false;
if (tb[FILTER_ACCESS]) {
filter->access_policy = (strcmp(tb[FILTER_ACCESS]->v.string, "Allow") == 0);
}
// Process the parsed options
if (tb[FILTER_DM_PARENT]) {
snprintf(filter->dm_parent, MAX_NAME_LENGTH, "%s", tb[FILTER_DM_PARENT]->v.string);
// Associate filter with the correct profile
struct profile *prof;
list_for_each_entry(prof, &profiles, list) {
if (strcmp(prof->name, filter->dm_parent) == 0) {
list_add_tail(&filter->list, &prof->filters);
break;
struct uci_element *el = NULL;
uci_foreach_element(&tb[FILTER_SCHEDULE]->v.list, el) {
find_and_add_schedule(el->name, filter); // Lookup schedule by name
}
}
filter->bundle_cnt = 0;
memset(filter->bundles, 0, sizeof(filter->bundles));
unsigned int bundle_cnt = 0;
struct uci_element *el = NULL;
uci_foreach_element(&tb[FILTER_BUNDLE]->v.list, el) {
if (bundle_cnt < MAX_BUNDLES_PER_FILTER) {
// filters could share bundles
// and it has been observed that using list_head for multiple purposes
// can be problematic, so using array
filter->bundles[bundle_cnt] = find_urlbundle(el->name); // Lookup bundle by name
if (filter->bundles[bundle_cnt]) {
bundle_cnt++;
}
}
}
filter->bundle_cnt = bundle_cnt;
if (tb[FILTER_FILTERTEXT]) {
struct uci_element *el;
uci_foreach_element(&tb[FILTER_FILTERTEXT]->v.list, el) {
struct url_list *filter_text = create_url_list_node(el->name);
list_add_tail(&filter_text->list, &filter->filter_text); // Add the filter text to the list
}
}
}
static void parse_urlfilter(bool *serv_enabled)
struct uci_context *ctx = uci_alloc_context();
struct uci_package *pkg;
struct uci_element *e;
INIT_LIST_HEAD(&profiles);
INIT_LIST_HEAD(&bundles);
// Load and parse 'parentalcontrol' package
if (uci_load(ctx, "parentalcontrol", &pkg) != UCI_OK) {
log_info("Failed to load parentalcontrol package");
*serv_enabled = false;
uci_foreach_element(&pkg->sections, e) {
struct uci_section *section = uci_to_section(e);
if (strcmp(section->type, "globals") == 0) {
parse_globals_section(section, serv_enabled);
break;
}
}
if (*serv_enabled != true) {
goto exit;
}
pthread_mutex_lock(&bundles_mutex); // Lock bundles list
uci_foreach_element(&pkg->sections, e) {
struct uci_section *section = uci_to_section(e);
if (strcmp(section->type, "urlbundle") == 0) {
parse_urlbundle_section(section);
}
}
pthread_mutex_unlock(&bundles_mutex); // Unlock bundles list
uci_foreach_element(&pkg->sections, e) {
struct uci_section *section = uci_to_section(e);
if (strcmp(section->type, "profile") == 0) {
parse_profile_section(section);
}
uci_foreach_element(&pkg->sections, e) {
struct uci_section *section = uci_to_section(e);
if (strcmp(section->type, "profile_urlfilter") == 0) {
parse_urlfilter_section(section);
// Get number of IPv4/v6 dns severs and IPv4/v6 addresses.
// @param filename name of resolver file
// @param store variable whether to store the IPv4/v6 address in global array.
static int fetch_nameservers(char *filename, bool store, size_t dns_list_offset, bool is_ipv6)
{
size_t count = 0;
size_t ip_addr_len;
FILE *fp;
memset(buffer, '\0', BUFSIZE);
if (filename == NULL || filename[0] == '\0') {
return -1;
}
fp = fopen(filename, "r");
if (fp == NULL) {
return -1;
}
if (is_ipv6)
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
while (fgets(buffer, sizeof(buffer), fp)) {
// Remove trailing newline
buffer[strcspn(buffer, "\n\r")] = 0;
char *token, *end;
token = strtok_r(buffer, " ", &end);
while (token != NULL) {
if (strncasecmp(token, "nameserver", 10) == 0) {
if ((is_ipv6 && 1 == validate_ipv6(end)) || (!is_ipv6 && 1 == validate_ip(end))) {
// Store the ip address to global array
if (true == store)
strncpy(dns_server_ip[count+dns_list_offset], end, ip_addr_len);
count++;
}
}
token = strtok_r(NULL, " ", &end);
}
}
fclose(fp);
return (int)count;
}
// Get the name of resolver file from the dhcp uci file
// @param filename variable to store filename
// @return 0 on success otherwise -1
static int get_resolver_file(char *filename)
{
struct uci_element *element = NULL;
struct uci_element *e = NULL;
struct uci_option *option = NULL;
struct uci_section *s = NULL;
struct uci_context *dhcp_ctx = uci_alloc_context();
if (!dhcp_ctx) {
log_error("failed to allocate uci context");
struct uci_package *dhcp_pkg = NULL;
log_error("failed to load dhcp uci file");
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
uci_free_context(dhcp_ctx);
return -1;
}
uci_foreach_element(&dhcp_pkg->sections, e) {
s = uci_to_section(e);
// read only filter section, skip the rest
if (strcmp(s->type, "dnsmasq") != 0)
continue;
uci_foreach_element(&s->options, element) {
option = uci_to_option(element);
if (strcmp(option->e.name, "resolvfile") == 0) {
strncpy(filename, option->v.string, 256);
uci_unload(dhcp_ctx, dhcp_pkg);
uci_free_context(dhcp_ctx);
return 0;
}
}
}
uci_unload(dhcp_ctx, dhcp_pkg);
uci_free_context(dhcp_ctx);
return -1;
}
// Create dns server ip list
// @return 0 on success else -1
dns_server_ip = NULL;
dns_server_cnt = 0;
log_error("cannot get resolver file, init dns server fail");
return -1;
}
// Get the count of nameservers corresponding to IPv4 address.
int ret;
size_t server_num = 0, ipv6_server_num = 0;
if (ret <= 0) {
log_error("No IPv4 nameservers found");
} else {
server_num = (size_t)ret;
}
// Get the count of nameservers corresponding to IPv6 address.
ret = fetch_nameservers(filename, 0, server_num, 1);
if (ret <= 0) {
log_error("No IPv6 nameservers found");
} else {
ipv6_server_num = (size_t)ret;
}
if (ipv6_server_num || server_num) {
// Create memory for storing the addresses.
dns_server_ip = (char **)calloc(server_num + ipv6_server_num, sizeof(char *));
if (dns_server_ip == NULL) {
log_error("Memory allocation failed for dns_server_ip");
return -1;
}
for (size_t i = 0; i < server_num; i++) {
dns_server_ip[i] = (char *)calloc(INET_ADDRSTRLEN, sizeof(char));
log_error("Memory allocation failed for dns_server_ip");
for (size_t j = 0; j < i; j++)
free(dns_server_ip[j]);
free(dns_server_ip);
return -1;
}
}
for (size_t i = 0; i < ipv6_server_num; i++) {
dns_server_ip[i + server_num] = (char *)calloc(INET6_ADDRSTRLEN, sizeof(char));
log_error("Memory allocation failed for ipv6 dns_server_ip");
for (size_t j = 0; j < i + server_num; j++)
free(dns_server_ip[j]);
free(dns_server_ip);
return -1;
}
}
// Store the address.
if (ipv6_server_num) {
if (fetch_nameservers(filename, 1, server_num, 1) <= 0) {
log_error("reading dns server failed");
return -1;
}
}
// Store the address.
if (server_num) {
if (fetch_nameservers(filename, 1, 0, 0) <= 0) {
log_error("reading dns server failed");
return -1;
}
}
// Update the dns server count
dns_server_cnt = server_num + ipv6_server_num;
}
return 0;
}
void free_dns_servers()
{
unsigned int i;
if (dns_server_cnt == 0 || dns_server_ip == NULL)
return;
for (i = 0; i < dns_server_cnt; i++) {
free(dns_server_ip[i]);
}
free(dns_server_ip);
dns_server_cnt = 0;
dns_server_ip = NULL;
}
// this function reads the uci config from /etc/config/parentalcontrol file and stores the
// information in daemon's data structures
int read_config(bool *serv_enabled)