extensive ENUM support update, including ENUMLOOKUP() dialplan function (issue #5201...
authorKevin P. Fleming <kpfleming@digium.com>
Wed, 14 Sep 2005 01:36:15 +0000 (01:36 +0000)
committerKevin P. Fleming <kpfleming@digium.com>
Wed, 14 Sep 2005 01:36:15 +0000 (01:36 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@6579 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_enumlookup.c
doc/README.enum [new file with mode: 0755]
enum.c
funcs/func_enum.c [new file with mode: 0755]
include/asterisk/enum.h

index aea6b9c..6a91420 100755 (executable)
@@ -38,7 +38,7 @@ static char *app = "EnumLookup";
 
 static char *synopsis = "Lookup number in ENUM";
 
 
 static char *synopsis = "Lookup number in ENUM";
 
-static char *descrip = 
+static char *descrip =
 "  EnumLookup(exten):  Looks up an extension via ENUM and sets\n"
 "the variable 'ENUM'. For VoIP URIs this variable will \n"
 "look like 'TECHNOLOGY/URI' with the appropriate technology.\n"
 "  EnumLookup(exten):  Looks up an extension via ENUM and sets\n"
 "the variable 'ENUM'. For VoIP URIs this variable will \n"
 "look like 'TECHNOLOGY/URI' with the appropriate technology.\n"
@@ -71,6 +71,9 @@ static int enumlookup_exec(struct ast_channel *chan, void *data)
        char dest[80];
        char tmp[256];
        char *c,*t;
        char dest[80];
        char tmp[256];
        char *c,*t;
+
+       tech[0] = '\0';
+
        struct localuser *u;
 
        if (!data || ast_strlen_zero(data)) {
        struct localuser *u;
 
        if (!data || ast_strlen_zero(data)) {
@@ -79,7 +82,7 @@ static int enumlookup_exec(struct ast_channel *chan, void *data)
        }
        LOCAL_USER_ADD(u);
        if (!res) {
        }
        LOCAL_USER_ADD(u);
        if (!res) {
-               res = ast_get_enum(chan, data, dest, sizeof(dest), tech, sizeof(tech));
+               res = ast_get_enum(chan, data, dest, sizeof(dest), tech, sizeof(tech), NULL, NULL);
                printf("ENUM got '%d'\n", res);
        }
        LOCAL_USER_REMOVE(u);
                printf("ENUM got '%d'\n", res);
        }
        LOCAL_USER_REMOVE(u);
@@ -105,7 +108,7 @@ static int enumlookup_exec(struct ast_channel *chan, void *data)
                        snprintf(tmp, sizeof(tmp), "%s/%s", h323driver, c);
 /* do a s!;.*!! on the H323 URI */
                        t = strchr(c,';');
                        snprintf(tmp, sizeof(tmp), "%s/%s", h323driver, c);
 /* do a s!;.*!! on the H323 URI */
                        t = strchr(c,';');
-                       if (t) 
+                       if (t)
                                *t = 0;
                        pbx_builtin_setvar_helper(chan, "ENUM", tmp);
                } else if (!strcasecmp(tech, "iax")) {
                                *t = 0;
                        pbx_builtin_setvar_helper(chan, "ENUM", tmp);
                } else if (!strcasecmp(tech, "iax")) {
@@ -130,7 +133,7 @@ static int enumlookup_exec(struct ast_channel *chan, void *data)
                                res = 0;
                        } else {
 /* now copy over the number, skipping all non-digits and stop at ; or NULL */
                                res = 0;
                        } else {
 /* now copy over the number, skipping all non-digits and stop at ; or NULL */
-                               t = tmp;        
+                               t = tmp;
                                while( *c && (*c != ';') && (t - tmp < (sizeof(tmp) - 1))) {
                                        if (isdigit(*c))
                                                *t++ = *c;
                                while( *c && (*c != ';') && (t - tmp < (sizeof(tmp) - 1))) {
                                        if (isdigit(*c))
                                                *t++ = *c;
diff --git a/doc/README.enum b/doc/README.enum
new file mode 100755 (executable)
index 0000000..a5619ce
--- /dev/null
@@ -0,0 +1,306 @@
+README.enum
+
+2005-09-06 
+jtodd@loligo.com
+
+The ENUMLOOKUP function is more complex than it first may appear, and
+this guide is to give a general overview and set of examples that may
+be well-suited for the advanced user to evaluate in their
+consideration of ENUM or ENUM-like lookup strategies.  This document
+assumes a familiarity with ENUM (RFC3761) or ENUM-like methods, as
+well as familiarity with NAPTR DNS records (RFC2915, RFC3401-3404).
+For an overview of NAPTR records, and the use of NAPTRs in the ENUM
+global phone-number-to-DNS mapping scheme, please see
+http://www.voip-info.org/tiki-index.php?page=ENUM for more detail.
+
+Using ENUM within Asterisk can be simple or complex, depending on how
+many failover methods and redundancy procedures you wish to utilize.
+Implementation of ENUM paths is supposedly defined by the person
+creating the NAPTR records, but the local administrator may choose to
+ignore certain NAPTR response methods (URI types) or prefer some over
+others, which is in contradiction to the RFC.  The ENUMLOOKUP method
+simply provides administrators a method for determining NAPTR results
+in either the globally unique ENUM (e164.arpa) DNS tree, or in other
+ENUM-like DNS trees which are not globally unique.  The methods to
+actually create channels ("dial") results given by the ENUMLOOKUP
+function is then up to the administrator to implement in a way that
+best suits their environment.
+
+Function: EnumLookup(<number>[,pointer_type[,options[,zone_suffix]]])
+
+  Performs an ENUM tree lookup on the specified number, method type,
+  and (optionally) ordinal offset, and returns one of four different values:
+
+   1) post-parsed NAPTR of one method (URI) type
+   2) count of elements of one method (URI) type
+   3) count of all method types
+   4) full URI of method at a particular point in the list of all possible methods 
+
+Arguments:
+
+number = telephone number or search string.  Only numeric values
+within this string are parsed; all other digits are ignored for
+search, but are re-written during NAPTR regexp expansion.
+
+service_type = tel, sip, h323, iax2, mailto, ...[any other string],
+     ALL. Default type is "sip".
+     Special name of "ALL" will create a list of method types across
+     all NAPTR records for the search number, and then put the results
+     in an ordinal list starting with 1. The position <number>
+     specified will then be returned, starting with 1 as the first
+     record (lowest value) in the list.  The service types are not
+     hardcoded in Asterisk except for the default (sip) if no other
+     service type specified; any method type string (IANA-approved or
+     not) may be used except for the string "ALL".  
+
+options = optional specifiers.
+    c = count. Returns the number of records of this type are returned
+    (regardless of order or priority.)  If "ALL" is the specified
+    service_type, then a count of all methods will be returned for the
+    DNS record.
+    <integer> = The record in priority/order sequence based on the
+    total count of records passed back by the query. If a service_type
+    is specified, all entries of that type will be sorted into an
+    ordinal list starting with 1 (by order first, then priority).
+    The default of <options> is "1"
+zone_suffix = allows customization of the ENUM zone. Default is e164.arpa.
+
+
+EXAMPLE USES:
+
+Let's use this ENUM list as an example (note that these examples exist
+in the DNS, and will hopefully remain in place as example
+destinations, but they may change or become invalid over time.  The
+end result URIs are not guaranteed to actually work, since some of
+these hostnames or SIP proxies are imaginary.  Of course, the tel:
+replies go to directory assistance for New York City and San
+Francisco...)  Also note that the complex SIP NAPTR at weight 30 will
+strip off the leading "+" from the dialed string if it exists.  This
+is probably a better NAPTR than hard-coding the number into the NAPTR,
+and it is included as a more complex regexp example, though other
+simpler NAPTRs will work just as well.
+
+
+0.2.0.1.1.6.5.1.0.3.1.fox-den.com. 3600 IN NAPTR 10 100 "u" "E2U+tel" "!^\\+13015611020$!tel:+12125551212!" .
+0.2.0.1.1.6.5.1.0.3.1.fox-den.com. 3600 IN NAPTR 21 100 "u" "E2U+tel" "!^\\+13015611020$!tel:+14155551212!" .
+0.2.0.1.1.6.5.1.0.3.1.fox-den.com. 3600 IN NAPTR 25 100 "u" "E2U+sip" "!^\\+13015611020$!sip:2203@sip.fox-den.com!" .
+0.2.0.1.1.6.5.1.0.3.1.fox-den.com. 3600 IN NAPTR 26 100 "u" "E2U+sip" "!^\\+13015611020$!sip:1234@sip-2.fox-den.com!" .
+0.2.0.1.1.6.5.1.0.3.1.fox-den.com. 3600 IN NAPTR 30 100 "u" "E2U+sip" "!^\\+*([^\\*]*)!sip:\\1@sip-3.fox-den.com!" .
+0.2.0.1.1.6.5.1.0.3.1.fox-den.com. 3600 IN NAPTR 55 100 "u" "E2U+mailto" "!^\\+13015611020$!mailto:jtodd@fox-den.com!" .
+
+Example 1: Simplest case, using first SIP return (use all defaults
+except for domain name)
+exten => 100,1,Set(foo=ENUMLOOKUP(13015611020,,,fox-den.com))
+  returns: ${foo}="2203@sip.fox-den.com"
+
+Example 2: What is the first "tel" pointer type for this number?
+(after sorting by order/preference; default of "1" is assumed in
+options field)
+exten => 100,1,Set(foo=${ENUMLOOKUP(13015611020,tel,,loligo.com)})
+  returns: ${foo}="+12125551212"
+
+Example 3: How many "sip" pointer type entries are there for this number?
+exten => 100,1,Set(foo=${ENUMLOOKUP(13015611020,sip,c,loligo.com)})
+  returns: ${foo}=3
+
+Example 4: For all the "tel" pointer type entries, what is the second
+one in the list? (after sorting by preference)
+exten => 100,1,Set(foo=${ENUMLOOKUP(13015611020,tel,2,loligo.com)})
+  returns: ${foo}="+5553"
+
+Example 5: How many NAPTRs (tel, sip, mailto, etc.) are in the list for this number?
+exten => 100,1,Set(foo=${ENUMLOOKUP(13015611020,ALL,c,loligo.com)})
+  returns: ${foo}=6
+
+Example 6: Give back the second full URI in the sorted list of all NAPTR URIs:
+exten => 100,1,Set(foo=${ENUMLOOKUP(13015611020,ALL,2,loligo.com)})
+  returns: ${foo}="tel:14155551212"  [note the "tel:" prefix in the string]
+
+Example 7: Look up first SIP entry for the number in the e164.arpa zone (all defaults)
+exten => 100,1,Set(foo=${ENUMLOOKUP(+437203001721)})
+  returns: ${foo}="enum-test@sip.nemox.net"  [note: this result is
+  subject to change as it is "live" DNS and not under my control]
+
+
+Example 8: Look up the ISN mapping in freenum.org alpha test zone
+exten => 100,1,Set(foo=${ENUMLOOKUP(1234*256,,,freenum.org)})
+  returns: ${foo}="1234@204.91.156.10"  [note: this result is subject
+  to change as it is "live" DNS]
+
+Example 9: Give back the first SIP pointer for a number in the
+enum.yoydynelabs.com zone (invalid lookup)
+exten => 100,1,Set(foo=${ENUMLOOKUP(1234567890,sip,1,enum.yoyodynelabs.com)})
+  returns: ${foo}=""
+
+
+Usage notes and subtle features:
+
+  a) The use of "+" in lookups is confusing, and warrants further
+  explanation.  All E.164 numbers ("global phone numbers") by
+  definition need a leading "+" during ENUM lookup.  If you neglect to
+  add a leading "+", you may discover that numbers that seem to exist
+  in the DNS aren't getting matched by the system or are returned with
+  a null string result.  This is due to the NAPTR reply requiring a
+  "+" in the regular expression matching sequence.  Older versions of
+  Asterisk add a "+" from within the code, which may confuse
+  administrators converting to the new function.  Please ensure that
+  all ENUM (e164.arpa) lookups contain a leading "+" before lookup, so
+  ensure your lookup includes the leading plus sign.  Other DNS trees
+  may or may not require a leading "+" - check before using those
+  trees, as it is possible the parsed NAPTRs will not provide correct
+  results unless you have the correct dialed string.  If you get
+  console messages like "WARNING[24907]: enum.c:222 parse_naptr: NAPTR
+  Regex match failed." then it is very possible that the returned
+  NAPTR expects a leading "+" in the search string (or the returned
+  NAPTR is mis-formed.)
+
+  b) If a query is performed of type "c" ("count") and let's say you
+  get back 5 records and then some seconds later a query is made
+  against record 5 in the list, it may not be the case that the DNS
+  resolver has the same answers as it did a second or two ago - maybe
+  there are only 4 records in the list in the newest query.  The
+  resolver should be the canonical storage location for DNS records,
+  since that is the intent of ENUM.  However, some obscure future
+  cases may have wildly changing NAPTR records within several seconds.
+  This is a corner case, and probably only worth noting as a very rare
+  circumstance. (note: I do not object to Asterisk's dnsmgr method of
+  locally caching DNS replies, but this method needs to honor the TTL
+  given by the remote zone master.  Currently, the ENUMLOOKUP function
+  does not use the dnsmgr method of caching local DNS replies.)
+
+  c) If you want strict NAPTR value ordering, then it will be
+  necessary to use the "ALL" method to incrementally step through the
+  different returned NAPTR pointers.  You will need to use string
+  manipulation to strip off the returned method types, since the
+  results will look like "sip:12125551212" in the returned value.
+  This is a non-trivial task, though it is required in order to have
+  strict RFC compliance and to comply with the desires of the remote
+  party who is presenting NAPTRs in a particular order for a reason.
+
+  d) Default behavior for the function (even in event of an error) is
+  to move to the next priority, and the result is a null value.  Most
+  ENUM lookups are going to be failures, and it is the responsibility
+  of the dialplan administrator to manage error conditions within
+  their dialplan.  This is a change from the old app_enumlookup method
+  and it's arbitrary priority jumping based on result type or failure.
+
+  e) Anything other than digits will be ignored in lookup strings.
+  Example: a search string of "+4372030blah01721" will turn into
+  1.2.7.1.0.0.3.0.2.7.3.4.e164.arpa. for the lookup.  The NAPTR
+  parsing may cause unexpected results if there are strings inside
+  your NAPTR lookups.
+
+  f) If there exist multiple records with the same weight and order as
+  a result of your query, the function will RANDOMLY select a single
+  NAPTR from those equal results.
+
+  g) Currently, the function ignores the settings in enum.conf as the
+  search zone name is now specified within the function, and the H323
+  driver can be chosen by the user via the dialplan.  There were no
+  other values in this file, and so it becomes deprecated.
+
+  h) The function will digest and return NAPTRs which use older
+  (depricated) style, reversed method strings such as "sip+E2U"
+  instead of the more modern "E2U+sip"
+
+  i) There is no provision for multi-part methods at this time.  If
+  there are multiple NAPTRs with (as an example) a method of
+  "E2U+voice:sip" and then another NAPTR in the same DNS record with a
+  method of ""E2U+sip", the system will treat these both as method
+  "sip" and they will be separate records from the perspective of the
+  function.  Of course, if both records point to the same URI and have
+  equal priority/weight (as is often the case) then this will cause no
+  serious difficulty, but it bears mentioning.
+
+  j) ISN (ITAD Subscriber Number) usage:  If the search number is of
+  the form ABC*DEF (where ABC and DEF are at least one numeric digit)
+  then perform an ISN-style lookup where the lookup is manipulated to
+  C.B.A.DEF.domain.tld (all other settings and options apply.)  See
+  http://www.freenum.org/ for more details on ISN lookups.  In the
+  unlikely event you wish to avoid ISN re-writes, put an "n" as the
+  first digit of the search string - the "n" will be ignored for the search.
+
+
+==EXAMPLES==
+
+All examples below except where noted use "e164.arpa" as the
+referenced domain, which is the default domain name for ENUMLOOKUP.
+All numbers are assumed to not have a leading "+" as dialed by the
+inbound channel, so that character is added where necessary during
+ENUMLOOKUP function calls.
+
+; example 1
+;
+; Assumes North American international dialing (011) prefix.
+; Look up the first SIP result and send the call there, otherwise
+;  send the call out a PRI.  This is the most simple possible
+;  ENUM example, but only uses the first SIP reply in the list of
+;  NAPTR(s). 
+;
+exten => _011.,1,Set(enumresult=${ENUMLOOKUP(+${EXTEN:3})})
+exten => _011.,n,Dial(SIP/${enumlookup})
+exten => _011.,n,Dial(Zap/g1/${EXTEN})
+; 
+; end example 1
+
+; example 2
+;
+; Assumes North American international dialing (011) prefix.
+; Check to see if there are multiple SIP NAPTRs returned by 
+;  the lookup, and dial each in order.  If none work (or none
+;  exist) then send the call out a PRI, group 1.
+;
+exten => _011.,1,Set(sipcount=${ENUMLOOKUP(${EXTEN:3},sip,c)}|counter=0)
+exten => _011.,n,While($["${counter}"<"${sipcount}"])
+exten => _011.,n,Set(counter=$[${counter}+1])
+exten => _011.,n,Dial(SIP/${ENUMLOOKUP(+${EXTEN:3},sip,${counter})})
+exten => _011.,n,EndWhile
+exten => _011.,n,Dial(Zap/g1/${EXTEN})
+;
+; end example 2
+
+; example 3
+;
+; This example expects an ${EXTEN} that is an e.164 number (like
+;  14102241145 or 437203001721)
+; Search through e164.arpa and then also search through e164.org
+;  to see if there are any valid SIP or IAX termination capabilities.
+;  If none, send call out via Zap channel 1.
+;
+; Start first with e164.arpa zone...
+;
+exten => _X.,1,Set(sipcount=${ENUMLOOKUP(${EXTEN},sip,c)}|counter=0)
+exten => _X.,2,GotoIf($["${counter}"<"${sipcount}"]?3:6)
+exten => _X.,3,Set(counter=$[${counter}+1])
+exten => _X.,4,Dial(SIP/${ENUMLOOKUP(+${EXTEN},sip,${counter})})
+exten => _X.,5,GotoIf($["${counter}"<"${sipcount}"]?3:6)
+;
+exten => _X.,6,Set(iaxcount=${ENUMLOOKUP(${EXTEN},iax2,c)}|counter=0)
+exten => _X.,7,GotoIf($["${counter}"<"${iaxcount}"]?8:11)
+exten => _X.,8,Set(counter=$[${counter}+1])
+exten => _X.,9,Dial(IAX2/${ENUMLOOKUP(+${EXTEN},iax,${counter})})
+exten => _X.,10,GotoIf($["${counter}"<"${iaxcount}"]?8:11)
+;
+exten => _X.,11,NoOp("No valid entries in e164.arpa for ${EXTEN} - checking in e164.org")
+;
+; ...then also try e164.org, and look for SIP and IAX NAPTRs...
+;
+exten => _X.,12,Set(sipcount=${ENUMLOOKUP(${EXTEN},sip,c,e164.org)}|counter=0)
+exten => _X.,13,GotoIf($["${counter}"<"${sipcount}"]?14:17)
+exten => _X.,14,Set(counter=$[${counter}+1])
+exten => _X.,15,Dial(SIP/${ENUMLOOKUP(+${EXTEN},sip,${counter},e164.org)})
+exten => _X.,16,GotoIf($["${counter}"<"${sipcount}"]?14:17)
+;
+exten => _X.,17,Set(iaxcount=${ENUMLOOKUP(${EXTEN},iax2,c,e164.org)}|counter=0)
+exten => _X.,18,GotoIf($["${counter}"<"${iaxcount}"]?19:22)
+exten => _X.,19,Set(counter=$[${counter}+1])
+exten => _X.,20,Dial(IAX2/${ENUMLOOKUP(+${EXTEN},iax,${counter},e164.org)})
+exten => _X.,21,GotoIf($["${counter}"<"${iaxcount}"]?19:22)
+;
+; ...then send out PRI.
+;
+exten => _X.,22,NoOp("No valid entries in e164.org for ${EXTEN} - sending out via Zap")
+exten => _X.,23,Dial(Zap/g1/${EXTEN})
+;
+; end example 3
diff --git a/enum.c b/enum.c
index 45370e0..6f475fe 100755 (executable)
--- a/enum.c
+++ b/enum.c
@@ -87,9 +87,12 @@ static int parse_ie(char *data, int maxdatalen, char *src, int srclen)
 /*--- parse_naptr: Parse DNS NAPTR record used in ENUM ---*/
 static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *answer, int len, char *naptrinput)
 {
 /*--- parse_naptr: Parse DNS NAPTR record used in ENUM ---*/
 static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *answer, int len, char *naptrinput)
 {
+
+       char tech_return[80];
        char *oanswer = answer;
        char flags[512] = "";
        char services[512] = "";
        char *oanswer = answer;
        char flags[512] = "";
        char services[512] = "";
+       unsigned char *p;
        char regexp[512] = "";
        char repl[512] = "";
        char temp[512] = "";
        char regexp[512] = "";
        char repl[512] = "";
        char temp[512] = "";
@@ -102,9 +105,10 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *a
        regex_t preg;
        regmatch_t pmatch[9];
 
        regex_t preg;
        regmatch_t pmatch[9];
 
+       tech_return[0] = '\0';
 
        dst[0] = '\0';
 
        dst[0] = '\0';
-       
+
        if (len < sizeof(struct naptr)) {
                ast_log(LOG_WARNING, "NAPTR record length too short\n");
                return -1;
        if (len < sizeof(struct naptr)) {
                ast_log(LOG_WARNING, "NAPTR record length too short\n");
                return -1;
@@ -113,29 +117,30 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *a
        len -= sizeof(struct naptr);
        if ((res = parse_ie(flags, sizeof(flags) - 1, answer, len)) < 0) {
                ast_log(LOG_WARNING, "Failed to get flags from NAPTR record\n");
        len -= sizeof(struct naptr);
        if ((res = parse_ie(flags, sizeof(flags) - 1, answer, len)) < 0) {
                ast_log(LOG_WARNING, "Failed to get flags from NAPTR record\n");
-               return -1; 
-       } else { 
-               answer += res; 
-               len -= res; 
+               return -1;
+       } else {
+               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");
        }
        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; 
-       } else { 
-               answer += res; 
-               len -= res; 
+               return -1;
+       } else {
+               answer += res;
+               len -= res;
        }
        if ((res = parse_ie(regexp, sizeof(regexp) - 1, answer, len)) < 0) {
                ast_log(LOG_WARNING, "Failed to get regexp from NAPTR record\n");
        }
        if ((res = parse_ie(regexp, sizeof(regexp) - 1, answer, len)) < 0) {
                ast_log(LOG_WARNING, "Failed to get regexp from NAPTR record\n");
-               return -1; 
-       } else { 
-               answer += res; 
-               len -= res; 
+               return -1;
+       } else {
+               answer += res;
+               len -= res;
        }
        }
+
        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;
        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;
-       } 
+       }
 
        if (option_debug > 2)   /* Advanced NAPTR debugging */
                ast_log(LOG_DEBUG, "NAPTR input='%s', flags='%s', services='%s', regexp='%s', repl='%s'\n",
 
        if (option_debug > 2)   /* Advanced NAPTR debugging */
                ast_log(LOG_DEBUG, "NAPTR input='%s', flags='%s', services='%s', regexp='%s', repl='%s'\n",
@@ -146,29 +151,27 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *a
                return -1;
        }
 
                return -1;
        }
 
