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 *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"
@@ -71,6 +71,9 @@ static int enumlookup_exec(struct ast_channel *chan, void *data)
        char dest[80];
        char tmp[256];
        char *c,*t;
+
+       tech[0] = '\0';
+
        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) {
-               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);
@@ -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,';');
-                       if (t) 
+                       if (t)
                                *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 */
-                               t = tmp;        
+                               t = tmp;
                                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)
 {
+
+       char tech_return[80];
        char *oanswer = answer;
        char flags[512] = "";
        char services[512] = "";
+       unsigned char *p;
        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];
 
+       tech_return[0] = '\0';
 
        dst[0] = '\0';
-       
+
        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");
-               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");
-               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");
-               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 (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;
        }
 
-       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 {
-               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
@@ -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;
-       } 
+       }
 
 
        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);
+       printf("Input: %s\n", naptrinput);
 #endif
 
 /*
@@ -221,8 +225,8 @@ static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, char *a
        }
        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';
@@ -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';
-       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 */
@@ -257,6 +287,10 @@ 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 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 */
@@ -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 */
-       /* 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
@@ -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));
-       
+
        /* 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;
+       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;
+       } 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 */
-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];
-       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;
-
-       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.tech = tech;                    /* Return string */
+       context.tech = tech;
        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;
-       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;
 
@@ -349,7 +460,9 @@ int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int ds
                } 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);
@@ -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;
+               if(suffix != NULL)
+                       break;
        }
        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);
+
+       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;
 }
 
-/*--- 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)
@@ -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++] = '.';
        }
-       
+
        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"
 
-/*! \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 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
-*/
-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
-       \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)