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>
*
* 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>
#include <uci.h>
#include "config.h"
#include "filter.h"
// 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)
Amin Ben Romdhane
committed
{
const char *domain_regex = "^(\\*\\.|[a-zA-Z0-9-]+\\.)?[a-zA-Z0-9-]+\\.[a-zA-Z]{2,}$";
// Compile the regular expression
reti = regcomp(®ex, domain_regex, REG_EXTENDED | REG_NOSUB);
if (reti) {
fprintf(stderr, "Could not compile regex\n");
return 0;
}
Amin Ben Romdhane
committed
// Execute the regular expression
reti = regexec(®ex, domain, 0, NULL, 0);
regfree(®ex); // Free the compiled regex
Amin Ben Romdhane
committed
// 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;
Amin Ben Romdhane
committed
}
}
// 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;
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
144
145
146
147
148
149
// 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);
}
}
// 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)
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;
// 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(®ex, 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(®ex, input, 0, NULL, 0);
regfree(®ex); // 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;
}
}
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);
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
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");
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
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");
valid_count++;
}
}
syslog(LOG_INFO, "%s:%u lines valid: %d\n",
__func__, __LINE__, valid_count);
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
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)
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
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
}
}
}
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);
// 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') {
syslog(LOG_ERR, "%s:%u filename is null.", __func__, __LINE__);
return -1;
}
fp = fopen(filename, "r");
if (fp == NULL) {
syslog(LOG_ERR, "%s:%u File pointer empty", __func__, __LINE__);
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
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__);
struct uci_package *dhcp_pkg = NULL;
syslog(LOG_ERR, "%s:%u failed to load dhcp uci file", __func__, __LINE__);
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
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
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__);
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__);
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__);
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__);
return -1;
}
for (size_t i = 0; i < server_num; i++) {
dns_server_ip[i] = (char *)calloc(INET_ADDRSTRLEN, sizeof(char));
syslog(LOG_ERR, "%s:%u Memory allocation failed for dns_server_ip", __func__, __LINE__);
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));
syslog(LOG_ERR, "%s:%u Memory allocation failed for ipv6 dns_server_ip", __func__, __LINE__);
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__);