Skip to content
Snippets Groups Projects
config.c 25.9 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * 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
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    #include "utils.h"
    
    #include "config.h"
    #include "filter.h"
    
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    char **dns_server_ip;
    size_t dns_server_cnt;
    
    char bundle_path[MAX_FILE_NAME] = {0};
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    
    // 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
    
    struct list_head profiles, bundles;
    
    // 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)
    
    		value += 7;
    
    	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);
    	}
    
    
    	return node;
    
    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++) {
    
    		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(&regex, mac_regex, REG_EXTENDED | REG_NOSUB);
    	if (reti) {
    
    		log_error("Could not compile regex");
    
    	// Check if the input is a MAC address
    
    	reti = regexec(&regex, input, 0, NULL, 0);
    
    	if (!reti) {
    
    		// 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
    
    		if (len <= strlen(input)) {
    
    			log_error("Resolved MAC buffer too small");
    
    
    		snprintf(resolved_mac, len, "%s", input);
    
    		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_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) {
    
    			*serv_enabled = true;
    
    
    	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_io_vector_adapter_destroy(source);
    		free(mac_arr);
    
    	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]));
    
    			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);
    
    	if (tb[PROFILE_HOST]) {
    
    		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;
    
    	if (tb[FILTER_SCHEDULE]) {
    
    		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));
    
    	if (tb[FILTER_BUNDLE]) {
    
    		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");
    
    		uci_perror(ctx, "Error");
    
    	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);
    
    	uci_free_context(ctx);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    // 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;
    
    	char buffer[BUFSIZE] = {0};
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	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)
    
    		ip_addr_len = INET6_ADDRSTRLEN;
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	else
    
    		ip_addr_len = INET_ADDRSTRLEN;
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	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");
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		return -1;
    	}
    
    
    	struct uci_package *dhcp_pkg = NULL;
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	if (uci_load(dhcp_ctx, "dhcp", &dhcp_pkg)) {
    
    		log_error("failed to load dhcp uci file");
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		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;
    }
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    // Create dns server ip list
    // @return 0 on success else -1
    
    int init_dns_server_list(void)
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    {
    	char filename[256] = {0};
    
    
    	dns_server_ip = NULL;
    	dns_server_cnt = 0;
    
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	if (get_resolver_file(&filename[0]) < 0) {
    
    		log_error("cannot get resolver file, init dns server fail");
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		return -1;
    	}
    	// Get the count of nameservers corresponding to IPv4 address.
    
    	int ret;
    	size_t server_num = 0, ipv6_server_num = 0;
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	ret = fetch_nameservers(filename, 0, 0, 0);
    
    	if (ret <= 0) {
    		log_error("No IPv4 nameservers found");
    	} else {
    		server_num = (size_t)ret;
    	}
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	// 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;
    	}
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	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");
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    			return -1;
    		}
    
    		for (size_t i = 0; i < server_num; i++) {
    
    			dns_server_ip[i] = (char *)calloc(INET_ADDRSTRLEN, sizeof(char));
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    			if (dns_server_ip[i] == NULL) {
    
    				log_error("Memory allocation failed for dns_server_ip");
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    				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));
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    			if (dns_server_ip[i + server_num] == NULL) {
    
    				log_error("Memory allocation failed for ipv6 dns_server_ip");
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    				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");
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    				return -1;
    			}
    		}
    
    		// Store the address.
    		if (server_num) {
    			if (fetch_nameservers(filename, 1, 0, 0) <= 0) {
    
    				log_error("reading dns server failed");
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    				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)
    
    	parse_urlfilter(serv_enabled);