diff --git a/funcs/func_enum.c b/funcs/func_enum.c index e1c29cbfc2050a0c9223a851396b75be34e37d75..0fba5af3b815cefcc183e269994177e2b7fb7eaa 100644 --- a/funcs/func_enum.c +++ b/funcs/func_enum.c @@ -78,16 +78,21 @@ static int function_enum(struct ast_channel *chan, const char *cmd, char *data, return -1; } - ast_copy_string(tech, args.tech ? args.tech : "sip", sizeof(tech)); + if (args.tech && !ast_strlen_zero(args.tech)) { + ast_copy_string(tech,args.tech, sizeof(tech)); + } else { + ast_copy_string(tech,"sip",sizeof(tech)); + } - if (!args.zone) + if (!args.zone) { args.zone = "e164.arpa"; - - if (!args.options) + } + if (!args.options) { args.options = ""; - - if (args.record) - record = atoi(args.record); + } + if (args.record) { + record = atoi(args.record) ? atoi(args.record) : record; + } /* strip any '-' signs from number */ for (s = p = args.number; *s; s++) { @@ -97,15 +102,14 @@ static int function_enum(struct ast_channel *chan, const char *cmd, char *data, } } - - res = ast_get_enum(chan, num, dest, sizeof(dest), tech, sizeof(tech), args.zone, args.options, 1, NULL); + res = ast_get_enum(chan, num, dest, sizeof(dest), tech, sizeof(tech), args.zone, args.options, record, NULL); p = strchr(dest, ':'); - if (p && strcasecmp(tech, "ALL")) + if (p && strcasecmp(tech, "ALL") && !strchr(args.options, 'u')) { ast_copy_string(buf, p + 1, len); - else + } else { ast_copy_string(buf, dest, len); - + } return 0; } @@ -323,6 +327,10 @@ static struct ast_custom_function enum_function = { .desc = "Option 'c' returns an integer count of the number of NAPTRs of a certain RR type.\n" "Combination of 'c' and Method-type of 'ALL' will return a count of all NAPTRs for the record.\n" + "Option 'u' returns the full URI and does not strip off the URI-scheme.\n" + "Option 's' triggers ISN specific rewriting\n" + "Option 'i' looks for branches into an Infrastructure ENUM tree\n" + "Option 'd' for a direct DNS lookup without any flipping of digits\n" "Defaults are: Method-type=sip, no options, record=1, zone-suffix=e164.arpa\n\n" "For more information, see doc/asterisk.pdf", .read = function_enum, @@ -332,23 +340,30 @@ static int function_txtcidname(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { int res; - char tech[80]; - char txt[256] = ""; - char dest[80]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(number); + AST_APP_ARG(zone); + ); buf[0] = '\0'; - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "TXTCIDNAME requires an argument (number)\n"); + ast_log(LOG_WARNING, "Syntax: TXTCIDNAME(number[,zone-suffix])\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, data); + + if (args.argc < 1) { + ast_log(LOG_WARNING, "Syntax: TXTCIDNAME(number[,zone-suffix])\n"); return -1; } - res = ast_get_txt(chan, data, dest, sizeof(dest), tech, sizeof(tech), txt, - sizeof(txt)); + if (!args.zone) { + args.zone = "e164.arpa"; + } - if (!ast_strlen_zero(txt)) - ast_copy_string(buf, txt, len); + res = ast_get_txt(chan, args.number, buf, len, args.zone); return 0; } @@ -356,11 +371,11 @@ static int function_txtcidname(struct ast_channel *chan, const char *cmd, static struct ast_custom_function txtcidname_function = { .name = "TXTCIDNAME", .synopsis = "TXTCIDNAME looks up a caller name via DNS", - .syntax = "TXTCIDNAME(<number>)", + .syntax = "TXTCIDNAME(<number>[,zone-suffix])", .desc = "This function looks up the given phone number in DNS to retrieve\n" "the caller id name. The result will either be blank or be the value\n" - "found in the TXT record in DNS.\n", + "found in the TXT record in DNS. The default zone-suffix is e164.arpa.\n", .read = function_txtcidname, }; diff --git a/include/asterisk/enum.h b/include/asterisk/enum.h index d6bbea294a4392045d2c03d93c3c36d25060567d..499e34673c2d92fbcbcfb8fb28e7b3a3078308db 100644 --- a/include/asterisk/enum.h +++ b/include/asterisk/enum.h @@ -45,7 +45,8 @@ struct enum_context { char *txt; /*!< TXT record in TXT lookup */ int txtlen; /*!< Length */ char *naptrinput; /*!< The number to lookup */ - int position; /*!< used as counter for RRs or specifies position of required RR */ + int position; /*!< specifies position of required RR */ + int count; /*!< used as counter for RRs */ int options; /*!< options , see ENUMLOOKUP_OPTIONS_* defined above */ struct enum_naptr_rr *naptr_rrs; /*!< array of parsed NAPTR RRs */ int naptr_rrs_count; /*!< Size of array naptr_rrs */ @@ -57,12 +58,18 @@ struct enum_context { \param number E164 number with or without the leading + \param location Number returned (or SIP uri) \param maxloc Max length - \param technology Technology (from url scheme in response) + \param technology Technology (from url scheme in response) You can set it to get particular answer RR, if there are many techs in DNS response, example: "sip" - If you need any record, then set it to empty string + If you need any record, then set it to "ALL" string \param maxtech Max length - \param suffix Zone suffix (if is NULL then use enum.conf 'search' variable) - \param options Options ('c' to count number of NAPTR RR) + \param suffix Zone suffix (WARNING: No defaults here any more) + \param options Options + 'c' - Count number of NAPTR RR + number - Position of the requested RR in the answer list + 'u' - Full URI return (does not strip URI scheme) + 'i' - Infrastructure ENUM lookup + 's' - ISN based lookup + 'd' - Direct DNS query \param record The position of required RR in the answer list \param argcontext Argument for caching results into an enum_context pointer (NULL is used for not caching) \retval 1 if found @@ -75,14 +82,11 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *location, i /*! \brief Lookup DNS TXT record (used by app TXTCIDnum \param chan Channel \param number E164 number with or without the leading + - \param location Number returned (or SIP uri) - \param maxloc Max length of number - \param technology Technology (not used in TXT records) - \param maxtech Max length \param txt Text string (return value) \param maxtxt Max length of "txt" + \param suffix Zone suffix */ -int ast_get_txt(struct ast_channel *chan, const char *number, char *location, int maxloc, char *technology, int maxtech, char *txt, int maxtxt); +int ast_get_txt(struct ast_channel *chan, const char *number, char *txt, int maxtxt, char *suffix); int ast_enum_init(void); int ast_enum_reload(void); diff --git a/main/enum.c b/main/enum.c index 78dafb6ef481a3a6be8df24df05701945f8c1041..6f821c26d333fa5aa47bd2f1ec07eccfa6df886c 100644 --- a/main/enum.c +++ b/main/enum.c @@ -35,9 +35,14 @@ * - ENUM SIP: http://www.ietf.org/rfc/rfc3764.txt * - IANA ENUM Services: http://www.iana.org/assignments/enum-services * + * - I-ENUM: + * http://tools.ietf.org/wg/enum/draft-ietf-enum-combined/ + * http://tools.ietf.org/wg/enum/draft-ietf-enum-branch-location-record/ + * * \par Possible improvement * \todo Implement a caching mechanism for multile enum lookups * - See http://bugs.digium.com/view.php?id=6739 + * \todo The service type selection needs to be redone. */ #include "asterisk.h" @@ -73,17 +78,289 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define T_TXT 16 #endif -#define TOPLEV "e164.arpa." /*!< The IETF Enum standard root, managed by the ITU */ +static char ienum_branchlabel[32] = "i"; +/* how to do infrastructure enum branch location resolution? */ +#define ENUMLOOKUP_BLR_CC 0 +#define ENUMLOOKUP_BLR_TXT 1 +#define ENUMLOOKUP_BLR_EBL 2 +static int ebl_alg = ENUMLOOKUP_BLR_CC; + +/* EBL record provisional type code */ +#define T_EBL 65300 -/* Linked list from config file */ -static struct enum_search { - char toplev[512]; - struct enum_search *next; -} *toplevs; +AST_MUTEX_DEFINE_STATIC(enumlock); -static int enumver; +/*! \brief Determine the length of a country code when given an E.164 string */ +/* + * Input: E.164 number w/o leading + + * + * Output: number of digits in the country code + * 0 on invalid number + * + * Algorithm: + * 3 digits is the default length of a country code. + * country codes 1 and 7 are a single digit. + * the following country codes are two digits: 20, 27, 30-34, 36, 39, + * 40, 41, 43-49, 51-58, 60-66, 81, 82, 84, 86, 90-95, 98. + */ +static int cclen(const char *number) +{ + int cc; + char digits[3] = ""; -AST_MUTEX_DEFINE_STATIC(enumlock); + if (!number || (strlen(number) < 3)) { + return 0; + } + + strncpy(digits, number, 2); + + if (!sscanf(digits, "%d", &cc)) { + return 0; + } + + if (cc / 10 == 1 || cc / 10 == 7) + return 1; + + if (cc == 20 || cc == 27 || (cc >= 30 && cc <= 34) || cc == 36 || + cc == 39 || cc == 40 || cc == 41 || (cc >= 40 && cc <= 41) || + (cc >= 43 && cc <= 49) || (cc >= 51 && cc <= 58) || + (cc >= 60 && cc <= 66) || cc == 81 || cc == 82 || cc == 84 || + cc == 86 || (cc >= 90 && cc <= 95) || cc == 98) { + return 2; + } + + return 3; +} + +struct txt_context { + char txt[1024]; /* TXT record in TXT lookup */ + int txtlen; /* Length */ +}; + +/*! \brief Callback for TXT record lookup, /ol version */ +static int txt_callback(void *context, unsigned char *answer, int len, unsigned char *fullanswer) +{ + struct txt_context *c = context; + unsigned int i; + + c->txt[0] = 0; /* default to empty */ + c->txtlen = 0; + + if (answer == NULL) { + return 0; + } + + /* RFC1035: + * + * <character-string> is a single length octet followed by that number of characters. + * TXT-DATA One or more <character-string>s. + * + * We only take the first string here. + */ + + i = *answer++; + len -= 1; + + if (i > len) { /* illegal packet */ + ast_log(LOG_WARNING, "txt_callback: malformed TXT record.\n"); + return 0; + } + + if (i >= sizeof(c->txt)) { /* too long? */ + ast_log(LOG_WARNING, "txt_callback: TXT record too long.\n"); + i = sizeof(c->txt) - 1; + } + + ast_copy_string(c->txt, (char *)answer, i + 1); /* this handles the \0 termination */ + c->txtlen = i; + + return 1; +} + +/*! \brief Determine the branch location record as stored in a TXT record */ +/* + * Input: CC code + * + * Output: number of digits in the number before the i-enum branch + * + * Algorithm: Build <ienum_branchlabel>.c.c.<suffix> and look for a TXT lookup. + * Return atoi(TXT-record). + * Return -1 on not found. + * + */ +static int blr_txt(const char *cc, const char *suffix) +{ + struct txt_context context; + char domain[128] = ""; + char *p1, *p2; + int ret; + + ast_mutex_lock(&enumlock); + + ast_verb(4, "blr_txt() cc='%s', suffix='%s', c_bl='%s'\n", cc, suffix, ienum_branchlabel); + + if (sizeof(domain) < (strlen(cc) * 2 + strlen(ienum_branchlabel) + strlen(suffix) + 2)) { + ast_mutex_unlock(&enumlock); + ast_log(LOG_WARNING, "ERROR: string sizing in blr_txt.\n"); + return -1; + } + + p1 = domain + snprintf(domain, sizeof(domain), "%s.", ienum_branchlabel); + ast_mutex_unlock(&enumlock); + + for (p2 = (char *) cc + strlen(cc) - 1; p2 >= cc; p2--) { + if (isdigit(*p2)) { + *p1++ = *p2; + *p1++ = '.'; + } + } + strcat(p1, suffix); + + ast_verb(4, "blr_txt() FQDN for TXT record: %s, cc was %s\n", domain, cc); + + ret = ast_search_dns(&context, domain, C_IN, T_TXT, txt_callback); + + if (ret > 0) { + ret = atoi(context.txt); + + if ((ret >= 0) && (ret < 20)) { + ast_verb(3, "blr_txt() BLR TXT record for %s is %d (apex: %s)\n", cc, ret, suffix); + return ret; + } + } + + ast_verb(3, "blr_txt() BLR TXT record for %s not found (apex: %s)\n", cc, suffix); + + return -1; +} + +struct ebl_context { + unsigned char pos; + char separator[256]; /* label to insert */ + int sep_len; /* Length */ + char apex[256]; /* new Apex */ + int apex_len; /* Length */ +}; + +/*! \brief Callback for EBL record lookup */ +static int ebl_callback(void *context, unsigned char *answer, int len, unsigned char *fullanswer) +{ + struct ebl_context *c = context; + unsigned int i; + + c->pos = 0; /* default to empty */ + c->separator[0] = 0; + c->sep_len = 0; + c->apex[0] = 0; + c->apex_len = 0; + + if (answer == NULL) { + return 0; + } + + /* draft-lendl-enum-branch-location-record-00 + * + * 0 1 2 3 4 5 6 7 + * +--+--+--+--+--+--+--+--+ + * | POSITION | + * +--+--+--+--+--+--+--+--+ + * / SEPARATOR / + * +--+--+--+--+--+--+--+--+ + * / APEX / + * +--+--+--+--+--+--+--+--+ + * + * where POSITION is a single byte, SEPARATOR is a <character-string> + * and APEX is a <domain-name>. + * + */ + + c->pos = *answer++; + len -= 1; + + if ((c->pos > 15) || len < 2) { /* illegal packet */ + ast_log(LOG_WARNING, "ebl_callback: malformed EBL record.\n"); + return 0; + } + + i = *answer++; + len -= 1; + if (i > len) { /* illegal packet */ + ast_log(LOG_WARNING, "ebl_callback: malformed EBL record.\n"); + return 0; + } + + ast_copy_string(c->separator, (char *)answer, i + 1); + c->sep_len = i; + + answer += i; + len -= i; + + if ((i = dn_expand((unsigned char *)fullanswer, (unsigned char *)answer + len, + (unsigned char *)answer, c->apex, sizeof(c->apex) - 1)) < 0) { + ast_log(LOG_WARNING, "Failed to expand hostname\n"); + return 0; + } + c->apex[i] = 0; + c->apex_len = i; + + return 1; +} + +/*! \brief Evaluate the I-ENUM branch as stored in an EBL record */ +/* + * Input: CC code + * + * Output: number of digits in the number before the i-enum branch + * + * Algorithm: Build <ienum_branchlabel>.c.c.<suffix> and look for an EBL record + * Return pos and fill in separator and apex. + * Return -1 on not found. + * + */ +static int blr_ebl(const char *cc, const char *suffix, char *separator, int sep_len, char* apex, int apex_len) +{ + struct ebl_context context; + char domain[128] = ""; + char *p1,*p2; + int ret; + + ast_mutex_lock(&enumlock); + + ast_verb(4, "blr_ebl() cc='%s', suffix='%s', c_bl='%s'\n", cc, suffix, ienum_branchlabel); + + if (sizeof(domain) < (strlen(cc) * 2 + strlen(ienum_branchlabel) + strlen(suffix) + 2)) { + ast_mutex_unlock(&enumlock); + ast_log(LOG_WARNING, "ERROR: string sizing in blr_EBL.\n"); + return -1; + } + + p1 = domain + snprintf(domain, sizeof(domain), "%s.", ienum_branchlabel); + ast_mutex_unlock(&enumlock); + + for (p2 = (char *) cc + strlen(cc) - 1; p2 >= cc; p2--) { + if (isdigit(*p2)) { + *p1++ = *p2; + *p1++ = '.'; + } + } + strcat(p1, suffix); + + ast_verb(4, "blr_ebl() FQDN for EBL record: %s, cc was %s\n", domain, cc); + + ret = ast_search_dns(&context, domain, C_IN, T_EBL, ebl_callback); + if (ret > 0) { + ret = context.pos; + + if ((ret >= 0) && (ret < 20)) { + ast_verb(3, "blr_txt() BLR EBL record for %s is %d/%s/%s)\n", cc, ret, context.separator, context.apex); + ast_copy_string(separator, context.separator, sep_len); + ast_copy_string(apex, context.apex, apex_len); + return ret; + } + } + ast_verb(3, "blr_txt() BLR EBL record for %s not found (apex: %s)\n", cc, suffix); + return -1; +} /*! \brief Parse NAPTR record information elements */ static unsigned int parse_ie(char *data, unsigned int maxdatalen, unsigned char *src, unsigned int srclen) @@ -107,27 +384,28 @@ static unsigned int parse_ie(char *data, unsigned int maxdatalen, unsigned char } /*! \brief Parse DNS NAPTR record used in ENUM ---*/ -static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, unsigned char *answer, int len, char *naptrinput) +static int parse_naptr(unsigned char *dst, int dstsize, char *tech, int techsize, unsigned char *answer, int len, unsigned char *naptrinput) { char tech_return[80]; - unsigned char *oanswer = answer; + char *oanswer = (char *)answer; char flags[512] = ""; char services[512] = ""; char *p; char regexp[512] = ""; char repl[512] = ""; char temp[512] = ""; + char errbuff[512] = ""; char delim; char *delim2; - char *pattern, *subst, *d; + char *pattern, *subst, *d, *number; int res; - int regexp_len, size, backref; + int regexp_len, rc; + int size; int d_len = sizeof(temp) - 1; regex_t preg; regmatch_t pmatch[9]; tech_return[0] = '\0'; - dst[0] = '\0'; if (len < sizeof(struct naptr)) { @@ -143,6 +421,7 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, unsigne answer += res; len -= res; } + if ((res = parse_ie(services, sizeof(services) - 1, answer, len)) < 0) { ast_log(LOG_WARNING, "Failed to get services from NAPTR record\n"); return -1; @@ -158,14 +437,15 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, unsigne len -= res; } - if ((res = dn_expand(oanswer, answer + len, answer, repl, sizeof(repl) - 1)) < 0) { + if ((res = dn_expand((unsigned char *)oanswer, (unsigned char *)answer + len, (unsigned char *)answer, repl, sizeof(repl) - 1)) < 0) { ast_log(LOG_WARNING, "Failed to expand hostname\n"); return -1; } - + ast_debug(3, "NAPTR input='%s', flags='%s', services='%s', regexp='%s', repl='%s'\n", naptrinput, flags, services, regexp, repl); + if (tolower(flags[0]) != 'u') { ast_log(LOG_WARNING, "NAPTR Flag must be 'U' or 'u'.\n"); return -1; @@ -231,28 +511,33 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, unsigne regfree(&preg); return -1; } - - if (regexec(&preg, naptrinput, 9, pmatch, 0)) { - ast_log(LOG_WARNING, "NAPTR Regex match failed.\n"); + + if (0 != (rc = regexec(&preg, (char *)naptrinput, 0, pmatch, 0))) { + regerror(rc, &preg, errbuff, sizeof(errbuff)); + ast_log(LOG_WARNING, "NAPTR Regex match failed. Reason: %s\n", errbuff); regfree(&preg); return -1; } regfree(&preg); d = temp; + + number = (char *)(naptrinput + (*naptrinput == '+')); + d_len--; while (*subst && (d_len > 0)) { - if ((subst[0] == '\\') && isdigit(subst[1]) && (pmatch[subst[1]-'0'].rm_so != -1)) { - backref = subst[1]-'0'; - size = pmatch[backref].rm_eo - pmatch[backref].rm_so; + if ((subst[0] == '\\' && isdigit(subst[1]))) { + size = strlen(number); + //ast_log(LOG_WARNING, "size:%d: offset:%s: temp:%s:\n",size,offset,temp); if (size > d_len) { ast_log(LOG_WARNING, "Not enough space during NAPTR regex substitution.\n"); return -1; - } - memcpy(d, naptrinput + pmatch[backref].rm_so, size); - d += size; + } + memcpy(d, number, size); d_len -= size; subst += 2; + d += size; + //ast_log(LOG_WARNING, "after dlen:%d: temp:%s:\n",d_len,temp); } else if (isprint(*subst)) { *d++ = *subst++; d_len--; @@ -262,62 +547,36 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, unsigne } } *d = 0; - ast_copy_string(dst, temp, dstsize); + ast_copy_string((char *)dst, temp,dstsize); dst[dstsize - 1] = '\0'; +// ast_log(LOG_WARNING, "after dst:%s: temp:%s:\n",dst,temp); if (*tech != '\0'){ /* check if it is requested NAPTR */ if (!strncasecmp(tech, "ALL", techsize)){ - return 1; /* return or count any RR */ + return 0; /* return or count any RR */ } - if (!strncasecmp(tech_return, tech, sizeof(tech_return)<techsize?sizeof(tech_return):techsize)){ + if (!strncasecmp(tech_return, tech, sizeof(tech_return) < techsize ? sizeof(tech_return): techsize)){ ast_copy_string(tech, tech_return, techsize); - return 1; /* we got out RR */ + return 0; /* we got our RR */ } else { /* go to the next RR in the DNS answer */ - return 0; + return 1; } } /* tech was not specified, return first parsed RR */ ast_copy_string(tech, tech_return, techsize); - return 1; + return 0; } /* do not return requested value, just count RRs and return thei number in dst */ #define ENUMLOOKUP_OPTIONS_COUNT 1 - - -/*! \brief Callback for TXT record lookup */ -static int txt_callback(void *context, unsigned char *answer, int len, unsigned char *fullanswer) -{ - struct enum_context *c = (struct enum_context *)context; - - if (answer == NULL) { - c->txt = NULL; - c->txtlen = 0; - return 0; - } - - /* skip over first byte, as for some reason it's a vertical tab character */ - answer += 1; - len -= 1; - - /* answer is not null-terminated, but should be */ - /* this is safe to do, as answer has extra bytes on the end we can - * safely overwrite with a null */ - answer[len] = '\0'; - /* now increment len so that len includes the null, so that we can - * compare apples to apples */ - len +=1; - - /* finally, copy the answer into c->txt */ - ast_copy_string(c->txt, (const char *) answer, len < c->txtlen ? len : (c->txtlen)); - - /* just to be safe, let's make sure c->txt is null terminated */ - c->txt[(c->txtlen) - 1] = '\0'; - - return 1; -} +/* do an ISN style lookup */ +#define ENUMLOOKUP_OPTIONS_ISN 2 +/* do a infrastructure ENUM lookup */ +#define ENUMLOOKUP_OPTIONS_IENUM 4 +/* do a direct DNS lookup: no reversal */ +#define ENUMLOOKUP_OPTIONS_DIRECT 8 /*! \brief Callback from ENUM lookup function */ static int enum_callback(void *context, unsigned char *answer, int len, unsigned char *fullanswer) @@ -326,15 +585,15 @@ static int enum_callback(void *context, unsigned char *answer, int len, unsigned void *p = NULL; int res; - res = parse_naptr(c->dst, c->dstlen, c->tech, c->techlen, answer, len, c->naptrinput); + res = parse_naptr((unsigned char *)c->dst, c->dstlen, c->tech, c->techlen, answer, len, (unsigned char *)c->naptrinput); if (res < 0) { - ast_log(LOG_WARNING, "Failed to parse naptr :(\n"); + ast_log(LOG_WARNING, "Failed to parse naptr\n"); return -1; - } else if (res > 0 && !ast_strlen_zero(c->dst)){ /* ok, we got needed NAPTR */ - if (c->options & ENUMLOOKUP_OPTIONS_COUNT){ /* counting RRs */ - c->position++; - snprintf(c->dst, c->dstlen, "%d", c->position); + } else if ((res == 0) && !ast_strlen_zero(c->dst)) { /* ok, we got needed NAPTR */ + if (c->options & ENUMLOOKUP_OPTIONS_COUNT) { /* counting RRs */ + c->count++; + snprintf(c->dst, c->dstlen, "%d", c->count); } else { if ((p = ast_realloc(c->naptr_rrs, sizeof(*c->naptr_rrs) * (c->naptr_rrs_count + 1)))) { c->naptr_rrs = p; @@ -349,10 +608,6 @@ static int enum_callback(void *context, unsigned char *answer, int len, unsigned return 0; } - if (c->options & ENUMLOOKUP_OPTIONS_COUNT) { /* counting RRs */ - snprintf(c->dst, c->dstlen, "%d", c->position); - } - return 0; } @@ -360,24 +615,52 @@ static int enum_callback(void *context, unsigned char *answer, int len, unsigned 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) { struct enum_context *context; - char tmp[259 + 512]; - char naptrinput[512]; - int pos = strlen(number) - 1; - int newpos = 0; + char tmp[512]; + char domain[256]; + char left[128]; + char middle[128]; + char naptrinput[128]; + char apex[128] = ""; int ret = -1; - struct enum_search *s = NULL; - int version = -1; /* for ISN rewrite */ char *p1 = NULL; char *p2 = NULL; + char *p3 = NULL; int k = 0; int i = 0; int z = 0; + int spaceleft = 0; + struct timeval time_start, time_end; + + if (ast_strlen_zero(suffix)) { + ast_log(LOG_WARNING, "ast_get_enum need a suffix parameter now.\n"); + return -1; + } + + ast_verb(2, "ast_get_enum(num='%s', tech='%s', suffix='%s', options='%s', record=%d\n", number, tech, suffix, options, record); + +/* + We don't need that any more, that "n" preceding the number has been replaced by a flag + in the options paramter. + ast_copy_string(naptrinput, number, sizeof(naptrinput)); +*/ +/* + * The "number" parameter includes a leading '+' if it's a full E.164 number (and not ISN) + * We need to preserve that as the regex inside NAPTRs expect the +. + * + * But for the domain generation, the '+' is a nuissance, so we get rid of it. +*/ + ast_copy_string(naptrinput, number[0] == 'n' ? number + 1 : number, sizeof(naptrinput)); + if (number[0] == '+') { + number++; + } if (!(context = ast_calloc(1, sizeof(*context)))) return -1; - ast_copy_string(naptrinput, number[0] == 'n' ? number + 1 : number, sizeof(naptrinput)); + if((p3 = strchr(naptrinput, '*'))) { + *p3='\0'; + } context->naptrinput = naptrinput; /* The number */ context->dst = dst; /* Return string */ @@ -385,89 +668,179 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int ds context->tech = tech; context->techlen = techlen; context->options = 0; - context->position = record; + context->position = record > 0 ? record : 1; + context->count = 0; context->naptr_rrs = NULL; context->naptr_rrs_count = 0; + /* + * Process options: + * + * c Return count, not URI + * i Use infrastructure ENUM + * s Do ISN transformation + * d Direct DNS query: no reversing. + * + */ if (options != NULL) { - if (*options == 'c') { - context->options = ENUMLOOKUP_OPTIONS_COUNT; - context->position = 0; + if (strchr(options,'s')) { + context->options |= ENUMLOOKUP_OPTIONS_ISN; + } else if (strchr(options,'i')) { + context->options |= ENUMLOOKUP_OPTIONS_IENUM; + } else if (strchr(options,'d')) { + context->options |= ENUMLOOKUP_OPTIONS_DIRECT; + } + if (strchr(options,'c')) { + context->options |= ENUMLOOKUP_OPTIONS_COUNT; + } + if (strchr(number,'*')) { + context->options |= ENUMLOOKUP_OPTIONS_ISN; } } - + ast_verb(2, "ENUM options(%s): pos=%d, options='%d'\n", options, context->position, context->options); ast_debug(1, "ast_get_enum(): n='%s', tech='%s', suffix='%s', options='%d', record='%d'\n", number, tech, suffix, context->options, context->position); - if (pos > 128) - pos = 128; - + /* + * This code does more than simple RFC3261 ENUM. All these rewriting + * schemes have in common that they build the FQDN for the NAPTR lookup + * by concatenating + * - a number which needs be flipped and "."-seperated (left) + * - some fixed string (middle) + * - an Apex. (apex) + * + * The RFC3261 ENUM is: left=full number, middle="", apex=from args. + * ISN: number = "middle*left", apex=from args + * I-ENUM: EBL parameters build the split, can change apex + * Direct: left="", middle=argument, apex=from args + * + */ + + /* default: the whole number will be flipped, no middle domain component */ + ast_copy_string(left, number, sizeof(left)); + middle[0] = '\0'; + /* + * I-ENUM can change the apex, thus we copy it + */ + ast_copy_string(apex, suffix, sizeof(apex)); /* ISN rewrite */ - p1 = strchr(number, '*'); + if ((context->options & ENUMLOOKUP_OPTIONS_ISN) && (p1 = strchr(number, '*'))) { + *p1++ = '\0'; + ast_copy_string(left, number, sizeof(left)); + ast_copy_string(middle, p1, sizeof(middle) - 1); + strcat(middle, "."); + + ast_verb(2, "ISN ENUM: left=%s, middle='%s'\n", left, middle); + /* Direct DNS lookup rewrite */ + } else if (context->options & ENUMLOOKUP_OPTIONS_DIRECT) { + left[0] = 0; /* nothing to flip around */ + ast_copy_string(middle, number, sizeof(middle) - 1); + strcat(middle, "."); + + ast_verb(2, "DIRECT ENUM: middle='%s'\n", middle); + /* Infrastructure ENUM rewrite */ + } else if (context->options & ENUMLOOKUP_OPTIONS_IENUM) { + int sdl = 0; + char cc[8]; + char sep[256], n_apex[256]; + int cc_len = cclen(number); + sdl = cc_len; + ast_mutex_lock(&enumlock); + ast_copy_string(sep, ienum_branchlabel, sizeof(sep)); /* default */ + ast_mutex_unlock(&enumlock); - if (number[0] == 'n') { /* do not perform ISN rewrite ('n' is testing flag) */ - p1 = NULL; - k = 1; /* strip 'n' from number */ - } + switch (ebl_alg) { + case ENUMLOOKUP_BLR_EBL: + ast_copy_string(cc, number, cc_len); /* cclen() never returns more than 3 */ + sdl = blr_ebl(cc, suffix, sep, sizeof(sep) - 1, n_apex, sizeof(n_apex) - 1); - if (p1 != NULL) { - p2 = p1 + 1; - while (p1 > number){ - p1--; - tmp[newpos++] = *p1; - tmp[newpos++] = '.'; - } - if (*p2) { - while (*p2 && newpos < 128){ - tmp[newpos++] = *p2; - p2++; + if (sdl >= 0) { + ast_copy_string(apex, n_apex, sizeof(apex)); + ast_verb(2, "EBL ENUM: sep=%s, apex='%s'\n", sep, n_apex); + } else { + sdl = cc_len; } - tmp[newpos++] = '.'; + break; + case ENUMLOOKUP_BLR_TXT: + ast_copy_string(cc, number, cc_len); /* cclen() never returns more than 3 */ + sdl = blr_txt(cc, suffix); + + if (sdl < 0) + sdl = cc_len; + break; + + case ENUMLOOKUP_BLR_CC: /* BLR is at the country-code level */ + default: + sdl = cc_len; + break; } - } else { - while (pos >= k) { - if (isdigit(number[pos])) { - tmp[newpos++] = number[pos]; - tmp[newpos++] = '.'; + if (sdl > strlen(number)) { /* Number too short for this sdl? */ + ast_log(LOG_WARNING, "I-ENUM: subdomain location %d behind number %s\n", sdl, number); + return 0; + } + ast_copy_string(left, number + sdl, sizeof(left)); + + ast_mutex_lock(&enumlock); + ast_copy_string(middle, sep, sizeof(middle) - 1); + strcat(middle, "."); + ast_mutex_unlock(&enumlock); + + /* check the space we need for middle */ + if ((sdl * 2 + strlen(middle) + 2) > sizeof(middle)) { + ast_log(LOG_WARNING, "ast_get_enum: not enough space for I-ENUM rewrite.\n"); + return -1; + } + + p1 = middle + strlen(middle); + for (p2 = (char *) number + sdl - 1; p2 >= number; p2--) { + if (isdigit(*p2)) { + *p1++ = *p2; + *p1++ = '.'; } - pos--; + } + *p1 = '\0'; + + ast_verb(2, "I-ENUM: cclen=%d, left=%s, middle='%s', apex='%s'\n", cc_len, left, middle, apex); + } + + if (strlen(left) * 2 + 2 > sizeof(domain)) { + ast_log(LOG_WARNING, "string to long in ast_get_enum\n"); + return -1; + } + + /* flip left into domain */ + p1 = domain; + for (p2 = left + strlen(left); p2 >= left; p2--) { + if (isdigit(*p2)) { + *p1++ = *p2; + *p1++ = '.'; } } + *p1 = '\0'; if (chan && ast_autoservice_start(chan) < 0) { ast_free(context); return -1; } - if (suffix) { - ast_copy_string(tmp + newpos, suffix, sizeof(tmp) - newpos); - ret = ast_search_dns(context, tmp, C_IN, T_NAPTR, enum_callback); - ast_debug(1, "ast_get_enum: ast_search_dns(%s) returned %d\n", tmp, ret); - } else { - ret = -1; /* this is actually dead code since the demise of app_enum.c */ - for (;;) { - ast_mutex_lock(&enumlock); - if (version != enumver) { - /* Ooh, a reload... */ - s = toplevs; - version = enumver; - } else { - s = s->next; - } - ast_mutex_unlock(&enumlock); + spaceleft = sizeof(tmp) - 2; + ast_copy_string(tmp, domain, spaceleft); + spaceleft -= strlen(domain); - if (!s) - break; - - ast_copy_string(tmp + newpos, s->toplev, sizeof(tmp) - newpos); - ret = ast_search_dns(&context, tmp, C_IN, T_NAPTR, enum_callback); - ast_debug(1, "ast_get_enum: ast_search_dns(%s) returned %d\n", tmp, ret); - if (ret > 0) - break; - } + if (*middle) { + strncat(tmp, middle, spaceleft); + spaceleft -= strlen(middle); } + strncat(tmp,apex,spaceleft); + time_start = ast_tvnow(); + ret = ast_search_dns(context, tmp, C_IN, T_NAPTR, enum_callback); + time_end = ast_tvnow(); + + ast_verb(2, "ast_get_enum() profiling: %s, %s, %d ms\n", + (ret == 0) ? "OK" : "FAIL", tmp, ast_tvdiff_ms(time_end, time_start)); + if (ret < 0) { ast_debug(1, "No such number found: %s (%s)\n", tmp, strerror(errno)); strcpy(dst, "0"); @@ -480,9 +853,9 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int ds for (i = 0; i < context->naptr_rrs_count; i++) { /* use order first and then preference to compare */ if ((ntohs(context->naptr_rrs[k].naptr.order) < ntohs(context->naptr_rrs[i].naptr.order) - && context->naptr_rrs[k].sort_pos > context->naptr_rrs[i].sort_pos) - || (ntohs(context->naptr_rrs[k].naptr.order) > ntohs(context->naptr_rrs[i].naptr.order) - && context->naptr_rrs[k].sort_pos < context->naptr_rrs[i].sort_pos)){ + && context->naptr_rrs[k].sort_pos > context->naptr_rrs[i].sort_pos) + || (ntohs(context->naptr_rrs[k].naptr.order) > ntohs(context->naptr_rrs[i].naptr.order) + && context->naptr_rrs[k].sort_pos < context->naptr_rrs[i].sort_pos)) { z = context->naptr_rrs[k].sort_pos; context->naptr_rrs[k].sort_pos = context->naptr_rrs[i].sort_pos; context->naptr_rrs[i].sort_pos = z; @@ -490,9 +863,9 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int ds } if (ntohs(context->naptr_rrs[k].naptr.order) == ntohs(context->naptr_rrs[i].naptr.order)) { if ((ntohs(context->naptr_rrs[k].naptr.pref) < ntohs(context->naptr_rrs[i].naptr.pref) - && context->naptr_rrs[k].sort_pos > context->naptr_rrs[i].sort_pos) - || (ntohs(context->naptr_rrs[k].naptr.pref) > ntohs(context->naptr_rrs[i].naptr.pref) - && context->naptr_rrs[k].sort_pos < context->naptr_rrs[i].sort_pos)){ + && context->naptr_rrs[k].sort_pos > context->naptr_rrs[i].sort_pos) + || (ntohs(context->naptr_rrs[k].naptr.pref) > ntohs(context->naptr_rrs[i].naptr.pref) + && context->naptr_rrs[k].sort_pos < context->naptr_rrs[i].sort_pos)) { z = context->naptr_rrs[k].sort_pos; context->naptr_rrs[k].sort_pos = context->naptr_rrs[i].sort_pos; context->naptr_rrs[i].sort_pos = z; @@ -509,7 +882,10 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int ds } } else if (!(context->options & ENUMLOOKUP_OPTIONS_COUNT)) { context->dst[0] = 0; + } else if ((context->options & ENUMLOOKUP_OPTIONS_COUNT)) { + snprintf(context->dst,context->dstlen,"%d",context->count); } + if (chan) ret |= ast_autoservice_stop(chan); @@ -526,84 +902,59 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int ds return ret; } -/* Get TXT record from DNS. Really has nothing to do with enum, but anyway... */ -int ast_get_txt(struct ast_channel *chan, const char *number, char *dst, int dstlen, char *tech, int techlen, char *txt, int txtlen) +/*!\brief Get TXT record from DNS. + * Really has nothing to do with enum, but anyway... + * + * Actually, there is now an internet-draft which describes how callerID should + * be stored in ENUM domains: draft-ietf-enum-cnam-04.txt + * + * The algorithm implemented here will thus be obsolete soon. + */ +int ast_get_txt(struct ast_channel *chan, const char *number, char *txt, int txtlen, char *suffix) { - struct enum_context context; + struct txt_context context; char tmp[259 + 512]; - char naptrinput[512] = "+"; int pos = strlen(number) - 1; int newpos = 0; int ret = -1; - struct enum_search *s = NULL; - int version = -1; - - strncat(naptrinput, number, sizeof(naptrinput) - 2); - context.naptrinput = naptrinput; - context.dst = dst; - context.dstlen = dstlen; - context.tech = tech; - context.techlen = techlen; - context.txt = txt; - context.txtlen = txtlen; + ast_debug(4, "ast_get_txt: Number = '%s', suffix = '%s'\n", number, suffix); - if (pos > 128) + if (chan && ast_autoservice_start(chan) < 0) { + return -1; + } + + if (pos > 128) { pos = 128; - while (pos >= 0) { - tmp[newpos++] = number[pos--]; - tmp[newpos++] = '.'; } - if (chan && ast_autoservice_start(chan) < 0) - return -1; - - for (;;) { - ast_mutex_lock(&enumlock); - if (version != enumver) { - /* Ooh, a reload... */ - s = toplevs; - version = enumver; - } else { - s = s->next; - } - if (s) { - ast_copy_string(tmp + newpos, s->toplev, sizeof(tmp) - newpos); + while (pos >= 0) { + if (isdigit(number[pos])) { + tmp[newpos++] = number[pos]; + tmp[newpos++] = '.'; } - ast_mutex_unlock(&enumlock); - if (!s) - break; - - ret = ast_search_dns(&context, tmp, C_IN, T_TXT, txt_callback); - if (ret > 0) - break; + pos--; } + + ast_copy_string(&tmp[newpos], suffix, sizeof(tmp) - newpos); + if (ret < 0) { ast_debug(2, "No such number found in ENUM: %s (%s)\n", tmp, strerror(errno)); ret = 0; + } else { + ast_copy_string(txt, context.txt, txtlen); } - if (chan) + if (chan) { ret |= ast_autoservice_stop(chan); - return ret; -} - -/*! \brief Add enum tree to linked list */ -static struct enum_search *enum_newtoplev(const char *s) -{ - struct enum_search *tmp; - - if ((tmp = ast_calloc(1, sizeof(*tmp)))) { - ast_copy_string(tmp->toplev, s, sizeof(tmp->toplev)); } - return tmp; + return ret; } /*! \brief Initialize the ENUM support subsystem */ static int private_enum_init(int reload) { struct ast_config *cfg; - struct enum_search *s, *sl; - struct ast_variable *v; + const char *string; struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; if ((cfg = ast_config_load2("enum.conf", "enum", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) @@ -611,34 +962,25 @@ static int private_enum_init(int reload) /* Destroy existing list */ ast_mutex_lock(&enumlock); - s = toplevs; - while (s) { - sl = s; - s = s->next; - ast_free(sl); - } - toplevs = NULL; if (cfg) { - sl = NULL; - v = ast_variable_browse(cfg, "general"); - while (v) { - if (!strcasecmp(v->name, "search")) { - s = enum_newtoplev(v->value); - if (s) { - if (sl) - sl->next = s; - else - toplevs = s; - sl = s; - } - } - v = v->next; + if ((string = ast_variable_retrieve(cfg, "ienum", "branchlabel"))) { + ast_copy_string(ienum_branchlabel, string, sizeof(ienum_branchlabel)); + } + + if ((string = ast_variable_retrieve(cfg, "ienum", "ebl_alg"))) { + ebl_alg = ENUMLOOKUP_BLR_CC; /* default */ + + if (!strcasecmp(string, "txt")) + ebl_alg = ENUMLOOKUP_BLR_TXT; + else if (!strcasecmp(string, "ebl")) + ebl_alg = ENUMLOOKUP_BLR_EBL; + else if (!strcasecmp(string, "cc")) + ebl_alg = ENUMLOOKUP_BLR_CC; + else + ast_log(LOG_WARNING, "No valid parameter for ienum/ebl_alg.\n"); } ast_config_destroy(cfg); - } else { - toplevs = enum_newtoplev(TOPLEV); } - enumver++; ast_mutex_unlock(&enumlock); manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: Enum\r\nStatus: Enabled\r\nMessage: ENUM reload Requested\r\n"); return 0;