#!/usr/local/bin/perl -wT
#!/usr/bin/perl -wT
#inserted /usr/local/bin/perl at top following John Bartelt's suggestion 10/3/2023w
#The following code is placed at the top to ensure we are able to use perl -d
##and stop things before they call other things.
my $debug; #For cronjobs use -1, for normal execution from command line use 0, 
           #for debugging information use > 0, max value = 3
if (-t STDOUT) {$debug=0;}
my $errLog="";
$ENV{PATH} = '/usr/local/bin:/bin:/usr/bin';#Untaint the path
use strict;
use warnings;
#################Testing################################
#setenv REQUEST_METHOD GET
#setenv QUERY_STRING 'proxy=1&in_form=1&begin_hour=00&begin_min=00&begin_sec=00&begin_day=10&begin_month=06&begin_year=2018&begin_offset=&begin_point=y&end_hour=23&end_min=59&end_sec=00&end_day=11&end_month=06&end_year=2018&end_offset=&end_point=y'
#echo $QUERY_STRING
#perl -dT bin/ping_data.pl
#################################################
#$|=1;
print "Status: 200 OK\nContent-type: text/html\n\n";#print header so get any errors etc
#my $version="4.5 7/27/2018 Les Cottrell";#Add css_sanitize
#my $version="4.51 10/30/2018 Les Cottrell";#Add $filename output to required permissions
#my $version="4.52 3/4/2019 Les Cottrell";#Get hostname when hostfqdn fails, add nslookup, add perl version.
#my $version="4.53 11/20/2019 Les Cottrell";#Removed need for cgi-wrap.
#my $version="4.54 12/27/2019 Les Cottrell"; #added test for wgetr
my $version="4.6 3/3/2020 Les Cottrell"; #Allow caps in hostname.
#Get the local host name
use Net::Domain qw(hostname hostfqdn hostdomain);
my $hostname = hostfqdn();
my $ipaddr="Unknown IP address";
my $warn="";
unless($hostname=~/^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$/) {#Name
#unless(($hostname=~/(([a-z0-9]+|([a-z0-9]+[-]+[a-z0-9]+))[.])+/)){#Name
  $warn="Invalid hostname='$hostname' returned by hostfqdn()."; 
  my @temp=`hostname`; chomp($temp[0]); $hostname=$temp[0];
  $warn.=" Linux `hostname` returns $temp[0]";
}
#else {
  use Socket;
  my $gethostbyname=gethostbyname($hostname || 'localhost');
  if (!defined($gethostbyname)) {
    my @ans=`hostname -I`;
    $ans[0]=~s/\s+//g;
    $ipaddr=$ans[0];
  }
  else {
    $ipaddr=inet_ntoa(scalar(gethostbyname($hostname||'localhost')));
  }
#}
####################################
my $user=scalar(getpwuid($<));
my $tmpBaseDir = "/tmp";
my $newBeaconTmpFile = "$tmpBaseDir/pinger2_newBeaconList.txt";
############# Authentication Code ######################
# By Fahad Satti (2/19/2012)
# This following part of (commented)code can be used as a method of
# authentication for remote users(preferably administrators). The idea
# behind this is to provide only authentic users access to the creation
# of $newBeaconTmpFile and for output of server data. It consists of two
# subroutines and one small form.
#sub getRemotePassword{
##a safe server to fetch password from.
#
#  my $GenPass="R1AC";
#  
#}
#
#sub authenticate{
#
#  my $localPassword=shift;
# 
#  if(!defined($localPassword) || $localPassword eq ""){
#    return "false";
#  }
#  #get a remote password from a random password generated by a public page
#  my $remotePassword=&getRemotePassword();
#  if(!defined($localPassword) || $localPassword eq ""){
#    return 'false';
#  }
#  if(!defined($remotePassword) || $remotePassword eq ""){
#    return 'false';
#  }
#
#  if($remotePassword -ne $localPassword){
#    return false;
#  }
#return true;
#
#}
############ End AUthentication Code ##################
sub TestFiles{
  my $filename = shift;
  my $writeableCheck = shift;
  if(!defined($filename) || $filename eq "" ){
    return "false";
  }
  if($filename =~/^([-\w.\/]+)$/)  {#untaint
    $filename=$1;
  }
  else {
    $errLog ="Invalid filename = $filename<br/>";
    return "false";
  }
  if(!(-e $filename)){
    $errLog .= "file: $filename, does not exist.<br/>";
    return "false";
  }
  if(-z $filename){
    $errLog .= "file: $filename, has a size of 0.<br/>";
    #return "false";
  }
  #my $filePermissionCmd = "ls -alh $filename";
  if(!(-r $filename)){
    $errLog .= "file: $filename, is not readable<br/>";#.`$filePermissionCmd`."<br/>";
    return "false";
  }
  if(defined($writeableCheck) && $writeableCheck!=0 && !(-w $filename)){
    $errLog .= "file: $filename, is not writeable<br/>";#.`$filePermissionCmd`."<br/>";
    return "false";
  }
  my %filemodes;
  $filemodes{'cgi-lib.pl'}            = 493;
  $filemodes{'data'}                  = 493;
  $filemodes{'beacons.txt'}           = 420;
  $filemodes{'pinger.xml'}            = 420;
  $filemodes{'offsite.nodes'}         = 420;
  $filemodes{'pingerCronStat.stderr'} = 420;
  $filemodes{'pingerCronStat.stdout'} = 420;
  $filemodes{'lynx'}                  = 493;
  $filemodes{'wget'}                  = 493;
  my $mode=(stat($filename)) [2] & 07777;
  #(my $fn = $filename) =~ s'^.*/'';
  $errLog .= "file: $filename found on $hostname\n<br/>";
  $errLog .= "<pre>".`ls -lt $filename`."</pre>\n";
  foreach my $key (keys %filemodes) {
    if($filename =~ /$key/) {
      my $octmode=sprintf("%lo", $mode & 07777);
      unless ($mode eq $filemodes{$key}) { 
        my $octfilemode=sprintf("%lo", $filemodes{$key} & 07777);
        $errLog .= "<b>Mode: $mode/0$octmode, "
                .  "should be $filemodes{$key}/0$octfilemode!</b>\n<br/>";
      } 
      else {
        $errLog .= "Mode: ". ((stat($filename))[2] & 07777) 
                . "/0$octmode are required permissions for $filename\n<br/>";
      }
    }
  }
  $errLog .= "Bytes: ". (stat($filename))[7] . "\n<br/>";
  $errLog .= "Modified Time: ". localtime((stat($filename))[9]) . "\n<br/>";
  #Find the free space
  my @cmds= ("df -h $filename", "ls -l $filename");
  my $return='true';
  foreach my $cmd (@cmds) {
    $cmd=untaint($cmd);
    if($cmd=~/^Error:/) {
      $errLog .= $cmd;
      $return='false';
      next;
    }
    else {
      my @result  = `$cmd`;
      $errLog .= "$cmd\n<pre>";
      foreach my $line (@result) {
        $line=~ s/\s+$//;
        $errLog .= "$line\n";
      }
    }  
    $errLog .= "</pre>\n";
  }
  return $return;
} # End sub TestFiles
##############################################################################
## Untaint will check for a vaild Unix file name and untaint it.
## Example:
##   $fn = untaint($fn)
##----------------------------------------------------------------------------
sub untaint {
  my $file = $_[0];
  unless ($file =~ /(^[\:\w\s+\.\-\+\d\/]*$)/) { #Taint check
    return "Error: $0 encountered tainted file name $file.\n";
  }
  return $1;
}
#####################Main program###################################
#   TAILORING SECTION for NEW Hosts, change these to the right locations
#See http://www-iepm.slac.stanford.edu/pinger/tools/install2.html.
#If you use the standard PingER2 installation then nothing should need changing.
my $error = "";
my $cgi_lib = "";#If none of our guesses below work then add the location for 
	 #for cgi-lib.pl for your host
