#VERSION,2.1.0
# $Id: nikto_core.plugin 176 2009-10-16 21:11:49Z jabra $
###############################################################################
#  Copyright (C) 2006 CIRT, Inc.
#  
#  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; version 2
#  of the License only.
#  
#  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.
###############################################################################

###############################################################################
# Nikto core functionality
###############################################################################
sub change_variables
{
   # $line is the unfiltered variable
   my $line=$_[0];
   # @subtests is the returned array of expanded variables
   my @subtests;
   my $cooked;

   if ($line !~ "\@")
   {
      push(@subtests, $line);
   }
   else
   {
      foreach my $varname (keys %VARIABLES)
      {
         if ($line =~ "$varname")
         {
            # We've found the variable; now to expand it!
            foreach my $value (split(/ /, $VARIABLES{$varname}))
            {
               $cooked = $line;
               $cooked =~ s/$varname/$value/g;
               push(@subtests, change_variables($cooked));
            }
         }
      }
   }

   return @subtests;
}

###############################################################################
sub is_404
{
 my ($uri, $content, $rescode) = @_;
 $ext=get_ext($uri);

 if   (($FoF{$ext}{mode} eq "STD") && (($rescode eq 401) || ($rescode eq 403) || ($rescode eq 404) || ($rescode eq 410))) { return 1; }
 elsif ($FoF{$ext}{mode} eq "STD")  { return 0; }
 elsif ($FoF{$ext}{mode} eq "REDIR") 
  { 
   if ($result{location} eq $FoF{$ext}{location}) { return 1; }
  }
 elsif (($FoF{$ext}{type} eq "CONTENT")  && ($content =~ /$FoF{$ext}{match}/i))    { return 1; } 
 elsif (($FoF{$ext}{type} eq "BLANK") && ($content eq ""))                         { return 1; } 
 elsif ($FoF{$ext}{type} eq "HASH")
   {
     my $content=rm_active_content($content, $uri);
     if (LW2::md4($content) eq $FoF{$ext}{match}) { return 1; } 
   }
return 0;
}
###############################################################################
sub nprint
{
   my $line=$_[0];
   chomp($line);
   # don't print debug & verbose to output file...
   if (defined $_[1])
   {
      if ($_[1] eq "d" && $OUTPUT{debug})
      { 
         print "D:" . localtime() . " $line\n";
      }
      if ($_[1] eq "v" && $OUTPUT{verbose})
      { 
         print "V:" . localtime() . " $line\n";
      }
      return;
   } 
   # print errors to STDERR
   if ($line =~ /ERROR\:/) { print STDERR "$line\n"; return; }
   
   # don't print to STDOUT if output file is "-"
   if ($CLI{format} =~ /^xml$/i and $CLI{file} eq "-") { return; }
 
   # don't print to STDOUT if output file is "-"
   if ($CLI{format} =~ /^html?$/i and $CLI{file} eq "-") { return; }
   

   # print to scan details to standard output if the users wants another format and is saving results to a file
   $line =~ s/((CVE|CAN)\-[0-9]{4}-[0-9]{4})/http:\/\/cve.mitre.org\/cgi-bin\/cvename.cgi?name\=$1/g;
   $line =~ s/(CA\-[0-9]{4}-[0-9]{2})/http:\/\/www.cert.org\/advisories\/$1.html/g;
   $line =~ s/BID\-([0-9]{4})/http:\/\/www.securityfocus.com\/bid\/$1/g;
   $line =~ s/(IN\-[0-9]{4}\-[0-9]{2})/http:\/\/www.cert.org\/incident_notes\/$1.html/gi;
   $line =~ s/(MS[0-9]{2}\-[0-9]{3})/http:\/\/www.microsoft.com\/technet\/security\/bulletin\/$1.asp/gi;
   print "$line\n";
 
   return;
}
###############################################################################
sub get_ext
{
 my $uri=$_[0] || return;
 if ($uri =~ /\/$/)        { return "DIRECTORY"; }
 $uri =~ s/^.*\///;
 $uri =~ s/(\?|\&|\%).*$//;
 if ($uri =~ /^\.[^.%]/)   { return "DOTFILE"; }
 if ($uri !~ /\./)      { return "NONE"; }
 $uri =~ s/\".*$//;
 $uri =~ s/^.*\.//;
 return $uri;
}
###############################################################################
sub date_disp
{
 my @time=localtime($_[0]);
 $time[5]+=1900;
 $time[4]++; if (length($time[4]) eq 1) { $time[4]="0$time[4]"; }
 $time[3]++; if (length($time[3]) eq 1) { $time[3]="0$time[3]"; }
 if (length($time[0]) eq 1) { $time[0]="0$time[0]"; }
 if (length($time[1]) eq 1) { $time[1]="0$time[1]"; }
 if (length($time[2]) eq 1) { $time[0]="0$time[2]"; }
 return "$time[5]-$time[4]-$time[3] $time[2]:$time[1]:$time[0]";
}
###############################################################################
sub map_codes
{
 my %REQS;
 my $rs=LW2::utils_randstr(8);

 # / for OK response
 $NIKTO{totalrequests}++;
 $request{'whisker'}->{'uri'} = "/";
 delete $request{'whisker'}->{'data'};
 $request{'whisker'}->{'method'} = GET;
 $request{'whisker'}->{'http_eol'}=$http_eol;

 delete $request{'whisker'}->{'Content-Length'};
 LW2::http_close(\%request);  # force-close any old connections
 dump_var("Request Hash", \%request);
 if ($CLI{pause} > 0) { sleep $CLI{pause}; }
 LW2::http_do_request_timeout(\%request,\%result);
 dump_var("Result Hash", \%result);
 if (defined $result{location}) 
  { 
   nprint("- Root page / redirects to: $result{location}");
    if ($result{location} =~ /^$request{'whisker'}{'host'}/i) # same host
     {
      $request{'whisker'}->{'uri'} = $result{location};
      $request{'whisker'}->{'uri'} =~ s/^http(s)?\:\/\/$request{'whisker'}{'host'}//i;
      $request{'whisker'}->{'http_eol'}=$http_eol;
      LW2::http_close(\%request);  # force-close any old connections
      LW2::http_fixup_request(\%request);
      dump_var("Request Hash", \%request);
      if ($CLI{pause} > 0) { sleep $CLI{pause}; }
      LW2::http_do_request_timeout(\%request,\%result);
      dump_var("Result Hash", \%result);
     }
    else  # different host... ugh... guess
     {
      $FoF{okay}{response}=200;
      $FoF{okay}{type}="STD";
     }
  }
 else
  {
   $FoF{okay}{response}=$result{'whisker'}->{'code'};
   my $content=rm_active_content($result{'whisker'}->{'data'});
   $FoF{okay}{type}="HASH";
   $FoF{okay}{match}=LW2::md4($content);
  }

 # these are some used in mutate that may not be in the db_tests
 $db_extensions{bak}=1;
 $db_extensions{data}=1;
 $db_extensions{dbc}=1;
 $db_extensions{dbf}=1;
 $db_extensions{lst}=1;
 $db_extensions{htx}=1;

 foreach my $ext (keys %db_extensions) 
   {
    if ($ext eq "DIRECTORY") { next; }  # don't test generic type holder as real extension (added to db_extensions by get_ext)
    if ($ext eq "NONE")      { next; }  # don't test generic type holder as real extension (added to db_extensions by get_ext)
    if ($ext eq "DOTFILE")   { next; }  # don't test generic type holder as real extension (added to db_extensions by get_ext)
    $REQS{"/$rs.$ext"}=$ext; 
   }
 # add those generic type holders back as real files
 $REQS{"/$rs/"}="DIRECTORY";
 $REQS{"/$rs"}="NONE";
 $REQS{"/.$rs"}="DOTFILE";
 
 foreach my $file (keys %REQS)
  {
   nprint("- Testing error for file: $file\n","v");
   $NIKTO{totalrequests}++;
   $request{'whisker'}->{'uri'}    = $file;
   $request{'whisker'}->{'method'} = GET;
   $request{'whisker'}->{'http_eol'}=$http_eol;
   LW2::http_close(\%request);  # force-close any old connections
   delete $request{'whisker'}->{'data'};
   delete $request{'Content-Encoding'};
   delete $request{'Content-Length'};
   LW2::http_fixup_request(\%request);

   dump_var("Request Hash", \%request);
   if ($CLI{pause} > 0) { sleep $CLI{pause}; }
   LW2::http_do_request_timeout(\%request,\%result);
   dump_var("Result Hash", \%result);

   $ext=$REQS{$file};
   $FoF{$ext}{response} = $result{'whisker'}->{'code'};
   if (defined $result{location}) 
     { 
   $FoF{$ext}{location} = $result{location};
   $file=char_escape($file);
   $FoF{$ext}{location} =~ s/$file//;
#  if ($ext eq "php")
#   {
#nprint "$ext $FoF{$ext}\n";
#nprint "loc is:$FoF{$ext}{location}\n";  exit; 
#}
     } # handle .com to .org redirs or whatnot

   # if it is not specific type, figure out Content or HASH method...
   if    ($FoF{$ext}{response} eq 404) { $FoF{$ext}{mode} = "STD";    next; }
   elsif ($FoF{$ext}{response} eq 200) { $FoF{$ext}{mode} = "OK";     }
   elsif ($FoF{$ext}{response} eq 410) { $FoF{$ext}{mode} = "STD";    next; }
   elsif ($FoF{$ext}{response} eq 401) { $FoF{$ext}{mode} = "STD";    next; }
   elsif ($FoF{$ext}{response} eq 403) { $FoF{$ext}{mode} = "STD";    next; }
   elsif ($FoF{$ext}{response} eq 300) { $FoF{$ext}{mode} = "REDIR";  next; }
   elsif ($FoF{$ext}{response} eq 301) { $FoF{$ext}{mode} = "REDIR";  next; }
   elsif ($FoF{$ext}{response} eq 302) { $FoF{$ext}{mode} = "REDIR";  next; }
   elsif ($FoF{$ext}{response} eq 303) { $FoF{$ext}{mode} = "REDIR";  next; }
   elsif ($FoF{$ext}{response} eq 307) { $FoF{$ext}{mode} = "REDIR";  next; }
   else  { $FoF{$ext}{mode} = "OTHER";  }

   # if we've got an OK/OTHER response, look at content first
   my $done=0;
   
   foreach my $string (keys %ERRSTRINGS)
       {
        nprint ("- Testing error string: $ERRSTRINGS{$string}","d");
        if ($result{'whisker'}->{'data'} =~ /$ERRSTRINGS{$string}/i)
        {
          $FoF{$ext}{type}="CONTENT";
          $FoF{$ext}{match}=$ERRSTRINGS{$string};
          $done=1;
          last;
         }
       }

   if (!$done) # we have to get desperate...
     {
      if (length($result{'whisker'}->{'data'}) eq 0) # blank content
       {
        $FoF{$ext}{type}="BLANK";
        $FoF{$ext}{match}="";
        $done=1;
       }
      if (!$done)  # md4!
      { 
       my $content=rm_active_content($result{'whisker'}->{'data'});
       $FoF{$ext}{match}=LW2::md4($content);
       $FoF{$ext}{type}="HASH";
      }
     }
  }

 # lastly, get a hash of index.php so we can cut down on some false positives...
   $NIKTO{totalrequests}++;
   $request{'whisker'}->{'uri'}    = "/index.php?";
   $request{'whisker'}->{'method'} = GET;
   $request{'whisker'}->{'http_eol'}=$http_eol;
   LW2::http_close(\%request);  # force-close any old connections
   delete $request{'whisker'}->{'data'};
   delete $request{'Content-Encoding'};
   delete $request{'Content-Length'};
   LW2::http_fixup_request(\%request);

   dump_var("Request Hash", \%request);
   if ($CLI{pause} > 0) { sleep $CLI{pause}; }
   LW2::http_do_request_timeout(\%request,\%result);
   dump_var("Result Hash", \%result);

   my $content=rm_active_content($result{'whisker'}->{'data'});
   $FoF{"index.php"}{match}=LW2::md4($content);
   $FoF{"index.php"}{type}="HASH";

 # foreach $ext (keys %FoF)   { nprint "$ext: mode $FoF{$ext}{mode}, response $FoF{$ext}{response}, type $FoF{$ext}{type}\n"; }
return;
}
###############################################################################
sub rm_active_content
{
 # Try to remove active content which could mess up the file's signature
 my $cont=$_[0];
 
 # Dates
 $cont =~ s/([0-9]{4}|[0-9]{1,2})(\-|\.|\/)[0-9]{1,2}(\-|\.|\/)([0-9]{4}|[0-9]{1,2})//g;
 $cont =~ s/(([0-9]{2}:[0-9]{2}(:)?([0-9]{2})?)|([0-9]{8,14}|[0-9]{6}))//g;
 $cont =~ s/(mon|tue|wed|thu|fri|sat|sun)(,)? [0-9]{1,2} (jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) [0-9]{4} ([0-9]{2}:[0-9]{2}(:)?([0-9]{2})?)?//ig;
 $cont =~ s/([0-9]{2,4})? ?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)([0-9]{2,4})?(\/)?([0-9]{2})?([0-9]{2})?//gi;
 
 # Page load times
 $cont =~ s/[0-9\.]+ second//gi;
 $cont =~ s/[0-9]+ queries//gi; # wordpress
 
 # Advertising
 
 # URI, if provided, plus encoded versions of it
 if (defined $_[1]) 
   { 
      my $e = $_[1];

      # match pages which link to themselves w/diff args
      $e =~ s/^\/index.php\??//;  
      $e =~ s/([^a-zA-Z0-9\s])/\\$1/g;
      $cont =~ s/$e//gs; 

      # again but with the index.php in place
      $e = $_[1];
      $e =~ s/([^a-zA-Z0-9\s])/\\$1/g;
      $cont =~ s/$e//gs; 

      # base 64
      $e=LW2::encode_base64($_[1]);
      $cont =~ s/$e//gs; 
   
      # hex encoded
      $e=LW2::encode_uri_hex($_[1]);
      $cont =~ s/$e//gs; 

      # unicode encoded
      $e=LW2::encode_unicode($_[1]);
      $cont =~ s/$e//gs; 

      # url encoding, full url
      $e = $_[1];
      $e =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
      $cont =~ s/$e//gs; 

      # url encoding, query portion
      if ($_[1] =~ /\?/)
         {
            $e = $_[1];
            $e =~ s/\?(.*$)//;
            my $qs = $1;
            $qs =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
            $e .= "?$qs";
            $cont =~ s/$e//gs; 
         }
   }

 return $cont;
}
###############################################################################
sub dump_target_info
{
   my ($mark) = @_;
   # print out initial connection info
   my $sslprint="";

   if ($mark->{ssl}) 
   { 
      my $sslcyphers=$result{whisker}->{ssl_cipher}      || "Unknown"; 
      my $sslissuers=$result{whisker}->{ssl_cert_issuer} || "Unknown";
      my $sslinfo=$result{whisker}->{ssl_cert_subject}   || "Unknown";
      $sslprint="$DIV\n";
      $sslprint.="+ SSL Info:        Ciphers: $sslcyphers\n                   Info:    $sslissuers\n                   Subject: $sslinfo";
   }

   if ($mark->{ip} =~ /[a-z]/i) { nprint("+ Target IP:       (proxied)"); }
   else { nprint("+ Target IP:          $mark->{ip}"); }

   nprint("+ Target Hostname:    $mark->{hostname}"); 
   nprint("+ Target Port:        $mark->{port}");
   if ((defined $CLI{vhost}) && ($CLI{vhost} ne $mark->{hostname}))
   { nprint("+ Virtual Host:   $CLI{vhost}"); }
   if (defined $request{'whisker'}->{'proxy_host'}) 
   { nprint("- Proxy:       $request{'whisker'}->{'proxy_host'}:$request{'whisker'}->{'proxy_port'}"); }
   if (defined $NIKTO{hostid}) { nprint("- Host Auth:       ID: $NIKTO{hostid}, PW: $NIKTO{hostpw}, Realm: $NIKTO{hostdomain}","v"); }
   if ($mark->{ssl}) { nprint($sslprint); }
   if (defined $NIKTO{anti_ids} && defined $CLI{evasion})
   {
      for (my $i=1;$i<=(keys %{$NIKTO{anti_ids}});$i++) { if ($CLI{evasion} =~ /$i/) { nprint("+ Using IDS Evasion:  $NIKTO{anti_ids}{$i}"); }}
   }
      if (defined $NIKTO{mutate_opts} && defined $CLI{mutate})
   {
      for (my $i=1;$i<=(keys %{$NIKTO{mutate_opts}});$i++) { if ($CLI{mutate} =~ /$i/) { nprint("+ Using Mutation:     $NIKTO{mutate_opts}{$i}"); }}
   }
   my $time=date_disp($mark->{start_time});
   nprint("+ Start Time:         $time");
   nprint($DIV);

   if ($mark->{banner} ne "") 
   { 
      nprint("+ Server: $mark->{banner}");
   }
   else 
   {
      nprint("+ Server: No banner retrieved");
   }

   return;
}
###############################################################################
sub general_config
{
 ## gotta set these first
 $|=1;
 $NIKTO{anti_ids}{1}="Random URI encoding (non-UTF8)";
 $NIKTO{anti_ids}{2}="Directory self-reference (/./)";
 $NIKTO{anti_ids}{3}="Premature URL ending";
 $NIKTO{anti_ids}{4}="Prepend long random string";
 $NIKTO{anti_ids}{5}="Fake parameter";
 $NIKTO{anti_ids}{6}="TAB as request spacer";
 $NIKTO{anti_ids}{7}="Change the case of the URL";
 $NIKTO{anti_ids}{8}="Use Windows directory separator (\\)";

 $NIKTO{mutate_opts}{1}="Test all files with all root directories";
 $NIKTO{mutate_opts}{2}="Guess for password file names";
 $NIKTO{mutate_opts}{3}="Enumerate user names via Apache (/~user type requests)";
 $NIKTO{mutate_opts}{4}="Enumerate user names via cgiwrap (/cgi-bin/cgiwrap/~user type requests)";
 $NIKTO{mutate_opts}{5}="Attempt to brute force sub-domain names, assume that the host name is the parent domain";
 $NIKTO{mutate_opts}{6}="Attempt to guess directory names from the supplied dictionary file";

 $NIKTO{display}{1}="Show redirects";
 $NIKTO{display}{2}="Show cookies received";
 $NIKTO{display}{3}="Show all 200/OK responses";
 $NIKTO{display}{4}="Show URLs which require authentication";
 $NIKTO{display}{V}="Verbose Output";
 $NIKTO{display}{D}="Debug Output";
 
 $NIKTO{tuning}{1}="Interesting File / Seen in logs";
 $NIKTO{tuning}{2}="Misconfiguration / Default File";
 $NIKTO{tuning}{3}="Information Disclosure";
 $NIKTO{tuning}{4}="Injection (XSS/Script/HTML)";
 $NIKTO{tuning}{5}="Remote File Retrieval - Inside Web Root";
 $NIKTO{tuning}{6}="Denial of Service";
 $NIKTO{tuning}{7}="Remote File Retrieval - Server Wide";
 $NIKTO{tuning}{8}="Command Execution / Remote Shell";
 $NIKTO{tuning}{9}="SQL Injection";
 $NIKTO{tuning}{0}="File Upload";
 $NIKTO{tuning}{a}="Authentication Bypass";
 $NIKTO{tuning}{b}="Software Identification";
 $NIKTO{tuning}{c}="Remote Source Inclusion";
 $NIKTO{tuning}{x}="Reverse Tuning Options (i.e., include all except specified)";

 $NIKTO{options_short}= "
       -Cgidirs+           scan these CGI dirs: 'none', 'all', or values like \"/cgi/ /cgi-a/\"
       -dbcheck            check database and other key files for syntax errors (cannot be abbreviated)
       -evasion+           ids evasion technique
       -Format+            save file (-o) format
       -host+              target host
       -Help               Extended help information
       -id+                host authentication to use, format is userid:password
       -mutate+            Guess additional file names
       -mutate-options+    Provide extra information for mutations
       -output+            Write output to this file
       -nocache            Disables the URI cache
       -nossl              Disables using SSL
       -no404              Disables 404 checks
       -port+              Port to use (default 80)
       -Display+           Turn on/off display outputs
       -ssl                Force ssl mode on port
       -Single             Single request mode
       -timeout+           Timeout (default 2 seconds)
       -Tuning+            Scan tuning
       -update             Update databases and plugins from cirt.net (cannot be abbreviated)
       -Version            Print plugin and database versions
       -vhost+             Virtual host (for Host header)
   + requires a value
   ";

 $NIKTO{options}="
   Options:
       -config+            Use this config file
       -Cgidirs+           Scan these CGI dirs: 'none', 'all', or values like \"/cgi/ /cgi-a/\"
       -Display+           Turn on/off display outputs:\n";
   foreach my $k (sort keys %{$NIKTO{display}}) 
      { $NIKTO{options} .= "                               $k     $NIKTO{display}{$k}\n"; }

 $NIKTO{options}.="       -dbcheck           Check database and other key files for syntax errors (cannot be abbreviated)
       -evasion+          IDS evasion technique:\n";
   foreach my $k (sort keys %{$NIKTO{anti_ids}}) 
      { $NIKTO{options} .= "                               $k     $NIKTO{anti_ids}{$k}\n"; }

 $NIKTO{options}.="       -findonly          Find http(s) ports only, don't perform a full scan
       -Format+           Save file (-o) format:
                                htm   HTML Format
                                csv   Comma-separated-value
                                txt   Plain text (default if not specified)
                                xml   XML Format
       -host+             Target host
       -Help              Extended help information
       -id+               Host authentication to use, format is userid:password
       -mutate+           Guess additional file names:\n";
   foreach my $k (sort keys %{$NIKTO{mutate_opts}}) 
      { $NIKTO{options} .= "                               $k     $NIKTO{mutate_opts}{$k}\n"; }

 $NIKTO{options}.="       -mutate-options    Provide information for mutates
       -nocache           Disables the URI cache
       -nossl             Disables using SSL
       -no404             Disables nikto attempting to guess a 404 page
       -output+           Write output to this file
       -port+             Port to use (default 80)
       -Pause+            Pause between tests (seconds)\n";

 $NIKTO{options}.="       -root+             Prepend root value to all requests, format is /directory
       -ssl               Force ssl mode on port
       -Single            Single request mode
       -timeout+          Timeout (default 2 seconds)
       -Tuning+           Scan tuning:\n";
   foreach my $k (sort keys %{$NIKTO{tuning}}) 
      { $NIKTO{options} .= "                               $k     $NIKTO{tuning}{$k}\n"; }

 $NIKTO{options}.="       -useproxy          Use the proxy defined in config.txt
       -update            Update databases and plugins from cirt.net (cannot be abbreviated)
       -Version           Print plugin and database versions
       -vhost+            Virtual host (for Host header)
   + requires a value
   ";

 ### CLI STUFF
 $CLI{pause}=$CLI{html}=$OUTPUT{verbose}=$CLI{skiplookup}=$NIKTO{totalrequests}=$OUTPUT{debug}=0;
 $CLI{all_options}=join(" ",@ARGV);
 
 # preprocess CLI options which cannot be abbreviated
 for (my $i=0;$i<=$#ARGV;$i++)
  {
   if    ($ARGV[$i] eq '-dbcheck')  { dbcheck(); }   
   elsif ($ARGV[$i] eq '-update')   { check_updates(); }
   elsif ($ARGV[$i] eq '-verbose')  { print STDERR "-verbose is deprecated, please  use '-D V' instead (verbose enabled)\n"; $OUTPUT{verbose}=1; $ARGV[$i]=""; }
   elsif ($ARGV[$i] eq '-debug')    { print STDERR "-debug is deprecated, please  use '-D D' instead (debug enabled)\n";     $OUTPUT{debug}=1;   $ARGV[$i]=""; }
  }

 GetOptions
 (
           "nolookup"                => \$CLI{skiplookup},
           "config=s"                => \$CLI{config},
           "Cgidirs=s"               => \$CLI{forcecgi},
           "mutate=s"                => \$CLI{mutate},
           "mutate-options=s"      => \$CLI{mutate-options},
           "id=s"                    => \$CLI{hostauth},
           "evasion=s"               => \$CLI{evasion},
           "port=s"                  => \$CLI{ports},
           "findonly"                => \$CLI{findonly},
           "root=s"                  => \$CLI{root},
           "timeout=s"               => \$CLI{timeout},
           "Pause=s"                 => \$CLI{pause},
           "ssl"                     => \$CLI{ssl},
           "nocache"                 => \$CLI{nocache},
           "nossl"                   => \$CLI{nossl},
           "no404"                   => \$CLI{nofof},
           "useproxy"                => \$CLI{useproxy},
           "Help"                    => \$CLI{help},
           "vhost=s"                 => \$CLI{vhost},
           "host=s"                  => \$CLI{host},
           "output=s"                => \$CLI{file},
           "Format=s"                => \$CLI{format},
           "Display=s"               => \$CLI{display},
           "Single"                  => \$CLI{Single},
           "Tuning=s"                => \$CLI{tuning},
           "Version"                 => \$CLI{version},
 ) or usage(0);

 if    ($CLI{help})     { usage(2);  }
 elsif ($CLI{version})  { version(); }
 elsif ($CLI{Single})   { single();  }
 
 # output file
 unless (defined $CLI{file})      { $CLI{file}="-"; }      # default to standard output

 unless (defined $CLI{format})    { $CLI{format}=""; }  # default to none
 elsif ($CLI{format} =~ /te?xt/i) { $CLI{format}="txt"; }
 elsif ($CLI{format} =~ /html?/i) { $CLI{format}="htm"; }
 elsif ($CLI{format} =~ /csv/i)   { $CLI{format}="csv"; }
 elsif ($CLI{format} =~ /xml/i)   { $CLI{format}="xml"; }
 else                             { nprint("+ ERROR: Invalid output format '$CLI{format}'"); exit; }
 
 # verify readable dtd 
 if ($CLI{format} =~ /xml/i and !-r $NIKTOCONFIG{NIKTODTD})   { nprint("+ ERROR: reading DTD"); exit; } 

 # screen output
 if (defined $CLI{display})
 {
    if ($CLI{display} =~ /d/i)  { $OUTPUT{debug}=1; }
    if ($CLI{display} =~ /v/i)  { $OUTPUT{verbose}=1; }
 }
 
 # port(s)
 if (defined $CLI{ports})
 {
    $CLI{ports}=~s/^\s+//;
    $CLI{ports}=~s/\s+$//;
    #if ($CLI{ports} eq "") { $CLI{ports}=80; }
    if ($CLI{ports} =~ /[^0-9\-\, ]/) { nprint("+ ERROR: Invalid port option '$CLI{ports}'"); exit; }
 }

 # Fixup
 if (defined $CLI{root})
 {
    $CLI{root} =~ s/\/$//;
    if (($CLI{root} !~ /^\//) && ($CLI{root} ne "")) { $CLI{root} = "/$CLI{root}"; }
 }

 if (defined $CLI{hostauth})
 {
  my @x=split(/:/,$CLI{hostauth});
  if (($#x > 2) || ($x[0] eq ""))
   { nprint("+ ERROR: '$CLI{hostauth}' (-i option) syntax is 'user:password' or 'user:password:domain' for host authentication."); exit;  }
 } 

 if (defined $CLI{evasion})
 {
    $CLI{evasion}=~s/[^0-9]//g;
 }

 $NIKTO{timeout}=$CLI{timeout} || 10;

 $NIKTO{useragent}="Mozilla/4.75 ($NIKTO{name}/$NIKTO{version})";
 
 # SSL Test
 if (!LW2::ssl_is_available()) { nprint("- ***** SSL support not available (see docs for SSL install instructions) *****"); }
 
 # Notices
 my $notice="";
 if (defined $CLI{root}) 
   { $notice .= "Prepending '$CLI{root}' to requests"; }
 if ($CLI{pause} > 0) 
   { $notice .= ", Pausing $CLI{pause} seconds per request"; }
 $notice =~ s/^, //; 
 if ($notice ne '')
   { nprint("-***** $notice *****"); }

 # get core version
 open(FI,"<$NIKTOCONFIG{PLUGINDIR}/nikto_core.plugin");
 my @F=<FI>;
 close(FI);
 my @VERS=grep(/^#VERSION/,@F);
 $NIKTO{core_version}=$VERS[0];
 $NIKTO{core_version}=~s/\#VERSION,//;
 chomp($NIKTO{core_version});
 $NIKTO{TMPL_HCTR}=0;
 $NIKTO{TMPL_SUMMARY}=0;

 return;
}
###############################################################################
sub resolve
{
   my $ident=$_[0] || return;

   my ($name, $ip, $dn)="";

   # ident is name, lookup IP
   if ($ident =~ /[^0-9\.]/)   # not an IP, assume name
   {
      if ($CLI{skiplookup})
      {
         nprint("+ ERROR: -skiplookup set, but given name\n");
         exit;
      }
      $ip=gethostbyname($ident);
      if (($ip eq "") && ($request{'whisker'}->{'proxy_host'} ne ""))    # can't resolve name to IP, but using proxy
      {
         $name=$ident;
         $ip=$name;
      }
      elsif (($ip eq "") && ($request{'whisker'}->{'proxy_host'} eq "")) # can't resolve name to IP, no proxy set
      { 
         nprint("+ ERROR: Cannot resolve hostname '$ident'\n"); 
         return;
      }
      else
      { 
         use IO::Socket;
         $ip=inet_ntoa($ip); 
         if (($ip !~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) && ($ip ne "")) # trap for proxy...
         {
            nprint("+ ERROR: Invalid IP '$ip'\n\n");
            return;
         }
         $name=$ident;
      }
   }
   else   # ident is IP, lookup name
   {
      if (($ident !~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) && ($ident ne "")) # trap for proxy...
      { 
         nprint("+ ERROR: Invalid IP '$ident'\n\n");
         return;
      }

      $ip=$ident;
      if (!$CLI{skiplookup}) 
      {
         use IO::Socket;
         my $temp_ip=inet_aton($ip);
         $name=gethostbyaddr($temp_ip,AF_INET); 
         # check reverse dns to avoid an inet_aton error
         my $rdnsip=gethostbyname($name);
         if ($rdnsip ne "")
         {
            $rdnsip=inet_ntoa($rdnsip);
            if ($ip ne $rdnsip) { $name=$ip; }  # Reverse DNS does not match
         }
         else { $name = $ip; }  # Reverse DNS does not exist
      }
      if ($name eq "") { $name=$ip; }
   }

   # set displayname -- name takes precedence
   if ($name ne "") { $dn=$name; }
   else             { $dn=$ip;   }

   return $name,$ip,$dn;
}
###############################################################################
sub set_targets
{
   my ($hostlist, $portlist, $ssl, $root)=@_;
   my $host_ctr=1;
   my @hosts=split(/,/,$hostlist);
   my @ports=split(/,/,$portlist);
   my @checkhosts;
   my @results;
   my @marks;
   my $defaultport=($ssl)?443:80;

   # Check for old style portlist and expand
   my @newports;
   foreach my $port (@ports)
   {
      if ($port =~ /-/)
      {
         my $start, $end;
         my @temp=split(/-/,$port);
         $start=$temp[0];
         $end=$temp[1];
         if ($start eq "") { $start=0; }
         if ($end eq "") { $end=65535; }
         if ($start > $end)
         {
            nprint("- ERROR port range $port doesn't make sense - assuming 80/tcp");
            next;
         }
         for (my $i=$start;$i<=$end;$i++)
         {
            push(@newports,$i);
         }
      }
      else
      {
         push(@newports, $port);
      }
   }
   @ports=@newports;
   
   nprint("- Getting targets","v");
   if (scalar(@ports) == 1)
   {
      # Only one port is set, assume that is the default port
      $defaultport=$ports[0];
   }
   
   # check whether it's a file or an entry
   foreach my $host (@hosts)
   {
      if (-e $host || $host eq "-")
      {
         @results=parse_hostfile($host);
         push(@checkhosts,@results);
      }
      else
      {
         push(@checkhosts,$host);
      }
   }
   
   # Now parse the list of checkhosts
   foreach my $host (@checkhosts)   
   { 
      my $defhost;
      my $defport;
      $host =~ s/\s+//g;
      if (!defined $host) { next; }

      # is it a URL?
      if ($host =~ /^https?:\/\//)
      {
         my @hostdata=LW2::uri_split($host);
         
         $defhost = $hostdata[2];
         $defport = $hostdata[3];

         if ((!defined $root) && (defined $hostdata[0]))
         {
            $root=$hostdata[0];
            nprint("- Added -root value of '$root'","d");
         }
      }
      else
      {
         my @h = split(/\:|\,/, $host);
         
         $defhost = $h[0];
         $defport = $h[1];
      }
      
      # Now skip through all ports if port hasn't been added
      if ($defport eq "" && scalar(@ports) > 0)
      {

         foreach $port (@ports)
         {
            my $markhash={};
            $markhash->{ident} = $defhost;
            $markhash->{port} = $port;
            nprint("- Target:$markhash->{ident} port:$markhash->{port}","v");
            push(@marks,$markhash);
         }
      }
      else
      {
         if ($defport eq "")
         {
            $defport = $defaultport;
         }
         my $markhash={};
         $markhash->{ident} = $defhost;
         $markhash->{port} = $defport;
         
         nprint("- Target:$markhash->{ident} port:$markhash->{port}","v");
         push(@marks,$markhash);
      }
   }

   return @marks;
}

sub parse_hostfile
{
   my ($file)=@_;
   my $nmap=0;
   my @results;
   my $hostdesc;

   open(IN,$file) || die print STDERR "+ ERROR: Cannot open '$file':$@\n";
   while(<IN>)
   {
      my $found=0;
      # Check whether this is an nmap oG file
      chomp;
      if (/^# Nmap [0-9.]* scan initiated/)
      {
         $nmap=1;
      }
      s/\#.*$//;
      if ($_ eq "") { next; }
      # Parse for nmap files
      if ($nmap == 1)
      {
         # First get the host name
         my @line=split(/ /);
         my @name=split(/\(|\)/, $line[2]);
         $hostdesc=($name[1] ne "")?$name[1]:$line[1];
         
         # now parse the ports list
         for (my $i = 3; $i <= $#line; $i++)
         {
            my @ports=split(/\//,$line[$i]);
            if ($ports[1] eq "open" && $ports[4] eq "http")
            {
               $found=1;
               $hostdesc .= ":" . $ports[0];
            }
         }
      }
      else
      {
         # just add it to the list
         $hostdesc=$_;
         $found=1;
      }
      push(@results,$hostdesc) if ($found);
   }
   close(IN);
   return(@results);
}

###############################################################################
sub load_databases
{
 my @dbs=qw/db_404_strings  db_outdated  db_realms  db_tests  db_variables/;
 my $prefix = $_[0];

 unless (defined $prefix)
 {
    $prefix="";
 }

 # verify required files
 for my $file (@dbs) 
  { 
   if (!-r "$NIKTOCONFIG{PLUGINDIR}/$file") { die nprint("+ ERROR: Can't find/read required file \"$NIKTOCONFIG{PLUGINDIR}/$file\""); }
  }

 for my $file (@dbs) 
  { 
   my $filename = $NIKTOCONFIG{PLUGINDIR} . "/" . $prefix . $file;
   if (!-r $filename) { next; }
   open(IN,"<$filename") || die nprint("+ ERROR: Can't open \"$filename\":$!\n");
   
   # db_tests
   if ($file eq 'db_tests') { push(@DBFILE,<IN>); next; }
   
   # all the other files require per-line processing
   else
     {
       my @file;
       # Cleanup
       while (<IN>)
        {
          chomp;
          $_ =~ s/#.*$//;
          $_ =~ s/\s+$//;
          $_ =~ s/^\s+//;
          if ($_ ne "") { push(@file,$_); }
        }
        
        # db_variables
        if ($file eq 'db_variables')
          {
            foreach my $l (@file) 
              { 
                if ($l =~ /^@/) 
                  {
                    my @temp=split(/=/,$l);
                    $VARIABLES{$temp[0]}.= "$temp[1]";
                  }
               }
            }

       # db_404_strings
       elsif ($file eq 'db_404_strings')
          {
            my $db_404_ctr=keys %ERRSTRINGS;
            foreach my $l (@file) 
              { 
                $ERRSTRINGS{$db_404_ctr} = $l;
                $db_404_ctr++;
              }
           }
   
       # db_outdated
       elsif ($file eq 'db_outdated')
          {
            foreach my $l (@file) 
              { 
                my @T=parse_csv($l);
                $OVERS{$T[1]}{$T[2]}=$T[3];
                $OVERS{$T[1]}{tid}=$T[0];
              }
           }
   
       # db_realms
       elsif ($file eq 'db_realms')
          {
            my $db_realms_ctr= keys %REALMS;
            foreach my $l (@file) 
              { 
                my @t=parse_csv($l);
                $REALMS{$db_realms_ctr}{tid}    = $t[0];
                $REALMS{$db_realms_ctr}{realm}  = $t[1];
                $REALMS{$db_realms_ctr}{id}     = $t[2];
                $REALMS{$db_realms_ctr}{pw}     = $t[3];
                $REALMS{$db_realms_ctr}{msg}    = $t[4];
                $db_realms_ctr++;
              }
            # Cheat and add CLI directly to REALMS
            if (defined $CLI{hostauth})
            {
             my @x=split(/:/,$CLI{hostauth});

             $REALMS{$db_realms_ctr}{tid} = "700500";
             $REALMS{$db_realms_ctr}{realm} = (defined $x[2])?$x[2]:'@ANY';
             $REALMS{$db_realms_ctr}{pw} = $x[1];
             $REALMS{$db_realms_ctr}{id} = $x[0];
             $REALMS{$db_realms_ctr}{msg} = "Credentials provided by CLI.";
            }
           }

   close(IN);
  }
 }
 
 return;
}
###############################################################################
sub dbcheck
{
 my @dbs=qw/db_404_strings  db_outdated  db_realms  db_tests  db_variables/;
 my $prefix = $_[0];

 if ($prefix eq ""  )  { nprint "\n-->\tNikto Databases\n"; }
 if ($prefix eq "u"  ) { nprint "\n-->\tUser Databases\n"; }

 for my $file (@dbs) 
  { 
   my $filename = $NIKTOCONFIG{PLUGINDIR} . "/" . $prefix . $file;
   if (!-r $filename) { next; }
   open(IN,"<$filename") || die nprint("+ ERROR: Can't open \"$filename\":$!\n");
   
   nprint "Syntax Check: $filename\n";
   
   if ($file eq 'db_outdated') 
     {
       foreach $line (<IN>)
         {
           $line =~ s/^\s+//;
           if ($line =~ /^\#/) { next; }
           chomp($line);
           if ($line eq "") { next; }
           my @L=parse_csv($line);
           if ($line !~ /^\".*\"\,\".*\"\,\".*\"$/) { print STDERR "\tERROR: Invalid syntax ($#L): $line\n"; next; }
           if ($#L ne 3) { print STDERR "\tERROR: Invalid syntax ($#L): $line\n"; next; }
           $ENTRIES{"$L[0]"}++;
         }

          foreach $entry (keys %ENTRIES) { if ($ENTRIES{$entry} > 1) { print STDERR "\tERROR: Duplicate ($ENTRIES{$entry}): $entry\n"; } }
          nprint "\t" . keys(%ENTRIES) . " entries\n";
     }
   elsif ($file eq 'db_tests') 
     {
       my %ENTRIES;
       foreach my $line (<IN>)
         {
           if ($line !~ /^\"/)  { next; }
           my @L=parse_csv($line);
           if ($L[4] !~ /(GET|POST|TRACE|TRACK|OPTIONS|SEARCH|INDEX)/i) { print STDERR "\tERROR: Possibly invalid method: $L[4] on ($line)\n"; }
           if ($L[5] eq "") { print STDERR "\tERROR: blank conditional: $line"; next; }
           if ($line !~ /^\".*\",\".*\",\".*\",\".*\",\".*\"/) { print STDERR "\tERROR: Invalid syntax ($#L): $line\n"; next; }
           if ($line !~ /^(\".*\",){11}\".*\"/) { print STDERR "\tERROR: Invalid syntax ($#L): $line\n"; next; }
           if (($L[3] =~ /^\@CGI/) && ($L[3] !~ /^\@CGIDIRS/)) { print STDERR "\tERROR: Possible \@CGIDIRS misspelling: $line"; }
           $ENTRIES{"$L[3],$L[4],$L[5],$L[6],$L[7],$L[8],$L[9],$L[10],$L[12]"}++;
         }

       foreach $entry (keys %ENTRIES) { if ($ENTRIES{$entry} > 1) { print STDERR "\tERROR: Duplicate ($ENTRIES{$entry}): $entry\n"; } }
       nprint "\t" . keys(%ENTRIES) . " entries\n";
     }
   elsif ($file eq 'db_variables') 
     {
       my $ctr=0;
       foreach $line (<IN>)
         {
           if ($line !~ /^\@/)  { next; }
           if ($line !~ /^\@.+\=.+$/i ) { print STDERR "\tERROR: Invalid syntax: $line\n"; }
           $ctr++;
         }
        nprint "\t$ctr entries\n";
     }
   elsif ($file eq 'db_realms') 
     {
       my $ctr=0;
       foreach $line (<IN>)
         {
           if ($line !~ /^\"/)  { next; }
           chomp($line);
           my @L=parse_csv($line);
           if ($#L ne 4) { print STDERR "\tERROR: Invalid syntax: $line\n"; }
           $ctr++;
         }
        nprint "\t$ctr entries\n";
     }
   elsif ($file eq 'db_404_strings') 
     {
       my $ctr=0;
       foreach $line (<IN>)
         {
           # not really any syntax to check
           $ctr++;
         }
        nprint "\t$ctr entries\n";
     }

   close(IN);  
  }
  
 if ($_[0] eq "") { dbcheck('u'); }  # do this once

 nprint "\n";
 exit;
}
###############################################################################
sub get_banner
{
   my ($mark) = @_;
   my %headers;
   
   (my $res, $content) = nfetch($mark,"/","GET","",\%headers);

   return $headers{server};
}
###############################################################################
sub port_check
{
   my ($hostname, $ip, $port) = @_;
   my %mark, %headers;
   
   $mark{hostname}=$hostname;
   $mark{ip}=$ip;
   $mark{port}=$port;
   $mark{ssl}=0;
   
   if (defined $CLI{vhost}) { $mark{vhost} = $CLI{vhost} };

   # test for proxy
   proxy_check() unless $PROXYCHECKED;

   my @checktypes=('HTTP', 'HTTPS');
   if ($CLI{ssl}) { shift(@checktypes); }
   if ($CLI{nossl}) { pop(@checktypes); }

   foreach my $method (split(/ /,$NIKTOCONFIG{CHECKMETHODS}))
   {
      $request{'whisker'}->{'method'}=$method;
      foreach my $checkssl (@checktypes)
      {
         nprint("- Checking for $checkssl on port $ip:$port, using $method","v");
         
         $mark{ssl}=($checkssl eq "HTTP")?0:1;
         my ($res, $content)=nfetch(\%mark, "/", $method, "", \%headers);

         if ($res) 
         { 
            # this will fix for some Apaches that are smart enough to answer non ssl reqs on an ssl server
            if (defined $content &&
                $content =~ /speaking plain HTTP to an SSL/) 
            {
               dump_var("Result Hash", \%result);
               next;
            }
            nprint("- $checkssl Server found: $ip:$port \t$headers{server}","d"); 
            return $mark{ssl}+1;
         }
      }
   }


   nprint("+ No web server found on $ip:$port");
   nprint("---------------------------------------------------------------------------");
   
   return 0;
}

sub load_plugins
{
   my @pluginlist=dirlist("$NIKTOCONFIG{PLUGINDIR}",'\.plugin$');

   foreach my $plugin (@pluginlist)
   {
      my $plugin_name = $plugin;
      $plugin_name =~ s/\.plugin$//;
      my $plugin_init = $plugin_name . "_init";
      eval {
         require "$NIKTOCONFIG{PLUGINDIR}/$plugin";
      };
      if ($@)
      {
         nprint("- Could not load or parse plugin: $plugin_name\n Error: ");
         warn $@;
         nprint("- The plugin could not be run.");
      }
      else
      {
         nprint("- Initialising plugin $plugin_name","v");
         # Call initialisation method
         if (defined &$plugin_init)
         {
            my $pluginhash=&$plugin_init;
            # Add default weights if not already assigned
            $pluginhash->{recon_weight}=50 unless (defined $pluginhash->{recon_weight});
            $pluginhash->{scan_weight}=50 unless (defined $pluginhash->{scan_weight});
            $pluginhash->{report_weight}=50 unless (defined $pluginhash->{report_weight});
            push(@PLUGINS, $pluginhash);
            nprint("- Loaded \"$pluginhash->{full_name}\" plugin.","v");
         }
      }
   }
}

###############################################################################
# this is ugly, and potentially dangerous if untrusted plugins are present
###############################################################################
sub run_plugins
{
   my ($mark) = @_;
   nprint("- Entering recon phase","v");
   # This is a frig until I can think of a better way of achieving it
   foreach my $i (1..100)
   {
      foreach my $plugin (@PLUGINS)
      {
         if (defined $plugin->{recon_method} && $plugin->{recon_weight} == $i)
         {
            my $run=1;
            # first check for conditionals
            if (defined $plugin->{recon_cond})
            {
               # Evaluate condition
               $run=eval($plugin->{recon_cond});
            }
            if ($run)
            {   
               nprint("- Running recon for \"$plugin->{full_name}\" plugin","v");
               &{$plugin->{recon_method}}($mark);
            }
         }
      }
   }

   nprint("- Entering scan phase","v");
   # This is a frig until I can think of a better way of achieving it
   foreach my $i (1..100)
   {
      foreach my $plugin (@PLUGINS)
      {
         if (defined $plugin->{scan_method} && $plugin->{scan_weight} == $i)
         {
            my $run=1;
            # first check for conditionals
            if (defined $plugin->{scan_cond})
            {
               # Evaluate condition
               $run=eval "$plugin->{scan_cond}";
            }
            if ($run)
            {   
               nprint("- Running scan for \"$plugin->{full_name}\" plugin","v");
               &{$plugin->{scan_method}}($mark);
            }
         }
      }
   }
   
   return;
}
   
sub report_head
{
   my ($format,$file) = @_;
   nprint("- Opening reports","v");
   
   # For tuning set up a list of report methods, formats and handles
   
   # This is a frig until I can think of a better way of achieving it
   foreach my $i (1..100)
   {
      foreach my $plugin (@PLUGINS)
      {
         if (defined $plugin->{report_item} && $plugin->{report_weight} == $i)
         {
            my $run=1;
            # first check for conditionals
            if (defined $plugin->{report_format})
            {
               # Evaluate condition
               $run=($format eq $plugin->{report_format});
            }
            if ($run)
            {   
               nprint("- Opening report for \"$plugin->{full_name}\" plugin","v");
               my $handle;
               if (defined $plugin->{report_head})
               {
                  $handle=&{$plugin->{report_head}}($file);
               }
               # Now store this 
               my $report_entry=
               {
                  host_start => $plugin->{report_host_start},
                  host_end   => $plugin->{report_host_end},
                  item       => $plugin->{report_item},
                  close      => $plugin->{report_close},
                  handle     => $handle,
               };
               push(@REPORTS, $report_entry);
            }
         }
      }
   }
   return;
}
###############################################################################
sub report_host_start
{
   my ($mark) = @_;
   # Go through all reporting modules
   foreach my $reporter (@REPORTS)
   {
      if (defined $reporter->{host_start})
      {
         &{$reporter->{host_start}}($reporter->{handle}, $mark);
      }
   }
}
###############################################################################
sub report_host_end
{
   my ($mark) = @_;
   # Go through all reporting modules
   foreach my $reporter (@REPORTS)
   {
      if (defined $reporter->{host_end})
      {
         &{$reporter->{host_end}}($reporter->{handle}, $mark);
      }
   }
}
###############################################################################
sub report_item
{
   my ($mark, $item) = @_;
   # Go through all reporting modules
   foreach my $reporter (@REPORTS)
   {
      if (defined $reporter->{item})
      {
         &{$reporter->{item}}($reporter->{handle}, $mark, $item);
      }
   }
}
###############################################################################
sub report_close
{
   # Go through all reporting modules
   foreach my $reporter (@REPORTS)
   {
      if (defined $reporter->{close})
      {
         &{$reporter->{close}}($reporter->{handle});
      }
   }
}
###############################################################################
sub check_updates
{
 LW2::http_init_request(\%request);
 my (%REMOTE, %LOCAL, @DBTOGET) = ();
 my ($pluginmsg, $remotemsg) = "";
 my $code_updates=0;
 my $serverdir="/nikto/UPDATES/$NIKTO{version}";
 
 my $server="www.cirt.net";
 $request{'whisker'}->{'version'}="1.1";
 $request{'whisker'}->{'port'}=80;
 $request{'whisker'}->{'anti_ids'}="";
 $request{'User-Agent'}="Nikto Update Agent";
 $request{'whisker'}->{'host'}=$server;
 
 for (my $i=0;$i<=$#ARGV;$i++) 
 { if (($ARGV[$i] eq "-u") || ($ARGV[$i] eq "-useproxy")) { $CLI{useproxy}=1; last; } }
 
 if (($NIKTOCONFIG{PROXYHOST} ne "") && ($CLI{useproxy}))
  {
   $request{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
   $request{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
  }

 # retrieve versions file
 LW2::http_close(\%request);  # force-close any old connections
 LW2::http_fixup_request(\%request);
 (my $RES, $CONTENT) = fetch("$serverdir/versions.txt","GET");
 if ($RES eq 407) # requires Auth
  {
   if ($NIKTOCONFIG{PROXYUSER} eq "")  
      {
      $NIKTOCONFIG{PROXYUSER}=read_data("Proxy ID: ","");
      $NIKTOCONFIG{PROXYPASS}=read_data("Proxy Pass: ","noecho");
     }
   LW2::auth_set("proxy-basic",\%request,$NIKTOCONFIG{PROXYUSER},$NIKTOCONFIG{PROXYPASS});

   # and try again
   LW2::http_close(\%request);  # force-close any old connections
   LW2::http_fixup_request(\%request);
   ($RES, $CONTENT) = fetch("$serverdir/versions.txt","GET");
  }

 if ($RES eq "") # lookup failure?
  {
      LW2::http_close(\%request);  # force-close any old connections
   $request{'whisker'}->{'host'}=$NIKTOCONFIG{CIRT};
   $request{'Host'}="www.cirt.net";
   LW2::http_fixup_request(\%request);
   ($RES, $CONTENT) = fetch("$serverdir/versions.txt","GET");
  }

 if ($RES ne 200) 
  { print STDERR "+ ERROR ($RES): Unable to get $request{'whisker'}->{'host'}$serverdir/versions.txt\n"; 
    exit; }

 # make hash
 for (split(/\n/,$CONTENT))
 { 
  my @l=parse_csv($_);
  if ($_ =~ /^msg/) { $remotemsg="$l[1]"; next; }
  $REMOTE{$l[0]}=$l[1]; 
 }
 
 # get local versions of plugins/dbs
 my @NIKTOFILES=dirlist($NIKTOCONFIG{PLUGINDIR},"");

 foreach my $file (@NIKTOFILES)
 {
  my $v="";
  open(LOCAL,"<$NIKTOCONFIG{PLUGINDIR}/$file") || print STDERR "+ ERROR: Unable to open '$NIKTOCONFIG{PLUGINDIR}/$file' for read: $@\n";
  my @l=<LOCAL>;
  close(LOCAL);
  
  my @VERS=grep(/^#VERSION/,@l);
  chomp($VERS[0]);
  $LOCAL{$file}=(parse_csv($VERS[0]))[1];
 }

 # check main nikto versions
 foreach my $remotefile (keys %REMOTE)
 {
  if ($remotefile eq "nikto") # main program version
   { 
    if ($REMOTE{$remotefile} > $NIKTO{version}) 
     { 
      nprint "+ Nikto has been updated to $REMOTE{$remotefile}, local copy is $NIKTO{version}\n";
      nprint "+ No update has taken place. Please upgrade Nikto by visiting http://$server/\n"; 
      if ($remotemsg ne "") {  nprint "+ $server message: $remotemsg\n"; }
      exit; 
     }
    next;
   }

  if (($LOCAL{$remotefile} eq "") || ($REMOTE{$remotefile} > $LOCAL{$remotefile}))
   { 
    push(@DBTOGET,$remotefile); 
    if ($remotefile !~ /^db_/) { $code_updates=1; }
   }
  elsif ($REMOTE{$remotefile} < $LOCAL{$remotefile})  # local is newer (!)
   { print STDERR "+ ERROR: Local '$remotefile' (ver $LOCAL{$remotefile}) is NEWER than remote (ver $REMOTE{$remotefile}).\n";  } 
 }

 # replace local files if updated
 foreach my $toget (@DBTOGET)
 {
  nprint "+ Retrieving '$toget'\n";
  (my $RES, $CONTENT) = fetch("$serverdir/$toget","GET");
  if ($RES ne 200) { print STDERR "+ ERROR: Unable to get $server$serverdir/$toget\n"; exit; }
  if ($CONTENT ne "") {
   open(OUT,">$NIKTOCONFIG{PLUGINDIR}/$toget") || die print STDERR "+ ERROR: Unable to open '$NIKTOCONFIG{PLUGINDIR}/$toget' for write: $@\n";
   print OUT $CONTENT;
   close(OUT); 
  }
 }

# CHANGES file
if ($code_updates)
 {
  nprint "+ Retrieving 'CHANGES.txt'\n";
  (my $RES, $CONTENT) = fetch("$serverdir/CHANGES.txt","GET");
  if (($CONTENT ne "") && ($RES eq 200)) {
   open(OUT,">$NIKTOCONFIG{DOCUMENTDIR}/CHANGES.txt") || die print STDERR "+ ERROR: Unable to open '$NIKTOCONFIG{DOCUMENTDIR}/CHANGES.txt' for write: $@\n";
   print OUT $CONTENT;
   close(OUT); 
  }
 }

if ($#DBTOGET < 0 )   { nprint "+ No updates required.\n"; }
if ($remotemsg ne "") { nprint "+ $server message: $remotemsg\n"; }
exit;
}
###############################################################################
# do_auth
# Inputs: $request, $result
# Returns: $request
sub do_auth
{
   my $request = $_[0];
   my $result = $_[1];
   my $mark = $_[2];
   my $authtype = "basic";
   my $realm = "";

   unless (defined $result{'www-authenticate'})
   {
      nprint("+ ERROR: No authentication header defined");
   }
   # Split up www-authenticate to realm and method
   my @authenticate = split(/ /,$result{'www-authenticate'});
   if ($#authenticate == 0)
   { # Only one parameter: realm
      $realm = $authenticate[0];
      if ($realm =~ /^ntlm/i)
      {
         $realm="";
         $authtype=$authenticate[0];
      }
   }
   else
   {
      $authtype = $authenticate[0];
      $realm = $authenticate[1];
      $realm =~ s/^realm=//;
   }

   # Now we have this we can try guessing the password
   foreach my $REALM (keys %REALMS)
   {
      next unless ($realm =~ /$REALMS{$REALM}{realm}/i || $REALMS{$REALM}{realm} eq '@ANY');
      if (($REALMS{$REALM}{id} eq "") && ($REALMS{$REALM}{pw} eq ""))
      { 
         my $uridir=$request{whisker}->{uri};
         $uridir =~ s#/[^/]*$#/#g;
         add_vulnerability($mark,"Blank credentials found at $uridir ($request{whisker}->{uri}), $REALMS{$REALM}{realm}: $REALMS{$REALM}{msg}", $REALMS{$REALM}{tid},0,"GET",$uridir,\%result); 
      }
      else
      { 
         my $save_auth=$result{'www-authenticate'};
         LW2::http_close(\%request);  # force-close any old connections
         LW2::auth_set($authtype,\%request,$REALMS{$REALM}{id},$REALMS{$REALM}{pw});
         $request{'whisker'}->{'http_eol'}=$http_eol;
         LW2::http_fixup_request(\%request);
         if ($CLI{pause} > 0) { sleep $CLI{pause}; }
         LW2::http_do_request_timeout(\%request,\%result); # test auth
         dump_var("Auth Request",\%request);
         dump_var("Auth Response",\%result);
         if ($result{'www-authenticate'} =~ /^ntlm/i)
         {
            # Deal with ntlm
            my @ntlm_x=split(/ /,$result{'www-authenticate'});
            if ($#ntlm_x == 1)
            {
               LW2::http_do_request_timeout(\%request,\%result);
            }
         }
         if ($result{'www-authenticate'} eq "")
         {  
            unless ($REALMS{$REALM}{tid} == 700500 ||
                    $REALMS{$REALM}{checked} == 1)
            {
               my $uridir=$request{whisker}->{uri};
               $uridir =~ s#/[^/]*$#/#g;
               add_vulnerability($mark,"Default account found for '$realm' at $uridir ($request{whisker}->{uri}) (ID '$REALMS{$REALM}{id}', PW '$REALMS{$REALM}{pw}'). $REALMS{$REALM}{msg}",$REALMS{$REALM}{tid},0,"GET",$uridir,\%result);
               $REALMS{$REALM}{checked}=1;
            }
            # Finally repeat the check
            LW2::http_do_request_timeout(\%request,\%result); # test auth
            # and leave
            last;
         }
         else { $result->{'www-authenticate'}=$save_auth; }
      }
   }
   return $result;
}

###############################################################################
# read_data ( prompt, mode )
# read STDIN data from the user
# portions of this (POSIX code) were taken from the 
# Term::ReadPassword module by Tom Phoenix <rootbeer@redcat.com> (many thanks).
# it has been modified to not require Term::ReadLine, but still requires
# POSIX::Termios of it's a POSIX machine
###############################################################################
sub read_data
{
 if ($NIKTOCONFIG{PROMPTS} =~ /no/i) { return; }
 my($prompt, $mode, $POSIX) = @_;
 my $input = "";

 if ($^O =~ /Win32/)  { $POSIX=0; }
 else                 { $POSIX=1; }

 my %SPECIAL = (
    "\x03"      => 'INT',       # Control-C, Interrupt
    "\x08"      => 'DEL',       # Backspace
    "\x7f"      => 'DEL',       # Delete
    "\x0d"      => 'ENT',       # CR, Enter
    "\x0a"      => 'ENT',       # LF, Enter
  );

 # if we're on a non-POSIX machine we can't not-echo the
 # characters, so just use getc to avoid the dependency on
 # POSIX::Termios. We would be best to get rid of this 
 # entirely and use another way...

 if ($POSIX)
  {
   local(*TTY, *TTYOUT);
   open TTY, "<&STDIN" or return;
   open TTYOUT, ">>&STDOUT" or return;

   # Don't buffer it!
   select( (select(TTYOUT), $|=1)[0] );
   print TTYOUT $prompt;

   # Remember where everything was
   my $fd_tty = fileno(TTY);
   my $term = POSIX::Termios->new();
   $term->getattr($fd_tty);
   my $original_flags = $term->getlflag();

   if ($mode eq "noecho") 
     { 
      my $new_flags = $original_flags & ~(ISIG | ECHO | ICANON); 
      $term->setlflag($new_flags);
     }
   $term->setattr($fd_tty, TCSAFLUSH);
KEYSTROKE:
   while (1) {
        my $new_keys = '';
        my $count = sysread(TTY, $new_keys, 99);
        if ($count) {
            for my $new_key (split //, $new_keys) {
                if (my $meaning = $SPECIAL{$new_key}) {
                    if ($meaning eq 'ENT')    { last KEYSTROKE; }
                    elsif ($meaning eq 'DEL') { chop $input; }
                    elsif ($meaning eq 'INT') { last KEYSTROKE; }
                    else { $input .= $new_key; }
                } 
                else { $input .= $new_key; }
            }
        } 
        else { last KEYSTROKE; }
    }
    # Done with waiting for input. Let's not leave the cursor sitting
    # there, after the prompt.
    print TTY "\n";
    nprint "\n";

    # Let's put everything back where we found it.
    $term->setlflag($original_flags);
    $term->setattr($fd_tty, TCSAFLUSH);
    close(TTY);
    close(TTYOUT);
  }
 else # non-POSIX
  {
   print $prompt;
   $input=<STDIN>;
   chomp($input);
  }

return $input;
}
###############################################################################
sub proxy_setup
{
   if (!$CLI{useproxy}) { return; }

   # HTTP proxy
   $request{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
   $request{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};

   return; 
}
###############################################################################
sub proxy_check
{
 $request{'whisker'}->{'method'}="GET";
 $request{'whisker'}->{'uri'}="/";
 $request{'whisker'}->{host}="www.cirt.net";

 if (defined $request{'whisker'}->{'proxy_host'})  # proxy is set up
 {
  LW2::http_close(\%request);  # force-close any old connections
  $request{'whisker'}->{'http_eol'}=$http_eol;
  LW2::http_fixup_request(\%request);
  if ($CLI{pause} > 0) { sleep $CLI{pause}; }
  LW2::http_do_request_timeout(\%request,\%result);

  # First check that we can connect to the proxy
  if (exists $result{'whisker'}{'error'})
  {
     if ($result{'whisker'}{'error'} =~ "Transport endpoint is not connected")
     {
        nprint("ERROR: Could not connect to the defined proxy $NIKTOCONFIG{PROXYHOST}");
        exit 1;
     }
  }
  if ($result{'whisker'}{'code'} eq "407")       # proxy requires auth
  { 
   # have id/pw?
   if ($NIKTOCONFIG{PROXYUSER} eq "") 
     { 
      $NIKTOCONFIG{PROXYUSER}=read_data("Proxy ID: ","");
      $NIKTOCONFIG{PROXYPASS}=read_data("Proxy Pass: ","noecho");
     }
   if ($result{'proxy-authenticate'} !~ /Basic/i)
    {
     my @x=split(/ /,$result{'proxy-authenticate'});
     nprint("+ Proxy server uses '$x[0]' rather than 'Basic' authentication. $NIKTO{name} $NIKTO{version} can't do that.");
     exit;
    }
   
   # test it...
   LW2::http_close(\%request);  # force-close any old connections
   LW2::auth_set("proxy-basic",\%request,$NIKTOCONFIG{PROXYUSER},$NIKTOCONFIG{PROXYPASS});   # set auth
   $request{'whisker'}->{'http_eol'}=$http_eol;
   LW2::http_fixup_request(\%request);
   if ($CLI{pause} > 0) { sleep $CLI{pause}; }
   LW2::http_do_request_timeout(\%request,\%result);
   if ($result{'proxy-authenticate'} ne "") 
    { 
     my @pauthinfo=split(/ /,$result{'proxy-authenticate'});
     my @pauthinfo2=split(/=/,$result{'proxy-authenticate'});
     $pauthinfo2[1]=~s/^\"//; $pauthinfo2[1]=~s/\"$//;
     nprint("+ Proxy requires authentication for '$pauthinfo[0]' realm '$pauthinfo2[1]', unable to authenticate.");
     exit; 
    }
   else { nprint("- Successfully authenticated to proxy.","v"); }
  }
 }
 
 $PROXYCHECKED=1;
 return;
}
###############################################################################
sub dirlist
{
   my $DIR=$_[0] || return;
   my $PATTERN=$_[1] || "";
   my @FILES_TMP = ();

   opendir(DIRECTORY,$DIR) || die print STDERR "+ ERROR: Can't open directory '$DIR': $@";
   foreach my $file (readdir(DIRECTORY))
   {
      if ($file =~ /^\./)    { next; } # skip hidden files, '.' and '..'
      if ($PATTERN ne "") { if ($file =~ /$PATTERN/) { push (@FILES_TMP,$file); } }
      else { push (@FILES_TMP,$file); }
   }
   closedir(DIRECTORY);

   return @FILES_TMP;
}
#######################################################################
sub dump_var
{
   return if !$OUTPUT{debug};
   my $msg     = $_[0];
   my %hash_in = %{$_[1]};
   my $display = LW2::dump('', \%hash_in);
   $display =~ s/^\$/'$msg'/;
   nprint($display,"d");
   return;
}
######################################################################
sub content_present
{
   my $result=FALSE;
   my $res=$_[0];

   # perform an extra check just in case the web server lies about finds 
   # basically assume that the value for a non-extension is the true 
   # code for "File not Found". 
   if ($res ne $FoF{NONE}{response}) 
   { 
      foreach $found (split(' ', $VARIABLES{"\@HTTPFOUND"}))
      {
         if ($res eq $found)
         {
            $result=TRUE;
         }
      }
   }

   return $result;
}
#######################################################################
sub fetch
{
 if ($CLI{pause} > 0) { sleep $CLI{pause}; }
 LW2::http_close(\%request);  # force-close any old connections
 $request{'whisker'}->{'uri'} = $_[0];
 if (defined $CLI{root})
 {
    $request{'whisker'}->{'uri'}    = $CLI{root} . $_[0];   # prepend -root option value
 }
 $request{'whisker'}->{'method'} = $_[1];
 $request{'whisker'}->{'http_eol'}=$http_eol;
 delete $request{'whisker'}->{'data'};
 delete $request{'Content-Encoding'};
 delete $request{'Content-Length'};
 my $header_hash=$_[3];

 if (defined $_[2])
 { 
    my $r=$_[2];
    $r =~ s/\\\"/\"/g; 
    $request{'whisker'}->{'data'} = $r;
 }
  
 # check for extra HTTP headers
 if (defined $_[3])
 {
    # loop through the hash ref passed and add each header to request
    while (my($key, $value) = each(%$header_hash))
    {
       $request{$key}=$value;
    }
 } 
 unless ($_[4]) { LW2::http_fixup_request(\%request); }
 # Cache
 if (defined $CACHE{$request{whisker}->{uri}} && !defined $CLI{nocache} &&
     $CACHE{$request{whisker}->{uri}}{method} eq $request{whisker}->{method})
 {
    # Get from cache
    nprint("- Retrieving $request{whisker}->{uri} from cache.","v");
    $result{whisker}->{code}=$CACHE{$request{whisker}->{uri}}{res};
    $result{whisker}->{data}=$CACHE{$request{whisker}->{uri}}{content};
 }
 else
 {
    LW2::http_do_request_timeout(\%request,\%result);
    $NIKTO{totalrequests}++;
    unless (defined $CLI{nocache})
    {
       $CACHE{$request{whisker}->{uri}}{method}=$result{whisker}->{method};
       $CACHE{$request{whisker}->{uri}}{res}=$result{whisker}->{code};
       $CACHE{$request{whisker}->{uri}}{content}=$result{whisker}->{data};
    }
    if ($OUTPUT{debug})
    {
       dump_var("Request Hash", \%request);
       dump_var("Result Hash", \%result);
    }
 }
 
 if (defined $CLI{display} && ($CLI{display} =~ /2/) && (defined($result{'whisker'}->{'cookies'})))
  { 
   foreach my $c (@{$result{'whisker'}->{'cookies'}}) 
    { 
      nprint("+ $request{'whisker'}->{'uri'} sent cookie: $c");
    }
  }

 # Clean up extra headers
 if (defined $_[3])
 {
    while (my($key, $value) = each(%$header_hash))
    {
       delete $request{$key};
    }
 }
 return $result{'whisker'}->{'code'}, $result{'whisker'}->{'data'};
}
#######################################################################
sub setup_hash
{
   my ($reqhash,$mark) = @_;

   # Do the standard set up for the hash 
   LW2::http_init_request($reqhash);
   $reqhash->{whisker}->{ssl_save_info}=1;
   $reqhash->{whisker}->{lowercase_incoming_headers}=1;
   $reqhash->{whisker}->{timeout}=$NIKTO{timeout};
   if (defined $CLI{evasion})
   {
      $reqhash->{whisker}->{encode_anti_ids}=$CLI{evasion};
   }
   $reqhash->{'User-Agent'}=$NIKTO{useragent};
   $reqhash->{whisker}->{retry}=0;
   $reqhash->{whisker}->{host} = $mark->{hostname} || $mark->{ip};
   if ($mark->{vhost})
   {
      $request{Host} = $mark->{vhost};
   }
   $reqhash->{whisker}->{port}    = $mark->{port};
   $reqhash->{whisker}->{ssl}     = $mark->{ssl};
   
   # Proxy stuff
   if ($PROXYCHECKED && defined $NIKTOCONFIG{PROXYHOST} &&
       defined $CLI{useproxy})
   {
      $reqhash->{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
      $reqhash->{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
      LW2::auth_set("proxy-basic",$reqhash,$NIKTOCONFIG{PROXYUSER},
                    $NIKTOCONFIG{PROXYPASS});   # set auth
   }
   
   return $reqhash;
}
#######################################################################
sub nfetch
{
   my ($mark, $uri, $method, $data, $headers, $noclean) = @_;
   if ($CLI{pause} > 0) { sleep $CLI{pause}; }

   my %request, %result;
   setup_hash(\%request, $mark);
 
   $request{whisker}->{uri} = $uri;
   if (defined $CLI{root})
   {
      $request{whisker}->{uri} = $CLI{root} . $uri; # prepend -root option value
   }
   $request{whisker}->{method} = $method;
   $request{whisker}->{http_eol}=$http_eol;
   my $header_hash=$headers;

   if (defined $data)
   { 
      my $r=$data;
      $r =~ s/\\\"/\"/g; 
      $request{whisker}->{data} = $r;
   }
  
   # check for extra HTTP headers
   if (defined $headers)
   {
      # loop through the hash ref passed and add each header to request
      while (my($key, $value) = each(%$header_hash))
      {
         $request{$key}=$value;
      }
   } 
   unless ($noclean) { LW2::http_fixup_request(\%request); }

   # Cache
   if (defined $CACHE{$uri} && !defined $CLI{nocache} &&
       $CACHE{$uri}{method} eq $method && $CACHE{$uri}{mark} eq $mark)
   {
      # Get from cache
      nprint("- Retrieving $uri from cache.","v");
      $result{whisker}->{code}=$CACHE{$uri}{res};
      $result{whisker}->{data}=$CACHE{$uri}{content};
   }
   else
   {
      LW2::http_do_request_timeout(\%request,\%result);
      $NIKTO{totalrequests}++;
      unless (defined $CLI{nocache})
      {
         $CACHE{$uri}{method}=$result{whisker}->{method};
         $CACHE{$uri}{res}=$result{whisker}->{code};
         $CACHE{$uri}{content}=$result{whisker}->{data};
         $CACHE{$uri}{mark}=$mark;
      }
      if ($OUTPUT{debug})
      {
         dump_var("Request Hash", \%request);
         dump_var("Result Hash", \%result);
      }
   }
 
   if (defined $CLI{display} && ($CLI{display} =~ /2/) && (defined($result{'whisker'}->{'cookies'})))
   { 
      foreach my $c (@{$result{'whisker'}->{'cookies'}}) 
      { 
         nprint("+ $uri sent cookie: $c");
      }
   }

   # If headers is defined, copy the whisker headers to the hash
   if (defined $headers)
   {
      # First clear the hash
      foreach my $header (keys %$headers)
      {
         delete($headers->{$header});
      }
      while (my ($key, $value) = each(%result))
      {
         if ($key ne "whisker" && $key ne "connection")
         {
            $headers->{$key}=$value;
         }
      }
   }

   return $result{'whisker'}->{'code'}, $result{'whisker'}->{'data'}, $result{'whisker'}->{'error'};
}
#######################################################################
sub set_scan_items
{
 my ($mark) = @_;
 # load the tests 
 my $shname=$mark->{hostname} || $mark->{ip};
 %TESTS = ();
 my @SKIPLIST=();
 if (defined $NIKTOCONFIG{SKIPIDS})
 {
    @SKIPLIST = split(/ /,$NIKTOCONFIG{SKIPIDS});
 }
  
 # now load checks
 foreach my $line (@DBFILE)
 {
  if ($line =~ /^\"/)  # check
  {
   chomp($line);
   # substitute for @IP, @HOSTNAME in check
   $line =~ s/\@IP/$mark->{ip}/g;
   $line =~ s/\@HOSTNAME/$shname/g;
   my @item=parse_csv($line);
   my $add=1;
     
   # check tuning options
   if ((defined $CLI{tuning}) && (defined $item[2]))
   {
      # Work out the required tuning from the CLI string
      my $exclude=0;
      foreach my $tune (split(//,$CLI{tuning}))
      {
         if ($tune eq "x")
         {
            $exclude=1;
            next;
         }
         if ($exclude == 0)
         {
            if ($item[2] !~ /$tune/) { $add = 0; }
            next;
         }
         if ($exclude == 1)
         {
            if ($item[2] =~ /$tune/) { $add = 0; }
         }
      }
   }
    # Skip list
    foreach my $id (@SKIPLIST) { if ($id eq $item[0]) { $add=0; } }

    # Add random text
    if ($add)
    {
     for (my $i=3;$i<=$#item;$i++)
     {
      if ($item[$i] =~ /JUNK\([0-9]+\)/)  # junk text
       {
        $item[$i]=~/JUNK\(([0-9]+)\)/;
        $item[$i] =~ s/JUNK\(([0-9]+)\)/LW2::utils_randstr($1)/e;
       }
     }
    
    # Build the check items. First check for any @ values to replace. this nasty set of loops allows for multiple values per line
    my $ext = get_ext($item[3]);
    $db_extensions{$ext}=1;
    # This escapes regex characters in the conditionals. This will have to change if regex is ever allowed in the db
    for (my $y=5;$y<=9;$y++) { $item[$y] =~ s/([^a-zA-Z0-9\s])/\\$1/g; }

    #if ($item[3] =~ /^\@/)  # multiple checks in one
    if ($item[3] =~ /^stop this happening/)  # multiple checks in one
     {
      my @clones=();
      my $todelete="";
      push(@clones,$item[3]);
      foreach my $varname (keys %VARIABLES)
       {
        for (my $i=0;$i<=$#clones;$i++)
         {
          if ($clones[$i] =~ /$varname/)
           {
            my @values=split(/ /,$VARIABLES{$varname});
            foreach my $val (@values) 
             {
              my $temp=$clones[$i];
              $temp =~ s/$varname/$val/g;
              push(@clones,$temp);
              $todelete=$i;
             }
           splice(@clones,$todelete,1);
           $i--; # step back in the @clones 'cause we deleted one. more work, but it guarantees it's complete
           }
         }
       }
      # now actually populate the checks 
      for (my $i=0;$i<=$#clones;$i++)
      {
       $mark->{total_checks}++;
       $TESTS{$item[0]}{uri}=$clones[$i];
       $TESTS{$item[0]}{osvdb}=$item[1];
       $TESTS{$item[0]}{method}=$item[4];
       $TESTS{$item[0]}{match_1}=$item[5];
       $TESTS{$item[0]}{match_1_or}=$item[6];
       $TESTS{$item[0]}{match_1_and}=$item[7];
       $TESTS{$item[0]}{fail_1}=$item[8];
       $TESTS{$item[0]}{fail_2}=$item[9];
       $TESTS{$item[0]}{message}=$item[10];
       $TESTS{$item[0]}{data}=$item[11];
       $TESTS{$item[0]}{headers}=$item[12];
      }
     }
    else # normal, single check
     {
       $mark->{total_checks}++;
       $TESTS{$item[0]}{uri}=$item[3];
       $TESTS{$item[0]}{osvdb}=$item[1];
       $TESTS{$item[0]}{method}=$item[4];
       $TESTS{$item[0]}{match_1}=$item[5];
       $TESTS{$item[0]}{match_1_or}=$item[6];
       $TESTS{$item[0]}{match_1_and}=$item[7];
       $TESTS{$item[0]}{fail_1}=$item[8];
       $TESTS{$item[0]}{fail_2}=$item[9];
       $TESTS{$item[0]}{message}=$item[10];
       $TESTS{$item[0]}{data}=$item[11];
       $TESTS{$item[0]}{headers}=$item[12];
     }
    }
   }
  }

nprint("- $mark->{total_checks} server checks loaded","v");
if ($mark->{total_checks} eq 0 && !defined $CLI{tuning}) { nprint("+ Unable to load valid checks!"); exit; }
return;
}
#######################################################################
sub max_test_id
{
   return (sort {$a<=>$b} keys %TESTS)[-1];
}
#######################################################################
sub char_escape
{
   $_[0] =~ s/([^a-zA-Z0-9 ])/\\$1/g;
   return $_[0];
}
#######################################################################
sub parse_csv
{
   my $text = $_[0] || return;
   my @new = ();
   push(@new, $+) while $text =~ m{
      "([^\"\\]*(?:\\.[^\"\\]*)*)",?
       |  ([^,]+),?
       | ,
   }gx;
   push(@new, undef) if substr($text, -1,1) eq ',';
   return @new;
}
#######################################################################
sub version
{
   my @NIKTOFILES=dirlist($NIKTOCONFIG{PLUGINDIR},"(^nikto|^db_)");

   nprint "$DIV\n$NIKTO{name} Versions\n$DIV\n";
   nprint "File                               Version      Last Mod\n";
   nprint "-----------------------------      --------     ----------\n";
   nprint "Nikto main                         $NIKTO{version}\n";
   nprint "LibWhisker                         $LW2::VERSION\n";

   foreach my $FILE (sort @NIKTOFILES)
   {
      open(FI,"<$NIKTOCONFIG{PLUGINDIR}/$FILE") || die print STDERR "- ERROR: Unable to open '$NIKTOCONFIG{PLUGINDIR}/$FILE': $!\n";;;
      my @F=<FI>;
      close(FI);
      my @VERS=grep(/^#VERSION/,@F);
      my @MODS=grep(/^# \$Id:/,@F);
      chomp($VERS[0]);
      chomp($MODS[0]);
      my @modification=split(/ /,$MODS[0]);
      $VERS[0] =~ s/^#VERSION,//;
      my $ws1=(35-length($FILE));
      my $ws2=(13-length($VERS[0]));
      nprint "$FILE". " " x $ws1  . "$VERS[0]". " " x $ws2. "$modification[4]\n";
   }
   nprint "$DIV\n";

   exit;
}
#######################################################################
sub send_updates
{
 if ($NIKTOCONFIG{UPDATES} !~ /yes|auto/i) { return; }
 my $have_updates=0;
 my ($updated_version, $answer, $RES);
 foreach my $ver (keys %UPDATES) 
  { 
   if ($UPDATES{$ver} eq 1) 
    { 
     if ($ver !~ /[0-9]/)   { next; }        # no version info...useless
     if ($ver eq "Win32") { next; }          # also no use
     if ($ver eq "Linux-Mandrake") { next; } # just... usually garbage
     $have_updates=1; 
     $updated_version .= "$ver ";
    } 
  }
  
 if (!$have_updates) { return; }
 if ($updated_version eq "") { return; }
 
 # make sure the db_outdatedb isn't *too* old
 open(OD,"<$NIKTOCONFIG{PLUGINDIR}/db_outdated") || die print STDERR "- ERROR: Unable to open '$NIKTOCONFIG{PLUGINDIR}/db_outdated': $!\n";;
 @F=<OD>;
 close(OD);
 
 my @LASTUPDATED=grep(/^#LASTMOD/,@F);
 chomp($LASTUPDATED[0]);
 $LASTUPDATED[0]=~s/^.*,//;
 my @lu=split(/\./,$LASTUPDATED[0]);
 my $lm="$lu[2]$lu[0]";
 my @NOW=localtime(time);
 $NOW[5]+=1900;
 $NOW[4]++;
 if ($NOW[4] < 10) { $NOW[4]="0$NOW[4]"; }
 my $now="$NOW[5]$NOW[4]";
 if (($now - $lm) > 4) { return; }  # DB is 4 months old... ignore the updates!
 
 $updated_version =~ s/\s+$//; 
 $updated_version =~ s/^\s+//; 
 
 if ($NIKTOCONFIG{UPDATES} eq "auto") { $answer = "y"; }
 else
  {
   $answer=read_data("\n
   ***** Portions of the server's ident string ($updated_version)
   are not in the Nikto database or is newer than the known string.
   Would you like to submit this information (*no server specific data*)
   to CIRT.net for a Nikto update (or you may email to sullo\@cirt.net)
   (y/n)? ","");
  }

 if ($answer !~ /y/i) { return; }
 
 LW2::http_init_request(\%request);
 my $server="www.cirt.net";
 $request{'whisker'}->{'version'}="1.1";
 $request{'whisker'}->{'port'}=80;
 $request{'whisker'}->{'anti_ids'}="";
 $request{'User-Agent'}="Nikto Update Agent";
 $request{'Host'}="www.cirt.net";
 
 for (my $i=0;$i<=$#ARGV;$i++) 
 { if (($ARGV[$i] eq "-u") || ($ARGV[$i] eq "-useproxy")) { $CLI{useproxy}=1; last; } }
 
 my  $ip=gethostbyname($server);
 if ($ip ne "") { $request{'whisker'}->{'host'}= inet_ntoa($ip); }
 else { $request{'whisker'}->{'host'}=$server; }
 if (($NIKTOCONFIG{PROXYHOST} ne "") && ($CLI{useproxy}))

  {
   $request{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
   $request{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
  }

 # send data
 LW2::http_close(\%request);  # force-close any old connections
 LW2::http_fixup_request(\%request);
 ($RES, $CONTENT) = fetch("/cgi-bin/versions?DATA=$updated_version","GET");

 # if res is blank... maybe only proxy to get to net?
 if (($RES eq "") && ($NIKTOCONFIG{PROXYHOST} ne ""))
  {
   $request{'whisker'}->{'proxy_host'}=$NIKTOCONFIG{PROXYHOST};
   $request{'whisker'}->{'proxy_port'}=$NIKTOCONFIG{PROXYPORT};
   ($RES, $CONTENT) = fetch("/cgi-bin/versions?DATA=$updated_version","GET");
  }

 if ($RES eq 407) # requires Auth
  {
   if ($NIKTOCONFIG{PROXYUSER} eq "")  
      {
      $NIKTOCONFIG{PROXYUSER}=read_data("Proxy ID: ","");
      $NIKTOCONFIG{PROXYPASS}=read_data("Proxy Pass: ","noecho");
     }
   LW2::auth_set("proxy-basic",\%request,$NIKTOCONFIG{PROXYUSER},$NIKTOCONFIG{PROXYPASS});

   # and try again
   LW2::http_close(\%request);  # force-close any old connections
   LW2::http_fixup_request(\%request);
  ($RES, $CONTENT) = fetch("/cgi-bin/versions?DATA=$updated_version","GET");
  }

 if ($RES eq "") # lookup failure?
  {
        LW2::http_close(\%request);  # force-close any old connections
        $request{'whisker'}->{'host'}=$NIKTOCONFIG{CIRT};
        $request{'Host'}="www.cirt.net";
        LW2::http_fixup_request(\%request);
   ($RES, $CONTENT) = fetch("/cgi-bin/versions?DATA=$updated_version","GET");
  }

 if ($CONTENT !~ /SUCCESS/) 
  { 
   print STDERR "- ERROR: ($RES, $CONTENT): Unable to send updated version string(s) to CIRT.net\n"; 
  }
 else
  {
   nprint "- Sent updated version string(s) to CIRT.net\n"; 
  }

return;
}
#######################################################################
sub usage
{
   if ($_[0] eq 2) { nprint($NIKTO{options}); }
   else { nprint($NIKTO{options_short}); }
   exit;
}
#######################################################################
sub initialise_db
{
   my $dbname=$_[0];
   my $filename="$NIKTOCONFIG{PLUGINDIR}/" . $dbname;
   my (@dbarray, @headers);
   my $hashref = {};

   # Check that the database exists
   unless (open(IN, "<$filename"))
   {
      nprint("- ERROR: Unable to open database file $dbname: $!.");
      return $dbarray;
   }

   # Now read the header values
   while (<IN>)
   {
      chomp;
      s/\#.*$//;
      if ($_ eq "" ) { next };
      unless (@headers)
      {
         @headers=parse_csv($_); 
      }
      else
      {
         # contents; so split them up and apply to hash
         my @contents=parse_csv($_);
         my $hashref={};
         for (my $i=0; $i<=$#contents; $i++)
         {
            $hashref->{$headers[$i]} = $contents[$i];
         }
         push(@dbarray, $hashref);
      }
   }
   return \@dbarray;
}
#######################################################################
sub add_vulnerability
{
   my ($mark, $message, $nikto_id, $osvdb, $method, $uri) = @_;
   $uri="/" unless (defined $uri);
   $method="GET" unless (defined $method);
   $osvdb="0" unless (defined $osvdb);

   my $result="";
   if (defined $_[7])
   {
      $result=$_[7]->{whisker}->{data};
   }
   my $outmessage=$message;
   my $resulthash={};

   unless ($osvdb eq "")
   {
      $outmessage ="OSVDB-$osvdb: $message";
   }
   nprint("+ $outmessage"); 
   %$resulthash = (
      mark     => $mark,
      message  => $message,
      nikto_id => $nikto_id,
      osvdb    => $osvdb,
      method   => $method,
      uri      => $uri,
      result   => $result,
   );
   $mark->{total_vulns}++;
   push(@RESULTS,$resulthash);
   # Now report it
   report_item($mark, $resulthash);
}
#######################################################################
sub nikto_core { return; } # trap for this plugin being called to run. lame.
#######################################################################

1;
