Skip to content

Instantly share code, notes, and snippets.

@jazzl0ver
Last active May 12, 2023 10:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jazzl0ver/9e350b614acae3a2309c999c27857aea to your computer and use it in GitHub Desktop.
Save jazzl0ver/9e350b614acae3a2309c999c27857aea to your computer and use it in GitHub Desktop.
[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
%config = (
mail_to => 'admin@example.com,boss@example.com',
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', );
#!/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 '' )
{
print
"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 '' )
{
print
"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