Allow using a YAML file to provide the list of resolvers to use.
[asterisk-agi-enum.git] / enum.agi
1 #!/usr/bin/env perl
2 #
3 # Make outgoing calls via enum NAPTR records.
4 #
5 # Add to your extensions.conf like so:
6 #       exten => _*164X.,n,AGI(enum.agi, +${EXTEN:4})
7 #
8 # Requires Net::DNS and Asterisk::AGI perl modules.
9 #
10 # Mahlon E. Smith <mahlon@martini.nu>
11 #
12 # ---------------------------------------------------------
13
14 use strict;
15 use warnings;
16
17 use Net::DNS;
18 use Asterisk::AGI;
19 use YAML::Syck qw/LoadFile/;
20
21 my ( @resolvers, $A, %AGI, $num, $resources );
22
23 # in order of preference - default.  Can be over ridden using
24 # /etc/asterisk/enum-agi.yml .
25 @resolvers = qw/ e164.org e164.arpa /;
26
27 my $config_file = "/etc/asterisk/enum-agi.yml";
28 if (-f $config_file) {
29   my $config = eval { LoadFile $config_file };
30   if ($@) {
31     die "Failed to load config file: $config_file, error: $@";
32   }
33
34   if (defined $config->{'resolvers'} && scalar(@{ $config->{'resolvers'} })) {
35     @resolvers = @{ $config->{'resolvers'} };
36   }
37 }
38
39 # setup AGI environment
40 $A   = Asterisk::AGI->new();
41 %AGI = $A->ReadParse;
42
43 unless ( $num = $ARGV[0] ) {
44     $A->verbose( "No number to parse!  Exiting.", 4 );
45     exit;
46 }
47 $ARGV[0] =~ s/\s+//g;
48 $num =~ s/[^\d]//g;
49 $num = join '.', split //, reverse $num;
50
51 foreach (@resolvers) {
52     last if $resources;
53     $A->verbose( "Finding resource at $_ for $ARGV[0]...", 4 );
54     $resources = get_resources( $num, $_ );
55 }
56 unless ($resources) {
57     $A->verbose( "No valid resources found for $ARGV[0].", 4 );
58     exit;
59 }
60
61 foreach (
62     sort {
63         $resources->{$a}->{'preference'} <=> $resources->{$b}->{'preference'}
64     }
65     keys %$resources
66   )
67 {
68     my $resource = $resources->{$_}->{'resource'};
69     if ( $A->exec( 'Dial', "$resource|60|r" ) ) {
70         last;
71         $A->hangup;
72     }
73 }
74
75 exit;
76
77 sub get_resources {
78     my ( $num, $resolver ) = @_;
79     my $resource;
80
81     my $res = Net::DNS::Resolver->new;
82     my $query = $res->query( "$num.$_", 'NAPTR' );
83
84     unless ($query) {
85         $A->verbose(
86             "ENUM query failed at $_ for $ARGV[0]: " . $res->errorstring, 4 );
87         return;
88     }
89
90     # build hash of potential resources to use
91     my %records;
92     my $count = 0;
93     foreach my $rr ( $query->answer ) {
94         next unless $rr->type eq 'NAPTR';
95         next unless $rr->service =~ /(sip|iax)/i;
96         $count++;
97
98         $resource = $ARGV[0];
99         my ( $pattern, $replacement ) = ( split '!', $rr->regexp )[ 1, 2 ];
100         $replacement =~ s/\@/\\\@/;
101         $resource    =~ s/^\+//;
102         $A->verbose( "$pattern, $replacement", 4 );
103         my $regexp = "\$resource =~ s/$pattern/$replacement/";
104         eval "$regexp";
105         if ($@) {
106           $A->verbose( "ENUM replacement faileD: $@", 1 );
107           next;
108         }
109         $A->verbose( "new resource: $resource", 4 );
110         $resource =~ s/^(\w+):/\U$1\//;
111         $records{$count} =
112           { preference => $rr->preference, resource => $resource };
113     }
114
115     return \%records;
116 }