#!/usr/bin/perl
####################################################
# npulse.cgi
# Network Monitoring Program
#
# Author: Steve Horsburgh  (npulse@horsburgh.com)
$Version = '0.51p2';
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# 
####################################################
$|=1;
require 5.004;
require "./preferences.pl";
require "$Libdir/util.pl";
require "$Libdir/filter.pl";
require $Cgilib;
#------------------------------------------------------------------#
#-- Main Program --------------------------------------------------#
#------------------------------------------------------------------#

$DoneHeader = 0;

&DEBUG("Starting npulse.cgi");
print &PrintHeader();
&ReadParse();

# Browser sniff

$IsIE    ||= ($ENV{'HTTP_USER_AGENT'} =~ /MSIE/) ? 1 : 0;
$IsNS    ||= (!$IsIE and ($ENV{'HTTP_USER_AGENT'} =~ /Mozilla/)) ? 1 : 0;
$IsMac   ||= ($ENV{'HTTP_USER_AGENT'} =~ /Mac/) ? 1 : 0;
$IsLinux ||= ($ENV{'HTTP_USER_AGENT'} =~ /linux/i) ? 1 : 0;

$IsIE = 0 if ($IsIE and $IsMac);
$IsNS = 0 if ($IsNS and $IsMac);

# Init some things

use locale;
foreach ('site','a','s','sort') {
  $in{$_} =~ s/[^\w\.\d-]//g if defined $in{$_};   # Trap bad/imposter characters
}
$HaveTelnet = (-f "$Libdir/telnet.pl")?1:0;
$in{'a'} = "display" unless $in{'a'};
if ($in{'a'} eq 'telnet') {
  &Error("Telnet support is disabled.") unless $HaveTelnet;
}

# Setup user info

$User = $ENV{'REMOTE_USER'} || &Error("Unable to determine user.");  
$AdminUser = ($User eq "admin") ? 1 : 0;

if ($AdminUser) {
  %allowedactions = (
                    display  => "$Libdir/gendisp.pl",
                    pref     => "$Libdir/prefs.pl",
                    setup    => "$Libdir/editor.pl",
                    recheck  => "$Libdir/gendisp.pl",
                    detail   => "$Libdir/detail.pl",
                    auto     => "$Libdir/prefs.pl",
                    filter   => "$Libdir/gendisp.pl",
                    telnet   => "$Libdir/telnet.pl",
                    sapi     => "$Libdir/sapi.pl",
                    script   => "$Libdir/prefs.pl",
                    status   => ""
                    );
  $allowedsubactions = 'traceroute quickport fullport history lookup discover save updateinfo help copyright on off cleareventlog usercmds recheckos runscript ';
  $UserDir = $Htmldir;

} else {
  %allowedactions = (
                    display  => "$Libdir/gendisp.pl",
                    detail   => "$Libdir/detail.pl",
                    filter   => "$Libdir/gendisp.pl"
                    );
  $allowedsubactions = 'traceroute quickport fullport history help copyright usercmds runscript ';
  $UserDir = $Htmldir.'/users/'.$User;
  unless (-d $UserDir) {
    mkdir($Htmldir.'/users',0750) unless (-d $Htmldir.'/users');
    mkdir($UserDir,0750);
  }
}
&GetKookie($UserDir);

# Now make sure we have valid commands

($ins = $in{'s'}) =~ s/_.*$/_/;
delete $in{'s'} unless ($in{'s'} && (index(lc($allowedsubactions),lc($ins)) >= 0));

# Load required modules and call the appropriate routine

if (exists $allowedactions{lc($in{'a'})}) {
  require $allowedactions{lc($in{'a'})} if $allowedactions{lc($in{'a'})};
  &{uc($in{'a'})}();
}

&WriteKookie($UserDir);
&DEBUG("Exiting");
exit;

#------------------------------------------------------------------#
#-- Main Routines -------------------------------------------------#
#------------------------------------------------------------------#

sub PREF {
    my($type) = $in{'s'} || "edit";
    &DEBUG("type = $type");
    &PREFS(lc($type)); #Does not return
}

sub SETUP {
    &DEBUG("in{s} = ",$in{'s'});
    &{uc($in{'s'})} if $in{'s'} =~ /discover|lookup/i;
    &EDITOR();  #Does not return
}

