TT#14809 vmnotify, vmsmsnotify rework

* vmnotify refactor from bash to perl
    * add "old" and "urgent" messages support
    * add notificitations support to an external app url via http
    * vmsmsnotify refactor for better logging, uses
      vmnotify.conf config
    * vmnotify and vmsmsnotify now log into
      /var/log/ngcp/vmnotify.log via rsyslog

Change-Id: I796b6940fb2dbb32ca808b34aaf668d3e8e6b278
changes/34/10934/9
Kirill Solomko 10 years ago
parent 758bc04c96
commit 81845e51a5

@ -1,84 +1,246 @@
#!/bin/bash
#!/usr/bin/perl -w
########################################################################
# vmnotify - An Asterisk-VoiceMail compatible MWI notification script
# (c) 2007 Sipwise. All rights reserved.
#
# Author: Andreas Granig <agranig@sipwise.com>
# (c) 2017 Sipwise GmbH. All rights reserved.
#
# Usage: vmnotify <context> <user> <messages>
# Author: Kirill Solomko <ksolomko@sipwise.com>
#
########################################################################
use strict;
use warnings;
use English;
use Readonly;
use Log::Log4perl qw(:easy);
use POSIX qw(strftime);
use Readonly;
use Config::Any;
use Sys::Syslog qw(:DEFAULT :macros setlogsock);
use IO::Socket;
use Data::UUID;
use Data::Dumper;
use HTTP::Request;
use LWP::UserAgent;
use JSON;
use IO::Socket::SSL;
unless (scalar @ARGV) {
print <<USAGE;
==
vmnotify - An Asterisk-VoiceMail compatible MWI notification script
==
$PROGRAM_NAME [options]
Options:
[basic]
<context> - voicemail context (default: "default")
<user> - destination user
<new messages> - amount of new messages
[old messages] - amount of old messages
[urgent messages] - amount of urgent messages
[extended]
<msgnum> - message number
<from> - from user
<date> - datetime string
<duration> - mesasge duration
USAGE
exit 0;
}
Readonly my $CONF_FILE => '/etc/ngcp-vmnotify/vmnotify.conf';
my %CONFIG = %{
Config::Any->load_files({
files => [ $CONF_FILE ],
use_ext => 1
})->[0]->{$CONF_FILE}
or do {
log_syslog(
"$PROGRAM_NAME error: Cannot load config $CONF_FILE: $ERRNO");
exit 1;
};
};
my $debug = $CONFIG{DEBUG} ? "DEBUG" : "INFO";
Log::Log4perl->init(\<<EOF);
log4perl.category.vmnotify=$debug, SYSLOG, SCREEN
log4perl.appender.SYSLOG=Log::Dispatch::Syslog
log4perl.appender.SYSLOG.facility=local0
log4perl.appender.SYSLOG.ident=vmnotify
log4perl.appender.SYSLOG.layout=PatternLayout
log4perl.appender.SYSLOG.layout.ConversionPattern=%-5p %m%n
log4perl.appender.SCREEN=Log::Log4perl::Appender::Screen
log4perl.appender.SCREEN.mode=append
log4perl.appender.SCREEN.layout=PatternLayout
log4perl.appender.SCREEN.layout.ConversionPattern=%-5p %m%n
EOF
my $log = Log::Log4perl->get_logger("vmnotify");
my %data = ();
#----------------------------------------------------------------------
sub log_syslog {
my $str = shift;
setlogsock "native", "unix", "udp";
openlog $PROGRAM_NAME, "pid", LOG_DEBUG;
syslog LOG_ERR, '%s', $str ;
return;
}
CONFFILE="/etc/ngcp-vmnotify/vmnotify.conf"
LOGGER="/usr/bin/logger -i -t vmnotify"
sub gen_callid {
my $self = shift;
function debug
{
LOG_ARGS="-p daemon.debug"
if [ "x$CONSOLE_DEBUG" = "x1" ]; then
LOG_ARGS="$LOG_ARGS -s"
fi
echo "$@" | $LOGGER $LOG_ARGS
return Data::UUID->create_str();
}
function error
{
LOG_ARGS="-p daemon.err"
if [ "x$CONSOLE_DEBUG" = "x1" ]; then
LOG_ARGS="$LOG_ARGS -s"
fi
echo "$@" | $LOGGER $LOG_ARGS
exit 1
sub gen_branchid {
my @list = ("a".."z",0..9,"A".."Z");
return "z9hG4bK" . join "",
map { $list[int(rand($#list))] } (1..8);
}
sub send_mwi_notify {
open(my $fh, "<", $CONFIG{SIPFILE})
or die "Cannot open file: $CONFIG{SIPFILE}";
binmode $fh;
my $mwi;
while (<$fh>) {
$_ =~ s/([^\r])\n/$1\r\n/;
$mwi .= $_;
}
close $fh;
die "Empty MWI. Cannot send SIP MWI notification\n" unless $mwi;
test -f $CONFFILE || error "Config file not found"
. $CONFFILE
my $log_str = sprintf <<EOF,
vmnotify to=%s: context=%s new=%d old=%d urgent=%d
EOF
@data{qw(user context new old urgent)};
chomp $log_str;
test -f $SIPSAK || error "sipsak command not found"
test -f $UUIDGEN || error "uuidgen command not found"
my %macros = (
body_mw => "Messages-Waiting: ". ($data{new} ? "yes" : "no"),
body_vm => "Voice-Message: $data{new}/$data{old} ($data{urgent}/0)",
call_id => $data{callid},
mbid => $data{user},
branch => gen_branchid(),
user => $data{user},
dsthost => $CONFIG{SERVER},
);
$macros{bodylen} = length($macros{body_mw})+2+length($macros{body_vm})+2;
debug "Arguments: $@"
map { $mwi =~ s/\$$_\$/$macros{$_}/mgi; } keys %macros;
my $sock = IO::Socket::INET->new(PeerAddr => $CONFIG{SERVER},
LocalAddr => $CONFIG{LOCAL_IP},
Proto => 'udp',
TimeOut => 3,
Blocking => 0)
or die sprintf "Cannot send %s server=%s error=%s\n",
$log_str, $CONFIG{SERVER}, $ERRNO;
$sock->send($mwi)
or die sprintf "Cannot send %s error=%s\n", $log_str, $ERRNO;
$sock->close();
$log->debug($log_str);
return;
}
if [ "x$SERVER" = "x" ]; then
error "Config parameter SERVER not specified"
fi
sub send_ext_notify {
CONTEXT=$1
USER=$2
MESSAGES=$3
my $url = $CONFIG{EXT_SERVER_URL} || return;
if [ "x$CONTEXT" = "x" ]; then
error "Usage: $0 <context> <cli> <messages>"
fi
if [ "x$USER" = "x" ]; then
error "Usage: $0 <context> <cli> <messages>"
fi
if [ "x$MESSAGES" = "x" ]; then
error "Usage: $0 <context> <cli> <messages>"
fi
my %url_ph = (
prefix => 'voicemail',
suffix => 'notify',
caller => $data{from},
caleee => $data{user},
callid => $data{callid},
token => '',
);
URI="sip:$USER@$SERVER"
foreach my $v (qw(prefix suffix caller callee callid token)) {
my $t = $url_ph{$v} ? $url_ph{$v}."/" : "";
$url =~ s/\$\{$v\}/$t/g;
}
if [ "x$MESSAGES" = "x0" ]; then
MW="no"
else
MW="yes"
fi
my $ua = new LWP::UserAgent;
$ua->agent('NGCP vmnotify 1.0');
$ua->ssl_opts(
verify_hostname => 0,
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
);
my $r = new HTTP::Request("POST", $url);
$r->header('Content-Type', 'application/json');
my $json = {
caller => $data{from},
callee => $data{user},
recording_id => $data{msgnum},
timestamp => $data{date},
duration => $data{duration},
};
$r->content(encode_json $json);
my $res = $ua->request($r);
my $log_str = sprintf <<EOF,
ext vmnotify url=%s from=%s to=%s msgnum=%d date=%s duration=%d
EOF
$url, @data{qw(from user msgnum date duration)};
chomp $log_str;
if ($res->is_success) {
$log->debug($log_str);
} else {
die sprintf "Cannot send %s error=%s\n", $log_str, $res->status_line
}
return;
}
sub main {
eval {
die "Incorrect arguments list" if $#ARGV < 2;
my $idx = 0;
foreach my $arg (qw(context user new old urgent)) {
$data{$arg} = $ARGV[$idx] // 0;
$idx++;
}
my $extended = 1;
foreach my $arg (qw(msgnum from date duration)) {
$data{$arg} = $ARGV[$idx] or $extended = 0;
$idx++;
}
$data{callid} = gen_callid().'@voip.sipwise.local';
send_mwi_notify();
send_ext_notify() if $extended;
};
if ($EVAL_ERROR) {
$log->error($EVAL_ERROR);
exit 1;
}
return;
}
NEW=$MESSAGES
OLD="0" # asterisk doesn't send number of old messages!
main();
BODY_MW="Messages-Waiting: $MW"
BODY_VM="Voice-Message: $NEW/$OLD"
BODYLEN=$((${#BODY_MW}+2+${#BODY_VM}+2))
CALL_ID=$($UUIDGEN)@voip.sipwise.local
MBID=$USER
BRANCH=$($UUIDGEN)
exit 0;
nohup $SIPSAK -S -i -k $LOCAL_IP -l 5081 -f $SIPFILE \
-G -g "!BODY_MW!$BODY_MW!BODY_VM!$BODY_VM!BODYLEN!$BODYLEN!CALL_ID!$CALL_ID!MBID!$MBID!BRANCH!$BRANCH!" \
$VERBOSE -E udp -s $URI 1>/dev/null 2>/dev/null &

@ -1,67 +1,113 @@
#!/usr/bin/perl -w
use strict;
use URI;
use English;
use Readonly;
use NGCP::API::Client;
use TryCatch;
use Sys::Syslog qw(:standard :macros);
use Data::Dumper;
sub DEBUG {
my ($msg) = @_;
# only log debug to syslog to not clutter console
syslog(LOG_DEBUG, $msg);
}
use Config::Any;
use Log::Log4perl;
sub INFO {
my ($msg) = @_;
print $msg, "\n";
syslog(LOG_INFO, $msg);
}
Readonly my $CONF_FILE => '/etc/ngcp-vmnotify/vmnotify.conf';
sub ERROR {
my ($msg) = @_;
print STDERR $msg, "\n";
syslog(LOG_ERR, $msg);
}
my %CONFIG = %{
Config::Any->load_files({
files => [ $CONF_FILE ],
use_ext => 1
})->[0]->{$CONF_FILE}
or do {
log_syslog(
"$PROGRAM_NAME error: Cannot load config $CONF_FILE: $ERRNO");
exit 1;
};
};
my $from = $ARGV[0];
my $dest = $ARGV[1];
my $mailbox = $ARGV[2];
my $caller = $ARGV[3];
my $duration = $ARGV[4];
my $date = $ARGV[5];
my $body = $ARGV[6];
my $debug = $CONFIG{DEBUG} ? "DEBUG" : "INFO";
$from =~ s/\s+//g;
Log::Log4perl->init(\<<EOF);
log4perl.category.vmnotify=$debug, SYSLOG, SCREEN
openlog("vmpagernotify", "ndelay,pid", LOG_LOCAL0);
log4perl.appender.SYSLOG=Log::Dispatch::Syslog
log4perl.appender.SYSLOG.facility=local0
log4perl.appender.SYSLOG.ident=vmnotify
log4perl.appender.SYSLOG.layout=PatternLayout
log4perl.appender.SYSLOG.layout.ConversionPattern=%-5p %m%n
DEBUG("sending pager notification to $dest using sender $from");
DEBUG("mailbox is $mailbox, caller is $caller, duration is $duration");
DEBUG("body is '$body'");
log4perl.appender.SCREEN=Log::Log4perl::Appender::Screen
log4perl.appender.SCREEN.mode=append
log4perl.appender.SCREEN.layout=PatternLayout
log4perl.appender.SCREEN.layout.ConversionPattern=%-5p %m%n
EOF
my $client = new NGCP::API::Client;
my $res;
#----------------------------------------------------------------------
$res = $client->request('GET', "/api/subscribers/?alias=$mailbox");
unless($res->is_success) {
ERROR "Failed to fetch subscriber for alias $mailbox, aborting!";
exit 1;
}
my $subs = $res->as_hash->{_embedded}->{'ngcp:subscribers'};
unless(ref $subs eq 'HASH') {
ERROR "Failed to fetch single subscriber for alias $mailbox, aborting!";
exit 1;
my $log = Log::Log4perl->get_logger("vmnotify");
sub send_notify {
my $from = $ARGV[0];
my $dest = $ARGV[1];
my $mailbox = $ARGV[2];
my $caller = $ARGV[3];
my $duration = $ARGV[4];
my $date = $ARGV[5];
my $body = $ARGV[6];
$from =~ s/\s+//g;
my $log_str = sprintf <<EOF,
vmsmsnotify from=%s dest=%s mailbox=%s caller=%s duration=%s date=%s body=%s
EOF
$from, $dest, $mailbox, $caller, $duration, $date, $body;
eval {
my $client = new NGCP::API::Client;
my $res = $client->request('GET', "/api/subscribers/?alias=$mailbox");
unless($res->is_success) {
die "Failed to fetch subscriber for alias $mailbox, aborting!";
}
my $subs = $res->as_hash->{_embedded}->{'ngcp:subscribers'};
unless(ref $subs eq 'HASH') {
die "Failed to fetch single subscriber for alias $mailbox, aborting!";
}
my $content = {
subscriber_id => $subs->{id},
caller => $from,
callee => $dest,
text => $body,
};
$res = $client->request(
'POST',
'/api/sms/?skip_checks=true&skip_journal=false',
$content
);
die $res->status_line,"\n" unless $res->is_success;
};
if ($EVAL_ERROR) {
die sprintf "Cannot send %s error=%s", $log_str, $EVAL_ERROR;
} else {
$log->debug($log_str);
}
return;
}
my $content = {
subscriber_id => $subs->{id},
caller => $from,
callee => $dest,
text => $body,
};
sub main {
eval {
send_notify();
};
if ($EVAL_ERROR) {
$log->error($EVAL_ERROR);
exit 1;
}
return;
}
main();
$res = $client->request('POST', '/api/sms/?skip_checks=true&skip_journal=false', $content);
exit 0;

Loading…
Cancel
Save