#!/usr/bin/perl
#
# $Id: gnugk-finger,v 1.5 2006/06/22 11:26:28 stan Exp $
#
# Queries GnuGK for registered endpoints and running calls.
#
# In Debian, you will need following additional packages:
#
#        libgetopt-declare-perl
#	 libexpect-perl
#
# Copyright (C) 2005 Stefan Anders <anders@vc.dfn.de>
#               2014 Jan Willamowius <jan@willamowius.de>, http://www.willamowius.com
#
# gnugk-finger is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# gnugk-finger is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#

use Getopt::Declare;
use Expect;
use Time::Local;

exit 1 if ! Getopt::Declare->new(q(
    [strict]
    -r		raw; for parsing by other scripts.
		{ $main::RAW = 1; }
    -c		show calls instead of registrations.
		{ $main::CALLS = 1; }
    <gk>	name of gatekeeper.
		{ $main::HOST = $gk; }

Purpose:
    Query gnugk for endpoints or calls. Tested with gnugk 3.7.
));

if ($HOST eq "") {
    print STDERR "Missing parameter gatekeeper name, please use -h for help.\n";
    exit 1;
}

if (!$CALLS) {
    ($error,@eps) = gnugk_endpoints($HOST);
    if ($error) {
	print STDERR "$error\n";
	exit 1;
    }
    if ($RAW) {
	foreach $ep (@eps) {
	    print "alias:  $ep->{'alias'}\n";
	    print "number: $ep->{'number'}\n";
	    print "date:   $ep->{'date'}\n";
	    print "ip:     $ep->{'ip'}\n";
	    print "port:   $ep->{'port'}\n";
	    print "type:   $ep->{'type'}\n";
	    print "ncalls: $ep->{'ncalls'}\n";
	    print "\n";
	}
	exit;
    }
    
    %headers = ("alias" => "h.323", "number" => "e.164", "date" => "since",
		"ip" => "ip", "port" => "port", "type" => "type", "ncalls" => "incall");
    
    foreach $header (keys(%headers)) {
	$lens{$header} = length($headers{$header});
    }
    
    foreach $ep (@eps) {
	foreach $header (keys(%headers)) {
	    $len = length($ep->{$header});
	    $lens{$header} = $len if $len > $lens{$header};
	}
    }
    foreach $header (keys(%lens)) {
	$totallen += $lens{$header} + 1;
    }
    $totallen--;
    @queue = ("alias","number","ip","port","type","date","ncalls");
    %alignright = ("ncalls" => 1);
    
    foreach $item (@queue) {
	printf("%-".$lens{$item}."s ",$headers{$item});
    }    
    print "\n";
    for ($i=0; $i<$totallen;$i++) {
	print "=";
    }
    print "\n";
    foreach $ep (@eps) {
	foreach $item (@queue) {
	    $align = "-";
	    if (exists($alignright{$item})) {
		$align = "";
	    }
	    printf("%".$align.$lens{$item}."s ",$ep->{$item});
	}
	print "\n";
    }
}
else {
    ($error,@calls) = gnugk_calls($HOST);
    if ($error) {
	print STDERR "$error\n";
	exit 1;
    }
    if ($RAW) {
	foreach $call (@calls) {
	    print "callno:   $call->{callno}\n";
	    print "start:    $call->{start}\n";
	    print "dur:      $call->{dur}\n";  
	    print "bw:       $call->{bw}\n";   
	    print "dialed:   $call->{dialed}\n";
	    print "src_ip:   $call->{src_ip}\n";
	    print "src_port: $call->{src_port}\n";
	    print "src_h323: $call->{src_h323}\n";
	    print "src_e164: $call->{src_e164}\n";
	    print "dst_ip:   $call->{dst_ip}\n";
	    print "dst_port: $call->{dst_port}\n";
	    print "dst_h323: $call->{dst_h323}\n";
	    print "dst_e164: $call->{dst_e164}\n";
	    print "\n";
	}
	exit;
    }

    @out = ();
    foreach $call (@calls) {
	$from = $call->{"src_ip"}.":".$call->{"src_port"}."(".
	    $call->{"src_h323"}.",".$call->{"src_e164"}.")"; 
	$to = $call->{"dst_ip"}.":".$call->{"dst_port"}."(".
	    $call->{"dst_h323"}.",".$call->{"dst_e164"}.")";
	$i = scalar(@out);
	$out[$i]->{"from"} = $from;
	$out[$i]->{"to"} = $to;
	$out[$i]->{"start"} = $call->{"start"};
	$out[$i]->{"bw"} = $call->{"bw"};
	$out[$i]->{"bw"} = "-" if $out[$i]->{"bw"} eq "";
	$out[$i]->{"callno"} = $call->{"callno"};
	$out[$i]->{"dur"} = $call->{"dur"};
	$out[$i]->{"dialed"} = $call->{"dialed"};
	$ss = $call->{"dur"};
	$mm = int($ss/60);
	$ss = $ss % 60;
	$hh = int($mm/60);
	$mm = $mm % 60;
	$hh = "0$hh" if length($hh) < 2;
	$mm = "0$mm" if length($mm) < 2;
	$ss = "0$ss" if length($ss) < 2;
	$out[$i]->{"dur"} = "$hh:$mm:$ss";
   }


    %headers = ("from" => "from", "to" => "to", "start" => "since",
		"bw" => "kbit/s", "callno" => "callno", "dur" => "duration",
		"dialed" => "dialed");
    
    foreach $header (keys(%headers)) {
	$lens{$header} = length($headers{$header});
    }
    
    foreach $call (@out) {
	foreach $header (keys(%headers)) {
	    $len = length($call->{$header});
	    $lens{$header} = $len if $len > $lens{$header};
	}
    }
    foreach $header (keys(%lens)) {
	$totallen += $lens{$header} + 1;
    }
    $totallen--;
    @queue = ("from","to","dialed","start","dur","bw","callno");
    %alignright = ("callno" => 1, "bw" => 1);
    
    foreach $item (@queue) {
	printf("%-".$lens{$item}."s ",$headers{$item});
    }    
    print "\n";
    for ($i=0; $i<$totallen;$i++) {
	print "=";
    }
    print "\n";
    foreach $call (@out) {
	foreach $item (@queue) {
	    $align = "-";
	    if (exists($alignright{$item})) {
		$align = "";
	    }
	    printf("%".$align.$lens{$item}."s ",$call->{$item});
	}
	print "\n";
    }


}

