Merge from vendor branch LIBPCAP:
[dragonfly.git] / contrib / ntp / scripts / ntpsweep.in
1 #! @PATH_PERL@ -w
2 #
3 # $Id: ntpsweep.in,v 1.1 2000/02/10 08:13:40 stenn Exp $
4 #
5 # DISCLAIMER
6
7 # Copyright (C) 1999,2000 Hans Lambermont and Origin B.V.
8
9 # Permission to use, copy, modify and distribute this software and its
10 # documentation for any purpose and without fee is hereby granted,
11 # provided that the above copyright notice appears in all copies and
12 # that both the copyright notice and this permission notice appear in
13 # supporting documentation. This software is supported as is and without
14 # any express or implied warranties, including, without limitation, the
15 # implied warranties of merchantability and fitness for a particular
16 # purpose. The name Origin B.V. must not be used to endorse or promote
17 # products derived from this software without prior written permission.
18 #
19 # Hans Lambermont <Hans.Lambermont@nl.origin-it.com>/<H.Lambermont@chello.nl>
20 # 14 Jan 2000
21
22 require 5.0;            # But actually tested on 5.004 ;)
23 use Getopt::Long;       # GetOptions()
24 use strict;
25
26 my $version = 1.3;
27 (my $program = $0) =~ s%.*/(.+?)(.pl)?$%$1%;
28
29 # Hardcoded paths/program names
30 my $ntpdate = "ntpdate";
31 my $ntpq = "ntpq";
32
33 # no STDOUT buffering
34 $| = 1;
35
36 my ($help, $single_host, $showpeers, $maxlevel, $strip, $askversion);
37 my $res = GetOptions("help!"      => \$help,
38                      "host=s"     => \$single_host,
39                      "peers!"     => \$showpeers,
40                      "maxlevel=s" => \$maxlevel,
41                      "strip=s"    => \$strip,
42                      "version!"   => \$askversion);
43
44 if ($askversion) {
45     print("$version\n");
46     exit 0;
47 }
48
49 if ($help || ((@ARGV != 1) && !$single_host)) {
50     warn <<EOF;
51 This is $program, version $version
52 Copyright (C) 1999,2000 Hans Lambermont and Origin B.V.  Disclaimer inside.
53
54 Usage:
55   $program [--help|--peers|--strip <string>|--maxlevel <level>|--version] \\
56     <file>|[--host <hostname>]
57
58 Description:
59   $program prints per host given in <file> the NTP stratum level, the
60   clock offset in seconds, the daemon version, the operating system and
61   the processor. Optionally recursing through all peers.
62
63 Options:
64 --help
65     Print this short help text and exit.
66 --version
67     Print version ($version) and exit.
68 <file>
69     Specify hosts file. File format is one hostname or ip number per line.
70     Lines beginning with # are considered as comment.
71 --host <hostname>
72     Speficy a single host, bypassing the need for a hosts file.
73 --peers
74     Recursively list all peers a host synchronizes to.
75     An '= ' before a peer means a loop. Recursion stops here.
76 --maxlevel <level>
77     Traverse peers up to this level (4 is a reasonable number).
78 --strip <string>
79     Strip <string> from hostnames.
80
81 Examples:
82     $program myhosts.txt --strip .foo.com
83     $program --host some.host --peers --maxlevel 4
84 EOF
85     exit 1;
86 }
87
88 my $hostsfile = shift;
89 my (@hosts, @known_hosts);
90 my (%known_host_info, %known_host_peers);
91
92 sub read_hosts()
93 {
94     local *HOSTS;
95     open (HOSTS, $hostsfile) ||
96         die "$program: FATAL: unable to read $hostsfile: $!\n";
97     while (<HOSTS>) {
98         next if /^\s*(#|$)/; # comment/empty
99         chomp;
100         push(@hosts, $_);
101     }
102     close(HOSTS);
103 }
104
105 # translate IP to hostname if possible
106 sub ip2name {
107     my($ip) = @_;
108     my($addr, $name, $aliases, $addrtype, $length, @addrs);
109     $addr = pack('C4', split(/\./, $ip));
110     ($name, $aliases, $addrtype, $length, @addrs) = gethostbyaddr($addr, 2);
111     if ($name) {
112         # return lower case name
113         return("\L$name");
114     } else {
115         return($ip);
116     }
117 }
118
119 # item_in_list($item, @list): returns 1 if $item is in @list, 0 if not
120 sub item_in_list {
121     my($item, @list) = @_;
122     my($i);
123     foreach $i (@list) {
124         return 1 if ($item eq $i);
125     }
126     return 0;
127 }
128
129 sub scan_host($;$;$) {
130     my($host, $level, @trace) = @_;
131     my $stratum = 0;
132     my $offset = 0;
133     my $daemonversion = "";
134     my $system = "";
135     my $processor = "";
136     my @peers;
137     my $known_host = 0;
138
139     if (&item_in_list($host, @known_hosts)) {
140         $known_host = 1;
141     } else {
142         # ntpdate part
143         open(NTPDATE, "$ntpdate -bd $host 2>/dev/null |") ||
144         die "Cannot open ntpdate pipe: $!\n";
145         while (<NTPDATE>) {
146             /^stratum\s+(\d+).*$/ && do {
147                 $stratum = $1;
148             };
149             /^offset\s+([0-9.-]+)$/ && do {
150                 $offset = $1;
151             };
152         }
153         close(NTPDATE);
154     
155         # got answers ? If so, go on.
156         if ($stratum) {
157             # ntpq part
158             my $ntpqparams = "-c 'rv 0 processor,system,daemon_version'";
159             open(NTPQ, "$ntpq $ntpqparams $host 2>/dev/null |") ||
160                 die "Cannot open ntpq pipe: $!\n";
161             while (<NTPQ>) {
162                 /daemon_version="(.*)"/ && do {
163                     $daemonversion = $1;
164                 };
165                 /system="([^"]*)"/ && do {
166                     $system = $1;
167                 };
168                 /processor="([^"]*)"/ && do {
169                     $processor = $1;
170                 };
171             }
172             close(NTPQ);
173             
174             # Shorten daemon_version string.
175             $daemonversion =~ s/(;|Mon|Tue|Wed|Thu|Fri|Sat|Sun).*$//;
176             $daemonversion =~ s/version=//;
177             $daemonversion =~ s/(x|)ntpd //;
178             $daemonversion =~ s/(\(|\))//g;
179             $daemonversion =~ s/beta/b/;
180             $daemonversion =~ s/multicast/mc/;
181         
182             # Shorten system string
183             $system =~ s/UNIX\///;
184             $system =~ s/RELEASE/r/;
185             $system =~ s/CURRENT/c/;
186
187             # Shorten processor string
188             $processor =~ s/unknown//;
189         }
190     
191         # got answers ? If so, go on.
192         if ($daemonversion) {
193             # ntpq again, find out the peers this time
194             if ($showpeers) {
195                 my $ntpqparams = "-pn";
196                 open(NTPQ, "$ntpq $ntpqparams $host 2>/dev/null |") ||
197                     die "Cannot open ntpq pipe: $!\n";
198                 while (<NTPQ>) {
199                     /^No association ID's returned$/ && do {
200                         last;
201                     };
202                     /^     remote/ && do {
203                         next;
204                     };
205                     /^==/ && do {
206                         next;
207                     };
208                     /^( |x|\.|-|\+|#|\*|o)([^ ]+)/ && do {
209                         push(@peers, ip2name($2));
210                         next;
211                     };
212                     print "ERROR: $_";
213                 }
214                 close(NTPQ);
215             }
216         }
217     
218         # Add scanned host to known_hosts array
219         push(@known_hosts, $host);
220         if ($stratum) {
221             $known_host_info{$host} = sprintf("%2d %9.3f %-11s %-12s %s",
222                 $stratum, $offset, substr($daemonversion,0,11),
223                 substr($system,0,12), substr($processor,0,9));
224         } else {
225             # Stratum level 0 is consider invalid
226             $known_host_info{$host} = sprintf(" ?");
227         }
228         $known_host_peers{$host} = [@peers];
229     }
230
231     if ($stratum || $known_host) { # Valid or known host
232         my $printhost = ' ' x $level . $host;
233         # Shorten host string
234         if ($strip) {
235             $printhost =~ s/$strip//;
236         }
237         # append number of peers in brackets if requested and valid
238         if ($showpeers && ($known_host_info{$host} ne " ?")) {
239             $printhost .= " (" . @{$known_host_peers{$host}} . ")";
240         }
241         # Finally print complete host line
242         printf("%-32s %s\n",
243             substr($printhost,0,32), $known_host_info{$host});
244         if ($showpeers && (eval($maxlevel ? $level < $maxlevel : 1))) {
245             my $peer;
246             push(@trace, $host);
247             # Loop through peers
248             foreach $peer (@{$known_host_peers{$host}}) {
249                 if (&item_in_list($peer, @trace)) {
250                     # we've detected a loop !
251                     $printhost = ' ' x ($level + 1) . "= " . $peer;
252                     # Shorten host string
253                     if ($strip) {
254                         $printhost =~ s/$strip//;
255                     }
256                     printf("%-32s %s\n",
257                         substr($printhost,0,32));
258                 } else {
259                     if (substr($peer,0,3) ne "127") {
260                         &scan_host($peer, $level + 1, @trace);
261                     }
262                 }
263             }
264         }
265     } else { # We did not get answers from this host
266         my $printhost = ' ' x $level . $host;
267         # Shorten host string
268         if ($strip) {
269             $printhost =~ s/$strip//;
270         }
271         printf("%-32s  ?\n", substr($printhost,0,32));
272     }
273 }
274
275 sub scan_hosts()
276 {
277     my $host;
278     for $host (@hosts) {
279         my @trace;
280         push(@trace, $host);
281         scan_host($host, 0, @trace);
282     }
283 }
284
285 # Main program
286
287 if ($single_host) {
288     push(@hosts, $single_host);
289 } else {
290     &read_hosts($hostsfile);
291 }
292
293 # Print header
294 print <<EOF;
295 Host                             st offset(s) version     system       processor
296 --------------------------------+--+---------+-----------+------------+---------
297 EOF
298
299 &scan_hosts();
300
301 exit 0;