From 81845e51a5a093d989fa704a8737f47f29243378 Mon Sep 17 00:00:00 2001 From: Kirill Solomko Date: Fri, 27 Jan 2017 16:37:55 +0100 Subject: [PATCH] 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 --- vmnotify | 282 +++++++++++++++++++++++++++++++++++++++++----------- vmsmsnotify | 146 +++++++++++++++++---------- 2 files changed, 318 insertions(+), 110 deletions(-) diff --git a/vmnotify b/vmnotify index 3436206..8c8d4ac 100755 --- a/vmnotify +++ b/vmnotify @@ -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 +# (c) 2017 Sipwise GmbH. All rights reserved. # -# Usage: vmnotify +# Author: Kirill Solomko # ######################################################################## +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 < - voicemail context (default: "default") + - destination user + - amount of new messages + [old messages] - amount of old messages + [urgent messages] - amount of urgent messages + + [extended] + - message number + - from user + - datetime string + - 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(\<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 < "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 " -fi -if [ "x$USER" = "x" ]; then - error "Usage: $0 " -fi -if [ "x$MESSAGES" = "x" ]; then - error "Usage: $0 " -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 <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 & diff --git a/vmsmsnotify b/vmsmsnotify index 244d140..60bc75d 100755 --- a/vmsmsnotify +++ b/vmsmsnotify @@ -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(\<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 <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;