Skip to content
Snippets Groups Projects
config.c 29 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>
     *
     * 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 <regex.h>
    #include <curl/curl.h>
    
    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;
    
    
    // Define structures for different UCI sections
    struct list_head profiles, bundles;
    
    // to hold global category lists
    struct urlbundle global_bundle = {0};
    
    
    /* 
     * Function to check whether a string is a proper domain/url
     * Valid domain: example.com
     * Valid domain: subdomain.example.com
     * Valid domain: my-domain.net
     * Valid domain: example-123.org
     * Invalid domain: example..com
     * Invalid domain: example@domain.com
     * Invalid domain: example
     * Invalid domain: 127.0.0.1
     */
    static int is_valid_domain(const char *domain)
    
    	regex_t regex;
    	int reti;
    
    	const char *domain_regex = "^(\\*\\.|[a-zA-Z0-9-]+\\.)?[a-zA-Z0-9-]+\\.[a-zA-Z]{2,}$";
    
    
    
    	// Compile the regular expression
    	reti = regcomp(&regex, domain_regex, REG_EXTENDED | REG_NOSUB);
    	if (reti) {
    		fprintf(stderr, "Could not compile regex\n");
    		return 0;
    	}
    
    	// Execute the regular expression
    	reti = regexec(&regex, domain, 0, NULL, 0);
    	regfree(&regex);  // Free the compiled regex
    
    	// If the regular expression matches, it's a valid domain
    	if (!reti) {
    		return 1;
    	} else if (reti == REG_NOMATCH) {
    		return 0;
    	} else {
    		// If there's some error, return false
    		return 0;
    
    // This function coverts 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 get the size of the file from the URL using a HEAD request
    static size_t get_content_length(const char *url)
    {
    	CURL *curl_handle;
    	CURLcode res;
    	curl_off_t content_length = 0;
    
    	curl_global_init(CURL_GLOBAL_DEFAULT);
    	curl_handle = curl_easy_init();
    	if (curl_handle) {
    		// Set URL
    		curl_easy_setopt(curl_handle, CURLOPT_URL, url);
    		// Set a HEAD request
    		curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1L);
    		// Follow redirects if necessary
    		curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
    
    		// Perform the request
    		res = curl_easy_perform(curl_handle);
    		if (res == CURLE_OK) {
    			// Get the content length from the header
    			res = curl_easy_getinfo(curl_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &content_length);
    			if (res != CURLE_OK || content_length <= 0) {
    				syslog(LOG_ERR, "%s:%u Failed to get content length from URL: %s\n",  __func__, __LINE__, curl_easy_strerror(res));
    				content_length = 0; // Fallback in case we don't get a valid length
    			}
    		} else {
    			syslog(LOG_ERR, "%s:%u curl_easy_perform() failed: %s\n",  __func__, __LINE__, curl_easy_strerror(res));
    		}
    
    		// Cleanup
    		curl_easy_cleanup(curl_handle);
    	}
    
    	curl_global_cleanup();
    
    	return (size_t)content_length;
    }
    
    
    // Function to create and initialize a new url_list node
    static struct url_list *create_url_list_node(const char *value)
    {
    	if (strncmp(value, "http://", 7) == 0)
    		value += 7;
    	else if (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;
    
    // Callback function used by libcurl to write data into the buffer
    static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp)
    
    	size_t total_size = size * nmemb;
    
    	struct dynamic_memory *mem = (struct dynamic_memory *)userp;
    
    	// Copy the new data to the buffer
    
    	memcpy(&(mem->data[mem->size]), contents, total_size);
    	mem->size += total_size;
    
    
    	// Null-terminate the buffer
    	mem->data[mem->size] = 0;
    
    	return total_size;
    
    // Function to free allocated memory
    static void free_memory(struct dynamic_memory *mem)
    {
    	if (mem->data) {
    		free(mem->data);
    		mem->data = NULL;
    	}
    }
    
    
    // Function to download a file from a URL using libcurl
    
    static int download_file(const char *url, struct dynamic_memory *mem)
    
    	CURL *curl_handle;
    	CURLcode res;
    
    	mem->size = 0;  // Initialize buffer size to zero
    
    	curl_global_init(CURL_GLOBAL_DEFAULT);
    	curl_handle = curl_easy_init();
    	if (curl_handle) {
    		curl_easy_setopt(curl_handle, CURLOPT_URL, url);
    		curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_callback);
    		curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)mem);
    
    		res = curl_easy_perform(curl_handle);
    		if (res != CURLE_OK) {
    
    			syslog(LOG_ERR, "%s:%u curl_easy_perform() failed: %s\n",  __func__, __LINE__, curl_easy_strerror(res));
    
    
    		curl_easy_cleanup(curl_handle);
    
    
    	curl_global_cleanup();
    
    	return 0;
    
    // Function to populate the download_url array with URLs from the downloaded file
    static void populate_bundle_from_memory(struct dynamic_memory *mem, struct urlbundle *bundle)
    
    	if (!mem || !bundle || !mem->data) {
    		syslog(LOG_ERR, "%s:%u null pointers provided!\n", __func__, __LINE__);
    		return;
    	}
    
    	if (!mem->size) {
    		syslog(LOG_ERR, "%s:%u download buffer empty!\n", __func__, __LINE__);
    		return;
    	}
    
    	// Ensure the data is null-terminated (it should be, but this is a safeguard)
    	mem->data[mem->size - 1] = '\0';  // Safeguard by making sure the last byte is null
    
    	// Step 3: Extract each URL from the memory block and store it in the new structure
    	char *end = NULL;
    	char *line = strtok_r(mem->data, "\n", &end);  // Tokenize based on newlines
    	unsigned int valid_count = 0;
    
    	while (line) {
    		if (is_valid_domain(line)) {
    
    			sm_put(bundle->download_url, line, "1");
    
    			valid_count++;
    		}
    
    		line = strtok_r(NULL, "\n", &end);
    
    
    	// Step 4: Update the bundle with the new URLs and count
    	syslog(LOG_INFO, "%s:%u Added %u valid URLs to the bundle\n", __func__, __LINE__, valid_count);
    
    static int resolve_mac_or_hostname(const char *input, char *resolved_mac, size_t len)
    
    	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) {
    		// Handle regex compilation error
    
    		syslog(LOG_ERR, "%s:%u Could not compile regex\n", __func__, __LINE__);
    
    	// Check if the input `input` is a MAC address
    	reti = regexec(&regex, input, 0, NULL, 0);
    	regfree(&regex); // Free the regex after use
    
    	if (!reti) {
    		// `input` is a valid MAC address, use it as is
    		if (len <= strlen(input)) {
    
    			syslog(LOG_ERR, "%s:%u Resolved MAC buffer too small\n", __func__, __LINE__);
    
    			return -1;
    		}
    		strncpy(resolved_mac, input, len - 1);
    		resolved_mac[len - 1] = '\0'; // Ensure null-termination
    		return 0;
    	} else {
    		// `input` is assumed to be a hostname, resolve it from /tmp/dhcp.leases
    
    		// Open and parse the /tmp/dhcp.leases file to find the MAC address
    		FILE *leases_file = fopen("/tmp/dhcp.leases", "r");
    		if (leases_file == NULL) {
    			// Handle file open error
    
    			syslog(LOG_ERR, "%s:%u Failed to open /tmp/dhcp.leases: %s\n",  __func__, __LINE__, strerror(errno));
    
    		char line[BUFSIZE] = {0};
    		char lease_time[16] = {0};
    		char mac_address[MAC_LEN] = {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 in the leases file
    		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) {
    				// Log warning about malformed line and skip it
    
    				syslog(LOG_WARNING, "%s:%u Malformed line in leases file, expected 5 fields, got %d: %s\n",
    					 __func__, __LINE__, fields, line);
    
    			if (strcasecmp(input, hostname) == 0) { // Case-insensitive comparison
    				// Hostname matches, return the corresponding MAC address
    				if (len <= strlen(mac_address)) {
    
    					syslog(LOG_ERR, "%s:%u Resolved MAC buffer too small\n", __func__, __LINE__);
    
    					fclose(leases_file);
    					return -1;
    				}
    				strncpy(resolved_mac, mac_address, len - 1);
    				resolved_mac[len - 1] = '\0'; // Ensure null-termination
    				found_mac = true;
    				break;
    			}
    		}
    
    		fclose(leases_file);
    
    		if (!found_mac) {
    			// No MAC address found for the given hostname
    
    			syslog(LOG_ERR, "%s:%u No MAC address found for hostname: %s\n",  __func__, __LINE__, 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) {
    
    		syslog(LOG_INFO, "%s:%u Failed to load schedules package\n", __func__, __LINE__);
    
    		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);
    
    // Helper function to find the urlbundle by name
    static struct urlbundle *find_urlbundle(const char *bundle_name)
    {
    	struct urlbundle *bundle = NULL;
    	list_for_each_entry(bundle, &bundles, list) {
    		if (strcmp(bundle->name, bundle_name) == 0) {
    			return bundle;
    		}
    
    static void handle_download_url(struct urlbundle *bundle, const char *url)
    
    	if (!bundle || !url || url[0] == '\0' )
    		return;
    
    	struct dynamic_memory mem = {0};
    
    	// Get the content length first
    	size_t content_length = get_content_length(url);
    
    	if (content_length == 0) {
    		syslog(LOG_ERR, "%s:%u Failed to retrieve content length, using 2 MB buffer.\n", __func__, __LINE__);
    		content_length = MAX_BUFFER_SIZE;
    	} else {
    		syslog(LOG_ERR, "%s:%u content length retrieved, length is: %zu.\n", __func__, __LINE__, content_length);
    	}
    
    	// Ensure the content length doesn't exceed the maximum buffer size (2 MB)
    	if (content_length > MAX_BUFFER_SIZE) {
    		syslog(LOG_ERR, "%s:%u The file size exceeds the maximum buffer size (2 MB).\n", __func__, __LINE__);
    		return;
    	}
    
    	// Allocate memory based on content length
    	mem.data = calloc(content_length + 1, 1);  // +1 for null terminator
    	if (!mem.data) {
    		syslog(LOG_ERR, "%s:%u Memory allocation failed!\n", __func__, __LINE__);
    		return;
    	}
    
    
    	if (download_file(url, &mem) == 0) {
    
    		syslog(LOG_INFO, "%s:%u Downloaded %zu bytes\n",  __func__, __LINE__, mem.size);
    
    		populate_bundle_from_memory(&mem, bundle);
    		free_memory(&mem);
    
    static void parse_urlbundle_section(struct uci_section *section)
    
    	enum {
    		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_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
    
    	struct urlbundle *new_bundle = calloc(1, sizeof(struct urlbundle));
    
    	INIT_LIST_HEAD(&new_bundle->custom_url);
    
    	uci_parse_section(section, bundle_opts, NUM_BUNDLE_ATTRS, tb);
    
    	// Process the parsed options
    	if (tb[BUNDLE_NAME]) {
    		snprintf(new_bundle->name, MAX_NAME_LENGTH, "%s", tb[BUNDLE_NAME]->v.string);
    	}
    
    	if (tb[BUNDLE_DOWNLOAD_URL]) {
    		handle_download_url(new_bundle, tb[BUNDLE_DOWNLOAD_URL]->v.string);
    	}
    
    	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 {
    
    				syslog(LOG_ERR, "%s:%u Invalid url in custom_url: %s\n",  __func__, __LINE__, el->name);
    
    
    	list_add_tail(&new_bundle->list, &bundles);
    
    static void populate_bundle_from_zip_file(const char *file_path, struct urlbundle *bundle)
    {
    	char buffer[BUFSIZE] = {0};
    	char line[BUFSIZE] = {0};  // Line buffer
    	gzFile gz_fp = NULL;
    	int line_index = 0;
    	int bytes_read = 0;
    	unsigned int line_count = 0;
    
    	if (!bundle) {
    		syslog(LOG_ERR, "%s:%u Invalid urlbundle pointer!", __func__, __LINE__);
    		return;
    	}
    
    	gz_fp = gzopen(file_path, "rb");
    	if (!gz_fp) {
    		syslog(LOG_ERR, "%s:%u gzFile pointer empty for %s", __func__, __LINE__, file_path);
    		return;
    	}
    
    	line_index = 0;
    	unsigned int valid_count = 0;
    
    	// Second pass: extract and store the lines in the new bundle
    	while ((bytes_read = gzread(gz_fp, buffer, sizeof(buffer) - 1)) > 0)
    	{
    		buffer[bytes_read] = '\0'; // Null-terminate the string
    		for (int i = 0; i < bytes_read; i++) {
    			if (buffer[i] == '\n' || line_index >= sizeof(line) - 1) {
    				// End of a line or line buffer full, process the line
    				line[line_index] = '\0';  // Null-terminate the line
    
    				if (is_valid_domain(line)) {
    
    					sm_put(bundle->download_url, line, "1");
    
    					valid_count++;
    				}
    
    				line_index = 0;  // Reset line buffer index
    			} else {
    				// Accumulate characters into the line buffer
    				line[line_index++] = buffer[i];
    			}
    		}
    	}
    
    	// Process any leftover characters in the line buffer
    	if (line_index > 0) {
    		line[line_index] = '\0';
    		if (is_valid_domain(line)) {
    
    			sm_put(bundle->download_url, line, "1");
    
    			valid_count++;
    		}
    	}
    
    	syslog(LOG_INFO, "%s:%u line_count: %d, valid_count: %u for file: %s\n", __func__, __LINE__, line_count, valid_count, file_path);
    
    	if (bytes_read < 0) {
    		syslog(LOG_ERR, "%s:%u Error reading from gzip file: %s\n", __func__, __LINE__, gzerror(gz_fp, NULL));
    	}
    
    	gzclose(gz_fp);
    }
    
    static void populate_bundle_from_text_file(const char *file_path, struct urlbundle *bundle)
    {
    	char buffer[BUFSIZE] = {0};
    	FILE *fp = NULL;
    
    	if (!bundle) {
    		syslog(LOG_ERR, "%s:%u Invalid urlbundle pointer!", __func__, __LINE__);
    		return;
    	}
    
    	fp = fopen(file_path, "r");
    	if (fp == NULL) {
    		syslog(LOG_ERR, "%s:%u File pointer empty for %s", __func__, __LINE__, file_path);
    		return;
    	}
    
    	// Second pass: read and store valid URLs
    	unsigned int valid_count = 0;
    	while (fgets(buffer, sizeof(buffer), fp)) {
    		buffer[strcspn(buffer, "\n\r")] = 0;  // Remove trailing newline or carriage return
    
    		if (is_valid_domain(buffer)) {
    
    			sm_put(bundle->download_url, buffer, "1");
    
    	syslog(LOG_INFO, "%s:%u lines valid: %d\n",
    				__func__, __LINE__, valid_count);
    
    
    	fclose(fp);
    }
    
    static void load_and_add_bundle_from_file(const char *file_name)
    {
    	char file_path[256] = {0};
    
    	if (file_name == NULL || file_name[0] == '\0') {
    		syslog(LOG_ERR, "%s:%u file_name is null.", __func__, __LINE__);
    		return;
    	}
    
    	snprintf(file_path, sizeof(file_path), "%s/%s", "/etc/urlfilter/bundles", file_name);
    
    	if (strstr(file_name, "gz")) {
    		populate_bundle_from_zip_file(file_path, &global_bundle);
    	} else {
    		populate_bundle_from_text_file(file_path, &global_bundle);
    	}
    }
    
    static void parse_global_section(struct uci_section *section)
    {
    	enum {
    		GLOBAL_BUNDLE_FILE,
    		NUM_GLOBAL_ATTRS  // Number of attributes in the global section
    	};
    
    	const struct uci_parse_option global_opts[] = {
    		[GLOBAL_BUNDLE_FILE] = { .name = "bundle_file", .type = UCI_TYPE_LIST },
    	};
    
    	struct uci_option *tb[NUM_GLOBAL_ATTRS] = {0};  // Initialize tb array to hold parsed options
    
    	uci_parse_section(section, global_opts, NUM_GLOBAL_ATTRS, tb);
    
    	// Process the parsed options
    	if (tb[GLOBAL_BUNDLE_FILE]) {
    
    		global_bundle.download_url = sm_new(1000000);
    
    		if (global_bundle.download_url == NULL) {
    			syslog(LOG_INFO, "Could not assign sm, return");
    			return;
    		}
    
    
    		struct uci_element *el;
    		uci_foreach_element(&tb[GLOBAL_BUNDLE_FILE]->v.list, el) {
    			// this is a category of urlbundle
    			// check if this is present in appropriate path and load it
    			load_and_add_bundle_from_file(el->name);
    		}
    	}
    }
    
    
    static struct host *create_host_node(const char *identifier)
    
    	struct host *new_host = calloc(1, sizeof(struct host));
    	snprintf(new_host->host_identifier, MAX_NAME_LENGTH, "%s", identifier);
    	INIT_LIST_HEAD(&new_host->list);
    	return new_host;
    }
    
    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->hosts);
    	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]) {
    		struct uci_element *el;
    
    		uci_foreach_element(&tb[PROFILE_HOST]->v.list, el) {
    
    			char resolved_mac[32] = {0};  // Buffer to store the resolved MAC address
    
    			int ret = resolve_mac_or_hostname(el->name, resolved_mac, sizeof(resolved_mac));
    
    			if (ret == 0) {
    				struct host *new_host = create_host_node(resolved_mac);
    				list_add_tail(&new_host->list, &new_profile->hosts);
    
    	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_STRING },
    		[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;
    		uci_foreach_element(&tb[FILTER_SCHEDULE]->v.list, el) {
    			find_and_add_schedule(el->name, filter);  // Lookup schedule by name
    		}
    	}
    
    	if (tb[FILTER_BUNDLE]) {
    		filter->bundle = find_urlbundle(tb[FILTER_BUNDLE]->v.string);  // Lookup bundle by name
    	}
    
    	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(void)
    
    	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) {
    
    		syslog(LOG_INFO, "%s:%u Failed to load parentalcontrol package\n", __func__, __LINE__);
    
    		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, "globals") == 0) {
    			parse_global_section(section);
    		}
    	}
    
    
    	uci_foreach_element(&pkg->sections, e) {
    		struct uci_section *section = uci_to_section(e);
    		if (strcmp(section->type, "urlbundle") == 0) {
    			parse_urlbundle_section(section);
    		}
    	}
    
    	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') {
    
    		syslog(LOG_ERR, "%s:%u filename is null.", __func__, __LINE__);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		return -1;
    	}
    
    	fp = fopen(filename, "r");
    	if (fp == NULL) {
    
    		syslog(LOG_ERR, "%s:%u File pointer empty", __func__, __LINE__);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		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) {
    
    		syslog(LOG_ERR, "%s:%u failed to allocate uci context", __func__, __LINE__);
    
    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)) {
    
    		syslog(LOG_ERR, "%s:%u failed to load dhcp uci file", __func__, __LINE__);
    
    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
    static int init_dns_server_list(void)
    {
    	char filename[256] = {0};
    
    	if (get_resolver_file(&filename[0]) < 0) {
    
    		syslog(LOG_ERR, "%s:%u cannot get resolver file, init dns server fail", __func__, __LINE__);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		return -1;
    	}
    	// Get the count of nameservers corresponding to IPv4 address.
    	int    ret;
    	size_t server_num, ipv6_server_num;
    
    	ret = fetch_nameservers(filename, 0, 0, 0);
    	if (ret <= 0)
    
    		syslog(LOG_ERR, "%s:%u No nameservers found", __func__, __LINE__);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	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)
    
    		syslog(LOG_ERR, "%s:%u No ipv6 nameservers found", __func__, __LINE__);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	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) {
    
    			syslog(LOG_ERR, "%s:%u Memory allocation failed for dns_server_ip", __func__, __LINE__);
    
    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) {
    
    				syslog(LOG_ERR, "%s:%u Memory allocation failed for dns_server_ip", __func__, __LINE__);
    
    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) {
    
    				syslog(LOG_ERR, "%s:%u Memory allocation failed for ipv6 dns_server_ip", __func__, __LINE__);
    
    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) {
    
    				syslog(LOG_ERR, "%s:%u reading dns server failed", __func__, __LINE__);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    				return -1;
    			}
    		}