sub RECHECK {
    exit if $DEMO;
    &StartAutoCheck($Autorecheck." auto".$$.".pid -1");
    &DISPLAY();  #Does not return
}

sub DETAIL {
    &DEBUG("in{s} = ",$in{'s'});
    &ReadServices();
    my($result) = &{uc($in{'s'})} if $in{'s'} =~ /traceroute|quickport|fullport|history|updateinfo|usercmds|recheckos|runscript/i;
    &DisplayDETAIL($in{'site'}, $result);  #Does not return
}

sub SAPI {
    &DEBUG("in{site,port,script,timeout} = ",join(", ",$in{site},$in{port},$in{script},$in{timeout}));
    my ($ok, $result) = &sapi($in{site},$in{port},$in{script},$in{timeout});
    &Error($result) unless $ok;
    &DisplayDETAIL($in{'site'},$result);
}

sub SCRIPT {
    &ScriptPage() if ($in{'edit'} or $in{'save'} or $in{'delete'});
    $in{'section'} = 'SCRIPTS';
    &PREFS();
}

sub AUTO {
    &Error("Auto Check mode is not available in DEMO mode.") if $DEMO;
    my($enabled);

    &DEBUG("in{s} = ",$in{'s'});
    if ($in{'s'} =~ /on/i) {
      $enabled = "enabled";
      $Autointerval = abs($Autointerval);
    } elsif ($in{'s'} =~ /off/i) {
      $Autointerval = -abs($Autointerval);
      $enabled = "disabled";
    }

    foreach (keys %in) { delete $in{$_}; }
    $in{'a'} = "pref";
    $in{'Autointerval'} = $Autointerval;
    &PREFS("save",1);
    &DEBUG("Auto Check has been ",$enabled);
    $in{'f'} ||= "default_basic";
    &DISPLAY("Auto Check has been ".$enabled);
}

