#! @PERLV_PATH@
##
## $Id: lg.cgi.in,v 1.54 2006/10/05 04:27:43 heas Exp $
##
## @PACKAGE@ @VERSION@
## Copyright (C) 1997-2006 by Terrapin Communications, Inc.
## All rights reserved.
##
## This software may be freely copied, modified and redistributed
## without fee for non-commerical purposes provided that this license
## remains intact and unmodified with any RANCID distribution.
##
## There is no warranty or other guarantee of fitness of this software.
## It is provided solely "as is". The author(s) disclaim(s) all
## responsibility and liability with respect to this software's usage
## or its effect upon hardware, computer systems, other software, or
## anything else.
##
## Except where noted otherwise, rancid was written by and is maintained by
## Henry Kilmer, John Heasley, Andrew Partan, Pete Whiting, and Austin Schutz.
##
#
# The original original lookingglass s/w was written by Ed Kern. It was
# a single script that used to be available at http://nitrous.digex.net/.
# Provided by permission and modified beyond recognition.
#
# Looking glass
# vars: query, router, args
BEGIN {
$me = $0;
$me =~ s/.*\/(\S+)$/$1/;
}
use CGI qw/:standard escapeHTML/;
use POSIX qw(strftime);
use Sys::Syslog;
use LockFile::Simple qw(lock trylock unlock);
my($BASEDIR) = "@prefix@";
my($SYSCONFDIR) = "@sysconfdir@";
my($LOCALSTATEDIR) = "@localstatedir@";
my($pingcmd) = "@LG_PING_CMD@";
my($query, $max_time_diff, $cache_dir, $cloginrc, @results);
my($type, $router_param, $remote_user, $arg, $router, $mfg);
my($LG_CACHE_DIR, $LG_CLOGINRC, $LG_IMAGE, $LG_LOG, $LG_ROUTERDB, $LG_AS_REG);
my($LG_BGP_RT, $LG_CACHE_TIME, $LG_SINGLE, $LG_STRIP);
if (!defined($ENV{HOME})) { $ENV{HOME} = "."; }
# note: the following functions are duplicated between lgform.cgi and lg.cgi
# to avoid the need for module inclusion headaches from within a httpd context.
# it is just easier to be self-contained.
# SO, ANY CHANGES HERE SHOULD BE REFLECTED IN THE OTHER .cgi.
# logging
sub dolog
{
my($level, $msg) = @_;
if (defined($LG_LOG) && $LG_LOG !~ /\//) {
openlog($me, "pid", $LG_LOG);
syslog($level, "%s", $msg);
closelog;
} else {
local(*LOG);
my($file);
if (defined($LG_LOG)) {
$file = $LG_LOG;
} else {
$file = "$cache_dir/lg.log";
}
# log date, hostname, query, addr
if (open(LOG, ">>$file") == 0) {
# stderr, if all else fails
printf(STDERR "[" . strftime("%a %b %e %H:%M:%S %Y", gmtime) .
"] could not open log file $file: $!\n");
printf(STDERR $msg);
} else {
printf(LOG $msg);
close(LOG);
}
}
return;
}
# read LG configuration file
sub readconf
{
my($conffile, $cmds);
local(*CONF);
if (defined($ENV{LG_CONF})) {
$conffile = $ENV{LG_CONF};
} elsif (-e "lg.conf") {
$conffile = "lg.conf";
} else {
$conffile = "$SYSCONFDIR/lg.conf";
}
if (! -f $conffile) {
return;
}
if (open(CONF, "< $conffile")) {
while (
$msg
$LG_INFO
EOF
print $q->end_html;
exit(0);
}
# convert an ipv4 address mask to prefix length
sub mask2len {
my($mask) = shift;
my($a, $b, $c, $d) = split('\.', $mask);
my($p, $len);
$p = ~ (($a << 24) + ($b << 16) + ($c << 8) + $d);
for ($len = 32; $p > 0; $len --) {
$p = $p >> 1;
}
return($len);
}
# end the page and exit.
sub end_page {
print <
END print $query->end_html; exit(0); } # start the page and log the transaction... sub start_page { my($mfg) = @_; my($cmd); my($timestr) = strftime("%a %b %e %H:%M:%S %Y", gmtime); dolog(LOG_INFO, sprintf("%s %s %s %s\n", $ENV{REMOTE_HOST}, $ENV{REMOTE_ADDR}, $ENV{REMOTE_USER}, "- - [$timestr] $type $router $arg")); print $query->header; if ($LG_STYLE) { print $query->start_html(-title =>"LookingGlass form", -style => {'src' => $LG_STYLE}); } else { print $query->start_html(-title =>"LookingGlass from"); } $timestr = strftime("%a %b %e %H:%M:%S %Y %Z", gmtime); # add the company image, LG_IMAGE print $LG_IMAGE; if ($mfg =~ /foundry/i) { $cmd = $foundryCmd{$type}; } elsif ($mfg =~ /juniper/i) { $cmd = $juniperCmd{$type}; } else { $cmd = $ciscoCmd{$type}; } print <
Query: $cmd
HEAD
if ($arg) { print "Argument(s): $arg\n"; }
print "
\n";
print <
END
return;
}
# Main()
# read the configuration file if it exists.
readconf();
# The script will now cache the results as simple files in the $cache_dir,
# named after the type of query (queries must, of course, be one word with
# no spaces). Modify $LG_CACHE_TIME to set the lifetime for cache entries.
# for most web servers, cache_dir must be writable by uid nobody
if (defined($LG_CACHE_DIR)) {
$cache_dir = $LG_CACHE_DIR;
} else {
$cache_dir = "./tmp";
}
# read routers table to get @rtrlist
readrouters();
# when to display cache? max time difference (in seconds)
if (defined($LG_CACHE_TIME)) {
$max_time_diff = $LG_CACHE_TIME;
} else {
$max_time_diff = "600" ;
}
# max seconds to wait for a 'router' lock to free up
$max_lock_wait = 30;
$lock_int = 5;
$max_lock_hold = 300;
# clogin setup
if (defined($LG_CLOGINRC)) {
$cloginrc = $LG_CLOGINRC;
} else {
$cloginrc = $ENV{HOME} . "/.cloginrc";
}
$query = new CGI;
# get form data and validate
$type = ($query->param('query'))[0];
$router_param = ($query->param('router'))[0];
$remote_user = $ENV{REMOTE_USER};
$arg = ($query->param('args'))[0];
# handle multiple args
$arg =~ s/["'`]//g; # these are BS in any arg for any query
@arg = split(' ', $arg);
# verify router, commands, arguments, etc.
($router, $mfg) = split(':', $router_param);
if (!defined($type) || !defined($router) || $router eq "") {
&Error("You must at least choose a Query and a router. Try buying " .
"a clue.\n");
}
if ($arg !~ /^[-A-Za-z0-9|_\/: \.^\$]*$/) {
&Error("Funny characters in argument; ignoring.\n");
}
if (length($arg) >= 50) {
&Error("Argument string too long; ignoring. \n");
}
if (! arraymember(\@rtrlist, $router)) {
my($timestr) = strftime("%a %b %e %H:%M:%S %Y", gmtime);
dolog(LOG_WARNING, sprintf("%s %s %s %s\n",
$ENV{REMOTE_HOST}, $ENV{REMOTE_ADDR}, $ENV{REMOTE_USER},
"- - [$timestr] lg.cgi: attempt to access $router\n"));
Error("access to $router not permitted");
}
# conversion of command "type" passed from lgform.cgi to the vendor's syntax.
if ($mfg =~ /cisco/i) {
%mfgCmd = (
# Debug Queries
log => "show logging",
# Interface Queries
framerelay => "show frame-relay pvc",
interface => "show interface",
intbrief => "show ip interface", # switch in {interface}
# Routing Queries
damp => "show ip bgp dampened-paths",
neighbor => "show ip bgp neighbor",
# Multicast Queries
mbgp => "show ip mbgp",
mbgpsum => "show ip mbgp summary",
mneighbor => "show ip bgp neighbor",
msdp => "show ip msdp summary",
msdpsa => "show ip msdp sa-cache",
msess => "show ip sdr",
mroute => "show ip mroute",
pim_interface => "show ip pim interface",
pim_neighbor => "show ip pim neighbor",
pim_rp => "show ip pim rp mapping",
# IPv6 Queries
#
#acl => "show access-list",
#aspath => "show ip as-path-access-list",
#communitylist => "show ip community-list",
ping => "ping",
prefix => "show ip bgp",
prefixlist => "show ip prefix-list",
regex => "show ip bgp regex",
route => "show ip route",
routemap => "show route-map",
rpf => "show ip rpf",
summary => "show ip bgp summary",
trace => "traceroute",
v6_bgp => "show bgp ipv6",
v6_interface => "show ipv6 interface",
v6_summary => "show bgp ipv6 summary"
);
} elsif ($mfg =~ /foundry/i) {
%mfgCmd = (
# Debug Queries
log => "show log",
ping => "ping",
trace => "traceroute",
# Interface Queries
#framerelay => "show frame-relay pvc", # no compatible command
interface => "show interface",
# Routing Queries
damp => "show ip bgp dampened-paths",
neighbor => "show ip bgp neighbor",
#regex => "show ip bgp aspath-regex",
route => "show ip route",
summary => "show ip bgp summary",
# Multicast Queries
#mbgp => "show ip mbgp",
#mbgpsum => "show bgp summary",
#mneighbor => "show ip bgp neighbor",
mroute => "show ip mroute",
msdp => "show ip msdp summary",
msdpsa => "show ip msdp sa-cache",
msess => "show ip sdr",
pim_interface => "show ip pim interface",
pim_neighbor => "show ip pim neighbor",
pim_rp => "show ip pim rp mapping",
rpf => "show ip rpf",
# IPv6 Queries
# v6_bgp => "show bgp ipv6",
# v6_interface => "show ipv6 interface",
# v6_summary => "show bgp ipv6 summary"
#
#acl => "show access-list",
#aspath => "show ip as-path-access-list",
#communitylist => "show ip community-list",
routemap => "show route-map",
prefix => "show ip bgp",
prefixlist => "show ip prefix-list"
);
} elsif ($mfg =~ /juniper/i) {
%mfgCmd = (
# Debug Queries
log => "show log messages",
ping => "ping rapid count 5",
trace => "traceroute",
# Interface Queries
framerelay => "show frame-relay pvc",
interface => "show interface",
#intbrief => "show ip interface", # switch in {interface}
# Routing Queries
damp => "show route damping suppressed terse table inet.0",
neighbor => "show bgp neighbor",
regex => "show route table inet.0 aspath-regex",
summary => "show bgp summary",
# Multicast Queries
mbgp => "show route table inet.2 terse",
mbgpsum => "show bgp summary",
mneighbor => "show bgp neighbor",
mroute => "show multicast route extensive",
msdp => "show msdp",
msdpsa => "show msdp source-active",
msess => "show multicast sessions",
pim_interface => "show pim interface",
pim_neighbor => "show pim neighbors",
pim_rp => "show pim rps",
pim_join => "show pim join",
rpf => "show multicast rpf",
# IPv6 Queries
v6_bgp => "show route table inet6.0",
v6_interface => "show interface",
v6_summary => "show bgp summary",
#
#acl => "show access-list",
#aspath => "show ip as-path-access-list",
#communitylist => "show ip community-list",
prefix => "show route table inet.0",
prefixlist => "show policy",
route => "show route table inet.0 terse",
routemap => "show policy"
);
}
# construct Display command from configuration
%cmdDisp=();
foreach $qtype (sort keys(%$queries)) {
next if (! scalar(%{$queries->{$qtype}}));
foreach $sub_type (sort keys(%{$queries->{$qtype}})) {
$cmdDisp{$sub_type} = $queries->{$qtype}->{$sub_type};
}
}
# make sure the command is not disabled
if (! defined($cmdDisp{$type})) {
&Error("Unknown command type: $type\n");
}
# not all cmds/queries are implemented for all platforms
if (! defined($mfgCmd{$type})) {
Error("$cmdDisp{$type} not implemented for $mfg or no suitable " .
"equivalent exists. sorry.\n");
}
$cmd = $mfgCmd{$type};
# handle each query/command type
if ($type eq "prefix" || $type eq "mbgp" || $type eq "route" ) {
if ($arg[0] !~ /^\d+\.\d+\.\d+\.\d+$/) {
&Error("The IP address \"$arg[0]\" is not valid and lacking an " .
"address would over-burden our router.\n");
} elsif (defined($arg[1]) && $arg[1] !~ /^\d+\.\d+\.\d+\.\d+$/) {
&Error("The IP netmask \"$arg[1]\" is not valid.\n");
}
if ($mfg =~ /juniper/i && defined($arg[1])) {
$arg = $arg[0] . "/" . mask2len($arg[1]);
}
} elsif ($type eq "v6_route" ){
# XXX: is this check of the address arg correct and pedantic?
if ($arg[0] !~ /[0-9a-fA-F:]+$/) {
&Error("The IPv6 address \"$arg[0]\" is not valid.\n");
}
} elsif ($type eq "framerelay") {
if ($mfg =~ /juniper/) {
&Error("Juniper does not have a show frame-relay pvc command. " .
"Use show interface.\n");
}
if ($arg[0] > 15 && $arg[0] < 1024) {
$arg = $arg[0];
} else {
undef($arg);
}
} elsif ($type eq "interface" || $type eq "v6_interface") {
# XXX: wtf is arg[1]?
# if ($arg[1] =~ /[-\/0-9:.]+/) {
# $arg = $arg[0] . " " . $arg[1];
# } else {
if ($mfg =~ /(cisco|foundry)/) {
if ($arg[0] !~ /^b[^ ]+[0-9]/i && $arg[0] =~ /^b/i) {
$type = "intbrief";
$arg = "brief";
} else {
$arg = $arg[0];
}
} elsif ($mfg =~ /juniper/) {
my($optind) = 0;
# arg 0 may be an intf name or a display option, but there can
# only be 2 args
$arg = "";
while ($optind <= $#arg && $optind < 2) {
$arg[$optind] =~ s/brief/terse/;
if ($arg[$optind] =~ /^([a-z0-9]{2}\-\d+\/\d+\/\d+(:\d+)?)/i) {
$arg .= " $1";
} elsif ($arg[$optind] =~ /^det/i) {
$arg .= " detail";
} elsif ($arg[$optind] =~ /^ter/i) {
$arg .= " terse";
} elsif ($arg[$optind] =~ /^ext/i) {
$arg .= " extensive";
}
$optind += 1;
}
}
} elsif ($type eq "log") {
if ($arg[0] =~ /^\s*\|?$/) {
shift(@arg);
}
$arg[0] =~ s/^\s*\|?//;
if ($arg[0] !~ /^\s*$/) {
if ($mfg =~ /cisco/i) {
$arg = " | include " . join(' ', @arg);
} elsif ($mfg =~ /juniper/i) {
$arg = " | match \\\"" . join(' ', @arg) . "\\\"";
} else {
undef($arg);
}
} else {
undef($arg);
}
} elsif ($type eq "ping" || $type eq "trace") {
if ($arg[0] !~ /^\d+\.\d+\.\d+\.\d+$/) {
if ($arg[0] !~ /^[A-Za-z0-9._-]+$/) {
&Error("That argument ($arg[0]) is not valid.\n");
}
}
$arg = $arg[0];
} elsif ($type eq "aspath" || $type eq "communitylist") {
if ($arg[0] !~ /^\d+$/ || ($arg[0] < 1 && $arg[0] > 199)) {
&Error("That argument ($arg[0]) is not valid.\n");
}
$arg = $arg[0];
} elsif ($type eq "acl") {
if ($arg[0] !~ /^\d+$/ || ($arg[0] < 100 && $arg[0] > 199) ||
($arg[0] < 1300 && $arg[0] > 2699)) {
&Error("That argument ($arg[0]) is not valid.\n");
}
$arg = $arg[0];
# don't show the jewels
# XXX: this error msg is useless, but show acl is un-implemented.
&Error($mfg) if ($arg == 98 || $arg == 99);
} elsif ($type eq "prefixlist" || $type eq "routemap") {
if ($arg[0] !~ /^[0-9A-Za-z][^\s\"]*$/) {
&Error("That argument ($arg[0]) is not valid.\n");
}
$arg = $arg[0];
} elsif ($type eq "regex") {
# bgp as-path regex
$arg = $arg[0];
if ($#arg >= 1) {
for ($n = 1; $n <= $#arg; $n++) { $arg .= " " . $arg[$n]; }
}
# remove leading/trailing whitespace
$arg =~ s/^\s*//; $arg =~ s/\s*$//;
if ($arg !~ /^[0-9_ ^.*+?[\])\(-]*\$?$/ || $arg =~ /^\s*$/) {
&Error("That argument ($arg[0]) is not valid.\n");
}
# pathetic excuses for lookups
if ($arg =~ /^[_.* ^]*(\*|1|701|1239|1280|1740|3561|5462|10303)+[_\$]*$/ ||
$arg =~ /^[_.* ^]*(1|701|1239|1280|1740|3561|5462|10303)+[_ .]*[\[*.]/) {
&Error("Get real. Such a query has potential to over-burden our " .
"router.\nLook that up on your own router.\n");
}
if ($mfg =~ /juniper/) {
$arg =~ s/_/ /g;
# pre-junos 4.4 does not allow anchors
if ($arg =~ /\^\$/) {
$arg =~ "()";
} else {
$arg =~ s/[\$^]/ /g;
}
$arg = "\\\"$arg\\\"";
}
# escape any ()s
$arg =~ s/([\(\)])/\\$1/g;
} elsif ($type eq "neighbor") {
if ($arg[0] !~ /^\d+\.\d+\.\d+\.\d+$/) {
if ($arg[0] !~ /([A-Za-z0-9-]*.)*[A-Za-z0-9-]*.(com|edu|net|org)/) {
&Error("That argument ($arg[0]) is not valid.\n");
}
}
$arg = $arg[0];
if (defined($arg[1]) && $arg[1] =~ /^(a|ro|f|re)/) {
if ($mfg =~ /juniper/) {
if ($arg[1] =~ /^a/) {
if (defined($LG_BGP_RT)) {
$cmd = "show route table inet.0 all advertising-protocol ".
"bgp";
}
} elsif ($arg[1] =~ /^f/) {
if (defined($LG_BGP_RT)) {
$cmd = "show route damping table inet.0 all ".
"receive-protocol bgp";
}
} elsif ($arg[1] =~ /^r/) {
if (defined($LG_BGP_RT)) {
$cmd = "show route table inet.0 all receive-protocol bgp";
}
}
} else {
if ($arg[1] =~ /^a/) {
if (defined($LG_BGP_RT)) { $arg .= " advertised-routes"; }
} elsif ($arg[1] =~ /^f/) {
$arg .= " flap-statistics";
} elsif ($arg[1] =~ /^ro/) {
if (defined($LG_BGP_RT)) { $arg .= " routes"; }
} elsif ($arg[1] =~ /^re/) {
if (defined($LG_BGP_RT)) { $arg .= " received-routes"; }
}
}
}
} elsif ($type eq "mneighbor") {
if ($arg[0] !~ /^\d+\.\d+\.\d+\.\d+$/) {
if ($arg[0] !~ /([A-Za-z0-9-]*.)*[A-Za-z0-9-]*.(com|edu|net|org)/) {
&Error("That argument ($arg[0]) is not valid.\n");
}
}
$arg = $arg[0];
if (defined($arg[1]) && $arg[1] =~ /^(a|ro|f|re)/) {
if ($mfg =~ /juniper/) {
if ($arg[1] =~ /^a/) {
$cmd .= " advertised-routes";
} elsif ($arg[1] =~ /^f/) {
$cmd .= " flap-statistics";
} elsif ($arg[1] =~ /^ro/) {
$cmd .= " routes";
} elsif ($arg[1] =~ /^re/) {
$cmd .= " received-routes";
}
} else {
if ($arg[1] =~ /^a/) {
$arg .= " advertised-routes";
} elsif ($arg[1] =~ /^f/) {
$arg .= " flap-statistics";
} elsif ($arg[1] =~ /^ro/) {
$arg .= " routes";
} elsif ($arg[1] =~ /^re/) {
$arg .= " received-routes";
}
}
}
} elsif ($type eq "damp" || $type eq "summary" || $type eq "mbgpsum") {
undef($arg);
}
# make stdout unbuffered, so result page streams.
$| = 1;
start_page();
# cache the following
if ($type eq "summary" || $type eq "mbgpsu" || $type eq "damp"
|| $type eq "log") {
if (!$arg) {
# cache requests with no addr/argument
local(*CACHE);
my($file) = "$cache_dir/$type" ;
$file =~ s/\s+/_/g;
$file .= "_$router";
if (-e $file) {
# see if cache exists
@stat = stat($file);
$ftime = $stat[9];
$dtime = time() - $stat[9];
# see if we are within cache time
if ($dtime <= $max_time_diff) {
if (open(CACHE, "<$file") == 0) {
dolog(LOG_ERR, "couldnt open cache file $file: $!\n");
} else {
print "From cache (number of seconds old (max " .
"$max_time_diff)): $dtime\n\n";
while (