exit;


sub gnugk_endpoints {
    my ($name) = @_;
    my ($gk,@out,$line,$ipport,$aliasstring,$type,$ip,$port,$alias,$number,$rest);
    my ($day,$mon,$year,$hh,$mm,$zone,$ncalls,$month,$sign,$zh,$zm,$date,@eps,$i);
    my (%months);

    $gk = Expect->new();
    $gk->raw_pty(1);
    $gk->log_user(0);

    if (! $gk->spawn("telnet $name 7000")) {
	return("Can't call telnet to $name: $!",());
    }
    if (! $gk->expect(10,"-re",'^\s*;\s*$')) {
	if ($gk->before() =~ /Access\s+forbidden/i) {
	    return ("Access forbidden!",());
	}
	if ($! =~ /Input\/Output\s+error/i) {
	    return ("Can't connect to $name",());
	}
	return("Can't connect to $name: $!",());
    }
    $gk->send("printallregistrationsverbose\n");
    if (! $gk->expect(10,"-re",'^\s*;\s*$')) {
	return("Got no valid answer from $name, perhaps version mismatch",());
    }
    @out = split("\r\n",$gk->before());
    $gk->send("q\n");
    $gk->soft_close();

    @calls = ();
    foreach $line (@out) {
	if ($line =~ /RCF\|([^\|]+)\|([^\|]+)\|([^\|]+)/) {
	    $ipport = $1;
	    $aliasstring = $2;
	    $type = $3;
	    ($ip,$port) = split(":",$ipport);
	    ($alias,$number) = parsealias($aliasstring);
	}
	elsif ($line =~ /^(\w+,\s+\d+\s+\w+\s+\d+\s+\d+:\d+:\d+\s+\S+)\s+\S+\s+<(\d+)>/) {
	    $ncalls = $2;
	    $ncalls -= 1 if $ncalls > 0;
	    $date = parsedate($1);
	    $i = scalar(@eps);
	    $eps[$i]->{'alias'}  = $alias;
	    $eps[$i]->{'number'} = $number;
	    $eps[$i]->{'date'}   = $date;
	    $eps[$i]->{'ip'}     = $ip;
	    $eps[$i]->{'port'}   = $port;
	    $eps[$i]->{'type'}   = $type;
	    $eps[$i]->{'ncalls'} = $ncalls;
	}
    }
    return ("",@eps);
}