sub DISPLAY {
    my $extra = shift;
    my $htmlstats = "";
    my $haveconfig = -f $Configfile;
    my $autolock = -f $Vardir."/autorecheck.lock";
    my $firstcheck = '<small>(The first check might take a few minutes as it discovers your network and each device\'s operating system.)</small>';
    require "$Libdir/gendisp.pl";
    &DEBUG("starting");
    &ReadServices();
    
    &FILTER() if $in{'f'} eq 'filter_new_filter';  #Catch new filter request

    if ($in{'s'} eq 'cleareventlog') {
      &ClearEventLog() if $AdminUser;
      $in{'s'} = '';
    }

    if ($in{'f'}) {
        unless ($haveconfig) {
          print &Header($Myname,"",0);
          print <<EOE;
            <h1>nPulse Message</h1>
            <p>If you are viewing this page from nPulse then you have either not yet entered valid 
            <b>Setup</b> information</a> or <b>Autocheck</b> has not finished it's first scan or 
            (if you have disabled the automatic checking) you have not yet clicked on the manual 
            Recheck button.</p><p>For more information see the <a href="$Help_url">Help Page</a>.</p>
EOE
          print &Footer();
          exit;
        }
        print &Header($Myname,$extra,1).&CREATE().&Footer();

    } elsif ($in{'s'}) {
      print &DisplayHTMLfile((($in{'s'} =~ /help/i)      ? $Helpfile :
                          ($in{'s'} =~ /copyright/i) ? $Copyrightfile : ''),$extra);
      
    } else {
      print &Header($Myname,$extra,0);
      $htmlstats = &AdminHeaderInfo() if $haveconfig;
      if ($AdminUser) {
        local($/) = undef;
        if (open(W, "<$AdminWelcome")) {
          eval (<W>);
          close(W);
        } else {
          print '<table border=0 cellspacing=5 cellpadding=0><tr valign="top"><td width="100%">',
                '<p>Welcome to the nPulse web-based network monitoring program for Linux and Unix.</p>';
          print '<p>To setup nPulse, click on the <a href="'.$nPulseurl.'?a=setup">Setup</a> button above ',
                'and enter your network information or have nPulse discover your network automatically.</p>'
                unless $haveconfig;
          print '<p>To start automatic network monitoring click the large red button in the upper right ',
                'corner of the screen to the "on" position. '.$firstcheck.'</p>'
                if ($haveconfig and !(-f $Vardir."/autorecheck.pid"));
          print '<p>Since Automatic Monitoring has been disabled, click the <a href="'.$nPulseurl.'?a=recheck">Recheck</a> button above ',
                'to check your network status. '.$firstcheck.'</p>'
                if (($Autointerval == 0) and $haveconfig);
          print '<p>Since you have already set up nPulse, you can jump right into the ',
                '<a href="'.$Compactdisplay_url.'">Compact</a>, ',
                '<a href="'.$Listdisplay_url.'">List</a>, or ',
                '<a href="'.$Basicdisplay_url.'">Basic</a> monitoring page. '.$firstcheck.'</p>'
                if ($haveconfig and !$autolock and (-f $Currentfile));
          print '<p>The first network scan is in progress. When it is finished, you can go to the ',
                '<a href="'.$Compactdisplay_url.'">Compact</a>, <a href="'.$Listdisplay_url.'">List</a>, or <a href="'.$Basicdisplay_url.'">Basic</a> monitoring page.'.$firstcheck.'</p>'
                if ($haveconfig and $autolock);
          print '<p>For help, check out the <a href="'.$Help_url.'">Help Page</a>.</p>',
                '<p>'.$htmlstats.'</p></td>';
          print "<td>",&DisplayEventLog(),"</td></tr></table>\n";
        }
      } else {
        local($/) = undef;
        if (open(W, "<$UserWelcome")) {
          eval (<W>);
          close(W);
        } else {
          print <<EOF2;
          <table border=0 cellspacing=5 cellpadding=0>
           <tr valign="top">
            <td width="100%">
              <p>Welcome to the nPulse web-based network monitoring program for Linux and Unix. 
              If your Network or System Administrator has already setup nPulse, then jump right into the <a href="$Compactdisplay_url">Compact</a>,
              <a href="$Listdisplay_url">List</a>, or 
              <a href="$Basicdisplay_url">Basic</a> monitoring page. 
              For help, check out the <a href="$Help_url">Help Page</a>.</p>
              <p>$htmlstats</p>
            </td>
EOF2
          print "<td>",&DisplayEventLog(),"</td></tr></table>\n";
        }
      }
      print <<EOF3;
<hr><p>The most recent versions of <b>nPulse</b> are available at
<a href="http://freshmeat.net/projects/npulse/">http://freshmeat.net/projects/npulse/</a></p>
<p><small>All company logos and names are copyrighted or trademarked by their respective companies.</small></p>
EOF3
      print &Footer();
    }
    &WriteKookie();
    &DEBUG("Exiting");
    exit;
}