-       if ((!strncasecmp(services, "e2u+sip", 7)) || 
-           (!strncasecmp(services, "sip+e2u", 7))) {
-               ast_copy_string(tech, "sip", techsize); 
-       } else if ((!strncasecmp(services, "e2u+h323", 8)) || 
-           (!strncasecmp(services, "h323+e2u", 8))) {
-               ast_copy_string(tech, "h323", techsize); 
-       } else if ((!strncasecmp(services, "e2u+x-iax2", 10)) || 
-           (!strncasecmp(services, "e2u+iax2", 8)) ||
-           (!strncasecmp(services, "iax2+e2u", 8))) {
-               ast_copy_string(tech, "iax2", techsize); 
-       } else if ((!strncasecmp(services, "e2u+x-iax", 9)) ||
-           (!strncasecmp(services, "e2u+iax", 7)) ||
-           (!strncasecmp(services, "iax+e2u", 7))) {
-               ast_copy_string(tech, "iax", techsize); 
-       } else if ((!strncasecmp(services, "e2u+tel", 7)) || 
-           (!strncasecmp(services, "tel+e2u", 7))) {
-               ast_copy_string(tech, "tel", techsize); 
-       } else if (!strncasecmp(services, "e2u+voice:", 10)) {
-               ast_copy_string(tech, services+10, techsize); 
+       p = strstr(services, "e2u+");
+       if(p == NULL)
+               p = strstr(services, "E2U+");
+       if(p){
+               p = p + 4;
+               if(strchr(p, ':')){
+                       p = strchr(p, ':') + 1;
+               }
+               ast_copy_string(tech_return, p, sizeof(tech_return));
        } else {
        } else {
-               ast_log(LOG_DEBUG, 
-               "Services must be e2u+${tech}, ${tech}+e2u, or e2u+voice: where $tech is from (sip, h323, tel, iax, iax2). \n");
-               return 0;
+
+               p = strstr(services, "+e2u");
+               if(p == NULL)
+                       p = strstr(services, "+E2U");
+               if(p){
+                       *p = 0;
+                       p = strchr(services, ':');
+                       if(p)
+                               *p = 0;
+                       ast_copy_string(tech_return, services, sizeof(tech_return));
+               }
        }
 
        /* DEDBUGGING STUB
        }
 
        /* DEDBUGGING STUB
@@ -179,7 +182,7 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *a
        if (regexp_len < 7) {
                ast_log(LOG_WARNING, "Regex too short to be meaningful.\n");
                return -1;
        if (regexp_len < 7) {
                ast_log(LOG_WARNING, "Regex too short to be meaningful.\n");
                return -1;
-       } 
+       }
 
 
        delim = regexp[0];
 
 
        delim = regexp[0];
@@ -197,6 +200,7 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *a
 #if 0
        printf("Pattern: %s\n", pattern);
        printf("Subst: %s\n", subst);
 #if 0
        printf("Pattern: %s\n", pattern);
        printf("Subst: %s\n", subst);
+       printf("Input: %s\n", naptrinput);
 #endif
 
 /*
 #endif
 
 /*
@@ -221,8 +225,8 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *a
        }
        regfree(&preg);
 
        }
        regfree(&preg);
 
-       d = temp; 
-       d_len--; 
+       d = temp;
+       d_len--;
        while( *subst && (d_len > 0) ) {
                if ((subst[0] == '\\') && isdigit(subst[1]) && (pmatch[subst[1]-'0'].rm_so != -1)) {
                        backref = subst[1]-'0';
        while( *subst && (d_len > 0) ) {
                if ((subst[0] == '\\') && isdigit(subst[1]) && (pmatch[subst[1]-'0'].rm_so != -1)) {
                        backref = subst[1]-'0';
@@ -246,9 +250,35 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *a
        *d = 0;
        ast_copy_string(dst, temp, dstsize);
        dst[dstsize - 1] = '\0';
        *d = 0;
        ast_copy_string(dst, temp, dstsize);
        dst[dstsize - 1] = '\0';
-       return 0;
+
+       if(*tech != '\0'){ /* check if it is requested NAPTR */
+               if(!strncasecmp(tech, "ALL", techsize)){
+                       return 1; /* return or count any RR */
+               }
+               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 */
+               } else { /* go to the next RR in the DNS answer */
+                       return 0;
+               }
+       }
+
+       /* tech was not specified, return first parsed RR */
+       ast_copy_string(tech, tech_return, techsize);
+
+       return 1;
 }
 
 }
 
