#!/usr/bin/perl 

# * Dominoaudit, Phil Robinson, Information Risk Management 2005
# * Audits a Domino server for default URLs, with or without credentials
# *
# * This code is free software; you can redistribute it and/or
# * modify it under the terms of the GNU General Public License
# * as published by the Free Software Foundation; either version 2
# * of the License, or (at your option) any later version.
# *
# * This code 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.
# * Or, point your browser to http://www.gnu.org/copyleft/gpl.html

use IO::Socket;
use IO::Select;
use Getopt::Std;
use MIME::Base64;

# globals

my $logfile;
my $debug;

sub printdebug
{
  my $output = shift;

  if ( $debug ne "" )
  {
    print STDERR "DEBUG: $output";
  }
}

sub printoutput
{
  my $output = shift;

  if ( $logfile ne "" )
  { 	
    print OUTPUT $output;
  }
    print "$output";
}

sub parse_octet
{
  $octet = shift;
  my @retlist;

  if ( $octet =~ /,/ || $octet =~ /-/ )
  {
    @elements = split(/,/, $octet);
    foreach $element (@elements)
    {
      if ( $element =~ /-/ )
      {
        ($lower, $upper) = split(/-/, $element);
        if ($lower > $upper) { die "Incorrect IP Range"; }
        for ($x = $lower ; $x <= $upper ; $x++ ) { push @retlist, $x }
      }
      else
      { push @retlist, $element }
    }
  }
  else { push @retlist, $octet }

  return @retlist;
}

sub parse_ip
{
  my $ip = shift;
  my @scanip;
  ($first, $second, $third, $fourth) = split(/\./,$ip);

  @firstlist = &parse_octet($first);
  @secondlist = &parse_octet($second);
  @thirdlist = &parse_octet($third);
  @fourthlist = &parse_octet($fourth);

  foreach $a (@firstlist)
  {
    foreach $b (@secondlist)
    {
      foreach $c (@thirdlist)
      {
        foreach $d (@fourthlist)
        {
          push @scanip, "$a.$b.$c.$d";
        }
      }
   }
  }

  return @scanip;
}

sub sendToWebserver
{
    my $serverIP = shift;
    my $serverPort = shift;
    my $lineToSend = shift;
    my $username = shift;
    my $password = shift;
    my $cookie = shift;
    my $authtype = shift;

    my(@webOutput);
    my($webTimeout);
    my($sock, $sel, @ready, $fh, $thisLine);

    $webTimeout = 5;

    chomp($lineToSend);

    $sock = IO::Socket::INET->new( PeerAddr => $serverIP,
				   PeerPort => $serverPort,
				   Proto    => 'tcp' 
				   );
    if( !($sock) )
    {
	return ();
    }

    print $sock $lineToSend."\n";

    if ( $authtype == 0 )
    {
      print $sock "\r\n";
    }
    elsif ($authtype == 1) 
    {
      print $sock "Authorization: Basic ".encode_base64($username.":".$password)."\r\n\r\n";
    }
    elsif ($authtype == 2) 
    {
      print $sock "Cookie: $cookie\r\n\r\n";
    }

    $sel = IO::Select->new($sock);

  READLINE:
    while( @ready = $sel->can_read($webTimeout) )
    {
	$fh = $ready[0];

	if( eof($fh) )
	{
	    last READLINE;
	}

	$thisLine = <$fh>;

	push(@webOutput, $thisLine);
    }

    $sock->close();
    return @webOutput;
}