sub gnugk_calls {
    my ($name) = @_;
    my ($gk,@out,$line,$callno,$dur,$got_src,$got_dst,$src_ip,$dst_ip);
    my ($src_port,$dst_port,$src_h323,$src_e164,$dst_h323,$dst_e164,$bw,$start,$i,@ret);
    my ($dial,$dial_h323,$dial_e164);

    $gk = Expect->new();
    $gk->raw_pty(1);
    $gk->log_user(0);

    if (! $gk->spawn("telnet $name 7000")) {
	return("gnugk_calls: can't spawn telnet to $name: $!",());
    }
    if (! $gk->expect(10,"-re",'^\s*;\s*$')) {
	return("gnugk_calls: got no prompt after login from $name: $!",());
    }
    $gk->send("printcurrentcallsverbose\n");
    if (! $gk->expect(10,"-re",'^\s*;\s*$')) {
	return("gnugk_calls: got no prompt after printallregistrationsverbose from $name",());
    }
    @out = split("\r\n",$gk->before());
    $gk->send("q\n");
    $gk->soft_close();


    # CurrentCalls
    # Call No. # | CallID | Call_Duration | Left_Time
    # Dial Dialed_Number
    # ACF|Caller_IP:Port|Caller_EPID|CRV
    # ACF|Callee_IP:Port|Callee_EPID|CRV
    # # Caller_Aliases|Callee_Aliases|Bandwidth|Connected_Time <r>
    # ...
    # Number of Calls: Current_Call Active: Active_Call From NB: Call_From_Neighbor

    # CurrentCalls
    # Call No. 48 | CallID 7d 5a f1 0a ad ea 18 10 89 16 00 50 fc 3f 0c f5 | 30 | 570
    # Dial 0225067272:dialedDigits
    # ACF|10.0.1.200:1720|1448_endp|19618
    # ACF|10.0.1.7:1720|1325_endp|19618
    # # Sherry:h323_ID|Accel-GW1:h323_ID|200000|Wed, 26 Jun 2002 17:29:55 +0800 <2>
    # Number of Calls: 1 Active: 1 From Neighbor: 0 From Parent: 0

    @ret = ();
    foreach $line (@out) {
	if ($line =~ /^Call No\. (\d+) \| CallID [^\|]+ \| (\d+) \| \d+/) {
	    $callno = $1;
	    $dur = $2;
	    $got_src = 0;
	    $got_dst = 0;
	}
	elsif ($line =~ /^ACF\|([^\|]*)\|[^\|]*\|\d+/) {
	    if (!$got_src) {
		$got_src = 1;
		($src_ip,$src_port) = split(":",strip($1));
	    }
	    elsif (!$got_dst) {
		$got_dst = 1;
		($dst_ip,$dst_port) = split(":",strip($1));
	    }
	}
	elsif ($line =~ /^Dial\s+(\S+)/) {
	    $dial = $1;
	}
        elsif ($line =~ /^\#\s+([^\|]*)\|([^\|]*)\|([^\|]*)\|(.*)\s+<\d+>/) {
	    $src = $1;
	    $dst = $2;
	    $bw = $3;
	    $start = parsedate($4);

	    $bw = "" if $bw < 0;

	    ($src_h323,$src_e164) = parsealias($src);
	    ($dst_h323,$dst_e164) = parsealias($dst);
	    ($dial_h323,$dial_e164) = parsealias($dial);

	    $i = scalar(@ret);
	    $ret[$i]->{"callno"}    = $callno;
	    $ret[$i]->{"start"}     = $start;
	    $ret[$i]->{"bw"}        = $bw;
	    $ret[$i]->{"dur"}       = $dur;
	    $ret[$i]->{"src_ip"}    = $src_ip;
	    $ret[$i]->{"src_port"}  = $src_port;
	    $ret[$i]->{"dst_ip"}    = $dst_ip;
	    $ret[$i]->{"dst_port"}  = $dst_port;
	    $ret[$i]->{"src_h323"}  = $src_h323;
	    $ret[$i]->{"dst_h323"}  = $dst_h323;
	    $ret[$i]->{"src_e164"}  = $src_e164;
	    $ret[$i]->{"dst_e164"}  = $dst_e164;
	    $ret[$i]->{"dialed"}    = $dial_e164;
	    $callno = $bw = $dur = $src_ip = $src_port = $dst_ip = $dst_port = $src = $dst = $dial = "";
	}
    }
    return ("",@ret);
}