sub STATUS {
  my $servstat = &ServerCheckStatus();
  my $autostat = &AutoCheckStatus();
  
  my($ipmask,$portmask,$numhosts,$numports,@ports);
  $numhosts = $numports = 0;
  if (-f $Configfile) {
    foreach (&ReadConfigfile()) {
      ($ipmask, $portmask) = split / /;
      foreach (&GenerateSiteList($ipmask)) {
        $numhosts++;
        @ports = &GeneratePortList($portmask);
        $numports += $#ports+1;
      }
    }
  }

  if ($servstat) {
    my($auto,$nauto,$autocheck);
    if ($autostat) { $auto = "Stop Process";  $nauto = "off"; }
    else           { $auto = "Start Process"; $nauto = "on"; }

    print &Header($Myname,"nPulse Status",2),
          '<blockquote><span style="',$Fsn,'">',scalar(localtime(time)),'<p>',
          'The Web Server is <font color="#',
          ($servstat?'00cc00">Running':'cc0000">Stopped'),'</font><p>',
          'The Automatic Network Monitor is <font color="#',
          ($autostat?'00cc00">'.((-f $Vardir.'/autorecheck.lock')?
          'Currently scanning the network':'Sleeping'):'cc0000">Stopped'),'</font> [',
          '<a href="',$nPulseurl,'?a=auto&s=',$nauto,'">',$auto,'</a>]<p>',
          'nPulse is monitoring ',$numhosts,' hosts and a total of ',$numports,' ports</span>',
          '<form method=get action="',$nPulseurl,'"><input type=hidden name=a value="status">',
          '<input type=hidden name=pinghosts value=1"><table border=0><tr><td>Perform a PING sweep on</td>',
          '<td nowrap><input type=text name=ipmask size=18 style="',$Fss,'"> ',
          '<input type=submit style="',$Fss,'" value="Go"></td></tr>',
          '<tr><td></td><td nowrap><span style="',$Fxs,'">(leave blank to use current ip addresses)</span></td></tr></table></form>',
          ($in{'pinghosts'}?&PINGHOSTS($in{'ipmask'}):''),
          '<form method=get action="',$nPulseurl,'"><input type=hidden name=a value="status">',
          '<input type=hidden name=portsweep value=1"><table border=0><tr><td>Perform a PORT sweep on</td>',
          '<td nowrap><span style="',$Fss,'"><input type=text name=ipmask size=18 style="',$Fss,'"> Port:<input type=text name=port size=5 style="',$Fss,'"> ',
          '<input type=submit style="',$Fss,'" value="Go"></span></td></tr>',
          '<tr><td></td><td nowrap><span style="',$Fxs,'">(leave blank to use current ip addresses)</span></td></tr></table></form>',
          ($in{'portsweep'}?&PORTSWEEP($in{'ipmask'},$in{'port'}):''),
          '</blockquote>'.&Footer();
  } else {
    print scalar(localtime(time)),"\n",
          'The Web Server is ',($servstat?'Running':'Stopped'),"\n",
          'The Automatic Network Monitor is ',
          ($autostat?((-f $Vardir.'/autorecheck.lock')?
          'Currently scanning the network':'Sleeping'):'Stopped'),"\n",
          'nPulse is monitoring ',int($numhosts),' hosts and a total of ',int($numports),' ports>',"\n";
  }
  exit;
}

sub PINGHOSTS {
  my $ipmask = shift;
  $ipmask =~ s/\*$/1-254/;
  my ($portmask, $cmd);
  my (@cmds, @output);
  
  if ($ipmask) {
    push(@cmds, $Nmapcmd." ".$nmap_general_options." ".$nmap_extra_options." -v -oM - -sP '".$ipmask."' 2>/dev/null | grep \"Host: \"");
  } else {
    foreach (&ReadConfigfile()) { 
      ($ipmask, $portmask) = split / /;
      push(@cmds, $Nmapcmd." ".$nmap_general_options." ".$nmap_extra_options." -v -oM - -sP '".$ipmask."' 2>/dev/null | grep \"Host: \"");
    }
  }
  
  foreach $cmd (@cmds) {
    &DEBUG("Running cmd: $cmd");
    @out = `$cmd`;
    chomp @out;
    push(@output, @out);
  }

  my $html = '<span style="'.$Fsn.'">Ping Sweep Results</span><table border=1><tr>'.
             '<td bgcolor="#333399" align="center" style="'.$Fsn.'">IP Address</td>'.
             '<td bgcolor="#333399" align="center" style="'.$Fsn.'">Host Name</td>'.
             '<td bgcolor="#333399" align="center" style="'.$Fsn.'">Alias</td>'.
             '<td bgcolor="#333399" align="center" style="'.$Fsn.'">Status</td>'.
             '<td bgcolor="#333399" align="center" style="'.$Fss.'">Currently<br>Monitored?</td>'.
             '</tr>';
  my (%site,$host,@rest,$ip,$name,$alias,$status,$new);
  foreach (@output) {
    ($host, @rest) = split /\t/;
    $host =~ s/^Host: //;

    ($ip, $name) = split(/ /, $host);
    $name =~ s/\(|\)//g;
    unless ($ip) { $ip = $name; $name = ""; }

    %site   = &GetStoredInfo($ip);
    $alias  = $site{'Alias_8'} || '&nbsp;';
    $status = ($rest[0] =~ /^Status: Down/)?'<font color="#cc0000">Down</font>':'<font color="#00cc00">Up</font>';
    $new    = $site{'ip'}?'Yes':'<font color="#cc0000"><b>No</b></font>';
    $name ||= $site{'name'};

    $html .= '<tr><td align="right" style="'.$Fss.'">'.($site{'ip'}?'<a href="'.$nPulseurl.'?a=detail&site='.$ip.'">'.$ip.'</a>':$ip).'</td>'.
                 '<td align="right" style="'.$Fss.'">'.$name.'</td>'.
                 '<td style="'.$Fss.'">'.$alias.'</td>'.
                 '<td align="center" style="'.$Fss.'">'.$status.'</td>'.
                 '<td align="center" style="'.$Fss.'">'.$new.'</td></tr>';
  }
  $html .= '</table>';
  return $html;
}

