Terry found this problem with running the expr2 parser on OSX. Make the #defines...
[asterisk/asterisk.git] / main / enum.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * Funding provided by nic.at
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 /*! \file
22  *
23  * \brief ENUM Support for Asterisk
24  *
25  * \author Mark Spencer <markster@digium.com>
26  *
27  * \arg Funding provided by nic.at
28  *
29  * \par Enum standards
30  *
31  * - NAPTR records: http://ietf.nri.reston.va.us/rfc/rfc2915.txt
32  * - DNS SRV records: http://www.ietf.org/rfc/rfc2782.txt
33  * - ENUM http://www.ietf.org/rfc/rfc3761.txt
34  * - ENUM for H.323: http://www.ietf.org/rfc/rfc3762.txt
35  * - ENUM SIP: http://www.ietf.org/rfc/rfc3764.txt
36  * - IANA ENUM Services: http://www.iana.org/assignments/enum-services
37  *
38  * \par Possible improvement
39  * \todo Implement a caching mechanism for multile enum lookups
40  * - See http://bugs.digium.com/view.php?id=6739
41  */
42
43 #include "asterisk.h"
44
45 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
46
47 #include <sys/socket.h>
48 #include <netinet/in.h>
49 #include <arpa/nameser.h>
50 #ifdef __APPLE__
51 #if __APPLE_CC__ >= 1495
52 #include <arpa/nameser_compat.h>
53 #endif
54 #endif
55 #include <resolv.h>
56 #include <ctype.h>
57 #include <regex.h>
58
59 #include "asterisk/enum.h"
60 #include "asterisk/dns.h"
61 #include "asterisk/channel.h"
62 #include "asterisk/config.h"
63 #include "asterisk/utils.h"
64 #include "asterisk/manager.h"
65
66 #ifdef __APPLE__
67 #undef T_NAPTR
68 #define T_NAPTR 35
69 #endif
70
71 #ifdef __APPLE__
72 #undef T_TXT
73 #define T_TXT 16
74 #endif
75
76 #define TOPLEV "e164.arpa."     /*!< The IETF Enum standard root, managed by the ITU */
77
78 /* Linked list from config file */
79 static struct enum_search {
80         char toplev[512];
81         struct enum_search *next;
82 } *toplevs;
83
84 static int enumver;
85
86 AST_MUTEX_DEFINE_STATIC(enumlock);
87
88 /*! \brief Parse NAPTR record information elements */
89 static unsigned int parse_ie(char *data, unsigned int maxdatalen, unsigned char *src, unsigned int srclen)
90 {
91         unsigned int len, olen;
92
93         len = olen = (unsigned int) src[0];
94         src++;
95         srclen--;
96
97         if (len > srclen) {
98                 ast_log(LOG_WARNING, "ENUM parsing failed: Wanted %d characters, got %d\n", len, srclen);
99                 return -1;
100         }
101
102         if (len > maxdatalen)
103                 len = maxdatalen;
104         memcpy(data, src, len);
105
106         return olen + 1;
107 }
108
109 /*! \brief Parse DNS NAPTR record used in ENUM ---*/
110 static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, unsigned char *answer, int len, char *naptrinput)
111 {
112         char tech_return[80];
113         unsigned char *oanswer = answer;
114         char flags[512] = "";
115         char services[512] = "";
116         char *p;
117         char regexp[512] = "";
118         char repl[512] = "";
119         char temp[512] = "";
120         char delim;
121         char *delim2;
122         char *pattern, *subst, *d;
123         int res;
124         int regexp_len, size, backref;
125         int d_len = sizeof(temp) - 1;
126         regex_t preg;
127         regmatch_t pmatch[9];
128
129         tech_return[0] = '\0';
130
131         dst[0] = '\0';
132
133         if (len < sizeof(struct naptr)) {
134                 ast_log(LOG_WARNING, "NAPTR record length too short\n");
135                 return -1;
136         }
137         answer += sizeof(struct naptr);
138         len -= sizeof(struct naptr);
139         if ((res = parse_ie(flags, sizeof(flags) - 1, answer, len)) < 0) {
140                 ast_log(LOG_WARNING, "Failed to get flags from NAPTR record\n");
141                 return -1;
142         } else {
143                 answer += res;
144                 len -= res;
145         }
146         if ((res = parse_ie(services, sizeof(services) - 1, answer, len)) < 0) {
147                 ast_log(LOG_WARNING, "Failed to get services from NAPTR record\n");
148                 return -1;
149         } else {
150                 answer += res;
151                 len -= res;
152         }
153         if ((res = parse_ie(regexp, sizeof(regexp) - 1, answer, len)) < 0) {
154                 ast_log(LOG_WARNING, "Failed to get regexp from NAPTR record\n");
155                 return -1;
156         } else {
157                 answer += res;
158                 len -= res;
159         }
160
161         if ((res = dn_expand(oanswer, answer + len, answer, repl, sizeof(repl) - 1)) < 0) {
162                 ast_log(LOG_WARNING, "Failed to expand hostname\n");
163                 return -1;
164         }
165
166         ast_debug(3, "NAPTR input='%s', flags='%s', services='%s', regexp='%s', repl='%s'\n",
167                 naptrinput, flags, services, regexp, repl);
168
169         if (tolower(flags[0]) != 'u') {
170                 ast_log(LOG_WARNING, "NAPTR Flag must be 'U' or 'u'.\n");
171                 return -1;
172         }
173
174         p = strstr(services, "e2u+");
175         if (p == NULL)
176                 p = strstr(services, "E2U+");
177         if (p){
178                 p = p + 4;
179                 if (strchr(p, ':')){
180                         p = strchr(p, ':') + 1;
181                 }
182                 ast_copy_string(tech_return, p, sizeof(tech_return));
183         } else {
184
185                 p = strstr(services, "+e2u");
186                 if (p == NULL)
187                         p = strstr(services, "+E2U");
188                 if (p) {
189                         *p = 0;
190                         p = strchr(services, ':');
191                         if (p)
192                                 *p = 0;
193                         ast_copy_string(tech_return, services, sizeof(tech_return));
194                 }
195         }
196
197         /* DEDBUGGING STUB
198         ast_copy_string(regexp, "!^\\+43(.*)$!\\1@bla.fasel!", sizeof(regexp) - 1);
199         */
200
201         regexp_len = strlen(regexp);
202         if (regexp_len < 7) {
203                 ast_log(LOG_WARNING, "Regex too short to be meaningful.\n");
204                 return -1;
205         }
206
207
208         delim = regexp[0];
209         delim2 = strchr(regexp + 1, delim);
210         if ((delim2 == NULL) || (regexp[regexp_len-1] != delim)) {
211                 ast_log(LOG_WARNING, "Regex delimiter error (on \"%s\").\n",regexp);
212                 return -1;
213         }
214
215         pattern = regexp + 1;
216         *delim2 = 0;
217         subst   = delim2 + 1;
218         regexp[regexp_len-1] = 0;
219
220 /*
221  * now do the regex wizardry.
222  */
223
224         if (regcomp(&preg, pattern, REG_EXTENDED | REG_NEWLINE)) {
225                 ast_log(LOG_WARNING, "NAPTR Regex compilation error (regex = \"%s\").\n",regexp);
226                 return -1;
227         }
228
229         if (preg.re_nsub > 9) {
230                 ast_log(LOG_WARNING, "NAPTR Regex compilation error: too many subs.\n");
231                 regfree(&preg);
232                 return -1;
233         }
234
235         if (regexec(&preg, naptrinput, 9, pmatch, 0)) {
236                 ast_log(LOG_WARNING, "NAPTR Regex match failed.\n");
237                 regfree(&preg);
238                 return -1;
239         }
240         regfree(&preg);
241
242         d = temp;
243         d_len--;
244         while (*subst && (d_len > 0)) {
245                 if ((subst[0] == '\\') && isdigit(subst[1]) && (pmatch[subst[1]-'0'].rm_so != -1)) {
246                         backref = subst[1]-'0';
247                         size = pmatch[backref].rm_eo - pmatch[backref].rm_so;
248                         if (size > d_len) {
249                                 ast_log(LOG_WARNING, "Not enough space during NAPTR regex substitution.\n");
250                                 return -1;
251                                 }
252                         memcpy(d, naptrinput + pmatch[backref].rm_so, size);
253                         d += size;
254                         d_len -= size;
255                         subst += 2;
256                 } else if (isprint(*subst)) {
257                         *d++ = *subst++;
258                         d_len--;
259                 } else {
260                         ast_log(LOG_WARNING, "Error during regex substitution.\n");
261                         return -1;
262                 }
263         }
264         *d = 0;
265         ast_copy_string(dst, temp, dstsize);
266         dst[dstsize - 1] = '\0';
267
268         if (*tech != '\0'){ /* check if it is requested NAPTR */
269                 if (!strncasecmp(tech, "ALL", techsize)){
270                         return 1; /* return or count any RR */
271                 }
272                 if (!strncasecmp(tech_return, tech, sizeof(tech_return)<techsize?sizeof(tech_return):techsize)){
273                         ast_copy_string(tech, tech_return, techsize);
274                         return 1; /* we got out RR */
275                 } else { /* go to the next RR in the DNS answer */
276                         return 0;
277                 }
278         }
279
280         /* tech was not specified, return first parsed RR */
281         ast_copy_string(tech, tech_return, techsize);
282
283         return 1;
284 }
285
286 /* do not return requested value, just count RRs and return thei number in dst */
287 #define ENUMLOOKUP_OPTIONS_COUNT       1
288
289
290 /*! \brief Callback for TXT record lookup */
291 static int txt_callback(void *context, unsigned char *answer, int len, unsigned char *fullanswer)
292 {
293         struct enum_context *c = (struct enum_context *)context;
294
295         if (answer == NULL) {
296                 c->txt = NULL;
297                 c->txtlen = 0;
298                 return 0;
299         }
300
301         /* skip over first byte, as for some reason it's a vertical tab character */
302         answer += 1;
303         len -= 1;
304
305         /* answer is not null-terminated, but should be */
306         /* this is safe to do, as answer has extra bytes on the end we can
307          * safely overwrite with a null */
308         answer[len] = '\0';
309         /* now increment len so that len includes the null, so that we can
310          * compare apples to apples */
311         len +=1;
312
313         /* finally, copy the answer into c->txt */
314         ast_copy_string(c->txt, (const char *) answer, len < c->txtlen ? len : (c->txtlen));
315
316         /* just to be safe, let's make sure c->txt is null terminated */
317         c->txt[(c->txtlen)-1] = '\0';
318
319         return 1;
320 }
321
322 /*! \brief Callback from ENUM lookup function */
323 static int enum_callback(void *context, unsigned char *answer, int len, unsigned char *fullanswer)
324 {
325         struct enum_context *c = context;
326         void *p = NULL;
327         int res;
328
329         res = parse_naptr(c->dst, c->dstlen, c->tech, c->techlen, answer, len, c->naptrinput);
330
331         if (res < 0) {
332                 ast_log(LOG_WARNING, "Failed to parse naptr :(\n");
333                 return -1;
334         } else if (res > 0 && !ast_strlen_zero(c->dst)){ /* ok, we got needed NAPTR */
335                 if (c->options & ENUMLOOKUP_OPTIONS_COUNT){ /* counting RRs */
336                         c->position++;
337                         snprintf(c->dst, c->dstlen, "%d", c->position);
338                 } else  {
339                         if ((p = ast_realloc(c->naptr_rrs, sizeof(*c->naptr_rrs) * (c->naptr_rrs_count + 1)))) {
340                                 c->naptr_rrs = p;
341                                 memcpy(&c->naptr_rrs[c->naptr_rrs_count].naptr, answer, sizeof(c->naptr_rrs->naptr));
342                                 c->naptr_rrs[c->naptr_rrs_count].result = ast_strdup(c->dst);
343                                 c->naptr_rrs[c->naptr_rrs_count].tech = ast_strdup(c->tech);
344                                 c->naptr_rrs[c->naptr_rrs_count].sort_pos = c->naptr_rrs_count;
345                                 c->naptr_rrs_count++;
346                         }
347                         c->dst[0] = 0;
348                 }
349                 return 0;
350         }
351
352         if (c->options & ENUMLOOKUP_OPTIONS_COUNT)      { /* counting RRs */
353                 snprintf(c->dst, c->dstlen, "%d", c->position);
354         }
355
356         return 0;
357 }
358
359 /* ENUM lookup */
360 int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int dstlen, char *tech, int techlen, char* suffix, char* options, unsigned int record, struct enum_context **argcontext)
361 {
362         struct enum_context *context;
363         char tmp[259 + 512];
364         char naptrinput[512];
365         int pos = strlen(number) - 1;
366         int newpos = 0;
367         int ret = -1;
368         struct enum_search *s = NULL;
369         int version = -1;
370         /* for ISN rewrite */
371         char *p1 = NULL;
372         char *p2 = NULL;
373         int k = 0;
374         int i = 0;
375         int z = 0;
376
377         if (!(context = ast_calloc(1, sizeof(*context))))
378                 return -1;
379
380         ast_copy_string(naptrinput, number[0] == 'n' ? number+1 : number, sizeof(naptrinput));
381
382         context->naptrinput = naptrinput;       /* The number */
383         context->dst = dst;                     /* Return string */
384         context->dstlen = dstlen;
385         context->tech = tech;
386         context->techlen = techlen;
387         context->options = 0;
388         context->position = record;
389         context->naptr_rrs = NULL;
390         context->naptr_rrs_count = 0;
391
392         if (options != NULL) {
393                 if (*options == 'c') {
394                         context->options = ENUMLOOKUP_OPTIONS_COUNT;
395                         context->position = 0;
396                 }
397         }
398
399         ast_debug(1, "ast_get_enum(): n='%s', tech='%s', suffix='%s', options='%d', record='%d'\n",
400                         number, tech, suffix, context->options, context->position);
401
402         if (pos > 128)
403                 pos = 128;
404
405         /* ISN rewrite */
406         p1 = strchr(number, '*');
407
408         if (number[0] == 'n') { /* do not perform ISN rewrite ('n' is testing flag) */
409                 p1 = NULL;
410                 k = 1; /* strip 'n' from number */
411         }
412
413         if (p1 != NULL) {
414                 p2 = p1+1;
415                 while (p1 > number){
416                         p1--;
417                         tmp[newpos++] = *p1;
418                         tmp[newpos++] = '.';
419                 }
420                 if (*p2) {
421                         while (*p2 && newpos < 128){
422                                 tmp[newpos++] = *p2;
423                                 p2++;
424                         }
425                         tmp[newpos++] = '.';
426                 }
427
428         } else {
429                 while (pos >= k) {
430                         if (isdigit(number[pos])) {
431                                 tmp[newpos++] = number[pos];
432                                 tmp[newpos++] = '.';
433                         }
434                         pos--;
435                 }
436         }
437
438         if (chan && ast_autoservice_start(chan) < 0) {
439                 ast_free(context);
440                 return -1;
441         }
442
443         if (suffix) {
444                 ast_copy_string(tmp + newpos, suffix, sizeof(tmp) - newpos);
445                 ret = ast_search_dns(context, tmp, C_IN, T_NAPTR, enum_callback);
446                 ast_debug(1, "ast_get_enum: ast_search_dns(%s) returned %d\n", tmp, ret);
447         } else {
448                 ret = -1;               /* this is actually dead code since the demise of app_enum.c */
449                 for (;;) {
450                         ast_mutex_lock(&enumlock);
451                         if (version != enumver) {
452                                 /* Ooh, a reload... */
453                                 s = toplevs;
454                                 version = enumver;
455                         } else {
456                                 s = s->next;
457                         }
458                         ast_mutex_unlock(&enumlock);
459
460                         if (!s)
461                                 break;
462         
463                         ast_copy_string(tmp + newpos, s->toplev, sizeof(tmp) - newpos);
464                         ret = ast_search_dns(&context, tmp, C_IN, T_NAPTR, enum_callback);
465                         ast_debug(1, "ast_get_enum: ast_search_dns(%s) returned %d\n", tmp, ret);
466                         if (ret > 0)
467                                 break;
468                 }
469         }
470
471         if (ret < 0) {
472                 ast_debug(1, "No such number found: %s (%s)\n", tmp, strerror(errno));
473                 strcpy(dst, "0");
474                 ret = 0;
475         }
476
477         if (context->naptr_rrs_count >= context->position && ! (context->options & ENUMLOOKUP_OPTIONS_COUNT)) {
478                 /* sort array by NAPTR order/preference */
479                 for (k = 0; k < context->naptr_rrs_count; k++) {
480                         for (i = 0; i < context->naptr_rrs_count; i++) {
481                                 /* use order first and then preference to compare */
482                                 if ((ntohs(context->naptr_rrs[k].naptr.order) < ntohs(context->naptr_rrs[i].naptr.order)
483                                                 && context->naptr_rrs[k].sort_pos > context->naptr_rrs[i].sort_pos)
484                                         || (ntohs(context->naptr_rrs[k].naptr.order) > ntohs(context->naptr_rrs[i].naptr.order)
485                                                 && context->naptr_rrs[k].sort_pos < context->naptr_rrs[i].sort_pos)){
486                                         z = context->naptr_rrs[k].sort_pos;
487                                         context->naptr_rrs[k].sort_pos = context->naptr_rrs[i].sort_pos;
488                                         context->naptr_rrs[i].sort_pos = z;
489                                         continue;
490                                 }
491                                 if (ntohs(context->naptr_rrs[k].naptr.order) == ntohs(context->naptr_rrs[i].naptr.order)) {
492                                         if ((ntohs(context->naptr_rrs[k].naptr.pref) < ntohs(context->naptr_rrs[i].naptr.pref)
493                                                         && context->naptr_rrs[k].sort_pos > context->naptr_rrs[i].sort_pos)
494                                                 || (ntohs(context->naptr_rrs[k].naptr.pref) > ntohs(context->naptr_rrs[i].naptr.pref)
495                                                         && context->naptr_rrs[k].sort_pos < context->naptr_rrs[i].sort_pos)){
496                                                 z = context->naptr_rrs[k].sort_pos;
497                                                 context->naptr_rrs[k].sort_pos = context->naptr_rrs[i].sort_pos;
498                                                 context->naptr_rrs[i].sort_pos = z;
499                                         }
500                                 }
501                         }
502                 }
503                 for (k = 0; k < context->naptr_rrs_count; k++) {
504                         if (context->naptr_rrs[k].sort_pos == context->position-1) {
505                                 ast_copy_string(context->dst, context->naptr_rrs[k].result, dstlen);
506                                 ast_copy_string(context->tech, context->naptr_rrs[k].tech, techlen);
507                                 break;
508                         }
509                 }
510         } else if (!(context->options & ENUMLOOKUP_OPTIONS_COUNT)) {
511                 context->dst[0] = 0;
512         }
513         if (chan)
514                 ret |= ast_autoservice_stop(chan);
515
516         if (!argcontext) {
517                 for (k = 0; k < context->naptr_rrs_count; k++) {
518                         ast_free(context->naptr_rrs[k].result);
519                         ast_free(context->naptr_rrs[k].tech);
520                 }
521                 ast_free(context->naptr_rrs);
522                 ast_free(context);
523         } else
524                 *argcontext = context;
525
526         return ret;
527 }
528
529 /* Get TXT record from DNS. Really has nothing to do with enum, but anyway... */
530 int ast_get_txt(struct ast_channel *chan, const char *number, char *dst, int dstlen, char *tech, int techlen, char *txt, int txtlen)
531 {
532         struct enum_context context;
533         char tmp[259 + 512];
534         char naptrinput[512] = "+";
535         int pos = strlen(number) - 1;
536         int newpos = 0;
537         int ret = -1;
538         struct enum_search *s = NULL;
539         int version = -1;
540
541         strncat(naptrinput, number, sizeof(naptrinput) - 2);
542
543         context.naptrinput = naptrinput;
544         context.dst = dst;
545         context.dstlen = dstlen;
546         context.tech = tech;
547         context.techlen = techlen;
548         context.txt = txt;
549         context.txtlen = txtlen;
550
551         if (pos > 128)
552                 pos = 128;
553         while (pos >= 0) {
554                 tmp[newpos++] = number[pos--];
555                 tmp[newpos++] = '.';
556         }
557
558         if (chan && ast_autoservice_start(chan) < 0)
559                 return -1;
560
561         for (;;) {
562                 ast_mutex_lock(&enumlock);
563                 if (version != enumver) {
564                         /* Ooh, a reload... */
565                         s = toplevs;
566                         version = enumver;
567                 } else {
568                         s = s->next;
569                 }
570                 if (s) {
571                         ast_copy_string(tmp + newpos, s->toplev, sizeof(tmp) - newpos);
572                 }
573                 ast_mutex_unlock(&enumlock);
574                 if (!s)
575                         break;
576
577                 ret = ast_search_dns(&context, tmp, C_IN, T_TXT, txt_callback);
578                 if (ret > 0)
579                         break;
580         }
581         if (ret < 0) {
582                 ast_debug(2, "No such number found in ENUM: %s (%s)\n", tmp, strerror(errno));
583                 ret = 0;
584         }
585         if (chan)
586                 ret |= ast_autoservice_stop(chan);
587         return ret;
588 }
589
590 /*! \brief Add enum tree to linked list */
591 static struct enum_search *enum_newtoplev(const char *s)
592 {
593         struct enum_search *tmp;
594
595         if ((tmp = ast_calloc(1, sizeof(*tmp)))) {              
596                 ast_copy_string(tmp->toplev, s, sizeof(tmp->toplev));
597         }
598         return tmp;
599 }
600
601 /*! \brief Initialize the ENUM support subsystem */
602 static int private_enum_init(int reload)
603 {
604         struct ast_config *cfg;
605         struct enum_search *s, *sl;
606         struct ast_variable *v;
607         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
608
609         if ((cfg = ast_config_load("enum.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
610                 return 0;
611
612         /* Destroy existing list */
613         ast_mutex_lock(&enumlock);
614         s = toplevs;
615         while (s) {
616                 sl = s;
617                 s = s->next;
618                 ast_free(sl);
619         }
620         toplevs = NULL;
621         if (cfg) {
622                 sl = NULL;
623                 v = ast_variable_browse(cfg, "general");
624                 while (v) {
625                         if (!strcasecmp(v->name, "search")) {
626                                 s = enum_newtoplev(v->value);
627                                 if (s) {
628                                         if (sl)
629                                                 sl->next = s;
630                                         else
631                                                 toplevs = s;
632                                         sl = s;
633                                 }
634                         }
635                         v = v->next;
636                 }
637                 ast_config_destroy(cfg);
638         } else {
639                 toplevs = enum_newtoplev(TOPLEV);
640         }
641         enumver++;
642         ast_mutex_unlock(&enumlock);
643         manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: Enum\r\nStatus: Enabled\r\nMessage: ENUM reload Requested\r\n");
644         return 0;
645 }
646
647 int ast_enum_init(void)
648 {
649         return private_enum_init(0);
650 }
651
652 int ast_enum_reload(void)
653 {
654         return private_enum_init(1);
655 }