+/* do not return requested value, just count RRs and return thei number in dst */
+#define ENUMLOOKUP_OPTIONS_COUNT       1
+
+struct enum_naptr_rr {
+       struct naptr naptr; /* order and preference of RR */
+       char *result; /* result of naptr parsing,e.g.: tel:+5553 */
+       char *tech; /* Technology (from URL scheme) */
+       int sort_pos; /* sort position */
+};
+
 struct enum_context {
        char *dst;      /* Destination part of URL from ENUM */
        int dstlen;     /* Length */
 struct enum_context {
        char *dst;      /* Destination part of URL from ENUM */
        int dstlen;     /* Length */
@@ -257,6 +287,10 @@ struct enum_context {
        char *txt;      /* TXT record in TXT lookup */
        int txtlen;     /* Length */
        char *naptrinput;       /* The number to lookup */
        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 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 */
 };
 
 /*--- txt_callback: Callback for TXT record lookup */
 };
 
 /*--- txt_callback: Callback for TXT record lookup */
@@ -278,7 +312,7 @@ static int txt_callback(void *context, char *answer, int len, char *fullanswer)
        len -= 1;
 
        /* answer is not null-terminated, but should be */
        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 
+       /* 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
            safely overwrite with a null */
        answer[len] = '\0';
        /* now increment len so that len includes the null, so that we can
@@ -287,7 +321,7 @@ static int txt_callback(void *context, char *answer, int len, char *fullanswer)
 
        /* finally, copy the answer into c->txt */
        ast_copy_string(c->txt, answer, len < c->txtlen ? len : (c->txtlen));
 
        /* finally, copy the answer into c->txt */
        ast_copy_string(c->txt, 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';
 
        /* just to be safe, let's make sure c->txt is null terminated */
        c->txt[(c->txtlen)-1] = '\0';
 
@@ -298,45 +332,122 @@ static int txt_callback(void *context, char *answer, int len, char *fullanswer)
 static int enum_callback(void *context, char *answer, int len, char *fullanswer)
 {
        struct enum_context *c = (struct enum_context *)context;
 static int enum_callback(void *context, char *answer, int len, char *fullanswer)
 {
        struct enum_context *c = (struct enum_context *)context;
+       void *p = NULL;
+       int res;
 
 
-       if (parse_naptr(c->dst, c->dstlen, c->tech, c->techlen, answer, len, c->naptrinput)) {
+       res = parse_naptr(c->dst, c->dstlen, c->tech, c->techlen, answer, len, c->naptrinput);
+
+       if(res < 0){
                ast_log(LOG_WARNING, "Failed to parse naptr :(\n");
                return -1;
                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  {
+                       p = realloc(c->naptr_rrs, sizeof(struct enum_naptr_rr)*(c->naptr_rrs_count+1));
+                       if(p){
+                               c->naptr_rrs = (struct enum_naptr_rr*)p;
+                               memcpy(&c->naptr_rrs[c->naptr_rrs_count].naptr, answer, sizeof(struct naptr));
+                               /* printf("order=%d, pref=%d\n", ntohs(c->naptr_rrs[c->naptr_rrs_count].naptr.order), ntohs(c->naptr_rrs[c->naptr_rrs_count].naptr.pref)); */
+                               c->naptr_rrs[c->naptr_rrs_count].result = strdup(c->dst);
+                               c->naptr_rrs[c->naptr_rrs_count].tech = strdup(c->tech);
+                               c->naptr_rrs[c->naptr_rrs_count].sort_pos = c->naptr_rrs_count;
+                               c->naptr_rrs_count++;
+                       }
+                       c->dst[0] = 0;
+               }
+               return 0;
        }
 
        }
 
-       if (!ast_strlen_zero(c->dst))
-               return 1;
+       if(c->options & ENUMLOOKUP_OPTIONS_COUNT){ /* counting RRs */
+               snprintf(c->dst, c->dstlen, "%d", c->position);
+       }
 
        return 0;
 }
 
 /*--- ast_get_enum: ENUM lookup */
 
        return 0;
 }
 
 /*--- ast_get_enum: ENUM lookup */
-int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int dstlen, char *tech, int techlen)
+int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int dstlen, char *tech, int techlen, char* suffix, char* options)
 {
        struct enum_context context;
        char tmp[259 + 512];
 {
        struct enum_context context;
        char tmp[259 + 512];
-       char naptrinput[512] = "+";
+       char naptrinput[512];
        int pos = strlen(number) - 1;
        int newpos = 0;
        int ret = -1;
        struct enum_search *s = NULL;
        int version = -1;
        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);
+       /* for ISN rewrite */
+       char *p1 = NULL;
+       char *p2 = NULL;
+       int k = 0;
+       int i = 0;
+       int z = 0;
+
+       if(number[0] == 'n'){
+               strncpy(naptrinput, number+1, sizeof(naptrinput));
+       } else {
+               strncpy(naptrinput, number, sizeof(naptrinput));
+       }
 
        context.naptrinput = naptrinput;        /* The number */
        context.dst = dst;                      /* Return string */
        context.dstlen = dstlen;
 
        context.naptrinput = naptrinput;        /* The number */
        context.dst = dst;                      /* Return string */
        context.dstlen = dstlen;
-       context.tech = tech;                    /* Return string */
+       context.tech = tech;
        context.techlen = techlen;
        context.techlen = techlen;
+       context.options = 0;
+       context.position = 1;
+       context.naptr_rrs = NULL;
+       context.naptr_rrs_count = 0;
+
+       if(options != NULL){
+               if(*options == 'c'){
+                       context.options = ENUMLOOKUP_OPTIONS_COUNT;
+                       context.position = 0;
+               } else {
+                       context.position = atoi(options);
+                       if(context.position < 1)
+                               context.position = 1;
+               }
+       }
 
        if (pos > 128)
                pos = 128;
 
        if (pos > 128)
                pos = 128;
-       while(pos >= 0) {
-               tmp[newpos++] = number[pos--];
-               tmp[newpos++] = '.';
+
+       /* ISN rewrite */
+       p1 = strchr(number, '*');
+
+       if(number[0] == 'n'){ /* do not perform ISN rewrite ('n' is testing flag) */
+               p1 = NULL;
+               k = 1; /* strip 'n' from number */
+       }
+
+       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++;
+                       }
+                       tmp[newpos++] = '.';
+               }
+
+       } else {
+               while(pos >= k) {
+                       if(isdigit(number[pos])){
+                               tmp[newpos++] = number[pos];
+                               tmp[newpos++] = '.';
+                       }
+                       pos--;
+               }
        }
        }