if(&TestFiles($cgi_lib,0)=~/^true/i){
require $cgi_lib;
}
elsif(&TestFiles("/afs/slac/g/www/cgi-lib/cgi-lib.pl",0)=~/^true/i){
require "/afs/slac/g/www/cgi-lib/cgi-lib.pl";
}
elsif(&TestFiles("/usr/local/cgi-bin/cgi-lib.pl",0)=~/^true/i){
require "/usr/local/cgi-bin/cgi-lib.pl";
}
elsif(&TestFiles("/usr/lib/cgi-bin/cgi-lib.pl",0)=~/^true/i) {
require "/usr/lib/cgi-bin/cgi-lib.pl";
}
elsif(&TestFiles("/usr/bin/cgi-bin/cgi-lib.pl",0)=~/^true/i) {
require "/usr/bin/cgi-bin/cgi-lib.pl";
}
elsif(&TestFiles("/var/www/cgi-bin/cgi-lib.pl",0)=~/^true/i) {
require "/var/www/cgi-bin/cgi-lib.pl";
}
elsif(&TestFiles("/usr/lib/cgi-bin/cgi-lib.pl",0)=~/^true/i) {
require "/usr/lib/cgi-bin/cgi-lib.pl";
}
elsif(&TestFiles("/users/u2/matthews/public_html/cgi-lib.pl",0)=~/^true/i) {
require "/users/u2/matthews/public_html/cgi-lib.pl";
}
else{
print "<div style='font-weight: bolder; color:red;'>"
      . " Error: cgi-lib.pl not found. Please provide a valid path for cgi-lib."
      . "</div><div>$errLog</div>\n";
  exit 1;
}
#$stats_dir is the directory where the current raw data measured by pinger is saved
#N.B. $stats_dir may get changed later if this is a proxy.
#     This is done after reading the QUERY_STRING via ReadParse
my $stats_dir = "";
if(&TestFiles($stats_dir)=~/^true/i){
}
if(&TestFiles("/afs/slac/package/pinger/pinger2/share/pinger")=~/^true/i){
  $stats_dir = "/afs/slac/package/pinger/pinger2/share/pinger";
}
elsif(&TestFiles("/usr/local/share/pinger/data")=~/^true/i){
  $stats_dir = "/usr/local/share/pinger/data";
}
elsif(&TestFiles("/nfs/slac/g/net/pinger/pinger2/data")=~/^true/i){
  $stats_dir = "/nfs/slac/g/net/pinger/pinger2/data";
}
elsif(&TestFiles("/nfs/slac/g/net/pinger/pinger_mon_data")=~/^true/i){
  $stats_dir = "/nfs/slac/g/net/pinger/pinger_mon_data";
}
elsif(&TestFiles("/home/pingman/share/pinger/data")=~/^true/i) {
  $stats_dir = "/home/pingman/share/pinger/data";
}
#elsif(&TestFiles("/nfs/slac/g/net/pinger/pinger2/data")=~/^true/i){
#  $stats_dir = "/nfs/slac/g/net/pinger/pinger2/data";
#}
else{
  $error = "<div style='font-weight: bolder; color:red;'>"
  . "Error: PingER data directory was not found. Please provide a valid path for stats_dir"
  . "</div><div>$errLog</div>";
  #exit 2;
}
#sites_fn is the file, path for beacons.txt file
my $sites_fn = "";
if(&TestFiles($sites_fn)=~/^true/i){
}
elsif(&TestFiles("/usr/local/share/pinger/beacons.txt")=~/^true/i){
  $sites_fn = "/usr/local/share/pinger/beacons.txt";
}
elsif(&TestFiles("/afs/slac/www/comp/net/mon/offsite.nodes")=~/^true/i){
  $sites_fn = "/afs/slac/www/comp/net/mon/offsite.nodes";
}
else{
  $error = "<div style='font-weight: bolder; color:red;'>"
  ."Error: PingER data directory was not found. Please provide a valid path for sites_fn"
  . "</div><div>$errLog</div>";
  #exit 3;
}
# pinger_xml_file is the current path for pinger.xml file for pinger2 
# and offsite.nodes file for pinger1
my $pinger_xml_file  = "";
if(&TestFiles($pinger_xml_file)=~/^true/i){
}
elsif(&TestFiles("/usr/local/share/pinger/pinger.xml")=~/^true/i){
  $pinger_xml_file  = "/usr/local/share/pinger/pinger.xml";
}
elsif(&TestFiles("/afs/slac/package/pinger/pinger2/share/pinger/pinger.xml")=~/^true/i){
  $pinger_xml_file  = "/afs/slac/package/pinger/pinger2/share/pinger/pinger.xml";
}
else{
  $error = "<div style='font-weight: bolder; color:red;'>"
  . "Error: PingER data directory was not found. Please provide a valid path for sites_fn"
  . "</div><div>$errLog</div>";	
  #exit 4;
}
# $httpd is the httpd.conf configuration file for Apache
my $httpd  = "/etc/httpd/conf/httpd.conf";
if(&TestFiles($httpd,0)=~/^true/i){
}
elsif(&TestFiles("/afs/slac/package/apache/servers/wanmon/conf/httpd.conf",0)=~/^true/i){
  $httpd = "/afs/slac/package/apache/servers/wanmon/conf/httpd.conf";
}
elsif(&TestFiles("/usr/local/apache2/conf/httpd.conf",0)=~/^true/i){
  $httpd ="/usr/local/apache2/conf/httpd.conf";
}
elsif(&TestFiles("/etc/apache2/apache2.conf",0)=~/^true/i) {
  $httpd ="/etc/apache2/apache2.conf";
}
else{
  #$error = "<div style='font-weight: bolder; color:red;'>"
  #. "Error: Apache configuration file $httpd not found "
  #. "or is not readable. Please provide a valid path "
  #. "and/or permissions."
  #. "</div>";#<div>$errLog</div>";	
  #exit 5;
}
my $std_beacons_file = "";
if(&TestFiles($std_beacons_file)=~/^true/i){
}
elsif(&TestFiles("/usr/local/share/pinger/beacons.txt")=~/^true/i){
  $std_beacons_file  = "/usr/local/share/pinger/beacons.txt";
}
elsif(&TestFiles("/afs/slac/g/www/www-iepm/pinger/beacons.txt")=~/^true/i){
  $std_beacons_file  = "/afs/slac/g/www/www-iepm/pinger/beacons.txt";
}
else{
  $error = "<div style='font-weight: bolder; color:red;'>"
  . "Error: beacons.txt was not found. Please provide a valid path for std_beacons_file."
  . "</div><div>$errLog</div>";
  #exit 6;
}

my $stem="/usr/local/share/pinger/";
if($hostname =~ /slac.stanford.edu$/) {
  $stem="/afs/slac.stanford.edu/package/pinger/pinger2/share/pinger/";
}
my @fns=("pingerCronStat.stdout", "pingerCronStat.stderr");
my @std_files=($stem.$fns[0], $stem.$fns[1], 
               "/usr/local/bin/lynx", "/usr/bin/lynx",
               "/usr/local/share/pinger/pinger2.pl",
               "/bin/mail",    "/bin/ping",  "/usr/sbin/ping",      "$newBeaconTmpFile",  
               "/usr/bin/dig", "/usr/local/bin/dig",                "/bin/ping6",
               "/usr/bin/wget", "/usr/local/bin/wget",  
             );
