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