-       
+
        if (chan && ast_autoservice_start(chan) < 0)
                return -1;
 
        if (chan && ast_autoservice_start(chan) < 0)
                return -1;
 
@@ -349,7 +460,9 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int ds
                } else {
                        s = s->next;
                }
                } else {
                        s = s->next;
                }
-               if (s) {
+               if(suffix != NULL){
+                       strncpy(tmp + newpos, suffix, sizeof(tmp) - newpos - 1);
+               } else if (s) {
                        strncpy(tmp + newpos, s->toplev, sizeof(tmp) - newpos - 1);
                }
                ast_mutex_unlock(&enumlock);
                        strncpy(tmp + newpos, s->toplev, sizeof(tmp) - newpos - 1);
                }
                ast_mutex_unlock(&enumlock);
@@ -358,17 +471,65 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int ds
                ret = ast_search_dns(&context, tmp, C_IN, T_NAPTR, enum_callback);
                if (ret > 0)
                        break;
                ret = ast_search_dns(&context, tmp, C_IN, T_NAPTR, enum_callback);
                if (ret > 0)
                        break;
+               if(suffix != NULL)
+                       break;
        }
        if (ret < 0) {
                ast_log(LOG_DEBUG, "No such number found: %s (%s)\n", tmp, strerror(errno));
                ret = 0;
        }
        }
        if (ret < 0) {
                ast_log(LOG_DEBUG, "No such number found: %s (%s)\n", tmp, strerror(errno));
                ret = 0;
        }