sub postToWebserver
{
    my $serverIP = shift;
    my $serverPort = shift;
    my $lineToSend = shift;
    my $postdata = shift;

    my(@webOutput);
    my($webTimeout);
    my($sock, $sel, @ready, $fh, $thisLine);

    $webTimeout = 5;

    chomp($lineToSend);

    $sock = IO::Socket::INET->new( PeerAddr => $serverIP,
				   PeerPort => $serverPort,
				   Proto    => 'tcp' 
				   );
    if( !($sock) )
    {
	return ();
    }

    $contentlength = length($postdata) + 1;
    print $sock $lineToSend."\n";
    print $sock "Content-Length: ".$contentlength."\n\n";
    print $sock $postdata."\n";

    $sel = IO::Select->new($sock);

  READLINE:
    while( @ready = $sel->can_read($webTimeout) )
    {
	$fh = $ready[0];

	if( eof($fh) )
	{
	    last READLINE;
	}

	$thisLine = <$fh>;

	push(@webOutput, $thisLine);
    }

    $sock->close();
    return @webOutput;
}

# This tries to get a given URL and returns the code given by the webserver
#  (or an empty string on error)
sub getURLRetCode
{
    my $serverIP = shift;
    my $serverPort = shift;
    my $url = shift;
    my $user = shift;
    my $pass = shift;
    my $cookie = shift;
    my $authtype = shift;

    my $retCode;

    my @output;

    @output = &sendToWebserver($serverIP, $serverPort, "GET ".$url." HTTP/1.0", $user, $pass, $cookie, $authtype);

    if( @output &&
	$output[0] =~ /^HTTP\/\d.\d\s(\d+)\s/ )
    {
	$retCode = $1;
    }
    else
    {
	$retCode = "";
    }

    return $retCode;
}

