d24e1b5f4471147a25cf6c19092cd8c38bb1cce5
[asterisk/asterisk.git] / main / dns_naptr.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief DNS NAPTR Record Support
22  *
23  * \author Joshua Colp <jcolp@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 #include <arpa/nameser.h>
33 #include <resolv.h>
34 #include <regex.h>
35
36 #include "asterisk/dns_core.h"
37 #include "asterisk/dns_naptr.h"
38 #include "asterisk/linkedlists.h"
39 #include "asterisk/dns_internal.h"
40 #include "asterisk/utils.h"
41
42 /*!
43  * \brief Result of analyzing NAPTR flags on a record
44  */
45 enum flags_result {
46         /*! Terminal record, meaning the DDDS algorithm can be stopped */
47         FLAGS_TERMINAL,
48         /*! No flags provided, likely meaning another NAPTR lookup */
49         FLAGS_EMPTY,
50         /*! Unrecognized but valid flags. We cannot conclude what they mean */
51         FLAGS_UNKNOWN,
52         /*! Non-alphanumeric or invalid combination of flags */
53         FLAGS_INVALID,
54 };
55
56 /*!
57  * \brief Analyze and interpret NAPTR flags as per RFC 3404
58  *
59  * \note The flags string passed into this function is NOT NULL-terminated
60  *
61  * \param flags The flags string from a NAPTR record
62  * \flags_size The size of the flags string in bytes
63  * \return flag result
64  */
65 static enum flags_result interpret_flags(const char *flags, uint8_t flags_size)
66 {
67         int i;
68         char known_flag_found = 0;
69
70         if (flags_size == 0) {
71                 return FLAGS_EMPTY;
72         }
73
74         /* Take care of the most common (and easy) case, one character */
75         if (flags_size == 1) {
76                 if (*flags == 's' || *flags == 'S' ||
77                                 *flags == 'a' || *flags == 'A' ||
78                                 *flags == 'u' || *flags == 'U') {
79                         return FLAGS_TERMINAL;
80                 } else if (!isalnum(*flags)) {
81                         return FLAGS_INVALID;
82                 } else {
83                         return FLAGS_UNKNOWN;
84                 }
85         }
86
87         /*
88          * Multiple flags are allowed, but you cannot mix the
89          * S, A, U, and P flags together.
90          */
91         for (i = 0; i < flags_size; ++i) {
92                 if (!isalnum(flags[i])) {
93                         return FLAGS_INVALID;
94                 } else if (flags[i] == 's' || flags[i] == 'S') {
95                         if (known_flag_found && known_flag_found != 's') {
96                                 return FLAGS_INVALID;
97                         }
98                         known_flag_found = 's';
99                 } else if (flags[i] == 'u' || flags[i] == 'U') {
100                         if (known_flag_found && known_flag_found != 'u') {
101                                 return FLAGS_INVALID;
102                         }
103                         known_flag_found = 'u';
104                 } else if (flags[i] == 'a' || flags[i] == 'A') {
105                         if (known_flag_found && known_flag_found != 'a') {
106                                 return FLAGS_INVALID;
107                         }
108                         known_flag_found = 'a';
109                 } else if (flags[i] == 'p' || flags[i] == 'P') {
110                         if (known_flag_found && known_flag_found != 'p') {
111                                 return FLAGS_INVALID;
112                         }
113                         known_flag_found = 'p';
114                 }
115         }
116
117         return (!known_flag_found || known_flag_found == 'p') ? FLAGS_UNKNOWN : FLAGS_TERMINAL;
118 }
119
120 /*!
121  * \brief Analyze NAPTR services for validity as defined by RFC 3404
122  *
123  * \note The services string passed to this function is NOT NULL-terminated
124  * \param services The services string parsed from a NAPTR record
125  * \param services_size The size of the services string
126  * \retval 0 Services are valid
127  * \retval -1 Services are invalid
128  */
129 static int services_invalid(const char *services, uint8_t services_size)
130 {
131         const char *current_pos = services;
132         const char *end_of_services = services + services_size;
133
134         if (services_size == 0) {
135                 return 0;
136         }
137
138         /* Services are broken into sections divided by a + sign. Each section
139          * must start with an alphabetic character, and then can only contain
140          * alphanumeric characters. The size of any section is limited to
141          * 32 characters
142          */
143         while (1) {
144                 char *plus_pos = memchr(current_pos, '+', end_of_services - current_pos);
145                 uint8_t current_size = plus_pos ? plus_pos - current_pos : end_of_services - current_pos;
146                 int i;
147
148                 if (!isalpha(current_pos[0])) {
149                         return -1;
150                 }
151
152                 if (current_size > 32) {
153                         return -1;
154                 }
155
156                 for (i = 1; i < current_size; ++i) {
157                         if (!isalnum(current_pos[i])) {
158                                 return -1;
159                         }
160                 }
161
162                 if (!plus_pos) {
163                         break;
164                 }
165                 current_pos = plus_pos + 1;
166         }
167
168         return 0;
169 }
170
171 /*!
172  * \brief Determine if flags in the regexp are invalid
173  *
174  * A NAPTR regexp is structured like so
175  * /pattern/repl/FLAGS
176  *
177  * This ensures that the flags on the regexp are valid. Regexp
178  * flags can either be zero or one character long. If the flags
179  * are one character long, that character must be "i" to indicate
180  * the regex evaluation is case-insensitive.
181  *
182  * \note The flags string passed to this function is not NULL-terminated
183  * \param flags The regexp flags from the NAPTR record
184  * \param end A pointer to the end of the flags string
185  * \retval 0 Flags are valid
186  * \retval -1 Flags are invalid
187  */
188 static int regexp_flags_invalid(const char *flags, const char *end)
189 {
190         if (flags >= end) {
191                 return 0;
192         }
193
194         if (end - flags > 1) {
195                 return -1;
196         }
197
198         if (*flags != 'i') {
199                 return -1;
200         }
201
202         return 0;
203 }
204
205 /*!
206  * \brief Determine if the replacement in the regexp is invalid
207  *
208  * A NAPTR regexp is structured like so
209  * /pattern/REPL/flags
210  *
211  * This ensures that the replacement on the regexp is valid. The regexp
212  * replacement is free to use any character it wants, plus backreferences
213  * and an escaped regexp delimiter.
214  *
215  * This function does not attempt to ensure that the backreferences refer
216  * to valid portions of the regexp's regex pattern.
217  *
218  * \note The repl string passed to this function is NOT NULL-terminated
219  *
220  * \param repl The regexp replacement string
221  * \param end Pointer to the end of the replacement string
222  * \param delim The delimiter character for the regexp
223  *
224  * \retval 0 Replacement is valid
225  * \retval -1 Replacement is invalid
226  */
227 static int regexp_repl_invalid(const char *repl, const char *end, char delim)
228 {
229         const char *ptr = repl;
230
231         if (repl == end) {
232                 /* Kind of weird, but this is fine */
233                 return 0;
234         }
235
236         while (1) {
237                 char *backslash_pos = memchr(ptr, '\\', end - ptr);
238                 if (!backslash_pos) {
239                         break;
240                 }
241
242                 ast_assert(backslash_pos < end - 1);
243
244                 /* XXX RFC 3402 is unclear about whether other backslash-escaped characters
245                  * (such as a backslash-escaped backslash) are legal
246                  */
247                 if (!strchr("12345689", backslash_pos[1]) && backslash_pos[1] != delim) {
248                         return -1;
249                 }
250
251                 ptr = backslash_pos + 1;
252         }
253
254         return 0;
255 }
256
257 /*!
258  * \brief Determine if the pattern in a regexp is invalid
259  *
260  * A NAPTR regexp is structured like so
261  * /PATTERN/repl/flags
262  *
263  * This ensures that the pattern on the regexp is valid. The pattern is
264  * passed to a regex compiler to determine its validity.
265  *
266  * \note The pattern string passed to this function is NOT NULL-terminated
267  *
268  * \param pattern The pattern from the NAPTR record
269  * \param end A pointer to the end of the pattern
270  *
271  * \retval 0 Pattern is valid
272  * \retval non-zero Pattern is invalid
273  */
274 static int regexp_pattern_invalid(const char *pattern, const char *end)
275 {
276         int pattern_size = end - pattern;
277         char pattern_str[pattern_size + 1];
278         regex_t reg;
279         int res;
280
281         /* regcomp requires a NULL-terminated string */
282         memcpy(pattern_str, pattern, pattern_size);
283         pattern_str[pattern_size] = '\0';
284
285         res = regcomp(&reg, pattern_str, REG_EXTENDED);
286
287         regfree(&reg);
288
289         return res;
290 }
291
292 /*!
293  * \brief Determine if the regexp in a NAPTR record is invalid
294  *
295  * The goal of this function is to divide the regexp into its
296  * constituent parts and then let validation subroutines determine
297  * if each part is valid. If all parts are valid, then the entire
298  * regexp is valid.
299  *
300  * \note The regexp string passed to this function is NOT NULL-terminated
301  *
302  * \param regexp The regexp from the NAPTR record
303  * \param regexp_size The size of the regexp string
304  *
305  * \retval 0 regexp is valid
306  * \retval non-zero regexp is invalid
307  */
308 static int regexp_invalid(const char *regexp, uint8_t regexp_size)
309 {
310         char delim;
311         const char *delim2_pos;
312         const char *delim3_pos;
313         const char *ptr = regexp;
314         const char *end_of_regexp = regexp + regexp_size;
315         const char *regex_pos;
316         const char *repl_pos;
317         const char *flags_pos;
318
319         if (regexp_size == 0) {
320                 return 0;
321         }
322
323         /* The delimiter will be a ! or / in most cases, but the rules allow
324          * for the delimiter to be nearly any character. It cannot be 'i' because
325          * the delimiter cannot be the same as regexp flags. The delimiter cannot
326          * be 1-9 because the delimiter cannot be a backreference number. RFC
327          * 2915 specified that backslash was also not allowed as a delimiter, but
328          * RFC 3402 does not say this. We've gone ahead and made the character
329          * illegal for our purposes.
330          */
331         delim = *ptr;
332         if (strchr("123456789\\i", delim)) {
333                 return -1;
334         }
335         ++ptr;
336         regex_pos = ptr;
337
338         /* Find the other two delimiters. If the delim is escaped with a backslash, it doesn't count */
339         while (1) {
340                 delim2_pos = memchr(ptr, delim, end_of_regexp - ptr);
341                 if (!delim2_pos) {
342                         return -1;
343                 }
344                 ptr = delim2_pos + 1;
345                 if (delim2_pos[-1] != '\\') {
346                         break;
347                 }
348         }
349
350         if (ptr >= end_of_regexp) {
351                 return -1;
352         }
353
354         repl_pos = ptr;
355
356         while (1) {
357                 delim3_pos = memchr(ptr, delim, end_of_regexp - ptr);
358                 if (!delim3_pos) {
359                         return -1;
360                 }
361                 ptr = delim3_pos + 1;
362                 if (delim3_pos[-1] != '\\') {
363                         break;
364                 }
365         }
366         flags_pos = ptr;
367
368         if (regexp_flags_invalid(flags_pos, end_of_regexp) ||
369                         regexp_repl_invalid(repl_pos, delim3_pos, delim) ||
370                         regexp_pattern_invalid(regex_pos, delim2_pos)) {
371                 return -1;
372         }
373
374         return 0;
375 }
376
377 #define PAST_END_OF_RECORD ptr >= end_of_record
378
379 struct ast_dns_record *dns_naptr_alloc(struct ast_dns_query *query, const char *data, const size_t size)
380 {
381         struct ast_dns_naptr_record *naptr;
382         char *ptr = NULL;
383         uint16_t order;
384         uint16_t preference;
385         uint8_t flags_size;
386         char *flags;
387         uint8_t services_size;
388         char *services;
389         uint8_t regexp_size;
390         char *regexp;
391         char replacement[256] = "";
392         int replacement_size;
393         const char *end_of_record;
394         enum flags_result flags_res;
395
396         ptr = dns_find_record(data, size, query->result->answer, query->result->answer_size);
397         ast_assert(ptr != NULL);
398
399         end_of_record = ptr + size;
400
401         /* ORDER */
402         /* This assignment takes a big-endian 16-bit value and stores it in the
403          * machine's native byte order. Using this method allows us to avoid potential
404          * alignment issues in case the order is not on a short-addressable boundary.
405          * See http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html for
406          * more information
407          */
408         ptr += dns_parse_short((unsigned char *) ptr, &order);
409         if (PAST_END_OF_RECORD) {
410                 return NULL;
411         }
412
413         /* PREFERENCE */
414         ptr += dns_parse_short((unsigned char *) ptr, &preference);
415         if (PAST_END_OF_RECORD) {
416                 return NULL;
417         }
418
419         /* FLAGS */
420         ptr += dns_parse_string(ptr, &flags_size, &flags);
421         if (PAST_END_OF_RECORD) {
422                 return NULL;
423         }
424
425         /* SERVICES */
426         ptr += dns_parse_string(ptr, &services_size, &services);
427         if (PAST_END_OF_RECORD) {
428                 return NULL;
429         }
430
431         /* REGEXP */
432         ptr += dns_parse_string(ptr, &regexp_size, &regexp);
433         if (PAST_END_OF_RECORD) {
434                 return NULL;
435         }
436
437         replacement_size = dn_expand((unsigned char *)query->result->answer, (unsigned char *) end_of_record, (unsigned char *) ptr, replacement, sizeof(replacement) - 1);
438         if (replacement_size < 0) {
439                 ast_log(LOG_ERROR, "Failed to expand domain name: %s\n", strerror(errno));
440                 return NULL;
441         }
442
443         ptr += replacement_size;
444
445         if (ptr != end_of_record) {
446                 ast_log(LOG_ERROR, "NAPTR record gave undersized string indications.\n");
447                 return NULL;
448         }
449
450         /* We've validated the size of the NAPTR record. Now we can validate
451          * the individual parts
452          */
453         flags_res = interpret_flags(flags, flags_size);
454         if (flags_res == FLAGS_INVALID) {
455                 ast_log(LOG_ERROR, "NAPTR Record contained invalid flags %.*s\n", flags_size, flags);
456                 return NULL;
457         }
458
459         if (services_invalid(services, services_size)) {
460                 ast_log(LOG_ERROR, "NAPTR record contained invalid services %.*s\n", services_size, services);
461                 return NULL;
462         }
463
464         if (regexp_invalid(regexp, regexp_size)) {
465                 ast_log(LOG_ERROR, "NAPTR record contained invalid regexp %.*s\n", regexp_size, regexp);
466                 return NULL;
467         }
468
469         /* replacement_size takes into account the NULL label, so a NAPTR record with no replacement
470          * will have a replacement_size of 1.
471          */
472         if (regexp_size && replacement_size > 1) {
473                 ast_log(LOG_ERROR, "NAPTR record contained both a regexp and replacement\n");
474                 return NULL;
475         }
476
477         naptr = ast_calloc(1, sizeof(*naptr) + size + flags_size + 1 + services_size + 1 + regexp_size + 1 + replacement_size + 1);
478         if (!naptr) {
479                 return NULL;
480         }
481
482         naptr->order = order;
483         naptr->preference = preference;
484
485         ptr = naptr->data;
486         ptr += size;
487
488         strncpy(ptr, flags, flags_size);
489         ptr[flags_size] = '\0';
490         naptr->flags = ptr;
491         ptr += flags_size + 1;
492
493         strncpy(ptr, services, services_size);
494         ptr[services_size] = '\0';
495         naptr->service = ptr;
496         ptr += services_size + 1;
497
498         strncpy(ptr, regexp, regexp_size);
499         ptr[regexp_size] = '\0';
500         naptr->regexp = ptr;
501         ptr += regexp_size + 1;
502
503         strcpy(ptr, replacement);
504         naptr->replacement = ptr;
505
506         naptr->generic.data_ptr = naptr->data;
507
508         return (struct ast_dns_record *)naptr;
509 }
510
511
512 static int compare_order(const void *record1, const void *record2)
513 {
514         const struct ast_dns_naptr_record **left = (const struct ast_dns_naptr_record **)record1;
515         const struct ast_dns_naptr_record **right = (const struct ast_dns_naptr_record **)record2;
516
517         if ((*left)->order < (*right)->order) {
518                 return -1;
519         } else if ((*left)->order > (*right)->order) {
520                 return 1;
521         } else {
522                 return 0;
523         }
524 }
525
526 static int compare_preference(const void *record1, const void *record2)
527 {
528         const struct ast_dns_naptr_record **left = (const struct ast_dns_naptr_record **)record1;
529         const struct ast_dns_naptr_record **right = (const struct ast_dns_naptr_record **)record2;
530
531         if ((*left)->preference < (*right)->preference) {
532                 return -1;
533         } else if ((*left)->preference > (*right)->preference) {
534                 return 1;
535         } else {
536                 return 0;
537         }
538 }
539
540 void dns_naptr_sort(struct ast_dns_result *result)
541 {
542         struct ast_dns_record *current;
543         size_t num_records = 0;
544         struct ast_dns_naptr_record **records;
545         int i = 0;
546         int j = 0;
547         int cur_order;
548
549         /* Determine the number of records */
550         AST_LIST_TRAVERSE(&result->records, current, list) {
551                 ++num_records;
552         }
553
554         /* No point in continuing if there are no records */
555         if (num_records == 0) {
556                 return;
557         }
558
559         /* Allocate an array with that number of records */
560         records = ast_alloca(num_records * sizeof(*records));
561
562         /* Move records from the list to the array */
563         AST_LIST_TRAVERSE_SAFE_BEGIN(&result->records, current, list) {
564                 records[i++] = (struct ast_dns_naptr_record *) current;
565                 AST_LIST_REMOVE_CURRENT(list);
566         }
567         AST_LIST_TRAVERSE_SAFE_END;
568
569         /* Sort the array by order */
570         qsort(records, num_records, sizeof(*records), compare_order);
571
572         /* Sort subarrays by preference */
573         for (i = 0; i < num_records; i = j) {
574                 cur_order = records[i]->order;
575                 for (j = i + 1; j < num_records; ++j) {
576                         if (records[j]->order != cur_order) {
577                                 break;
578                         }
579                 }
580                 qsort(&records[i], j - i, sizeof(*records), compare_preference);
581         }
582
583         /* Place sorted records back into the original list */
584         for (i = 0; i < num_records; ++i) {
585                 AST_LIST_INSERT_TAIL(&result->records, (struct ast_dns_record *)(records[i]), list);
586         }
587 }
588
589 const char *ast_dns_naptr_get_flags(const struct ast_dns_record *record)
590 {
591         struct ast_dns_naptr_record *naptr = (struct ast_dns_naptr_record *) record;
592
593         ast_assert(ast_dns_record_get_rr_type(record) == ns_t_naptr);
594         return naptr->flags;
595 }
596
597 const char *ast_dns_naptr_get_service(const struct ast_dns_record *record)
598 {
599         struct ast_dns_naptr_record *naptr = (struct ast_dns_naptr_record *) record;
600
601         ast_assert(ast_dns_record_get_rr_type(record) == ns_t_naptr);
602         return naptr->service;
603 }
604
605 const char *ast_dns_naptr_get_regexp(const struct ast_dns_record *record)
606 {
607         struct ast_dns_naptr_record *naptr = (struct ast_dns_naptr_record *) record;
608
609         ast_assert(ast_dns_record_get_rr_type(record) == ns_t_naptr);
610         return naptr->regexp;
611 }
612
613 const char *ast_dns_naptr_get_replacement(const struct ast_dns_record *record)
614 {
615         struct ast_dns_naptr_record *naptr = (struct ast_dns_naptr_record *) record;
616
617         ast_assert(ast_dns_record_get_rr_type(record) == ns_t_naptr);
618         return naptr->replacement;
619 }
620
621 unsigned short ast_dns_naptr_get_order(const struct ast_dns_record *record)
622 {
623         struct ast_dns_naptr_record *naptr = (struct ast_dns_naptr_record *) record;
624
625         ast_assert(ast_dns_record_get_rr_type(record) == ns_t_naptr);
626         return naptr->order;
627 }
628
629 unsigned short ast_dns_naptr_get_preference(const struct ast_dns_record *record)
630 {
631         struct ast_dns_naptr_record *naptr = (struct ast_dns_naptr_record *) record;
632
633         ast_assert(ast_dns_record_get_rr_type(record) == ns_t_naptr);
634         return naptr->preference;
635 }