my $std_cron_out_file = $stem.$fns[0];
my $std_cron_err_file = $stem.$fns[1];
foreach my $fn (@std_files) {
  unless(&TestFiles($fn) =~ /^true/i) {
    #$error = "<div style='font-weight: bolder; color:red;'>"
    #     .   "Error: $fn was not found. Please provide a valid path for file."
    #     .   "</div><div>$errLog</div>";
    #exit 7;
  }
}
########################################################################
# If the host is at slac.stanford.edu then use cgi-wrap else use cgi-bin
# Removed 11/20/2019 by Cottrell
#my $cgi-bin="cgi-bin";
#if($hostname =~ /.+\.slac\.stanford\.edu/ && $hostname !~/^dhcp/){
#  $cgi-bin="cgi-wrap";
#}
########################################################
#Set up USAGE
(my $progname = $0) =~ s'^.*/'';#strip path components, if any
#See beyond __END__ for more

my $USAGE = " $progname
  is a CGI script that enables one to gather the pingER data for a 
  user specified time window for user specified  remote sites().
  It is acccessed via a URL. The default by accessing 
  http://&lt;monitoring_host&gt;/cgi-bin/ping_data.pl is to display a
  form allowing the user to select the time and remote site(s).
  It is also available as a program API (using Lynx or wget) 
  with the user specifying the various parameters
  as part of the QUERY_STRING.
  Typically is is installed on a web server running as a CGI script
  on the PingER Measurement ASgent (MA).
  The parameters are:
   either:
     begin_year, begin_month, begin_day, begin_hour, begin_min, begin_sec, begin_offset: 
       gives the starting time, begin_offset is the offset from UDT.
     end_year,   end_month,   end_day,   end_hour,   end_min,   end_sec,   end_offset:   
       gives the ending time
   or:
     begin, end: begin and end times as Unix epoch times.
   in_form: 1 if no form is to be displayed, undefined otherwise in which case form displayed and exits
   begin_point: y|n, include or do not include beginning point (default = no).
   end_point: y|n, see above but replace begnning by ending

Input:
  The list of remote nodes is obtained from $sites_fn
  The raw PingER data is obtained from the directory $stem, $stats_dir
    The raw data format is in text form and is described at
    https://confluence.slac.stanford.edu/display/IEPM/PingER+Monitor+node+format
    The records within the time window are found using a binary search on
    the Unix time stamps in the records
Output:
  If successful then data is returned in records of the form:
    pinger.slac.stanford.edu 134.79.104.80 www.eldjazair.net.dz 193.194.64.71 100 1301011226 10 10 217.482 217.923 219.169 0 1 2 3 4 5 6 7 8 9 218 217 219 218 218 217 217 217 217 217
  If there is no data then 'No data' will appear in the data. This is needed
  by getdata.pl which gets the data.

Examples of URL calls:
  http://www.slac.stanford.edu/cgi-bin/$progname?help=1
  http://www.comsats.edu.pk/cgi-bin/$progname
  http://pinger.comsats.edu.pk/cgi-bin/$progname?in_form=1&begin_hour=00&begin_min=00&begin_sec=00&begin_day=06&begin_month=08&begin_year=2007&begin_offset=&begin_point=y&end_hour=23&end_min=59&end_sec=59&end_day=06&end_month=08&end_year=2007&end_offset=&end_point=y
 More generically
  http://www.xxx.yyy.zzz/cgi-bin/$progname?site=a&site=b&site=c&begin=x&end=y&begin_point=n&end_point=n
  http://www.xxx.yy.zzz/cgi-bin/$progname?site=a&site=b&site=c&begin_hour=HH&begin_min=MM&begin_sec=SS&begin_offset=[-]HH&begin_day=DD&begin_month=MM&begin_year=YYYY&end_hour=HH&end_min=MM&end_sec=SS&end_offset=[-]HH&end_day=DD&end_month=MM&end_year=YYYY&begin_point=n&end_point=n

Usage: It is usually called from getdata.pl which in turn is called from getdata_all.pl.  

Acknowledgments:
  $progname originally written by  David E. Martin of HEP Network Resource Center, Fermilab, Batavia, IL
  based on work done by Connie Logg and Les Cottrell at the Stanford Linear Accelerator Center
  Heavily modified over the years by Les Cottrell, SLAC and others in particular
  Fahad Satti.
Version=$version
";
#############################################################
# put all values from command line into $in associative array
use HTML::Entities;#Sanitize responses, see https://www.perl.com/pub/2002/02/20/css.html/
if(defined($ENV{QUERY_STRING})) {
  $ENV{QUERY_STRING}=HTML::Entities::encode($ENV{QUERY_STRING});
}
our %in=();
&ReadParse(*in);
xss_sanitize(\%in);
sub xss_sanitize {
  my $ref=shift;
  foreach my $key(sort(keys %$ref)){
    if (defined($key)) {
      $ref->{$key}=HTML::Entities::encode($ref->{$key});
    }
  }
}
############################################################
if(defined($in{'proxy'})){ 
  if($in{'proxy'} eq '1') {#for debugging only
    $stats_dir='/nfs/slac/g/net/pinger/pingerdata/hep/data/proxy/2001:da8:270:2018:f816:3eff:fef3:bd3';
  }
  else {
    $stats_dir=$in{'proxy'};
  }
}
#print STDERR "\$stats_dir=$stats_dir\n";
#print STDERR "$0 ".__LINE__." read QUERY_STRING \$in{'proxy'}=$in{'proxy'}, \$user=$user\n";
##############################################################
#Process options
my $help;
use Getopt::Long;
GetOptions ("help"   => \$help) or die('Error in command line options\n');
################ Process User Selection ##################################
#If help requested then provide it
if (defined($in{'help'}) || defined($help)) {
  print "<html><head><title>ping_data.pl Help</title></head>\n"
      . "<body>$error<h1>Help for http://$hostname/cgi-bin/ping_data.pl</h1>\n"
      . "<pre>$USAGE</pre></body></html>\n";
  exit 100;
}
#################################################################################
my @cmds=('/bin/date',             '/usr/bin/perl -v',
          '/bin/uname -a',         '/usr/bin/uptime',
          'which grep',
          '/bin/grep "version"     /usr/local/share/pinger/pinger2.pl',  
          '/bin/grep "version"     /afs/slac/package/pinger/pinger2/share/pinger/pinger2.pl',
          '/bin/grep "logLevel = " /usr/local/share/pinger/pinger2.pl',
          '/bin/grep "logLevel = " /afs/slac/package/pinger/pinger2/share/pinger/pinger2.pl',
          'grep "version"     /usr/local/share/pinger/pinger2.pl',  
          'grep "version"     /afs/slac/package/pinger/pinger2/share/pinger/pinger2.pl',
          'grep "logLevel = " /usr/local/share/pinger/pinger2.pl',
          'grep "logLevel = " /afs/slac/package/pinger/pinger2/share/pinger/pinger2.pl',
          'which ps',
          '/bin/ps -efl | grep pinger2',
          'ps -efl | grep pinger2',
          '/bin/cat /etc/hosts',  '/bin/cat /etc/resolv.conf',
          '/usr/bin/whoami',      '/usr/ucb/whoami',      '/bin/pwd', 
          '/usr/sbin/sestatus',   '/bin/ps -efl | grep httpd',
          "/bin/ls -lt $stats_dir",
          'which crontab',
          '/usr/bin/crontab -l',  '/bin/crontab -l', 'crontab -l', 
          '/usr/local/bin/trscrontab -l > pinger-crontab', '/bin/cat pinger-crontab',
          '/bin/hostname',        '/bin/domainname',
          "/usr/bin/nslookup $hostname", 
          #'/bin/grep "version" /var/www/cgi-bin/ping_data.pl',
          #'/bin/grep "version" /afs/slac/package/pinger/ping_data.pl',
         );
