Last active
May 12, 2023 10:24
-
-
Save jazzl0ver/9e350b614acae3a2309c999c27857aea to your computer and use it in GitHub Desktop.
Checking phone lines availability with Asterisk (https://voxlink.ru/kb/asterisk-configuration/proverka-dostupnosti-gorodskih-nomerov-sredstvami-asterisk/)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[trunk-checker-dst] | |
exten => _X.,1,ResetCDR | |
same => n,NoCDR | |
same => n,Set(DB(trunk_checker/${EXTEN})=${EPOCH}) | |
same => n(end),Busy(2) | |
[from-trunk-pre] | |
exten => _X.,1,Set(DIALED_NUM=${EXTEN}) | |
same => n,Goto(cont,1) | |
exten => _+X.,1,Set(DIALED_NUM=${EXTEN}) | |
same => n,Goto(cont,1) | |
exten => cont,1,Gosub(sub-fix-cid,s,1) | |
same => n,GotoIf($["${CALLERID(num)}" = "84444444444"]?trunk-checker-dst,${DIALED_NUM},1) | |
same => n,Goto(from-trunk,${DIALED_NUM},1) | |
[sub-fix-cid] | |
exten => s,1,GotoIf($[${REGEX("^[0-9]{7}$" ${CALLERID(num)})} = 0]?cont1) ;" | |
same => n,Set(CALLERID(num)=8495${CALLERID(num)}) | |
same => n,Set(CALLERID(ANI-num)=${CALLERID(num)}) | |
same => n,Goto(end) | |
same => n(cont1),GotoIf(${REGEX("^[78]?[2-9][0-9]{9}$" ${CALLERID(num)})}?fix_cid) | |
same => n,GotoIf(${REGEX("^\+?7[2-9][0-9]{9}$" ${CALLERID(num)})}?fix_cid) | |
same => n,Goto(end) | |
same => n(fix_cid),Set(CALLERID(num)=8${CALLERID(num):-10}) | |
same => n,Set(CALLERID(ANI-num)=${CALLERID(num)}) | |
same => n(end),Return | |
[trunk-checker-dial] | |
exten => _X.,1,GotoIf($["${CHECK_THRU_TRUNK}" = ""]?end) | |
same => n,Dial(SIP/${CHECK_THRU_TRUNK}/${EXTEN},60) | |
same => n(end),Hangup | |
[trunk-checker-src] | |
exten => s,1,ResetCDR | |
same => n,NoCDR | |
same => n,Answer | |
same => n,Wait(2) | |
same => n,Hangup |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%config = ( | |
mail_to => '[email protected],[email protected]', | |
trunk => 'out_prov', | |
context => 'trunk-checker-src', | |
cid => '74444444444', | |
db_family => 'trunk_checker', | |
ami_host => 'localhost', | |
ami_port => '5038', | |
ami_user => 'admin', | |
ami_secret => 'admin_secret', | |
); | |
@check_nums = ( '81111111111', '82222222222', '83333333333', ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
use warnings; | |
use strict; | |
use IO::Socket; | |
#use Data::Dumper; | |
use utf8; | |
use constant AMI_CONNECT_TIMEOUT => 3; | |
use constant AMI_READ_TIMEOUT => 30; | |
use constant AMI_RES_VALUE => 0; | |
use constant AMI_RES_ACTIONID => 1; | |
use constant AMI_RES_DATA => 2; | |
# PID | |
use constant PID_DIR => '/var/run/trunk_checker'; | |
use constant PID_FILE => 'trunk_checker'; | |
# Get script name and path | |
my $cwd = ""; | |
my $script_name = ""; | |
if ( $0 =~ /^(.*)\/([^\/]+)$/ ) { $cwd = $1; $script_name = $2 } | |
else { print "Couldn't determine cwd (WTF?)"; exit 1 } | |
# Variables | |
my $config_file = $cwd . '/trunk_checker.conf'; | |
our @check_nums = (); | |
my @failed_check = (); | |
our %config = (); | |
my $check_delay = 6; | |
my $allowed_time_diff = 40; | |
my $sendEmail = $cwd . '/sendEmail.pl'; | |
unless ( do $config_file ) { | |
print "ERROR Config file $config_file is missing or invalid: $!\n"; | |
exit 1; | |
} | |
if ( not defined $config{trunk} | |
or $config{trunk} eq '' | |
or not defined $config{context} | |
or $config{context} eq '' | |
or not defined $config{cid} | |
or not defined $config{db_family} | |
or $config{db_family} eq '' ) | |
{ | |
"ERROR Config file $config_file is missing required parameters (trunk, context, cid, db_family are required)\n"; | |
exit 1; | |
} | |
if ( not defined $config{ami_host} | |
or $config{ami_host} eq '' | |
or not defined $config{ami_port} | |
or $config{ami_port} eq '' | |
or not defined $config{ami_user} | |
or $config{ami_user} eq '' | |
or not defined $config{ami_secret} | |
or $config{ami_secret} eq '' ) | |
{ | |
"ERROR Config file $config_file is missing AMI connection parameters (ami_host, ami_port, ami_user, ami_secret)\n"; | |
exit 1; | |
} | |
### PID stuff | |
my $pid_file = PID_DIR . '/' . PID_FILE; | |
# Check if still running | |
my $cur_pid = ''; | |
if ( -r $pid_file ) { | |
if ( open PID, $pid_file ) { | |
$cur_pid = <PID>; | |
chomp $cur_pid; | |
$cur_pid = '' unless $cur_pid =~ /^[0-9]+$/; | |
close PID; | |
} | |
} | |
if ($cur_pid) { | |
if ( open PS, "ps -p $cur_pid -o comm,args -no-headers |" ) { | |
while (<PS>) { | |
chomp $_; | |
if ( $_ =~ /$script_name/ ) { | |
print "Still running $cur_pid\n"; | |
exit; | |
} | |
} | |
} | |
close PS; | |
} | |
# Leave PID file | |
unless ( -d PID_DIR ) { | |
mkdir PID_DIR | |
or print "Couldn't create " . PID_DIR . "\n"; | |
} | |
if ( open PID, ">$pid_file" ) { | |
PID->autoflush(1); | |
print PID "$$\n"; | |
close PID; | |
} | |
else { print "Couldn't write PID file $pid_file\n" } | |
### | |
# connect to Asterisk AMI | |
my $ami_sock = ami_connect( | |
$config{ami_host}, $config{ami_port}, | |
$config{ami_user}, $config{ami_secret} | |
); | |
if ( !$ami_sock ) { | |
print "ERROR Couldn't connect to " | |
. $config{ami_host} . ":" | |
. $config{ami_port} . "\n"; | |
exit 1; | |
} | |
my $num_i = 0; | |
foreach my $num (@check_nums) { | |
$num_i++; | |
my $chan = 'Local/' . $num . '@trunk-checker-dial/n'; | |
my $exten = 's'; | |
my $prio = '1'; | |
ami_orig_app( $ami_sock, $chan, $config{context}, $exten, $prio, | |
$config{cid}, "", { CHECK_THRU_TRUNK => $config{trunk} } ); | |
sleep($check_delay); | |
my $db_key = $num; | |
my $resp = ami_dbget( $ami_sock, $config{db_family}, $db_key ); | |
if ( defined $resp->[AMI_RES_DATA] and @{ $resp->[AMI_RES_DATA] } ) { | |
my $call_receive_time = $resp->[AMI_RES_DATA]->[0]; | |
if ( $call_receive_time =~ /^\d+$/ ) { | |
my $now = time(); | |
if ( abs( $call_receive_time - $now ) > $allowed_time_diff ) { | |
push( @failed_check, $num ); | |
} | |
} | |
else { push( @failed_check, $num ); } | |
} | |
else { push( @failed_check, $num ); } | |
sleep(3); | |
} | |
if (@failed_check) { | |
my $subj = | |
'Some phone numbers didn\'t respond'; | |
my $message = | |
"Following phone numbers didn\'t respond:\n" | |
. join( ', ', @failed_check ); | |
if ( defined $config{mail_to} and $config{mail_to} ne '' ) { | |
`$sendEmail -u '$subj' -t '$config{mail_to}' -m '$message' -o message-charset=UTF-8`; | |
} | |
else { | |
print $message."\n"; | |
} | |
} | |
close $ami_sock; | |
### | |
### SCRIPT END | |
### | |
### Connect to Asterisk manager interface | |
### login and password are read from /etc/asterisk/manager.conf | |
sub ami_connect { | |
my ( $ami_host, $ami_port, $ami_user, $ami_pass ) = @_; | |
my $sock = 0; | |
$sock = IO::Socket::INET->new( | |
Proto => "tcp", | |
PeerAddr => $ami_host, | |
PeerPort => $ami_port, | |
Timeout => AMI_CONNECT_TIMEOUT | |
); | |
$sock or return 0; | |
$sock->autoflush(1); | |
my $action_id = gen_pass(10); | |
my $login_msg = | |
"Action: login\r\nActionID: $action_id\r\nUsername: $ami_user\r\nSecret: $ami_pass\r\nEvents: off\r\n\r\n"; | |
print $sock "$login_msg"; | |
my $response = [ "", "" ]; | |
while ( $response->[AMI_RES_ACTIONID] ne $action_id ) { | |
$response = read_ami_message($sock); | |
if ( $response->[AMI_RES_VALUE] eq "Error" ) { | |
print "ERROR Authentication failed\n"; | |
close $sock; | |
return 0; | |
} | |
elsif ( $response->[AMI_RES_VALUE] eq "" ) { | |
print "ERROR AMI read timed out\n"; | |
close $sock; | |
return 0; | |
} | |
} | |
return $sock; | |
} | |
sub ami_orig_app { | |
my ( $sock, $chan, $cont, $ext, $prio, $cid, $src_ext, $vars ) = @_; | |
if ( $cid =~ /[^0-9+]/ ) { | |
if ($src_ext) { $cid = $cid . '<' . $src_ext . '>' } | |
else { $cid = $cid . '<' . $ext . '>' } | |
} | |
my $action_id = gen_pass(10); | |
my $msg = | |
"Action: Originate\r\nActionID: $action_id\r\nChannel: $chan\r\nContext: $cont\r\nExten: $ext\r\nPriority: $prio\r\nTimeout: 14000\r\nCallerid: $cid\r\n"; | |
my $var_str = ""; | |
foreach ( keys %$vars ) { | |
$var_str = $var_str . "$_=$vars->{$_}|"; | |
} | |
$msg = $msg . "Variable: " . substr( $var_str, 0, -1 ) . "\r\n" | |
if $var_str ne ""; | |
$msg = $msg . "\r\n"; | |
print $sock "$msg"; | |
my $ami_message = read_ami_message($sock); | |
if ( $ami_message->[AMI_RES_VALUE] eq "" ) { | |
print "ERROR AMI read timed out\n"; | |
} | |
return $ami_message; | |
} | |
sub ami_dbget { | |
my $sock = ${ shift(@_) }; | |
my ( $family, $key ) = @_; | |
my $action_id = gen_pass(10); | |
my $msg = | |
"Action: DBGet\r\nActionID: $action_id\r\nFamily: $family\r\nKey: $key\r\n\r\n"; | |
print $sock "$msg"; | |
my $response = [ '', '' ]; | |
my $res = [ 'dummy', '' ]; | |
while ( $res->[AMI_RES_ACTIONID] ne $action_id ) { | |
$res = read_ami_message( $sock, 'DBGet', AMI_READ_TIMEOUT ); | |
if ( $res->[AMI_RES_VALUE] eq "" ) { | |
print STDERR "ERROR AMI DBGet read timed out\n$msg"; | |
return $response; | |
} | |
elsif ( $res->[AMI_RES_VALUE] eq "Success" ) { | |
$response->[AMI_RES_DATA] = []; | |
my $res1 = [ 'dummy', '' ]; | |
while ( $res1->[AMI_RES_VALUE] ne '' ) { | |
$res1 = read_ami_message( $sock, 'DBGetResponse', 2 ); | |
if ( $res1->[AMI_RES_ACTIONID] eq $action_id ) { | |
if ( $res1->[AMI_RES_VALUE] eq 'DBGetResponse' ) { | |
if ( defined $res1->[AMI_RES_DATA]->{'Val'} ) { | |
push( | |
@{ $response->[AMI_RES_DATA] }, | |
$res1->[AMI_RES_DATA]->{'Val'} | |
); | |
} | |
else { | |
print STDERR | |
"ERROR 'Val' not defined in DBGetResponse\n"; | |
} | |
} | |
elsif ( $res1->[AMI_RES_VALUE] eq 'DBGetComplete' ) { | |
last; | |
} | |
elsif ( $res1->[AMI_RES_VALUE] eq '' ) { | |
print STDERR | |
"ERROR AMI DBGetResponse read timed out\n"; | |
last; | |
} | |
else { | |
print STDERR | |
"ERROR AMI DBGetResponse read returned unexpected response $res1->[AMI_RES_VALUE]\n"; | |
last; | |
} | |
} | |
} | |
} | |
} | |
return $response; | |
} | |
# | |
# Parse AMI message, return @result | |
# $result->[0] = Event or Response | |
# $result->[1] = ActionID | |
# $result->[2] = hash of parameters | |
# | |
sub read_ami_message { | |
my ( $sock, $log_label, $timeout ) = shift(@_); | |
if ( not defined $log_label ) { $log_label = 'Unspecified'; } | |
if ( not defined $timeout ) { $timeout = AMI_READ_TIMEOUT; } | |
my $result = [ "", "", {} ]; | |
eval { | |
local $SIG{ALRM} = sub { exit }; | |
alarm $timeout; | |
my $ami_line = "start"; | |
while ( $ami_line ne "" ) { | |
$ami_line = <$sock>; | |
if ( not defined $ami_line ) { last; } | |
$ami_line =~ s/\r\n//g; | |
#print "$ami_line\n"; | |
if ( $ami_line =~ /^([^:]+): (.*)$/ ) { | |
if ( $1 eq "Event" or $1 eq "Response" ) { | |
$result->[AMI_RES_VALUE] = $2; | |
} | |
elsif ( $1 eq "ActionID" ) { $result->[AMI_RES_ACTIONID] = $2 } | |
else { $result->[AMI_RES_DATA]->{$1} = $2 } | |
} | |
} | |
}; | |
return $result; | |
} | |
### Generate password | |
### gen_pass(pass_len,var,num_only) | |
### var - if set, pass_len will randomly vary by +-2 characters | |
### num_len - if set, only numbers will be used in password generation | |
sub gen_pass { | |
my $pass_len = shift(@_); | |
my $var = shift(@_); | |
my $num_only = shift(@_); | |
my @chars = ( 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '_', '-' ); | |
if ($var) { $pass_len = int( rand 4 ) + $pass_len - 2 } | |
if ($num_only) { @chars = ( '0' .. '9' ) } | |
my $pass = ""; | |
foreach ( 1 .. $pass_len ) { $pass .= $chars[ rand @chars ]; } | |
return $pass; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment