Newer
Older
/*
* 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
*/
#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)
{
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) {
(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) {
(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
char url_regex[MAX_URL_LENGTH] = {0};
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(®ex, url_regex, REG_ICASE | REG_EXTENDED);
if (reti) {
continue;
}
// Execute the regular expression
reti = regexec(®ex, c_name, 0, NULL, 0);
regfree(®ex);
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)
{
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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)
{
(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));
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)
{
(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));
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,
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
{
// 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)
{
struct sockaddr_in a;
struct sockaddr_in6 addr_in6;
size_t ipv6_addr_count = 0;
size_t j, k = 0;
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));
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);
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)
{
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));
}
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);
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)
{
if (!bundle) {
return 1;
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,
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,
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,
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 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
// logic:
// 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(®ex, url_regex, REG_ICASE | REG_EXTENDED);
if (reti) {
continue;
}
// Execute the regular expression
reti = regexec(®ex, (char *)answers[0].name, 0, NULL, 0);
regfree(®ex);
if (reti != 0) {
continue;
}
// if we are here it means strstr or regex matched
// 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,
}
}
// Create a new entry in the global_dns_cache
add_dns_cache_entry(dns, answers, ipv4_addr_cnt, url_cname,
}
// 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)
// 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);
if (ret == 0)
return;
ret = update_dns_cache_from_download_url(filter->bundles[i],
dns, answers, ipv4_addr_cnt,
url_cname, cname_cnt, ipv6_addr_cnt);
if (ret == 0)
return;
}
ret = update_dns_cache_from_url_list(&filter->filter_text,
dns, answers, ipv4_addr_cnt,
url_cname, cname_cnt, ipv6_addr_cnt);