foreach my $cmds (@cmds) {
  my ($cmd,$opt)=split(/\s/,$cmds, 2);
  if(!defined($opt)) {$opt="";}
  else               {$opt=" $opt";}
  if($cmd=~/([\w+|\/|\s|-]+)/) {#Untaint
    if(-x $cmd) {
	  my $sysoutput=`$1$opt`;
	  my $cmdin=$1;
	  if ($cmdin=~/sestatus/ && $sysoutput=~/enabled/i){
	    $errLog.="Output from: $cmdin$opt\n<br><br><font color=\"red\"><b>".$sysoutput."</b></font><br><br>\n";
	  }
	  #elsif($cmdin=~/sestatus/){
	  #  $errLog.="Output from: $cmdin$opt\n<br><br><font color=\"green\"><b>".$sysoutput."</b></font><br><br>\n";
	  #}
	  else{
        $errLog.="Output from: $cmdin$opt\n<pre>".$sysoutput."</pre>\n";
	  }
    }
    else {
      $errLog.="<pre>$cmd $opt: Command not found</pre>\n";
    }
  } 
}   
################Done with changes#######################

use strict; 


##########################################################
use Time::Local; #require "timelocal.pl";
use POSIX; #require "ctime.pl";

###########################################################
#Get default time stamps for form
my $time=time(); #Unix epoch time for today
my ($temp, $mday, $mon, $year);
(undef, undef, undef, $mday, $mon, $year, undef, undef, undef) = localtime($time);
$year=1900+$year;
$mon++;
if($mon < 10)  {$mon ="0".$mon;}  #Add leading zero
if($mday < 10) {$mday="0".$mday;} #Add leading zero
my $yday=$mday-1; if($yday<=0){$yday=1;}
if($yday < 10) {$yday="0".$yday;}

my $stats_base_fn = "ping-";
my $localpassword = "";

############## Authentication variable setup #############################
#if(defined($in{'password'})){
#  $localpassword = $in{'password'};
#}
############## Process the admininistration requested in top boxes#########
if (defined($in{'admin_func'}) && ($in{'admin_func'} eq 'fileLog')) {
  print "<div>$errLog</div>\n</HTML>\n";
  exit 101;
}
if (defined($in{'admin_func'}) && ($in{'admin_func'} eq 'update')) {
  &touch( $newBeaconTmpFile, 'create'); print "</HTML>\n";
  exit 102;
}
if (defined($in{'admin_func'}) && ($in{'admin_func'} eq 'remove')) {
  &touch( $newBeaconTmpFile, 'remove');  print "</HTML>\n";
  exit 102;
}
if (defined($in{'admin_func'}) && ($in{'admin_func'} eq 'httpd')) {
  &printFile($httpd, 'Apache configuration file'); 
  exit 103;
}
if(defined($in{'admin_func'}) && ($in{'admin_func'} eq 'pinger_xml')){
  &printFile($pinger_xml_file, 'PingER Landmarks file');
  exit 104;
}
if(defined($in{'admin_func'}) && ($in{'admin_func'} eq 'std_cron_out')){
  &printFile($std_cron_out_file, 'Cron Output File');
  exit 105;
}
if(defined($in{'admin_func'}) && ($in{'admin_func'} eq 'std_cron_err')){
  &printFile($std_cron_err_file, 'Cron Error File');
  exit 106;
}
if(defined($in{'admin_func'}) && ($in{'admin_func'} eq 'beacons')){
  my $beaconCount = &printFile($std_beacons_file, 'beacons.txt File');
  if($beaconCount<2){
    if(-e  $newBeaconTmpFile){
      print "A previous Beacon List file already exists in $tmpBaseDir.<br/>\n";
    }
    else{
      print "Beacons.txt File contains $beaconCount lines.<br/>\n";
      &touch($newBeaconTmpFile, 'create');
    }
  }
  exit 107;
}
if(defined($in{'admin_func'}) && ($in{'admin_func'} eq 'new_beacons')){
  if(-e  $newBeaconTmpFile){
    print "A previous Beacon List file already exists in $tmpBaseDir.<br/>\n";
  }
  else{
    &touch($newBeaconTmpFile, 'create');
  }
}
#############################################################################
# if in_form not defined then return the form.
if (!defined $in{'in_form'}) {
  print " <HTML> <HEAD> ";
  print " <style type='text/css'></style>\n";
  print "<script language='JavaScript' type='text/javascript'>
	var NS4 = (navigator.appName == 'Netscape' && parseInt(navigator.appVersion) < 5);
	function addOption(theSel, theText, theValue)
	{
	  var newOpt = new Option(theText, theValue);
	  var selLength = theSel.length;
	  theSel.options[selLength] = newOpt;
	}
	function deleteOption(theSel, theIndex)
	{ 
	  var selLength = theSel.length;
            if(selLength>0)
            {
              theSel.options[theIndex] = null;
            }
        }
        function moveOptions(theSelFrom, theSelTo)
        {
          var selLength = theSelFrom.length;
          var selectedText = new Array();
          var selectedValues = new Array();
          var selectedCount = 0;
          var i;
          // Find the selected Options in reverse order
          // and delete them from the 'from' Select.
          for(i=selLength-1; i>=0; i--){
            if(theSelFrom.options[i].selected){
              selectedText[selectedCount] = theSelFrom.options[i].text;
              selectedValues[selectedCount] = theSelFrom.options[i].value;
              deleteOption(theSelFrom, i);
              selectedCount++;
            }
          }
          // Add the selected text/values in reverse order.
          // This will add the Options to the 'to' Select
          // in the same order as they were in the 'from' Select.
          for(i=selectedCount-1; i>=0; i--){
            addOption(theSelTo, selectedText[i], selectedValues[i]);
          }
          if(NS4) history.go(0);
        }
        function selectAllOptions(selStr)
        {
	  var selObj = document.getElementById(selStr);
		for (var i=0; i<selObj.options.length; i++) {
			selObj.options[i].selected = true;
		}
	}
        function deSelectAllOptions(selStr)
        {
          var selObj = document.getElementById(selStr);
          for (var i=0; i<selObj.options.length; i++) {
            selObj.options[i].selected = false;
          }
        }
        function searchOptSites() {
          var input=document.getElementById('search_opt_sites').value.toLowerCase();
          var output=document.getElementById('opt_sites').options;
          deSelectAllOptions('opt_sites');
          for(var i=0;i<output.length;i++) {
            if(input==''){
             deSelectAllOptions('opt_sites');
             break;
            }
            if(output[i].value.indexOf(input)!=-1){
              output[i].selected=true;
            }
         }
       }
        function searchSelSites() {
          var input=document.getElementById('search_sites').value.toLowerCase();
          var output=document.getElementById('sites').options;
          deSelectAllOptions('sites');
          for(var i=0;i<output.length;i++) {
            if(input==''){
             deSelectAllOptions('sites');
             break;
            }
            if(output[i].value.indexOf(input)!=-1){
              output[i].selected=true;
            }
         }
       }
</script>";

  print "<TITLE>Get Ping Data from $hostname</TITLE>\n"
      . "</HEAD>\n";

################## Authetication Form ############################
#if(!defined($localPassword) || $localPassword -eq "" || !authenticate($localPassword)){
#  print "<BODY>\n";
#  print "<div style='text-align:center;postition:absolute;'>";
#  print "<FORM ACTION='/cgi-bin/ping_data.pl' METHOD='GET'>"
#      . "<INPUT TYPE='password' NAME='password' VALUE=''>"
#      . "<INPUT TYPE='submit' VALUE='login'>";
#  print "</div>";
#  print "</BODY>\n";
#  exit;
#  
#}
############### End Authentication Form ##########################
  print "$error<div align='center'><h1>Get Ping Data from $hostname</h1></div>"
    . "<div id='menubar_top' "
    . "style='text-align: center;"
    . " position:absolue;width: 100%;left:0%;height:5%;'>\n"
    . "<div style='margin-left:auto;padding:5px;"
    . "float:left; margin-right: auto;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='?admin_func=pinger_xml' title='pinger.xml configuration file'>"
    . "PingER.xml[offsite.nodes]</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='?admin_func=std_cron_err' title='If Deployed; PingER2 Cron Error Output'>"
    . "PingER2 Cron Errors</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='?admin_func=std_cron_out' title='If Deployed; PingER2 Cron Standard Output'>"
    . "PingER2 Cron Output</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='?admin_func=beacons' title='If Deployed PingER2;Show Beacons.txt File'>"
    . "Beacons.txt</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='?admin_func=new_beacons' title='Generate a new Beacons.txt File'>"
    . "Pull Beacons</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='http://www-iepm.slac.stanford.edu/pinger/faq.html' title='Show FAQ'>"
    . "FAQ</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='?admin_func=httpd' title='Show httpd.conf'>"
    . "Web configuration(httpd.conf)</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'" 
    . " href='?help=1' title='Show Usage Information'>"
    . "Help!!</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='?admin_func=update' title='Update pinger.xml'>"
    . "Update</a>\n"
    . "</div>\n"
    . "<div style='margin-left:auto;float:left;"
    . " margin-right: auto;padding:5px;display:inline;'>\n"
    . "<a style='display:block;border-style:ridge;background-color:#eeeeee;'"
    . " href='?admin_func=fileLog' title='Show configuration information'>"
    . "Config</a>\n"
    . "</div>\n"
    . "</div><br>\n"
    . "<div id='info'>\n"
    . "This is a test form to retrieve ping data from $hostname "
    . "using $progname version=$version.\n";  
print <<'EOM1';
    <br/>
    There are similar retrieval scripts running 
    <A HREF="http://pinger.fnal.gov/participants.html">elsewhere</A>.  
    Most of the time the data will be retrieved by automated scripts.
    This form 
    is a good way of testing
    installations (see 
    <a href="http://www.w3schools.com/Dom/dom_validate.asp">Verify XML Syntax</a>
    for how to verify the PingER.xml file) and looking at up-to-the-minute data.
    <br/>
    <font color=red>Please note <strong>SLAC</strong> only 
     provides the most recent data available via 
     <a href="http://slac.stanford.edu/cgi-bin/ping_data.pl">this web page</a>.
     For older data please send email to 
     <a href='http://www.slac.stanford.edu/suggestion/cottrell'>   
     Les Cottrell</a> with information
     on metric(s) desired, date range, regions etc. 
     </font>
    <HR>
		</div>
EOM1
  print "<div id='mainForm' align='center'>"
      . "<FORM ACTION='/cgi-bin/ping_data.pl' METHOD='GET' onsubmit='selectAllOptions(\"sites\");'>"
      . "<INPUT TYPE=HIDDEN NAME='in_form' VALUE='1'>";
  my ($node,$site)=split(/\./,$hostname,2);
  print "<div align='center' id='main'><label><B>Ping Data from $hostname to:</B></label>\n"
      . "<div id='selection'><table><tr><td><div id='showOpts'>"
      . "<SELECT NAME='opt_sites' id='opt_sites' SIZE='20' multiple='multiple'"
      . " title='Multiple Selection is allowed, press [ctrl]||[shift] and then select multiple sites'>\n";
  my $sites="";
  my $nodes=0;
  my $site_name;
  open(NODE_LIST, "<$sites_fn");
  while (<NODE_LIST>) {
    chop;
    if (/^\#/ || /^Plot group/ || /^\s*$/) {
      next;
    }
    $nodes++;
    ($site_name) = split;
    $sites.="$site_name,";
  }
  my @site_names=split(/\,/,$sites);
  foreach $site_name (sort @site_names) {
    print "<OPTION VALUE=\"$site_name\">$site_name\n";
  }
  print "</SELECT></div>\n"
      . "Search: <input type='text' id='search_opt_sites' title='Warning!!! \nUsing this "
      . " search feature will deselect all previously selected nodes.' \n"
      . " onkeyup=\"searchOptSites()\">\n"
      . "</td>\n"
      . "<td align='center' valign='middle'>\n"
      . "<input type='button' value='Add' "
      . "  onclick='moveOptions(this.form.opt_sites, this.form.sites);' />\n"
      . "<br />\n"
      . "<input type='button' value='Remove'"
      . "  onclick='moveOptions(this.form.sites, this.form.opt_sites);' />"
      . " </td>\n"
      . "<td><div id='selOpts'>\n"
      . "<select name='sites' id='sites' size='20' multiple='multiple'></select>\n"
      . "</div>\n"
      . "Search: <input type='text' id='search_sites' title='Warning!!! Using this"
      . " search feature will deselect all previously selected nodes.' "
      . "  onkeyup=\"searchSelSites()\">\n"
      . "</td>\n"
      . "</tr></table></div>\n"
      . "Note: Selecting no site will cause data for <B>all</B> $nodes sites to be returned.\n</div>";
  print <<'EOM';
  <table><tr><td>
  <TABLE BORDER CELLPADDING="2">
    <CAPTION>
      <FONT SIZE=+1>Start (default GMT)</FONT>
    </CAPTION>
    <TR>
      <TD>
	<CENTER>
	  HH:MM:SS
	</CENTER>
      </TD>
      <TD>
	<CENTER>
	  DD:MM:YYYY
	</CENTER>
      </TD>
      <TD>
	<CENTER>
	  Offset from GMT
	</CENTER>
      </TD>
      <TD>
	<CENTER>
	  Endpoint
	</CENTER>
      </TD>
    </TR>
    <TR>
      <TD>
	<INPUT NAME="begin_hour" MAXLENGTH=2 SIZE=2 TYPE=TEXT VALUE="00">:
	<INPUT NAME="begin_min" MAXLENGTH=2 SIZE=2 TYPE=TEXT VALUE="00">:
	<INPUT TYPE="text" NAME="begin_sec" VALUE="00" SIZE="2" MAXLENGTH="2">
      </TD>
      <TD>
EOM
      print "      <INPUT NAME='begin_day' MAXLENGTH=2 SIZE=2 TYPE=TEXT VALUE=$yday>:\n"
          . "      <INPUT NAME='begin_month' MAXLENGTH=2 SIZE=2 TYPE=TEXT VALUE=$mon>:\n"
          . "      <INPUT TYPE='text' NAME='begin_year' VALUE='$year' SIZE='4' MAXLENGTH='4'>\n";
  print <<'EOM1';
       </TD>
       <TD>
	<CENTER>
	  <INPUT TYPE="text" NAME="begin_offset" SIZE="3" MAXLENGTH="3">
	</CENTER>
      </TD>
      <TD>
	<INPUT TYPE="checkbox" NAME="begin_point" VALUE="y" CHECKED>Include
      </TD>
    </TR>
  </TABLE>
  </td>
  <td>
  <TABLE BORDER CELLPADDING="2">
    <CAPTION>
      <FONT SIZE=+1>End (default GMT)</FONT>
    </CAPTION>
    <TR>
      <TD>
	<CENTER>
	  HH:MM:SS
	</CENTER>
      </TD>
      <TD>
	<CENTER>
	  DD:MM:YYYY
	</CENTER>
      </TD>
      <TD>
	<CENTER>
	  Offset from GMT
	</CENTER>
      </TD>
      <TD>
	<P ALIGN=Center>
	Endpoint
      </TD>
    </TR>
    <TR>
      <TD>
	<INPUT NAME="end_hour" MAXLENGTH=2 SIZE=2 TYPE=TEXT VALUE="23">:
	<INPUT NAME="end_min" MAXLENGTH=2 SIZE=2 TYPE=TEXT VALUE="59">:
	<INPUT TYPE="text" NAME="end_sec" VALUE="00" SIZE="2" MAXLENGTH="2">
      </TD>
      <TD>
EOM1
  print "      <INPUT NAME='end_day' MAXLENGTH=2 SIZE=2 TYPE=TEXT VALUE=$mday>:\n"
      . "      <INPUT NAME='end_month' MAXLENGTH=2 SIZE=2 TYPE=TEXT VALUE=$mon>:\n"
      . "      <INPUT TYPE='text' NAME='end_year' VALUE='$year' SIZE='4' MAXLENGTH='4'>\n";
  print <<'EXECUTE';
      </TD>
      <TD>
	<CENTER>
	  <INPUT TYPE="text" NAME="end_offset" SIZE="3" MAXLENGTH="3">
	</CENTER>
      </TD>
      <TD VALIGN="Middle">
	<INPUT TYPE="checkbox" NAME="end_point" VALUE="y" CHECKED>Include
      </TD>
    </TR>
  </TABLE>
  </td>
  </tr>
  </table>
  <INPUT TYPE=submit VALUE="Get Data!"> 
  <INPUT TYPE=reset VALUE="Clear Form">
</FORM>
</div>
<HR>

EXECUTE
  my $time=scalar(localtime());
  print "Form created by $0 for $user on $hostname($ipaddr). \n"
      . "Local time=$time. \n" 
      . "Raw PingER data obtained from $stats_dir. \n"
      . "Remote sites obtained from $sites_fn. \n"
      . "The most current version of this script and instructions to install can be found at: \n"
      . "<a href='http://www-iepm.slac.stanford.edu/pinger/tools/install2.html'>"
      . "Installing ping_data.pl and traceroute.pl for PingER2</a>. \n"
      . "The <a href='http://www-iepm.slac.stanford.edu/pinger/pinger.xml'>"
      . "master template pinger.xml</a> is available from SLAC. \n"
      . "There is <a href='http://www.slac.stanford.edu/cgi-bin/ping_data.pl?help=1'>USAGE</a> "
      . "information on the script.\n</HTML>\n";
  exit;
}

#############################################################################
#Not requesting help or a form so provide results
my ($begin, $end); 
if(!defined($in{'begin_offset'})||($in{'begin_offset'}!~/^\d+$/)) {
  $in{'begin_offset'}=0;
}
if(!defined($in{'end_offset'})  ||($in{'end_offset'}  !~/^\d+$/)) {
  $in{'end_offset'}  =0;
}
if (defined $in{'begin_hour'}) {
  $begin = &timegm($in{'begin_sec'},$in{'begin_min'},$in{'begin_hour'},$in{'begin_day'}, ($in{'begin_month'} -1), ($in{'begin_year'} -1900));
  $begin = $begin - ($in{'begin_offset'} *3600);
  $end = &timegm($in{'end_sec'},$in{'end_min'},$in{'end_hour'},$in{'end_day'}, ($in{'end_month'} -1), $in{'end_year'} -1900);
  $end = $end - ($in{'end_offset'} *3600);
}
elsif (defined $in{'begin'}) {
  $begin = $in{'begin'};
  $end = $in{'end'};
}
else {
  print '<B>No begin and end times</B>\n</HTML>\n';
  exit;
}
# if no sites specified, return all sites
my $all_sites;
my %site_list;
if (!defined $in{'sites'}) {
  $all_sites = 1;
}
else {
  $all_sites = 0;
  # put (lower-cased) site names in associate array for easy checking
  for (split(/\0/, $in{'sites'})) {
    tr/A-Z/a-z/;
    $site_list{$_} = 1;
  }
}
# Exclude the end points unless begin_point (or end_point) is specfied.
if ($in{'begin_point'} ne 'y') { ++$begin }
if ($in{'end_point'} ne 'y')   { --$end }
if ($end < $begin) {
  print '<B>End < Begin</B>\n</HTML>\n';
  exit;
}
my ($begin_sec, $begin_min, $begin_hour, $begin_mday, $begin_mon, $begin_year, $begin_wday, $begin_yday, $begin_isdst) = gmtime($begin);
my ($end_sec, $end_min, $end_hour, $end_mday, $end_mon, $end_year, $end_wday, $end_yday, $end_isdst) = gmtime($end);
# Add 2 minutes of fuzz to the timestamp search limits to allow for
# the timestamps' being a little out of strict sort order.
my $begin_search = $begin - 120;
my $end_search   = $end + 120;
my $fn;
my $nline=0;
#print '<pre>';
#my $pingerDataDir='/nfs/slac/g/net/pinger/pinger2/data';
#$stats_dir=$pingerDataDir;# kluge#removed by Cottrell 8/24/2021
if($hostname=~/\.slac\./) {
  $stats_dir='/nfs/slac/g/net/pinger/pinger2/data';#kluge add by Cottrell 8/28/2021
}
for my $month ((($begin_year +1900) * 12 + $begin_mon )..(($end_year+1900) * 12 + $end_mon)) {
  $fn = sprintf("%s/%s%4.4d-%2.2d.txt", $stats_dir, $stats_base_fn, int($month / 12), ($month % 12) +1);
  if (!open(PINGDATA, $fn)) {
    if(!(-d $stats_dir)){
      print "Unable to locate Data Dir = ".$stats_dir."\n";
    }
    if(!(-e $fn)){
      print "Unable to locate Data File = ".$fn."\n";
    }
    print "#Warning $progname can't open $fn: $!\n";
    next;
  }
  if($debug>=0) {
    print STDERR "$0 "
        . "opened \$fn=$fn\n";#, \$stats_dir=$stats_dir, \$stats_base_fn=$stats_base_fn\n";
  }
  # Use a binary search to quickly position the PINGDATA file to a little
  # before the $begin timestamp data.
  &SeekBinSrch(*PINGDATA, $begin_search, 1500);
  while (<PINGDATA>) {
    #eg $-=pinger.slac.stanford.edu 134.79.104.80 www.construccionsroca.ad 194.158.89.28 1000 1614641932 10 10 180.983 181.040 181.203 1 2 3 4 5 6 7 8 9 10 181 181 181 181 181 180 180 180 181 180
    my ($src_name, $src_ip, $dest_name, $dest_ip, $pingsize, $ctime) = split(/\s+/, $_);
    # The following 5 lines are optimized for speed at the expense of beauty.
    next if ($ctime < $begin);
    if ($ctime > $end) {
      if ($ctime < $end_search) {next;}
      last;
    }
    $nline++;
    $dest_name =~ tr/A-Z/a-z/;
    print if (defined $site_list{$dest_name} || $all_sites);#Pass selected lines to caller
  } # while (<PINGDATA>)
  close (PINGDATA);
} # for $month. PINGDATA now positioned close to relevant data
if($nline>0) {
  if($debug>0) {
    print "#$progname positioned file $fn<br> & found $nline lines\n#between dates specified:"
        . ($begin_year+1900)."/".($begin_mon+1)."/".($begin_mday)
        . " $begin_hour:$begin_min:$begin_sec.."
        . ($end_year  +1900)."/".($end_mon  +1)."/".($end_mday)
        . " $end_hour:$end_min:$end_sec.<br>\n"
        . "#time searched from $begin-120 secs(". scalar(gmtime($begin-120))
        . ")to $end+120 secs(".scalar(gmtime($end+120)).")<br>\n";
  }    
}
else {
  print "#No data found by $progname in $nline lines of data in $fn between dates specified<br>\n"
      . ($begin_year+1900)."/".($begin_mon+1)."/".($begin_mday)
      . " $begin_hour:$begin_min:$begin_sec.."
      . ($end_year  +1900)."/".($end_mon  +1)."/".($end_mday)
      . " $end_hour:$end_min:$end_sec.<br>\n"
      . "time searched from $begin-120 secs(". scalar(gmtime($begin-120))
      . ")to $end+120 secs(".scalar(gmtime($end+120)).")<br>\n";
  my $n=5;
  my @tails=("/usr/bin/head", "/bin/head", "/usr/bin/tail", "/bin/tail");
  foreach my $tail (@tails) {
    if(-e $tail){
      if($tail=~/([\w\/]+)/){$tail=$1;}
      else {print "#Command $tail is tainted\n"; next;}
      if($n=~/(\d+)/)       {$n=$1;}
      else {print "#Line count $n is tainted\n";    next;}
      if($fn=~/([\w\/\-\.]+)/) {$fn=$1;}
      else {print "#File name $fn is tainted\n";   next;}
      my $cmd="$tail -$n $fn";
      my @ans=`$cmd`;
      print "#Results (".scalar(@ans)." lines) from $cmd are:<br>\n";
      if(scalar(@ans)<=0) {print "$cmd returned ?=$? !=$!\n";}
      else                {
        foreach my $line (@ans) {
          my @tokens=split(/\s+/,$line);
          my $epoch=$tokens[5];
          my (undef,$min,$hour,$mday,$mon,$year,undef,undef,undef) = gmtime($epoch);
          my $human=($year+1900)."-".($mon+1)."-$mday $hour:$min";;
          print "#$human:$line<br>";
          #print "#$line<br>";
        }
      }
    }
  }
} 
#print "</body></html>\n";
exit 0;
#############################################################################
#
sub epoch2Date {
  my $epoch=$_[0];
  my $date=gmtime($epoch);
  return $date;
}
#############################################################################
# Create, find, or remove a 0 length file with rw world access.
sub touch {
  my $file=$_[0];
  my $action=$_[1];
  if($action eq 'find') {
    if(-e $file) {return 1;}
    else         {return 0;}
  }
  else {
    my %cmd;
    $cmd{'create'}="touch $file";
    $cmd{'remove'}="rm $file";
    my $TmpFileCmd=$cmd{$action};
    print "$action (using $cmd{$action}) the file ($file) on $user\@$hostname. "
        . "This triggers updating pinger.xml, <br>\n";
  }
  if($action eq 'create') {
    my $cnt=chmod(0666, $file);
    print "Unable to change mode to 0666 for $file<br>\n" if ($cnt != 1) ;
    my (undef, undef, $mode, undef, $uid, $gid, undef, $size, undef, $mtime,
        undef, undef, undef) = stat($file); 
    my $octmode=sprintf("%lo", $mode & 07777);
    print "file has mode=0$octmode, last modified=".localtime($mtime)."\n"; 
    return 1;
  } 
  else {return 0;}
}
#############################################################################
sub SeekBinSrch # ( *FH, $key, $close_enough )
# Subroutine to seek to the vicinity of the $begin data records in the
# ping_data file using a binary search.  The binary search only attempts
# to get within $close_enough bytes of the "exact" position in the sorted
# file which contains the search key (or, if no such record exists in the
# file, then to where such a record would be placed if it existed).