sub modDominoCheckRun
{
    my $targetIP = shift;
    my $targetPort = shift;
    my $targetUser = shift;
    my $targetPass = shift;
    my $targetCookie = shift;
    my $targetAuth = shift;

    my $log = shift;

    my (@output, @results);

    my @dominoDbs=(     "/account.nsf?OpenDatabase",
			"/names.nsf?OpenDatabase",
			"/admin4.nsf?OpenDatabase",
			"/webadmin.nsf?OpenDatabase",
			"/accounts.nsf?OpenDatabase",
			"/admin5.nsf?OpenDatabase",
			"/admin.nsf?OpenDatabase",
			"/a_domlog.nsf?OpenDatabase",
			"/agentrunner.nsf?OpenDatabase",
			"/AgentRunner.nsf?OpenDatabase",
			"/alog.nsf?OpenDatabase",
			"/archive/a_domlog.nsf?OpenDatabase",
			"/archive/l_domlog.nsf?OpenDatabase",
			"/billing.nsf?OpenDatabase",
			"/bookmark.nsf?OpenDatabase",
			"/books.nsf?OpenDatabase",
			"/busytime.nsf?OpenDatabase",
			"/calendar.nsf?OpenDatabase",
			"/catalog.nsf?OpenDatabase",
			"/cersvr.nsf?OpenDatabase",
			"/certa.nsf?OpenDatabase",
			"/certlog.nsf?OpenDatabase",
			"/certsrv.nsf?OpenDatabase",
			"\@CGIDIRSftp.pl?OpenDatabase",
			"/chatlog.nsf?OpenDatabase",
			"/clbusy.nsf?OpenDatabase",
			"/cldbdir.nsf?OpenDatabase",
			"/clusta4.nsf?OpenDatabase",
			"/collect4.nsf?OpenDatabase",
			"/cpa.nsf?OpenDatabase",
			"/customerdata.nsf?OpenDatabase",
			"/da.nsf?OpenDatabase",
			"/database.nsf?OpenDatabase",
			"/dba4.nsf?OpenDatabase",
			"/db.nsf?OpenDatabase",
			"/dclf.nsf?OpenDatabase",
			"/DEASAppDesign.nsf?OpenDatabase",
			"/DEASLog01.nsf?OpenDatabase",
			"/DEASLog02.nsf?OpenDatabase",
			"/DEASLog03.nsf?OpenDatabase",
			"/DEASLog04.nsf?OpenDatabase",
			"/DEASLog05.nsf?OpenDatabase",
			"/DEASLog.nsf?OpenDatabase",
			"/decsadm.nsf?OpenDatabase",
			"/decsdoc.nsf?OpenDatabase",
			"/decslog.nsf?OpenDatabase",
			"/DEESAdmin.nsf?OpenDatabase",
			"/default.nsf?OpenDatabase",
			"/dirassist.nsf?OpenDatabase",
			"/doc/domguide.nsf?OpenDatabase",
			"/doc/dspug.nsf?OpenDatabase",
			"/doc/help4.nsf?OpenDatabase",
			"/doc/helpadmin.nsf?OpenDatabase",
			"/doc/helpadmn.nsf?OpenDatabase",
			"/doc/helplt4.nsf?OpenDatabase",
			"/doc/internet.nsf?OpenDatabase",
			"/doc/javapg.nsf?OpenDatabase",
			"/doc/lccon.nsf?OpenDatabase",
			"/doc/migrate.nsf?OpenDatabase",
			"/doc/npn_admn.nsf?OpenDatabase",
			"/doc/npn_rn.nsf?OpenDatabase",
			"/doc/readmec.nsf?OpenDatabase",
			"/doc/readmes.nsf?OpenDatabase",
			"/doc/smhelp.nsf?OpenDatabase",
			"/doc/srvinst.nsf?OpenDatabase",
			"/doc/wksinst.nsf?OpenDatabase",
			"/doladmin.nsf?OpenDatabase",
			"/dols_help.nsf?OpenDatabase",
			"/domadmin.nsf?OpenDatabase",
			"/domcfg.nsf?OpenDatabase",
			"/domguide.nsf?OpenDatabase",
			"/domino.nsf?OpenDatabase",
			"/domlog.nsf?OpenDatabase",
			"/dspug.nsf?OpenDatabase",
			"/event.nsf?OpenDatabase",
			"/events4.nsf?OpenDatabase",
			"/events5.nsf?OpenDatabase",
			"/events.nsf?OpenDatabase",
			"/group.nsf?OpenDatabase",
			"/groups.nsf?OpenDatabase",
			"/help4.nsf?OpenDatabase",
			"/help5_admin.nsf?OpenDatabase",
			"/help5_client.nsf?OpenDatabase",
			"/help5_designer.nsf?OpenDatabase",
			"/helpadmin.nsf?OpenDatabase",
			"/help/decsdoc.nsf?OpenDatabase",
			"/help/dols_help.nsf?OpenDatabase",
			"/help/domguide.nsf?OpenDatabase",
			"/help/dspug.nsf?OpenDatabase",
			"/help/help4.nsf?OpenDatabase",
			"/help/help5_admin.nsf?OpenDatabase",
			"/help/help5_client.nsf?OpenDatabase",
			"/help/help5_designer.nsf?OpenDatabase",
			"/help/helpadmin.nsf?OpenDatabase",
			"/help/helplt4.nsf?OpenDatabase",
			"/help/internet.nsf?OpenDatabase",
			"/help/javapg.nsf?OpenDatabase",
			"/help/lccon.nsf?OpenDatabase",
			"/help/lsxlc.nsf?OpenDatabase",
			"/helplt4.nsf?OpenDatabase",
			"/help/migrate.nsf?OpenDatabase",
			"/help/npn_admn.nsf?OpenDatabase",
			"/help/npn_rn.nsf?OpenDatabase",
			"/help/readmec.nsf?OpenDatabase",
			"/help/readme.nsf?OpenDatabase",
			"/help/readmes.nsf?OpenDatabase",
			"/help/smhelp.nsf?OpenDatabase",
			"/help/srvinst.nsf?OpenDatabase",
			"/hidden.nsf?OpenDatabase",
			"/homepage.nsf?OpenDatabase",
			"/iNotes/Forms5.nsf?OpenDatabase",
			"/iNotes/Forms5.nsf/\$DefaultNav",
			"/internet.nsf?OpenDatabase",
			"/javapg.nsf?OpenDatabase",
			"/jotter.nsf?OpenDatabase",
			"/kbccv11.nsf?OpenDatabase",
			"/kbnv11.nsf?OpenDatabase",
			"/kbssvv11.nsf?OpenDatabase",
			"/lccon.nsf?OpenDatabase",
			"/lcon.nsf?OpenDatabase",
			"/ldap.nsf?OpenDatabase",
			"/l_domlog.nsf?OpenDatabase",
			"/leiadm.nsf?OpenDatabase",
			"/leilog.nsf?OpenDatabase",
			"/leivlt.nsf?OpenDatabase",
			"/log4a.nsf?OpenDatabase",
			"/loga4.nsf?OpenDatabase",
			"/log.nsf?OpenDatabase",
			"/lsxlc.nsf?OpenDatabase",
			"/mab.nsf?OpenDatabase",
			"/mail/adminisist.nsf?OpenDatabase",
			"/mail/admin.nsf?OpenDatabase",
			"/mailw46.nsf?OpenDatabase",
			"/migrate.nsf?OpenDatabase",
			"/msdwda.nsf?OpenDatabase",
			"/mtatbls.nsf?OpenDatabase",
			"/mtdata/mtstore.nsf?OpenDatabase",
			"/mtstore.nsf?OpenDatabase",
			"/nntp/nd000000.nsf?OpenDatabase",
			"/nntp/nd000001.nsf?OpenDatabase",
			"/nntp/nd000002.nsf?OpenDatabase",
			"/nntp/nd000003.nsf?OpenDatabase",
			"/nntp/nd000004.nsf?OpenDatabase",
			"/nntppost.nsf?OpenDatabase",
			"/notes.nsf?OpenDatabase",
			"/npn_admn.nsf?OpenDatabase",
			"/npn_rn.nsf?OpenDatabase",
			"/.nsf/../winnt/win.ini?OpenDatabase",
			"/ntsync45.nsf?OpenDatabase",
			"/ntsync4.nsf?OpenDatabase",
			"/perweb.nsf?OpenDatabase",
			"/private.nsf?OpenDatabase",
			"/products.nsf?OpenDatabase",
			"/public.nsf?OpenDatabase",
			"/qpadmin.nsf?OpenDatabase",
			"/qstart.nsf?OpenDatabase",
			"/quickplace/quickplace/main.nsf?OpenDatabase",
			"/quickstart/qstart50.nsf?OpenDatabase",
			"/quickstart/wwsample.nsf?OpenDatabase",
			"/readmec.nsf?OpenDatabase",
			"/readme.nsf?OpenDatabase",
			"/readmes.nsf?OpenDatabase",
			"/reports.nsf?OpenDatabase",
			"/sample/faqw46.nsf?OpenDatabase",
			"/sample/framew46.nsf?OpenDatabase",
			"/sample/pagesw46.nsf?OpenDatabase",
			"/sample/siregw46.nsf?OpenDatabase",
			"/sample/site1w46.nsf?OpenDatabase",
			"/sample/site2w46.nsf?OpenDatabase",
			"/sample/site3w46.nsf?OpenDatabase",
			"/schema50.nsf?OpenDatabase",
			"/scripts/iisadmin/bdir.htr?OpenDatabase",
			"/secret.nsf?OpenDatabase",
			"/secure.nsf?OpenDatabase",
			"/servlet/SchedulerTransfer?OpenDatabase",
			"/servlets/SchedulerTransfer?OpenDatabase",
			"/setup.nsf?OpenDatabase",
			"/setupweb.nsf?OpenDatabase",
			"/smbcfg.nsf?OpenDatabase",
			"/smconf.nsf?OpenDatabase",
			"/smency.nsf?OpenDatabase",
			"/smhelp.nsf?OpenDatabase",
			"/smmsg.nsf?OpenDatabase",
			"/smquar.nsf?OpenDatabase",
			"/smsolar.nsf?OpenDatabase",
			"/smtime.nsf?OpenDatabase",
			"/smtpibwq.nsf?OpenDatabase",
			"/smtp.nsf?OpenDatabase",
			"/smtpobwq.nsf?OpenDatabase",
			"/smtptbls.nsf?OpenDatabase",
			"/smvlog.nsf?OpenDatabase",
			"/software.nsf?OpenDatabase",
			"/srvinst.nsf?OpenDatabase",
			"/statmail.nsf?OpenDatabase",
			"/statrep.nsf?OpenDatabase",
			"/stats217.nsf?OpenDatabase",
			"/stats572.nsf?OpenDatabase",
			"/stats675.nsf?OpenDatabase",
			"/stats988.nsf?OpenDatabase",
			"/stauths.nsf?OpenDatabase",
			"/stautht.nsf?OpenDatabase",
			"/stconfig.nsf?OpenDatabase",
			"/stconf.nsf?OpenDatabase",
			"/stdnaset.nsf?OpenDatabase",
			"/stdomino.nsf?OpenDatabase",
			"/stlog.nsf?OpenDatabase",
			"/streg.nsf?OpenDatabase",
			"/stsrc.nsf?OpenDatabase",
			"/test.nsf?OpenDatabase",
			"/today.nsf?OpenDatabase",
			"/user.nsf?OpenDatabase",
			"/userreg.nsf?OpenDatabase",
			"/users.nsf?OpenDatabase",
			"/vpuserinfo.nsf?OpenDatabase",
			"/web.nsf?OpenDatabase",
			"/webuser.nsf?OpenDatabase",
			"/welcome.nsf?OpenDatabase",
			"/wksinst.nsf?OpenDatabase");

    my($davidBanner, $bigNum, $midNum, $ikleNum);
    my($isForm);
    my($i, $url, $RetVal, $thisLine);

    # Now check for Domino open databases

    for($i=0;$i<scalar(@dominoDbs);$i++)
    {
	$url = $dominoDbs[$i];
	&printdebug("Checking $url \n");
        
        if ( $targetAuth == 0 )
        {
           $RetVal = &getURLRetCode($targetIP, $targetPort, $url, "", "" , "", 0);
        }
        elsif ( $targetAuth ==1 ) 
        {
           $RetVal = &getURLRetCode($targetIP, $targetPort, $url, $targetUser, $targetPass , "", 1);
	}
	elsif ( $targetAuth == 2 )
        {
           $RetVal = &getURLRetCode($targetIP, $targetPort, $url, "", "" , $targetCookie, 2);
        }
        &printdebug("Got Return Code ".$RetVal."\n"); 
        if ($RetVal == 200)
        {
           &printdebug("Return code of 200, checking for form login".$url."\n");
           if ( $targetAuth == 0 )
           {
              @results = &sendToWebserver($targetIP, $targetPort, "GET ".$url." HTTP/1.0", "", "", "", 0);
           }
           elsif ( $targetAuth == 1 ) 
           {
              @results = &sendToWebserver($targetIP, $targetPort, "GET ".$url." HTTP/1.0", $targetUser, $targetPass, "", 1);
	   }
	   elsif ( $targetAuth == 2 )
           {
              @results = &sendToWebserver($targetIP, $targetPort, "GET ".$url." HTTP/1.0", "", "", $targetCookie, 2);
           }
           
	   $isForm = 0;
           foreach $thisLine (@results)
           {
               if( $thisLine =~ /\/names\.nsf\?Login/ )
               {
                  $isForm = 1;
                  &printdebug("Form login found.. Not Open\n");
               }
           }
           if ( $isForm == 0 )
           {
	      &printdebug("Domino default database available: ".$url."\n");
	      &printoutput("$targetIP $url $targetUser $targetPass\n");
 	   }
       }
    }
}


