#!/usr/bin/perl -w
########################################################################
# vmnotify - An Asterisk-VoiceMail compatible MWI notification script
# (c) 2017 Sipwise GmbH. All rights reserved.
#
# 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;
}

sub gen_callid {
    my $self = shift;

    return Data::UUID->create_str();
}

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;

    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;

    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} = 2+length($macros{body_mw})+2+length($macros{body_vm})+2;

    map { $mwi =~ s/\$$_\$/$macros{$_}/gi; } 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;
}

sub send_ext_notify {

    my $url = $CONFIG{EXT_SERVER_URL} || return;

    my %url_ph = (
        prefix => 'voicemail',
        suffix => 'notify',
        caller => $data{from},
        callee => $data{user},
        callid => $data{callid},
        token  => '',
    );

    my $mm = 0;
    foreach my $v (qw(prefix suffix caller callee callid token)) {
        my $t = $url_ph{$v} ? $url_ph{$v}."/" : "";
        if ($url =~ s/\$\{$v\}/$t/g) {
            $mm = 1;
        }
    }
    $url = substr($url, 0, -1) if $mm;

    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();
        if ($extended &&
            $CONFIG{EXT_NOTIFY} && $CONFIG{EXT_NOTIFY} eq "yes") {
            send_ext_notify();
        }
    };
    if ($EVAL_ERROR) {
        $log->error($EVAL_ERROR);
        exit 1;
    }

    return;
}

main();

exit 0;