# More precisely, the binary search finds a region of the file such that
# (1) the size of the region is less than or equal to $close_enough bytes;
# (2) the beginning of the region is the beginning of a record whose key is
#     less than or equal to the search key if such a record exists, otherwise
#     the start of the region is the start of the file; and
# (3) the first record that begins after the end of the region has a key
#     which is greater than or equal to the search key if such a record
#     exists, otherwise the end of the region is the end of the file.
# The file is left positioned to the beginning of the region.
#
# Author: John Halperin <jxh@slac.stanford.edu>  6 June 1997.
{
  local(*FH) = $_[0];
  my($key, $close_enough) = @_[1..2];
  my($lo_posn, $hi_posn);

#  return unless (defined(<FH>));#remove warning "Value of <HANDLE> construct can be "0"; test with defined()"
  return unless (-f FH);		# can't handle pipes or other oddities
      
  $lo_posn = 0;
  $hi_posn = (-s FH)  or  die "BinSrchFile: error stat'ing FH: $!\n";

  my $loop_ct = 0;
  my($mid_posn, $rec_start, $rec, $rec_key, $lt_eq_gt, $errct);

BinSrch:
  while (($hi_posn - $lo_posn) > $close_enough && ++$loop_ct < 40)  {
    $mid_posn = ($lo_posn + $hi_posn) >> 1;
    seek(FH, $mid_posn, 0)  or
	die "BinSrchFile: seek($mid_posn) failed: $!\n";
    <FH>;				# skip to start of next record
    for ($errct = 0; ; ) {
      $rec_start = (tell);
      last BinSrch if ($rec_start >= $hi_posn);
      if(!defined($rec = <FH>)){
        die "SeekBinSrch: error reading file: $!\n";
      }
      $rec_key = (split(/ /, $rec, 7))[5];
      last if (defined($rec_key) && $rec_key =~ m/^\d+$/);
      warn "BinSrchFile: record at posn $rec_start has a bad timestamp\n",
        "*** $rec";
      die "BinSrchFile terminating\n" if (++$errct > 10);
    }
    $lt_eq_gt = $key - $rec_key;

    if ($lt_eq_gt < 0)  {
      $hi_posn = $mid_posn;		# srch key < rec, so drop ceiling
    }
    elsif ($lt_eq_gt > 0)  {
      $lo_posn = $rec_start;		# srch key > rec, so raise floor
    }
    else {
      $hi_posn = $lo_posn = $rec_start;	# key == rec; found.
    }
  } # BinSrch: while ()
  die "BinSrchFile failed; looping?\n"
    if (($hi_posn - $lo_posn) > $close_enough);

  seek(FH, $lo_posn, 0)  or
    die "BinSrchFile: seek($lo_posn) failed: $!\n";
} # &SeekBinSrch()
###############################################################
sub printFile {
  my $file=$_[0]; my $name=$_[1]; my $nline=0;
  if(-e $file){
    if(open(FILE,$file)){
      my @stat=stat($file);
      my $mtime=$stat[9];
      print scalar(localtime())." $name [$file], last modified at local time: "
          . scalar(localtime($mtime))
          . "<br>\n"
          . "If it is an xml file use view source to see uninterpreted file.\n\n<pre>\n";
      while(<FILE>){
        $nline++;
        my $line=trim($_); chomp($line);
        unless ($line eq "") {print "$line\n";}
      }
      print "</pre>\n----Output complete, found $nline lines----------------\n";
      close (FILE);
    }
    else{
      print "Can't open $name [$file]: $!\n";
    }
  }
  else{
    print "$name [$file]: No such File Found\n";
  }
  return $nline;
}
###############Strip leading & training whitespace from string#####
sub trim($)
{
        my $string = shift;
        $string =~ s/^\s+//;
        $string =~ s/\s+$//;
        return $string;
}