+
+       if(context.naptr_rrs_count >= context.position && ! (context.options & ENUMLOOKUP_OPTIONS_COUNT)){
+               /* sort array by NAPTR order/preference */
+               for(k=0; k<context.naptr_rrs_count; k++){
+                       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)){
+                                       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;
+                                       continue;
+                               }
+                               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)){
+                                               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;
+                                       }
+                               }
+                       }
+               }
+               for(k=0; k<context.naptr_rrs_count; k++){
+                       if(context.naptr_rrs[k].sort_pos == context.position-1){
+                               ast_copy_string(context.dst, context.naptr_rrs[k].result, dstlen);
+                               ast_copy_string(context.tech, context.naptr_rrs[k].tech, techlen);
+                               break;
+                       }
+               }
+       } else if( !(context.options & ENUMLOOKUP_OPTIONS_COUNT) ) {
+               context.dst[0] = 0;
+       }
+
        if (chan)
                ret |= ast_autoservice_stop(chan);
        if (chan)
                ret |= ast_autoservice_stop(chan);
+
+       for(k=0; k<context.naptr_rrs_count; k++){
+               free(context.naptr_rrs[k].result);
+               free(context.naptr_rrs[k].tech);
+       }
+
+       free(context.naptr_rrs);
+
        return ret;
 }
 
        return ret;
 }
 
