1 
2 #include "compat.h"
3 
4 #include <assert.h>
5 #include <ctype.h>
6 #include <getopt.h>
7 #include <stdio.h>
8 #include <stdint.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <time.h>
12 
13 #include "dnscrypt.h"
14 #include "fpst.h"
15 
16 #define MAX_QNAME_LENGTH 255U
17 
18 typedef enum BlockType {
19     BLOCKTYPE_UNDEFINED,
20     BLOCKTYPE_EXACT,
21     BLOCKTYPE_PREFIX,
22     BLOCKTYPE_SUFFIX,
23     BLOCKTYPE_SUBSTRING
24 } BlockType;
25 
26 typedef struct Blocking_ {
27     FPST *domains;
28     FPST *domains_rev;
29     FPST *domains_substr;
30 } Blocking;
31 
32 static char *
skip_spaces(char * str)33 skip_spaces(char *str)
34 {
35     while (*str != 0 && isspace((int) (unsigned char) *str)) {
36         str++;
37     }
38     return str;
39 }
40 
41 static char *
skip_chars(char * str)42 skip_chars(char *str)
43 {
44     while (*str != 0 && !isspace((int) (unsigned char) *str)) {
45         str++;
46     }
47     return str;
48 }
49 
50 static void
str_tolower(char * str)51 str_tolower(char *str)
52 {
53     while (*str != 0) {
54         *str = (char) tolower((unsigned char) *str);
55         str++;
56     }
57 }
58 
59 static void
str_reverse(char * str)60 str_reverse(char *str)
61 {
62     size_t i = 0;
63     size_t j = strlen(str);
64     char   t;
65 
66     while (i < j) {
67         t = str[i];
68         str[i++] = str[--j];
69         str[j] = t;
70     }
71 }
72 
73 static char *
untab(char * line)74 untab(char *line)
75 {
76     char *ptr;
77 
78     while ((ptr = strchr(line, '\t')) != NULL) {
79         *ptr = ' ';
80     }
81     return line;
82 }
83 
84 static char *
trim_comments(char * line)85 trim_comments(char *line)
86 {
87     char *ptr;
88     char *s1;
89     char *s2;
90 
91     while ((ptr = strchr(line, '\n')) != NULL ||
92            (ptr = strchr(line, '\r')) != NULL) {
93         *ptr = 0;
94     }
95     line = skip_spaces(line);
96     if (*line == 0 || *line == '#') {
97         return NULL;
98     }
99     s1 = skip_chars(line);
100     if (*(s2 = skip_spaces(s1)) == 0) {
101         *s1 = 0;
102         return line;
103     }
104     if (*s2 == '#') {
105         return NULL;
106     }
107     *skip_chars(s2) = 0;
108 
109     return s2;
110 }
111 
112 static void
free_list(const char * key,uint32_t val)113 free_list(const char *key, uint32_t val)
114 {
115     (void) val;
116     free((void *) key);
117 }
118 
119 static int
parse_domain_list(FPST ** const domain_list_p,FPST ** const domain_rev_list_p,FPST ** const domain_substr_list_p,const char * const file)120 parse_domain_list(FPST ** const domain_list_p,
121                   FPST ** const domain_rev_list_p,
122                   FPST ** const domain_substr_list_p,
123                   const char * const file)
124 {
125     char       buf[MAX_QNAME_LENGTH + 1U];
126     char      *line;
127     FILE      *fp;
128     FPST      *domain_list;
129     FPST      *domain_list_tmp;
130     FPST      *domain_rev_list;
131     FPST      *domain_rev_list_tmp;
132     FPST      *domain_substr_list;
133     FPST      *domain_substr_list_tmp;
134     size_t     line_len;
135     BlockType  block_type = BLOCKTYPE_UNDEFINED;
136     int        ret = -1;
137 
138     assert(domain_list_p != NULL);
139     assert(domain_rev_list_p != NULL);
140     assert(domain_substr_list_p != NULL);
141     *domain_list_p = NULL;
142     *domain_rev_list_p = NULL;
143     *domain_substr_list_p = NULL;
144     domain_list = fpst_new();
145     domain_rev_list = fpst_new();
146     domain_substr_list = fpst_new();
147     if ((fp = fopen(file, "r")) == NULL) {
148         return -1;
149     }
150     while (fgets(buf, (int) sizeof buf, fp) != NULL) {
151         if ((line = trim_comments(untab(buf))) == NULL || *line == 0) {
152             continue;
153         }
154         line_len = strlen(line);
155         if (line[0] == '*' && line[line_len - 1] == '*') {
156             line[line_len - 1] = 0;
157             line++;
158             block_type = BLOCKTYPE_SUBSTRING;
159         } else if (line[line_len - 1] == '*') {
160             line[line_len - 1] = 0;
161             block_type = BLOCKTYPE_PREFIX;
162         } else {
163             if (line[0] == '*') {
164                 line++;
165             }
166             if (line[0] == '.') {
167                 line++;
168             }
169             str_reverse(line);
170             block_type = BLOCKTYPE_SUFFIX;
171         }
172         if (*line == 0) {
173             continue;
174         }
175         str_tolower(line);
176         if ((line = strdup(line)) == NULL) {
177             break;
178         }
179         if (block_type == BLOCKTYPE_SUFFIX) {
180             if ((domain_rev_list_tmp = fpst_insert_str(domain_rev_list, line,
181                                                        (uint32_t) block_type)) == NULL) {
182                 free(line);
183                 break;
184             }
185             domain_rev_list = domain_rev_list_tmp;
186         } else if (block_type == BLOCKTYPE_PREFIX) {
187             if ((domain_list_tmp = fpst_insert_str(domain_list, line,
188                                                    (uint32_t) block_type)) == NULL) {
189                 free(line);
190                 break;
191             }
192             domain_list = domain_list_tmp;
193         } else if (block_type == BLOCKTYPE_SUBSTRING) {
194             if ((domain_substr_list_tmp = fpst_insert_str(domain_substr_list, line,
195                                                           (uint32_t) block_type)) == NULL) {
196                 free(line);
197                 break;
198             }
199             domain_substr_list = domain_substr_list_tmp;
200         } else {
201             free(line);
202         }
203     }
204     if (!feof(fp)) {
205         fpst_free(domain_list, free_list);
206         fpst_free(domain_rev_list, free_list);
207         fpst_free(domain_substr_list, free_list);
208     } else {
209         *domain_list_p = domain_list;
210         *domain_rev_list_p = domain_rev_list;
211         *domain_substr_list_p = domain_substr_list;
212         ret = 0;
213     }
214     fclose(fp);
215     logger(LOG_INFO, "Blacklist [%s] loaded", file);
216 
217     return ret;
218 }
219 
220 static _Bool
substr_match(FPST * list,const char * str,const char ** found_key_p,uint32_t * found_block_type_p)221 substr_match(FPST *list, const char *str,
222              const char **found_key_p, uint32_t *found_block_type_p)
223 {
224     while (*str != 0) {
225         if (fpst_str_starts_with_existing_key(list, str, found_key_p,
226                                               found_block_type_p)) {
227             return 1;
228         }
229         str++;
230     }
231     return 0;
232 }
233 
234 int
blocking_init(struct context * c,const char * file)235 blocking_init(struct context *c, const char *file)
236 {
237     Blocking *blocking;
238 
239     if ((blocking = calloc((size_t) 1U, sizeof *blocking)) == NULL) {
240         return -1;
241     }
242     c->blocking = blocking;
243     blocking->domains = NULL;
244     blocking->domains_rev = NULL;
245     blocking->domains_substr = NULL;
246 
247     return parse_domain_list(&blocking->domains, &blocking->domains_rev,
248                              &blocking->domains_substr, file);
249 }
250 
251 void
blocking_free(struct context * c)252 blocking_free(struct context *c)
253 {
254     Blocking *blocking = c->blocking;
255 
256     if (blocking == NULL) {
257         return;
258     }
259     fpst_free(blocking->domains, free_list);
260     blocking->domains = NULL;
261     fpst_free(blocking->domains_rev, free_list);
262     blocking->domains_rev = NULL;
263     fpst_free(blocking->domains_substr, free_list);
264     blocking->domains_substr = NULL;
265     free(blocking);
266 }
267 
268 void
str_lcpy(char * dst,const char * src,size_t dsize)269 str_lcpy(char *dst, const char *src, size_t dsize)
270 {
271     size_t nleft = dsize;
272 
273     if (nleft != 0) {
274         while (--nleft != 0) {
275             if ((*dst++ = *src++) == 0) {
276                 break;
277             }
278         }
279     }
280     if (nleft == 0 && dsize != 0) {
281         *dst = 0;
282     }
283 }
284 
285 static int
name_matches_blacklist(const Blocking * const blocking,char * const name)286 name_matches_blacklist(const Blocking * const blocking, char * const name)
287 {
288     char        rev[MAX_QNAME_LENGTH + 1U];
289     const char *found_key;
290     size_t      name_len;
291     uint32_t    found_block_type;
292     _Bool       block = 0;
293 
294     rev[MAX_QNAME_LENGTH] = 0;
295     name_len = strlen(name);
296     if (name_len >= sizeof rev) {
297         return -1;
298     }
299     if (name_len > (size_t) 1U && name[name_len - 1U] == '.') {
300         name[--name_len] = 0;
301     }
302     if (name_len <= 0) {
303         return 0;
304     }
305     str_tolower(name);
306     do {
307         str_lcpy(rev, name, sizeof rev);
308         str_reverse(rev);
309         if (fpst_starts_with_existing_key(blocking->domains_rev,
310                                           rev, name_len,
311                                           &found_key, &found_block_type)) {
312             const size_t found_key_len = strlen(found_key);
313 
314             assert(found_block_type == BLOCKTYPE_SUFFIX);
315             if (found_key_len <= name_len &&
316                 (rev[found_key_len] == 0 || rev[found_key_len] == '.')) {
317                 block = 1;
318                 break;
319             }
320             if (found_key_len < name_len) {
321                 size_t owner_part_len = name_len;
322 
323                 while (owner_part_len > 0U && rev[owner_part_len] != '.') {
324                     owner_part_len--;
325                 }
326                 rev[owner_part_len] = 0;
327                 if (owner_part_len > 0U && fpst_starts_with_existing_key
328                     (blocking->domains_rev, rev, owner_part_len,
329                      &found_key, &found_block_type)) {
330                     const size_t found_key_len = strlen(found_key);
331                     if (found_key_len <= owner_part_len &&
332                         (rev[found_key_len] == 0 || rev[found_key_len] == '.')) {
333                         block = 1;
334                         break;
335                     }
336                 }
337             }
338         }
339         if (fpst_starts_with_existing_key(blocking->domains,
340                                           name, name_len,
341                                           &found_key, &found_block_type)) {
342             assert(found_block_type == BLOCKTYPE_PREFIX);
343             block = 1;
344             break;
345         }
346         if (blocking->domains_substr != NULL &&
347             substr_match(blocking->domains_substr, name,
348                          &found_key, &found_block_type)) {
349             assert(found_block_type == BLOCKTYPE_SUBSTRING);
350             block = 1;
351             break;
352         }
353     } while (0);
354 
355     return (int) block;
356 }
357 
358 int
is_blocked(struct context * c,struct dns_header * header,size_t dns_query_len)359 is_blocked(struct context *c, struct dns_header *header, size_t dns_query_len)
360 {
361     unsigned char *ansp;
362     unsigned char *p;
363     char          *name;
364 
365     if (c->blocking == NULL) {
366         return 0;
367     }
368     if (ntohs(header->qdcount) != 1) {
369         return -1;
370     }
371     if (!(ansp = skip_questions(header, dns_query_len))) {
372         return -1;
373     }
374     p = (unsigned char *)(header + 1);
375     if (!extract_name(header, dns_query_len, &p, c->namebuff, 1, 4)) {
376         return -1;
377     }
378     name = c->namebuff;
379 
380     return name_matches_blacklist(c->blocking, name);
381 }
382