__END__
########################### Copyright Notice ###################################
#             Copyright (c)1996 Universities Research Association, Inc.        #
#                         All Rights Reserved                                  #
# /*---------------------------------------------------------------*/
# /*          STANFORD UNIVERSITY NOTICES FOR SLAC SOFTWARE        */
# /*               ON WHICH COPYRIGHT IS DISCLAIMED                */
# /*                                                               */
# /* AUTHORSHIP                                                    */
# /* This software was created by <insert names>, Stanford Linear  */
# /* Accelerator Center, Stanford University.                      */
# /*                                                               */
# /* ACKNOWLEDGEMENT OF SPONSORSHIP                                */
# /* This software was produced by the Stanford Linear Accelerator */
# /* Center, Stanford University, under Contract DE-AC03-76SFO0515 */
# /* with the Department of Energy.                                */
# /*                                                               */
# /* GOVERNMENT DISCLAIMER OF LIABILITY                            */
# /* Neither the United States nor the United States Department of */
# /* Energy, nor any of their employees, makes any warranty,       */
# /* express or implied, or assumes any legal liability or         */
# /* responsibility for the accuracy, completeness, or usefulness  */
# /* of any data, apparatus, product, or process disclosed, or     */
# /* represents that its use would not infringe privately owned    */
# /* rights.                                                       */
# /*                                                               */
# /* STANFORD DISCLAIMER OF LIABILITY                              */
# /* Stanford University makes no representations or warranties,   */
# /* express or implied, nor assumes any liability for the use of  */
# /* this software.                                                */
# /*                                                               */
# /* STANFORD DISCLAIMER OF COPYRIGHT                              */
# /* Stanford University, owner of the copyright, hereby disclaims */
# /* its copyright and all other rights in this software.  Hence,  */
# /* anyone may freely use it for any purpose without restriction. */
# /*                                                               */
# /* MAINTENANCE OF NOTICES                                        */
# /* In the interest of clarity regarding the origin and status of */
# /* this SLAC software, this and all the preceding Stanford       */
# /* University notices are to remain affixed to any copy or       */
# /* derivative of this software made or distributed by the        */
# /* recipient and are to be affixed to any copy of software made  */
# /* or distributed by the recipient that contains a copy or       */
# /* derivative of this software.                                  */
# /*                                                               */
# /* SLAC Software Notices, Set 4 (OTT.002a, 2004 FEB 03)          */
# /*---------------------------------------------------------------*/
# Copyright (c) 2006, 2007, 2008, 2009, 2010
# The Board of Trustees of
# the Leland Stanford Junior University. All Rights Reserved.
#
# *** changes in version 1.1.1
# - selecting no sites returns all sites
# - refer to /usr/local/netmon

# *** changes in version 1.1.2
# - fast skip to starting time, and exit after ending time

# *** changes in version 1.1.4, June 24, 2006
# - Provide default dates and times in form boxes
# - Sort the sites, increase the size of box, compress output

# *** changes in version 1.1.5, July 20, 2006
# - Automate getting the hostname and add version number output.
# - Correct URL for getting FTP data.

# *** changes in version 1.1.6 August 18, 2006, Les Cottrell
# Check if running on host at slac.stanford.edu, if so use cgi-wrap, else use cgi-bin
# Add warning if can't open file

# *** changes in version 1.1.7 March 29, 2007, Les Cottrell
# use #!/usr/bin/perl -- rather than #!/usr/local/bin/perl5

# *** changes in version 1.1.8 May 30, 2007
# Report no data found in file.
# Prevent $yday going < 01
#my $version="1.1.8, 5/30/07, Cottrell.";

# *** changes in version 2.0 8/6/07, add help, use strict -T

# *** changes in version 3.1 2/17/10, provide access to help, pinger.xml,
#     error and log. Get file locations automatically. 
#     Allow target node selection.

# *** changes in version 3.2 2/23/10, added require "/usr/lib/cgi-bin/cgi-lib.pl";
#Below is an example of the data format
minos.slac.stanford.edu 134.79.196.100 dukhet.phy.duke.edu 152.3.57.20 100 862526343 10 10 90 106 133

# *** changes in version 3.3 2/23/10,
#     * Added a subroutine(TestFiles) at the top, to check required files for
#       ** existance
#       ** size > 0
#       ** is readable
#       ** is writeable(except in case of cgi-lib)
#     * Added, checks to look for the most important files, like
#       pinger.xml, cgi-lib.pl and pinger data dir in most obvious locations.
#       This should help remove the need to make any changes to this script.

# *** changes in version 3.4 2/24/10,
#     * Added the ability to output PingER2 FAQ
#     * Added the ability to output beacons.txt file

# *** changes in version 3.5 2/24/10,
#     * Added, search capability.

# *** changes in version 3.5.1 2/27/10,
#     * Added, mode and Last modified output for files.

# *** Changes in version 3.6 8/30/2010,
#     * Added octal display of permissions, space check, 
#     * info on checking the pinger.xml file 
   
# *** Changes in version 3.7 11/17/2010
#     * Provided date stamp of cronStat files. 
#     * Added test to see if lynx, mail, dig, ping, ping6 present

# *** Changes in version 3.8 11/30/10,
#     * Added ability to list httpd.conf file.

# *** Changes in version 3.81 12/20/2010,
#     * Added selinux check.

# *** Changes in version 3.82 01/02/2011,
#     * Added "/usr/local/apache2/conf/httpd.conf" to possible httpd.conf locations
#     * Added pwd, whoami and list of httpd processes

# *** Changes in version 3.83 01/07/2011,
#     * Added /etc/hosts and /etc/resolv.conf, ip addr, local time,
#     * location of master template for pinger.xml 

# *** Changes in version 3.84 02/18/2011,
#     * Added /bin/hostname, /bin/domainname. 

# *** Changes in version 3.9 3/24/2011,
#     * Added ps -efl | grep pinger2, ls -lt $stats_dir, added more
#     * diagnostics for No data found.

# *** Changes in version 3.91 12/21/2011
#     * Added look for $httpd in /etc/apache2/apache2.conf for acmesecurity

# *** Changes in version 3.92 2/19/2012
#     * Added code to create a pinger2_newBeaconsList.txt file in /tmp space.
#       ** Create this file automatically, if the number of lines 
#          in beacon.txt is less than 2.
#       ** Create this file, if the user specifically demands it by clicking on
#          new beacons link.

# *** Changes in version 3.93 11/13/2012
#     * More clearly identify that default is GMT for start and End times.

# *** Changes in version 3.94 1/17/2014
#     * Added ls -l to file information, plus grep of pinger2.pl version.

# *** Changes in version 3.95 3/10/2014
#     * Added check on Unix version

# *** Changes in version 3.95 6/12/2014
#     * Allowed httpd file to not have writeable privs.

# *** Changes in version 3.96 6/13/2014
#     * Log output shown as red and bold when SELinux is found enabled.

# *** Changes in version 3.97 7/3/2014
#     * Removed warning if can't find the $httpd file. 
# The issue is quite strange. For some reason apache can't access the
# /etc/httpd/ directory when ping_data.pl<http://ping_data.pl> is 
# called from the web but from the terminal it works just fine 

# *** Changes in version 3.98 8/19/2014
#     * Added /usr/lib/cgi-bin to search for cgi-lib, also added date command to get local clock time.
#     default location for cgi-lib in Raspbian Wheezy (OS for the raspberry pi)
#     (both as root and apache).

# *** Changes in version 3.99 11/26/2014
#     * Added uptime

# *** Changes in version 4.0 12/4/2014
#     * Attempted to make IPv6 compliant

# *** Changes in version 4.1 6/9/2015
#     * Fixed up hostname and IP address for Raspberry pi

# *** Change in version  4.2 4/21/2018
#    * fixed Help/$USAGE to work with scriptable
#my $versioni='4.2; 4/21/2018 Les Cottrell'; #Fixed help/$USAGE for Script table

# *** Change in version 4.3 5/28/2018
#    *Increased diagnostic output when it can't find data in the specified window.
#    $version='4.3 5/28/2018 Les Cottrell'

# *** Change in version 4.4 6/10/2018 Les Cottrell
#    *Proxy added 
