#! /usr/bin/perl -w
# John Hay -- John.Hay@icomtek.csir.co.za / jhay@FreeBSD.org

package ntptrace;
use 5.006_000;
use strict;
use lib "/usr/local/share/ntp/lib";
use NTP::Util qw(ntp_read_vars do_dns);

exit run(@ARGV) unless caller;

sub run {
    my $opts;
    if (!processOptions(\@_, $opts)) {
        usage(1);
    };

    my $dodns     = $opts->{numeric} ? 0 : 1;
    my $max_hosts = $opts->{'max-hosts'};
    my $host      = shift || $opts->{host};
    my $nb_host   = 0;

    for (;;) {
        $nb_host++;
        my $pe = $host;
        my $vars = [];

        my %info = get_info($host);
   
        last if not %info;
        
        if ( defined $info{warn} ) {
            my $hostname = get_hostname($host);
            print $hostname, " ", $info{warn}, "\n\n";
            return -1;
        } 

        my $dhost = $host;
        if ($dodns) {
            my $name = do_dns($host);
            $dhost = $name if defined $name;
        }

        printf "%s: stratum %d, offset %f, synch distance %f",
            $dhost, $info{stratum}, $info{offset}, $info{syncdistance};
        printf ", refid '%s'", $info{refid} if $info{stratum} == 1;
        print "\n";

        last if $info{stratum} == 0 || $info{stratum} == 1 || 
                $info{stratum} == 16;
        last if $info{refid} =~ /^127\.127\.\d{1,3}\.\d{1,3}$/;
        last if $nb_host == $max_hosts;

        # my $next_host = get_next_host($info{peer}, $host);
        # This script is not implemented for IPV6. Please refer to RFC 5905

        my $next_host = $info{refid};

        last if ( !defined($next_host) || $next_host eq '');
        last if $next_host  =~ /^127\.127\.\d{1,3}\.\d{1,3}$/;

        $host = $next_host;

    }
    return 0;
}

sub get_hostname() {
    my ($ip) = @_;

    my @nslookup = `nslookup $ip`;
    foreach (@nslookup) {   
       if ( $_ =~ /name\s=\s(.+)$/ ) {
           return $1;
       }
     }
     return;
}  

sub get_info {
    my ($host) = @_;
    my ($rootdelay, $rootdisp, $info) = (0, 0);

    $info = ntp_read_vars(0, [], $host);

    return if not defined $info;
    return %$info if defined $info->{warn}; 
    return if not exists $info->{stratum};

    $info->{offset} /= 1000;

    if ( defined($info->{rootdispersion}) ) {
        $info->{syncdistance} = ($info->{rootdispersion} + ($info->{rootdelay} / 2)) / 1000;
    }
    else {
        $info->{syncdistance} = ($info->{rootdisp} + ($info->{rootdelay} / 2)) / 1000;
    }
    return %$info;
}


sub get_next_host {
    my ($peer, $host) = @_;

    my $info = ntp_read_vars($peer, [qw(srcadr)], $host);
    return if not defined $info;
    return $info->{srcadr};
}

# EDIT THIS FILE WITH CAUTION  (ntptrace-opts)
#
# It has been AutoGen-ed  June 29, 2015 at 9e:bc:c6 PM by AutoGen 5.18.5
# From the definitions    ntptrace-opts.def
# and the template file   perlopt

use Getopt::Long qw(GetOptionsFromArray);
Getopt::Long::Configure(qw(no_auto_abbrev no_ignore_case_always));

my $usage;

sub usage {
    my ($ret) = @_;
    print STDERR $usage;
    exit $ret;
}

sub paged_usage {
    my ($ret) = @_;
    my $pager = $ENV{PAGER} || '(less || more)';

    open STDOUT, "| $pager" or die "Can't fork a pager: $!";
    print $usage;

    exit $ret;
}

sub processOptions {
    my $args = shift;

    my $opts = {
        'numeric' => '',
        'max-hosts' => '99',
        'host' => '127.0.0.1',
        'help' => '', 'more-help' => ''
    };
    my $argument = '[host]';
    my $ret = GetOptionsFromArray($args, $opts, (
        'numeric|n', 'max-hosts|m=i', 'host|r=s',
        'help|?', 'more-help'));

    $usage = <<'USAGE';
ntptrace - Trace peers of an NTP server - Ver. 4.2.8p3
USAGE: ntptrace [ -<flag> [<val>] | --<name>[{=| }<val>] ]... [host]

    -n, --numeric                Print IP addresses instead of hostnames
    -m, --max-hosts=num          Maximum number of peers to trace
    -r, --host=str               Single remote host
    -?, --help                   Display usage information and exit
        --more-help              Pass the extended usage text through a pager

Options are specified by doubled hyphens and their name or by a single
hyphen and the flag character.
USAGE

    usage(0)       if $opts->{'help'};
    paged_usage(0) if $opts->{'more-help'};
    $_[0] = $opts;
    return $ret;
}

END { close STDOUT };

1;
__END__
