Skip to content
Snippets Groups Projects
dns_cache.c 13.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Rahul Thakur's avatar
    Rahul Thakur committed
    /*
     * dns_cache.c: dns cache handling
     *
     * Copyright (C) 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
     */
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <regex.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/udp.h>
    #include <netinet/tcp.h>
    
    #include "utils.h"
    #include "dns_cache.h"
    
    dns_cache *global_dns_cache = NULL;
    size_t dns_cache_cnt = 0;
    
    // Find the url corresponding to the IP address from the dns cache
    // @param payload_pkt ip address
    // @return url on success else NULL.
    char *match_ip_address_from_dns_cache(char *ip_address, bool is_ipv6)
    {
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	for (i = 0; i < dns_cache_cnt; i++) {
    		if (!is_ipv6) {
    			for (j = 0; j < (global_dns_cache + i)->ip_addr_cnt; j++) {
    
    				if ((global_dns_cache + i)->ip_addr[j] && ip_address
    
    				    && strcmp((global_dns_cache + i)->ip_addr[j], ip_address) == 0) {
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    					(global_dns_cache + i)->match_cnt++;
    					return (global_dns_cache + i)->url;
    				}
    			}
    		} else {
    			for (j = 0; j < (global_dns_cache + i)->ipv6_addr_cnt; j++) {
    
    				if ((global_dns_cache + i)->ipv6_addr[j] && ip_address
    
    				    && strcmp((global_dns_cache + i)->ipv6_addr[j], ip_address) == 0) {
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    					(global_dns_cache + i)->match_cnt++;
    					return (global_dns_cache + i)->url;
    				}
    			}
    		}
    	}
    	return NULL;
    }
    
    // Find the url corresponding to the cname from the dns cache
    // @param c_name cname
    // @return url on success else NULL.
    char *match_cname_from_dns_cache(char *c_name)
    {
    
    	// TODO: is wildcard_to_regex needed here? or just use strstr
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	regex_t regex;
    
    	char url_regex[MAX_URL_LENGTH] = {0};
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	for (i = 0; i < dns_cache_cnt; i++) {
    		for (j = 0; j < (global_dns_cache + i)->cname_cnt; j++) {
    			wildcard_to_regex((global_dns_cache + i)->cname[j], url_regex);
    			int reti = regcomp(&regex, url_regex, REG_ICASE | REG_EXTENDED);
    			if (reti) {
    				continue;
    			}
    			// Execute the regular expression
    			reti = regexec(&regex, c_name, 0, NULL, 0);
    			regfree(&regex);
    
    			if (!reti)
    				return (global_dns_cache + i)->url;
    		}
    	}
    	return NULL;
    }
    
    // Allocate memory for storing cname in dns cache
    // @param index of global_dns_cache
    // @param cnt number of cname in the response
    // @return 0 on success else -1.
    static int create_cname_list(size_t i, size_t cnt)
    {
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	char *tmp;
    
    	(global_dns_cache + i)->cname_cnt = 0;
    
    	(global_dns_cache + i)->cname = (char **)calloc(cnt, sizeof(char *));
    	if ((global_dns_cache + i)->cname == NULL)
    		return -1;
    
    	for (j = 0; j < cnt; j++) {
    		tmp  = (char *)calloc(MAX_URL_LENGTH, sizeof(char));
    		if (tmp == NULL) {
    			if ((global_dns_cache + i)->cname != NULL)
    				free((global_dns_cache + i)->cname);
    			return -1;
    		}
    		(global_dns_cache + i)->cname[j] = tmp;
    	}
    
    	(global_dns_cache + i)->cname_cnt = cnt;
    
    	return 0;
    }
    
    // Allocate memory for storing IPv4 address for dns cache
    // @param index of global_dns_cache
    // @param count number of ip address response
    // @return 0 on success else -1.
    static int create_ip_addr_list(size_t i, size_t count)
    {
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	(global_dns_cache + i)->ip_addr_cnt = 0;
    
    	(global_dns_cache + i)->ip_addr = (char **)calloc(count, sizeof(char *));
    	if ((global_dns_cache + i)->ip_addr == NULL)
    		return -1;
    
    	for (j = 0; j < count; j++) {
    
    		char *tmp = (char *)calloc(INET_ADDRSTRLEN, sizeof(char));
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		if (tmp == NULL) {
    			if ((global_dns_cache + i)->ip_addr != NULL)
    				free((global_dns_cache + i)->ip_addr);
    			return -1;
    		}
    		(global_dns_cache + i)->ip_addr[j] = tmp;
    	}
    
    	(global_dns_cache + i)->ip_addr_cnt = count;
    
    	return 0;
    }
    
    // Allocate memory for storing IPv6 address for dns cache
    // @param index of global_dns_cache
    // @param count number of ipv6 address response
    // @return nothing
    static int create_ipv6_addr_list(size_t i, size_t count)
    {
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	(global_dns_cache + i)->ipv6_addr_cnt = 0;
    	(global_dns_cache + i)->ipv6_addr = (char **)calloc(count, sizeof(char *));
    	if ((global_dns_cache + i)->ipv6_addr == NULL)
    		return -1;
    
    	for (j = 0; j < count; j++) {
    
    		char *tmp = (char *)calloc(INET6_ADDRSTRLEN, sizeof(char));
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		if (tmp == NULL) {
    			if ((global_dns_cache + i)->ipv6_addr != NULL)
    				free((global_dns_cache + i)->ipv6_addr);
    			return -1;
    		}
    		(global_dns_cache + i)->ipv6_addr[j] = tmp;
    	}
    
    	(global_dns_cache + i)->ipv6_addr_cnt = count;
    	return 0;
    }
    
    static int init_dns_cache_entry(size_t ipv4_addr_cnt, size_t ipv6_addr_cnt,
    
    				size_t cname_cnt, size_t i)
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    {
    	// Check if memory is already present, if yes then free the memory.
    	if ((ipv4_addr_cnt > 0) && ((global_dns_cache + i)->ip_addr) != NULL) {
    		free((global_dns_cache + i)->ip_addr);
    		(global_dns_cache + i)->ip_addr = NULL;
    		(global_dns_cache + i)->ip_addr_cnt = 0;
    	}
    
    	// Check if memory is already present, if yes then free the memory.
    	if ((ipv6_addr_cnt > 0) && ((global_dns_cache + i)->ipv6_addr) != NULL) {
    		free((global_dns_cache + i)->ipv6_addr);
    		(global_dns_cache + i)->ipv6_addr = NULL;
    		(global_dns_cache + i)->ipv6_addr_cnt = 0;
    	}
    
    	if ((global_dns_cache + i)->cname != NULL) {
    		free((global_dns_cache + i)->cname);
    		(global_dns_cache + i)->cname = NULL;
    		(global_dns_cache + i)->cname_cnt = 0;
    	}
    
    	if ((ipv4_addr_cnt > 0) && (create_ip_addr_list(i, ipv4_addr_cnt) == -1)) {
    		return -1;
    	}
    
    	if ((ipv6_addr_cnt > 0) && (create_ipv6_addr_list(i, ipv6_addr_cnt) == -1)) {
    		return -1;
    	}
    
    	if ((cname_cnt > 0) && (create_cname_list(i, cname_cnt) == -1)) {
    		return -1;
    	}
    
    	return 0;
    }
    
    static void update_dns_cache_entry(struct DNS_HEADER *dns, struct RES_RECORD answers[MAX_DNS_ANS_RECORD],
    
    				   size_t ipv4_addr_cnt, char url_cname[][MAX_URL_LENGTH], size_t cname_cnt,
    				   size_t ipv6_addr_cnt, size_t i)
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    {
    	struct sockaddr_in a;
    	struct sockaddr_in6 addr_in6;
    
    	size_t ipv6_addr_count = 0;
    	size_t j, k = 0;
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    	if (init_dns_cache_entry(ipv4_addr_cnt, ipv6_addr_cnt, cname_cnt, i)) {
    		return;
    	}
    
    	for (j = 0; j < cname_cnt; j++)
    		snprintf((global_dns_cache + i)->cname[j], MAX_URL_LENGTH, "%s", url_cname[j]);
    
    	for (j = 0; j < ntohs(dns->ans_count); j++) {
    		if ((ipv4_addr_cnt == k) && (ipv6_addr_cnt == ipv6_addr_count))
    			return;
    
    		// Check if the response has ipv4 address or not.
    		if (ntohs(answers[j].resource->type) == 1) {
    			long *p;
    
    			p = (long *) answers[j].rdata;
    			a.sin_addr.s_addr = (in_addr_t)(*p);
    
    			snprintf((global_dns_cache + i)->ip_addr[k], INET_ADDRSTRLEN, "%s", inet_ntoa(a.sin_addr));
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    			k++;
    		} else if (ntohs(answers[j].resource->type) == T_AAAA) {
    			unsigned char *p;
    
    			p = (unsigned char *) answers[j].rdata;
    			memcpy(&addr_in6.sin6_addr, p, sizeof(struct in6_addr));
    
    			inet_ntop(AF_INET6, &addr_in6.sin6_addr, (global_dns_cache + i)->ipv6_addr[ipv6_addr_count], INET6_ADDRSTRLEN);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    			ipv6_addr_count++;
    		}
    	}
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	return;
    }
    
    static void add_dns_cache_entry(struct DNS_HEADER *dns, struct RES_RECORD answers[MAX_DNS_ANS_RECORD],
    
    				size_t ipv4_addr_cnt, char url_cname[][MAX_URL_LENGTH], size_t cname_cnt, size_t ipv6_addr_cnt)
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    {
    	dns_cache *new_entry;
    	if (global_dns_cache == NULL) {
    		global_dns_cache = (dns_cache *)calloc(1, sizeof(dns_cache));
    	} else {
    		new_entry = (dns_cache *)realloc(global_dns_cache, sizeof(dns_cache) * (dns_cache_cnt + 1));
    		if (new_entry == NULL) {
    			return;
    		}
    		global_dns_cache = new_entry;
    
    
    		memset(global_dns_cache + dns_cache_cnt, 0, sizeof(dns_cache));
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	}
    
    	if (global_dns_cache == NULL) {
    		return;
    	}
    
    	snprintf((global_dns_cache + dns_cache_cnt)->url, MAX_URL_LENGTH, "%s", (char *)answers[0].name);
    
    	update_dns_cache_entry(dns, answers, ipv4_addr_cnt, url_cname,
    
    			       cname_cnt, ipv6_addr_cnt, dns_cache_cnt);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	dns_cache_cnt++;
    }
    
    
    static int update_dns_cache_from_download_url(struct urlbundle *bundle, struct DNS_HEADER *dns,
    
    		struct RES_RECORD answers[MAX_DNS_ANS_RECORD], size_t ipv4_addr_cnt,
    		char url_cname[][MAX_URL_LENGTH], size_t cname_cnt, size_t ipv6_addr_cnt)
    {
    
    	char *url = (char *)answers[0].name;
    
    	// check stringstore from file
    	if (stringstore_check_string_from_file(&bundle->download_url, url)) {
    
    		// check if url is already present in the global_dns_cache
    
    		for (size_t j = 0; j < dns_cache_cnt; j++) {
    
    			if (strstr((global_dns_cache + j)->url, url) != NULL) {
    
    				// url is present in global cache, update entry
    				update_dns_cache_entry(dns, answers, ipv4_addr_cnt, url_cname,
    
    						       cname_cnt, ipv6_addr_cnt, j);
    
    				return 0;
    			}
    		}
    
    		// Create a new entry in the global_dns_cache
    		add_dns_cache_entry(dns, answers, ipv4_addr_cnt, url_cname, cname_cnt, ipv6_addr_cnt);
    		return 0;
    	} else {
    
    		char processed_url[MAX_URL_LENGTH] = {0};
    		if (strncmp(url, "www.", 4) == 0) {
    			url += 4;
    			snprintf(processed_url, sizeof(processed_url), "%s", url);
    		} else {
    			snprintf(processed_url, sizeof(processed_url), "www.%s", url);
    		}
    
    
    		if (stringstore_check_string_from_file(&bundle->download_url, processed_url)) {
    
    			// check if url is already present in the global_dns_cache
    			for (size_t j = 0; j < dns_cache_cnt; j++) {
    				if (strstr((global_dns_cache + j)->url, (char *)answers[0].name) != NULL) {
    					// url is present in global cache, update entry
    					update_dns_cache_entry(dns, answers, ipv4_addr_cnt, url_cname,
    
    							       cname_cnt, ipv6_addr_cnt, j);
    
    
    					return 0;
    				}
    			}
    
    			// Create a new entry in the global_dns_cache
    			add_dns_cache_entry(dns, answers, ipv4_addr_cnt, url_cname, cname_cnt, ipv6_addr_cnt);
    			return 0;
    		} else {
    			// using the else because it seems better to be explicit
    			return 1;
    		}
    	}
    
    static int update_dns_cache_from_url_list(struct list_head *lh, struct DNS_HEADER *dns,
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		struct RES_RECORD answers[MAX_DNS_ANS_RECORD], size_t ipv4_addr_cnt,
    		char url_cname[][MAX_URL_LENGTH], size_t cname_cnt, size_t ipv6_addr_cnt)
    {
    
    	if (!lh)
    		return 1;
    
    	size_t j = 0;
    	char url_regex[1024] = {0};
    	regex_t regex;
    
    	struct url_list *url = NULL;
    
    
    	list_for_each_entry(url, lh, list) {
    		// Check if url->url is a substring of payload_pkt
    
    		// so if incoming request is for help.xyz.com
    		// and *.xyz.com is in our list, then add help.xyz.com
    		// to the cache
    
    		// TECH NOTE: if user specifies xyz.com, help.xyz.com will also be blocked
    		// the regex now seems a bit unnecessary because it is only used for wildcard
    
    		// if url does not start with a * and then strstr fails, then don't try regex
    
    		// else try regex, if regex does not match, move to the next entry
    
    		if (url->url[0] != '*' && strstr((char *)answers[0].name, url->url) == NULL) {
    
    			wildcard_to_regex(url->url, url_regex);
    			int reti = regcomp(&regex, url_regex, REG_ICASE | REG_EXTENDED);
    			if (reti) {
    				continue;
    			}
    			// Execute the regular expression
    			reti = regexec(&regex, (char *)answers[0].name, 0, NULL, 0);
    			regfree(&regex);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    
    			if (reti != 0) {
    				continue;
    			}
    
    		// if we are here it means strstr or regex matched
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		// check if url is already present in the global_dns_cache
    		for (j = 0; j < dns_cache_cnt; j++) {
    			if (strstr((global_dns_cache + j)->url, (char *)answers[0].name) != NULL) {
    				// url is present in global cache, update entry
    				update_dns_cache_entry(dns, answers, ipv4_addr_cnt, url_cname,
    
    						       cname_cnt, ipv6_addr_cnt, j);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    			}
    		}
    
    		// Create a new entry in the global_dns_cache
    		add_dns_cache_entry(dns, answers, ipv4_addr_cnt, url_cname,
    
    				    cname_cnt, ipv6_addr_cnt);
    
    }
    
    // Function stores the IP addresses of blacklisted URL's in dns cache.
    // @param dns header
    // @param answers dns response buffer
    // @param ipv4_addr_cnt number of ipv4 addresses
    // @param url_cname array with cname
    // @param cname_cnt number of cname per dns response.
    // @returns nothing
    
    void store_qname_ipaddr_in_cache(struct DNS_HEADER *dns,
    
    				 struct RES_RECORD answers[MAX_DNS_ANS_RECORD], size_t ipv4_addr_cnt,
    				 char url_cname[][MAX_URL_LENGTH], size_t cname_cnt, size_t ipv6_addr_cnt)
    
    {
    	// Check if the number of answers is not 0.
    	if (ntohs(dns->ans_count) <= 0)
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		return;
    
    	struct profile *p = NULL;
    
    	// Iterate over the global profile list
    	// so, we loop over all filters of all profiles
    	// to cover all possible urls
    	list_for_each_entry(p, &profiles, list) {
    		// we loop over all filters of this profile
    		struct urlfilter *filter = NULL;
    
    		list_for_each_entry(filter, &p->filters, list) {
    			// Check if the filter is enabled
    			if (!filter->enable) {
    				continue;
    			}
    
    			if (filter->bundle_cnt) {
    				for (unsigned short i = 0; i < filter->bundle_cnt; i++) {
    					ret = update_dns_cache_from_url_list(&filter->bundles[i]->custom_url,
    
    									     dns, answers, ipv4_addr_cnt,
    									     url_cname, cname_cnt, ipv6_addr_cnt);
    
    					ret = update_dns_cache_from_download_url(filter->bundles[i],
    							dns, answers, ipv4_addr_cnt,
    							url_cname, cname_cnt, ipv6_addr_cnt);
    
    			ret = update_dns_cache_from_url_list(&filter->filter_text,
    
    							     dns, answers, ipv4_addr_cnt,
    							     url_cname, cname_cnt, ipv6_addr_cnt);
    
    			if (ret == 0)
    				return;
    		}