sub PORTSWEEP {
  my($ipmask,$port) = @_;
  $port =~ s/ //gs;
  $ipmask =~ s/ //gs;
  $ipmask =~ s/\*$/1-254/;

  if (($port != int($port)) or ($port <= 0) or ($port >= 65535) or ($ipmask =~ /[^0-9\.-]/)) {
    return '<span style="'.$Fsn.'">Invalid Address ('.$ipmask.') or Port Value ('.$port.')</span>';
  }

  my ($portmask, $cmd);
  my (@cmds, @output, @ports);
  
  if ($ipmask) {
    push(@cmds, $Nmapcmd." ".$nmap_general_options." ".$nmap_extra_options." -v -oM - ".$NmapScan." -p ".$port." '".$ipmask."' 2>/dev/null | grep \"Host: \"");
  } else {
    foreach (&ReadConfigfile()) { 
      ($ipmask, $portmask) = split / /;
      push(@cmds, $Nmapcmd." ".$nmap_general_options." ".$nmap_extra_options." -v -oM - ".$NmapScan." -p ".$port." '".$ipmask."' 2>/dev/null | grep \"Host: \"");
    }
  }
  
  foreach $cmd (@cmds) {
    &DEBUG("Running cmd: $cmd");
    @out = `$cmd`;
    chomp @out;
    push(@output, @out);
  }

  my $html = '<span style="'.$Fsn.'">Port '.$port.($Services{$port}?' ('.$Services{$port}.')':'').' Sweep Results</span><table border=1><tr>'.
             '<td bgcolor="#333399" align="center" style="'.$Fsn.'">IP Address</td>'.
             '<td bgcolor="#333399" align="center" style="'.$Fsn.'">Host Name</td>'.
             '<td bgcolor="#333399" align="center" style="'.$Fsn.'">Alias</td>'.
             '<td bgcolor="#333399" align="center" style="'.$Fsn.'">Status</td>'.
             '<td bgcolor="#333399" align="center" style="'.$Fss.'">Currently<br>Monitored?</td>'.
             '</tr>';
  my (%site,$host,@rest,$ip,$name,$alias,$status,$new);
  my $found = 0;
  foreach (@output) {
    ($host, @rest) = split /\t/;
    $host =~ s/^Host: //;

    ($ip, $name) = split(/ /, $host);
    $name =~ s/\(|\)//g;
    unless ($ip) { $ip = $name; $name = ""; }

    %site   = &ReadCurrentFile($ip);
    next unless $site{'ip'};
    $found = 1;
    $alias  = $site{'Alias_8'} || '&nbsp;';
    $status = ($rest[0] =~ /Ports: $port\/open/)?'<font color="#00cc00">Open</font>':'<font color="#cc0000">Closed</font>';

    @ports = split(/\s*,\s*/,$site{'portlist'});
    if (grep(/^$port$/,@ports)) {
      $new = 'Yes';
    } else {
      $new = '<font color="#cc0000"><b>No</b></font>';
    }

    $name ||= $site{'name'};

    $html .= '<tr><td align="right" style="'.$Fss.'"><a href="'.$nPulseurl.'?a=detail&site='.$ip.'">'.$ip.'</a></td>'.
                 '<td align="right" style="'.$Fss.'">'.$name.'</td>'.
                 '<td style="'.$Fss.'">'.$alias.'</td>'.
                 '<td align="center" style="'.$Fss.'">'.$status.'</td>'.
                 '<td align="center" style="'.$Fss.'">'.$new.'</td></tr>';
  }
  $html = '<span style="'.$Fsn.'"><b>No hosts ('.$ipmask.') responded with port '.$port.' open.</b></span>' unless $found;
  $html .= '</table>' if $found;
  return $html;
}
