1#!/usr/bin/perl
2# vim:ts=4
3# ifstatus.pl v1.0
4#
5# This is an Extension script for the routers.cgi program.  Install it
6# in your cgi-bin directory, and modify your MRTG .cfg files to call it:
7#
8# routers.cgi*Extension[targetname]: "Show current interface status" /cgi-bin/ifstatus.cgi camera2-sm.gif
9#
10# See the routers.cgi documentation for how to link this script in to your
11# MRTG .cfg files.  This does not require 'insecure' mode in most cases.
12#
13# Remember to change the first line and the default .conf file location below
14#
15# Steve Shipway Jan 2006
16# This script is covered by the terms of the GNU GPL.
17###########################################################################
18# Version 1.0: corrected bug, added number formatting
19###########################################################################
20
21use strict;
22use CGI;
23use Net::SNMP;
24
25my($VERSION) = "v1.0";
26
27# Variables
28my( $conffile ) = '/u01/etc/routers2.conf';
29
30my( $device, $community, $targetwindow, $target, $file, $backurl, $ifno )
31	= ( "","","graph","","","","");
32my( $routersurl, $msg );
33my( $q ) = new CGI;
34my( %config, %headeropts );
35my( $thishost ) = $q->url();
36my( $NT, $pathsep) = ( 0, '/' );
37my( %portstat ) = ();
38
39my($SHDESC ) = "1.3.6.1.2.1.2.2.1.2";
40my($DESC   ) = "1.3.6.1.2.1.31.1.1.1.18";
41my($SYSDESC) = "1.3.6.1.2.1.1.1.0";
42my($IFINDEX) = "1.3.6.1.2.1.2.2.1.1";
43my($IFDESCR) = "1.3.6.1.2.1.2.2.1.2";
44my($IFTYPE ) = "1.3.6.1.2.1.2.2.1.3";
45my($IFMTU  ) = "1.3.6.1.2.1.2.2.1.4";
46my($IFSPEED) = "1.3.6.1.2.1.2.2.1.5";
47my($IFADMINSTATUS) = "1.3.6.1.2.1.2.2.1.7";
48my($IFOPERSTATUS) = "1.3.6.1.2.1.2.2.1.8";
49my($IFINOCTETS) = "1.3.6.1.2.1.2.2.1.10";
50my($IFOUTOCTETS) = "1.3.6.1.2.1.2.2.1.16";
51my($IFINERRORS) = "1.3.6.1.2.1.2.2.1.14";
52my($IFOUTERRORS) = "1.3.6.1.2.1.2.2.1.20";
53my($IPIFINDEX) = "1.3.6.1.2.1.4.20.1.2";
54my($IPROUTEGW) = "1.3.6.1.2.1.4.21.1.7";
55my($IFDUPLEX) = "1.3.6.1.4.1.9.5.1.4.1.1.10";
56my($IFNAME) = "1.3.6.1.2.1.31.1.1.1.1";
57
58my(%DUPLEX) = ( 1=>"Force half duplex", 2=>"Force full duplex", 3=>"Disagreement with device", 4=>"Autonegotiate" );
59my(%ADMINSTATUS) = ( 1=>"Up", 2=>"Down", 3=>"Testing" );
60my(%OPERSTATUS) = ( 1=>"Up", 2=>"Down", 3=>"Testing" );
61
62$thishost =~ /http:\/\/([^\/]+)\//;
63$thishost = $1;
64
65sub fmt($$) {
66	my($n,$d) = @_;
67	my($s) = '';
68
69	if($n >= 1000000000 ) {
70		$n /= 1000000000;
71		$s = 'G';
72	} elsif($n >= 1000000 ) {
73		$n /= 1000000;
74		$s = 'M';
75	} elsif($n >= 1000 ) {
76		$n /= 1000;
77		$s = 'K';
78	}
79
80	return sprintf('%.'.$d.'f %s',$n,$s);
81}
82
83#######################################################################
84# readconf: pass it a list of section names
85sub readconf(@)
86{
87	my ($inlist, $i, @secs, $sec);
88
89	@secs = @_;
90	%config = ();
91
92	# set defaults
93	%config = ( 'routers.cgi-confpath' => ".",);
94
95	( open CFH, "<".$conffile ) || do {
96		errorpage( "Error: unable to open file $conffile");
97		exit 0;
98	};
99
100	$inlist=0;
101	$sec = "";
102	while( <CFH> ) {
103		/^\s*#/ && next;
104		/^\s*\[(\S*)\]/ && do {
105			$sec = $1; $inlist=0;
106			foreach $i ( @secs ) { if ( $i eq $1 ) { $inlist=1; last; } }
107			next;
108		};
109		if ( $inlist ) { /(\S+)\s*=\s*(\S.*?)\s*$/ and $config{"$sec-$1"}=$2; }
110	}
111	close CFH;
112
113	# Activate NT compatibility options.
114	# $^O is the OS name, NT usually produces 'MSWin32'.  By checking for 'Win'
115	# we should be able to cover most possibilities.
116	if ( (defined $config{'web-NT'} and $config{'web-NT'}=~/[1y]/i)
117		or $^O =~ /Win/ or $^O =~ /DOS/i  ) {
118		$pathsep = "\\";
119		$NT = 1;
120	}
121
122	# some path corrections: remove trailing path separators on f/s paths
123	foreach ( qw/dbpath confpath graphpath graphurl/ ) {
124		$config{"routers.cgi-$_"} =~ s/[\/\\]$//;
125	}
126	$config{"routers.cgi-iconurl"}.= '/'
127		if( $config{"routers.cgi-iconurl"} !~ /\/$/);
128
129}
130#######################################################################
131# read $file and get the interface details for $target
132sub read_file()
133{
134	my( $c, $i, $f );
135	if( $ifno != "" ) { # we already know it
136		$portstat{'Interface Number'} = $ifno;
137		return if($community) ;
138	}
139	$f = $config{"routers.cgi-confpath"}."$pathsep$file";
140	if(! -r $f or !$target) {
141		errorpage("Cannot read file $f");
142		return;
143	}
144	open CFG, "<$f" or return;
145	while( <CFG> ) {
146		if( /^\s*Target\[(\S+)\]\s*:\s*(\S+):(\S+)@([^:\s]+)/i ) {
147			next if($1 ne $target);
148			$c = $3; $i = $2;
149			$community = $c if($c);
150			if( !$ifno and $i =~ /\d+/ ) { $ifno = $i;
151				$portstat{'Interface Number'} = $i; last; }
152			if( $i =~ /^\/([\d\.]+)/ ) {
153				$portstat{'IP Address'} = $1; last; }
154			if( $i =~ /^#(\S+)/ ) {
155				$portstat{'Interface Name'} = $1; last; }
156			last if($ifno);
157		} elsif( /^\s*routers2?\.cgi\*Ifno\[(\S+)\]\s*:\s*(\d+)/i ) {
158			next if($1 ne $target);
159			$ifno = $2;
160			last;
161		}
162	}
163	close CFG;
164	return;
165}
166#######################################################################
167# return error message if there was an error
168# load up the $portstat hash, keyed on description
169sub snmpquery()
170{
171	my( $tick, $cross );
172	my( $snmp, $snmperr, $resp, $resp2 );
173	$tick = $q->img({ src=>($config{'routers.cgi-iconurl'}."tick-sm.gif"),
174		alt=>"Yes", border=>0});
175	$cross = $q->img({ src=>($config{'routers.cgi-iconurl'}."cross-sm.gif"),
176		alt=>"No", border=>0});
177
178	($snmp, $snmperr) = Net::SNMP->session(
179		-hostname=>$device, -community=>$community,
180		-timeout=>4 );
181	if($snmperr) { return $snmperr; }
182
183	$resp = $snmp->get_request(
184			"$IFDESCR.$ifno",
185			"$IFADMINSTATUS.$ifno",
186			"$IFOPERSTATUS.$ifno",
187			"$IFSPEED.$ifno",
188			"$IFMTU.$ifno",
189			"$IFTYPE.$ifno",
190			"$IFINOCTETS.$ifno",
191			"$IFOUTOCTETS.$ifno",
192			"$IFINERRORS.$ifno",
193			"$IFOUTERRORS.$ifno"
194	);
195	if(!$resp) { $snmp->close; return "Unable to read device details"; }
196	$portstat{"Description"} = $resp->{"$IFDESCR.$ifno"};
197 	$portstat{"Errors total IN"} = $resp->{"$IFINERRORS.$ifno"};
198 	$portstat{"Errors total OUT"} = $resp->{"$IFOUTERRORS.$ifno"};
199 	$portstat{"Traffic total IN"} = fmt($resp->{"$IFINOCTETS.$ifno"},2)."B";
200 	$portstat{"Traffic total OUT"} = fmt($resp->{"$IFOUTOCTETS.$ifno"},2)."B";
201 	$portstat{"Interface Speed"} = fmt($resp->{"$IFSPEED.$ifno"},0)."bps";
202 	$portstat{"MTU"} = $resp->{"$IFMTU.$ifno"}." bytes";
203	$portstat{"Admin status"} =
204		($resp->{"$IFADMINSTATUS.$ifno"}==1?$tick:$cross)
205		." (".$ADMINSTATUS{$resp->{"$IFADMINSTATUS.$ifno"}}.")";
206	$portstat{"Operational status"} =
207		($resp->{"$IFOPERSTATUS.$ifno"}==1?$tick:$cross)
208		." (".$OPERSTATUS{$resp->{"$IFOPERSTATUS.$ifno"}}.")";
209	if( !defined $portstat{"Interface Name"} ) {
210		$resp2 = $snmp->get_request( "$IFNAME.$ifno" );
211 		$portstat{"Interface Name"} = $DUPLEX{$resp2->{"$IFNAME.ifno"}}
212			if($resp2);
213	}
214	if( $portstat{"Interface Name"} =~ /(\d+).*\/(\d+)$/ ) {
215		$resp2 = $snmp->get_request( "$IFDUPLEX.$1.$2" );
216 		$portstat{"Duplex"} = $DUPLEX{$resp2->{"$IFDUPLEX.$1.$2"}} if($resp2);
217	}
218
219	$snmp->close;
220	return 0;
221}
222#######################################################################
223sub do_footer()
224{
225	print $q->hr."\n";
226	print $q->a({ href=>"javascript:location.reload(true);" },
227		$q->img({alt=>"Refresh", border=>0,
228		src=>$config{'routers.cgi-iconurl'}."refresh.gif" }))."\n";
229	print $q->hr.$q->small("$VERSION: Interface Status extension script for routers.cgi")."\n";
230	print $q->end_html();
231}
232#######################################################################
233# at this point, %portstat should hold the interface number that we query.
234sub mypage()
235{
236	my( $javascript ) = "function RefreshMenu()
237	{
238	var mwin; var uopts;
239	mwin = parent.menu;
240	uopts = 'T';
241	if( parent.menub ) { mwin = parent.menub; uopts = 't'; }
242	mwin.location = '".$routersurl."?if=__none&rtr="
243		.$q->escape($file)."&page=menu&xmtype=options&uopts='+uopts;
244	}";
245
246	print $q->start_html({-title=>"Current Interface Status",
247		-script=>$javascript, -onLoad=>"RefreshMenu()"});
248
249	print $q->h1("Current Interface Status");
250
251	print "<TABLE border=2 align=center>\n";
252	foreach ( sort keys %portstat ) {
253		print "<TR><TD>$_</TD><TD>".$portstat{$_}."</TD></TR>\n";
254	}
255
256	print "</TABLE>\n";
257
258	do_footer();
259}
260
261#######################################################################
262sub errorpage($)
263{
264	my( $javascript ) = "function RefreshMenu()
265	{
266	var mwin; var uopts;
267	mwin = parent.menu;
268	uopts = 'T';
269	if( parent.menub ) { mwin = parent.menub; uopts = 't'; }
270	mwin.location = '".$routersurl."?if=__none&rtr="
271		.$q->escape($file)."&page=menu&xmtype=options&uopts='+uopts;
272	}";
273
274	print $q->start_html({-title=>"Error",
275		-script=>$javascript, -onLoad=>"RefreshMenu()"});
276
277	print $q->h1("Unable to retrieve interface status");
278
279	print $q->p($_[0])."\n";
280	do_footer();
281
282}
283#######################################################################
284
285# Process parameters
286$device = $q->param('h') if(defined $q->param('h'));
287$file   = $q->param('fi') if(defined $q->param('fi'));
288$target = $q->param('ta') if(defined $q->param('ta'));
289$ifno = $q->param('ifno') if(defined $q->param('ifno'));
290$community = $q->param('c') if(defined $q->param('c'));
291$backurl = $q->param('b') if(defined $q->param('b'));
292$targetwindow = $q->param('t') if(defined $q->param('t'));
293$conffile = $q->param('conf') if(defined $q->param('conf'));
294$routersurl = $q->param('url') if(defined $q->param('url'));
295$routersurl = "http://$thishost/cgi-bin/routers2.cgi" if(!$routersurl);
296
297readconf('routers.cgi','web');
298
299# HTTP headers
300%headeropts = ( -expires=>"now" );
301$headeropts{target} = $targetwindow if($targetwindow);
302print $q->header(\%headeropts);
303
304read_file;
305
306if(!$target) {
307	errorpage("No target given");
308} elsif(!$community) {
309	errorpage("Unable to identify an SNMP community for the device.");
310} elsif($ifno == "") {
311	errorpage("Unable to identify a valid interface on the device.");
312} else {
313	if( $msg = snmpquery() ) {
314		errorpage("Unable to SNMP query the device.\n$msg");
315	} else {
316		mypage();
317	}
318}
319
320# End
321exit(0);
322