-/*--- ast_get_txt: Get TXT record from DNS. 
+/*--- ast_get_txt: 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)
        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)
@@ -398,7 +559,7 @@ int ast_get_txt(struct ast_channel *chan, const char *number, char *dst, int dst
                tmp[newpos++] = number[pos--];
                tmp[newpos++] = '.';
        }
                tmp[newpos++] = number[pos--];
                tmp[newpos++] = '.';
        }
-       
+
        if (chan && ast_autoservice_start(chan) < 0)
                return -1;
 
        if (chan && ast_autoservice_start(chan) < 0)
                return -1;
 
diff --git a/funcs/func_enum.c b/funcs/func_enum.c
new file mode 100755 (executable)
index 0000000..3128047
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Enum Functions
+ *
+ * Copyright (C) 2005
+ *
+ * Oleksiy Krivoshey <oleksiyk@gmail.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <stdlib.h>
+
+#include "asterisk.h"
+
+#ifndef BUILTIN_FUNC
+#include "asterisk/module.h"
+#endif /* BUILTIN_FUNC */
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+
+#include "asterisk/enum.h"
+
+static char* synopsis = "Syntax: ENUMLOOKUP(number[,Method-type[,options|record#[,zone-suffix]]])\n";
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static char *function_enum(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+       int res=0;
+       char tech[80];
+       char dest[80] = "";
+       char *zone;
+       char *options;
+       struct localuser *u;
+       char *params[4];
+       char *p = data;
+       char *s;
+       int i = 0;
+
+
+       if (!data || ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, synopsis);
+               return "";
+       }
+
+       do {
+               if(i>3){
+                       ast_log(LOG_WARNING, synopsis);
+                       return "";
+               }
+               params[i++] = p;
+               p = strchr(p, '|');
+               if(p){
+                       *p = '\0';
+                       p++;
+               }
+       } while(p);
+
+       if(i < 1){
+               ast_log(LOG_WARNING, synopsis);
+               return "";
+       }
+
+       if( (i > 1 && strlen(params[1]) == 0) || i < 2){
+               ast_copy_string(tech, "sip", sizeof(tech));
+       } else {
+               ast_copy_string(tech, params[1], sizeof(tech));
+       }
+
+       if( (i > 3 && strlen(params[3]) == 0) || i<4){
+               zone = "e164.arpa";
+       } else {
+               zone = params[3];
+       }
+
+       if( (i > 2 && strlen(params[2]) == 0) || i<3){
+               options = "1";
+       } else {
+               options = params[2];
+       }
+
+       /* strip any '-' signs from number */
+       p = params[0];
+       /*
+       while(*p == '+'){
+               p++;
+       }
+       */
+       s = p;
+       i = 0;
+       while(*p && *s){
+               if(*s == '-'){
+                       s++;
+               } else {
+                       p[i++] = *s++;
+               }
+       }
+       p[i] = 0;
+
+       LOCAL_USER_ACF_ADD(u);
+
+       res = ast_get_enum(chan, p, dest, sizeof(dest), tech, sizeof(tech), zone, options);
+
+       LOCAL_USER_REMOVE(u);
+
+       p = strchr(dest, ':');
+       if(p && strncasecmp(tech, "ALL", sizeof(tech))) {
+               ast_copy_string(buf, p+1, sizeof(dest));
+       } else {
+               ast_copy_string(buf, dest, sizeof(dest));
+       }
+
+       return buf;
+}
+
+
+#ifndef BUILTIN_FUNC
+static
+#endif
+struct ast_custom_function enum_function = {
+       .name = "ENUMLOOKUP",
+       .synopsis = "ENUMLOOKUP allows for general or specific querying of NAPTR records"
+       " or counts of NAPTR types for ENUM or ENUM-like DNS pointers",
+       .syntax = "ENUMLOOKUP(number[,Method-type[,options|record#[,zone-suffix]]])",
+       .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"
+       "Defaults are: Method-type=sip, no options, record=1, zone-suffix=e164.arpa\n\n"
+       "For more information, see README.enum",
+       .read = function_enum,
+};
+
+#ifndef BUILTIN_FUNC
+
+static char *tdesc = "ENUMLOOKUP allows for general or specific querying of NAPTR records or counts of NAPTR types for ENUM or ENUM-like DNS pointers";
+
+int unload_module(void)
+{
+       return ast_custom_function_unregister(&enum_function);
+}
+
+int load_module(void)
+{
+       return ast_custom_function_register(&enum_function);
+}
+
+char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       return 0;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
+#endif /* BUILTIN_FUNC */
+
index e52d1db..732a42a 100755 (executable)
 
 #include "asterisk/channel.h"
 
 
 #include "asterisk/channel.h"
 