# main

$Usage = qq{
IRM Domino Auditer - by Morfsta 2004

Usage: $0 [options] -h <IP Address / Range>

Options:
         -h IP Address/Range e.g. 10.0.244-246.0,5,6,9
	 -p port # (80)
	 -u username
	 -w password
	 -a auth url to check auth against (names.nsf)
         -o Output file (outputs open ports only)
	 -d Debug Mode (more output)
};

die $Usage if (!getopts('h:p:u:a:w:o:d'));

my $ip = $opt_h || die "$Usage Need to specify an IP address range";
my $print = $opt_l || 0;
my $port = $opt_p || 80;
my $username = $opt_u || "";
my $password = $opt_w || "";
my $authurl = $opt_a || "names.nsf";
my @iplist = &parse_ip($ip);
$debug = $opt_d || "";
$logfile = $opt_o || "";

my $formlogin;
my $basiclogin;
my $useauth;
my $isform;

my $postreq;
my $postres;
my $ua;

if ( $debug ne "" )
{
  &printdebug("Debug mode on\n");
}

if ( $logfile ne "" )
{
  &printdebug("Using logfile: $logfile\n");
  open(OUTPUT, ">$logfile") || die "Cannot write to logfile.. $logfile";
}

foreach $host (@iplist)
{
$formlogin = 0;
$basiclogin = 0;
$useauth = 0;
$formsuccess = 0;

if ( $username ne "" )
{
  &printdebug("Checking authentication against /".$authurl."\n");
  $rc = &getURLRetCode($host, $port, "/".$authurl, "", "" , "", 0);
  &printdebug("Got RetCode : ".$rc."\n");
  if ( $rc == 200 )
  {
    &printdebug("RetCode = 200, is it an authpage?\n");
    @results = &sendToWebserver($host, $port, "GET /".$authurl." HTTP/1.0", "", "", "", 0);
    $isForm = 0;
    foreach $thisLine (@results)
    {
       if( $thisLine =~ /\/names\.nsf\?Login/ )
       {
          $isForm = 1;
       }
    }

    if ( $isForm == 0 )
    { 
      &printdebug("Weird, Not using authentication? Continuing audit without it\n");
    }
    else
    {
      &printdebug("Uses form-based login\n");
      $formlogin = 1;
      $useauth = 1;
    }
  }
  elsif ( $rc == 401 ) 
  { 
    &printdebug("Using BASIC authentication\n");
    $basiclogin = 1;
    $useauth = 1;
  }
  else
  {
    &printdebug("Strange return code : ".$rc."\nContinuing audit without auth\n");
    $useauth = 0;
  }
 
  if ( $useauth == 1 )
  {
    &printdebug("Checking for valid authentication credentials..\n"); 
    if ( $basiclogin == 1 )
    {
      $rc = &getURLRetCode($host, $port, "/".$authurl, $username, $password , "", 1);
      if ( $rc == 401 )
      {
        &printdebug("Invalid Credentials.. Continuing audit without auth\n");
        $useauth = 0;
      }
      else
      {
        &printdebug("Authentication okay! Auditing site with credentials");
      }
    }
    elsif ( $formlogin == 1 )
    {
      &printdebug("Checking for valid forms login...\n");
      @results = &postToWebserver($host, $port, "POST /"."names.nsf?Login HTTP/1.0", "Username=".$username."&Password=".$password);
      foreach $thisLine (@results)
      {
        if ( $thisLine =~ /Set-Cookie/ )
        {
          $formsuccess = 1;
	  ($rest, $cookie) = split(/:/, $thisLine);
	  &printdebug("Success, got cookie : $cookie\n");
	  chomp($cookie);
        }
      }
      if ( $formsuccess == 0 )
      { 
        &printDebug("Login failed, continuing without auth\n");
        $useauth = 0;
      }
    }
}  
}

if ( $useauth == 1 && $formlogin == 1 )
{
    &printdebug("Checking site with formlogin credentials\n");
    &modDominoCheckRun($host, $port, $username, $password, $cookie, 2);
}
elsif ( $useauth == 1 && $basiclogin == 1 )
{
   &printdebug("Checking site with basicauth credentials\n");
   &modDominoCheckRun($host, $port, $username, $password, $cookie, 1);
}
else 
{
   &printdebug("Checking site without credentials\n");
   &modDominoCheckRun($host, $port, "", "", "", 0);
}}

if ( $logfile ne "" )
{
  close(OUTPUT) || die "Cannot close logfile.. $logfile";
}