sub parsealias {
    my ($string) = @_;
    my (@h323,@e164,$part);

    @h323 = ();
    @e164 = ();
    foreach $part (split("=",strip($string))) {
	if ($part =~ /(.+):h323_ID/) {
	    push(@h323,$1);
	}
	elsif ($part =~ /(.+):dialedDigits/) {
	    push(@e164,$1);
	}
    }
    return(join(",",@h323),join(",",@e164));
}

sub parsedate {
    my ($time) = @_;
    my ($wday,$mday,$mon,$year,$hh,$mm,$ss,$sign,$tzhh,$tzmm,%months,$epoch,$zs);

    return "unknown" if $time =~ /unconnected/i;
    if ($time !~ /^(\w{3}), (\d{2}) (\w{3}) (\d{4}) (\d{2}):(\d{2}):(\d{2}) ([\+\-])(\d{2}):*(\d{2})$/) {
        print STDERR "unknown GnuGK timeformat: '$time'\n";
	exit 1;
    }
    $wday = $1;
    $mday = $2;
    $mon = $3;
    $year = $4;
    $hh = $5;
    $mm = $6;
    $ss = $7;
    $sign = $8;
    $tzhh = $9;
    $tzmm = $10;

    %months = ( "Jan" => 0, "Feb" => 1, "Mar" => 2, "Apr" => 3, "May" => 4, "Jun" => 5,
                "Jul" => 6, "Aug" => 7, "Sep" => 8, "Oct" => 9, "Nov" => 10, "Dec" => 11);
    $mon = $months{$mon};
    #
    # Wie berücksichtigt man die Zeitzone ?
    # So tun, als ob das Datum GMT wäre und
    # ohne Zeitzone in Epoch umrechnen.
    # Danach die Zeitzone in Sekunden umwandeln
    # und passend zur Epoch addieren bzw davon abziehen.
    # Das Ergebnis wieder zurückwandeln.
    #
    if ($tzhh != 0 || $tzmm != 0) {
        $epoch = timegm($ss,$mm,$hh,$mday,$mon,($year-1900));
        $zs = (($tzhh*60) + $tzmm) * 60;
        if ($sign eq "+") {
            $epoch -= $zs;
        }
        else {
            $epoch += $zs;
        }
        ($ss,$mm,$hh,$mday,$mon,$year) = localtime($epoch);
        $year += 1900;
    }

    $mon++;
    $hh   = "0$hh"   if length($hh)   < 2;
    $mm   = "0$mm"   if length($mm)   < 2;
    $ss   = "0$ss"   if length($ss)   < 2;
    $mday = "0$mday" if length($mday) < 2;
    $mon  = "0$mon"  if length($mon)  < 2;

    return "$mday.$mon.$year, $hh:$mm";
}

sub strip {
    my ($string) = @_;
    $string =~ s/^\s*//g;
    $string =~ s/\s*$//g;
    return $string;
}