-/*! \brief Lookup entry in ENUM Returns 1 if found, 0 if not found, -1 on hangup 
+/*! \brief Lookup entry in ENUM Returns 1 if found, 0 if not found, -1 on hangup
        \param chan     Channel
        \param chan     Channel
-       \param number   Number in E164 format without the + (for e164.arpa) or format 
-                       requested by enum service used (enum.conf)
+       \param number   E164 number with or without the leading +
        \param location Number returned (or SIP uri)
        \param maxloc   Max length
        \param tech     Technology (from url scheme in response)
        \param maxtech  Max length
        \param location Number returned (or SIP uri)
        \param maxloc   Max length
        \param tech     Technology (from url scheme in response)
        \param maxtech  Max length
-*/
-extern int ast_get_enum(struct ast_channel *chan, const char *number, char *location, int maxloc, char *technology, int maxtech);
+       \param tech     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
+       \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, or number - the position of required RR in the answer list
 
 
+*/
+extern int ast_get_enum(struct ast_channel *chan, const char *number, char *location, int maxloc, char *technology, int maxtech, char* suffix, char* options);
 /*!    \brief Lookup DNS TXT record (used by app TXTCIDnum
        \param chan     Channel
 /*!    \brief Lookup DNS TXT record (used by app TXTCIDnum
        \param chan     Channel
-       \param number   E164 number without the +
+       \param number   E164 number with or without the leading +
        \param locatio  Number returned (or SIP uri)
        \param maxloc   Max length of number
        \param tech     Technology (not used in TXT records)
        \param locatio  Number returned (or SIP uri)
        \param maxloc   Max length of number
        \param tech     Technology (not used in TXT records)