If you tell enough stories, perhaps the moral will show up.

2007-10-12

NMAP

This is a perl script that shows what perl does well. It takes a couple of programs which provide useful output -- nmap and nbtstat -- and combines them into a single daily archive showing what's on your network. Key features are:

  • The input is a list of named IP ranges.
  • The output is written to hosts.txt and yyyy-mm-dd.hosts.txt automatically building an archive of what's up in the networks you care about.
To run it
  1. Install nmap and make sure it's in the path. nbtstat is part of any Windows installation.
  2. Install Perl, which means Active State.
  3. Create a range file that's just a text file with a few lines like these: (Obviously with your network ranges. You can use any range specification that nmap will accept.)
      # my range.txt 
    servers    192.168.10.0/24 
    perimeter  192.168.12.0/24 
  4. Make sure your reverse DNS is working, or you won't get any DNS names.
  5. Run it with a command like c:\perl\bin\perl nmap.pl range.txt
  6. check the output in hosts.txt to see server names, and, for windows servers, domain and server names.
To get the benefit, set it to run every day using a scheduled task (I find this is easiest with a .CMD file containing full path names for everything) and in a few months you'll have some worthwhile history.

# Start of nmap.pl by umacf24
# This program takes a single parameter -- a file containing  pairs.
# Example (but leave off the #)
# servers    192.168.10.0/24
# perimeter 192.168.12.0/24
#
use strict;
use warnings;

# NBTSTAT codes from Jim Halfpenny
my %group  = (  hex("00"), "00 Dom",
  hex("01"), "01 M Browser",
  hex("1C"), "1C Domain Controller",
  hex("1E"), "1E S Browser", # Browser Elections
);
my %unique = (  hex("00"), "00 WS",    # Workstation service
  hex("01"), "01 Msgr", # Messenger Service
  hex("03"), "03 Msgr", # Messenger Service
  hex("06"), "06 RAS Server Service",
  hex("1B"), "1B Domain Master Browser",
  hex("1D"), "1D Master Browser",
  hex("1F"), "1F NetDDE Service",
  hex("20"), "20 SVR", # File Server Service
  hex("21"), "21 RAS Client Service",
  hex("22"), "22 MS EXC Interchange(MSMail Connector)",
  hex("23"), "23 MS EXC Store",
  hex("24"), "24 MS EXC Directory",
  hex("30"), "30 Modem Sharing Server Service",
  hex("31"), "31 Modem Sharing Client Service",
  hex("43"), "43 SMS Clients Remote Control",
  hex("44"), "44 SMS Administrators Remote Control Tool",
  hex("45"), "45 SMS Clients Remote Chat",
  hex("46"), "46 SMS Clients Remote Transfer",
  hex("4C"), "4C DEC Pathworks TCPIP service on Windows NT",
  hex("42"), "42 McAfee AV",
  hex("52"), "52 DEC Pathworks TCPIP service on Windows NT",
  hex("87"), "87 MS EXC MTA",
  hex("6A"), "6A MS EXC IMC",
  hex("BE"), "BE Netmon Agent",
  hex("BF"), "BF Netmon Application",
);

# results stored in an array for re-use
my @nmapout;

my $ofn='hosts.txt';
my $opath='.\\ip\\';

my $header = "Key\t++ Up but unnamed, \n\t** Named but not responding to ping.\n\t?? Improper NBT names\n";
$header .= "Ping scan run between ".localtime(time())." ";

my $ipall; my $ipup; my $ipdown; # Address counters

# Process the file of network names. Run NMAP against the network spec (iprange).
while (<>)
{
  next if (/^\s*#/) ;
  my @line = split /\s+/;
  if (2==@line)
  {
      my $ipname = sprintf "%-15.15s ", $line[0]; # Fixed width
      my $iprange = $line[1];      

      my $cmd = ".\\bin\\nmap -sP -R -oG - $iprange |";    # local nmap
      print STDERR "$cmd\n";
    
      open (NMAP, $cmd);
      while ()
      {
          chomp;
          next if /Smurf/;
          if (/^Host: /)
          {
              $ipall++;
              my $hostline=$_;
              $ipdown++ if ($hostline =~ /Status: Down$/);
              $ipup++ if ($hostline =~ /Status: Up$/);
              my $flag ='   ';
              $flag = '++ ' if ($hostline =~ /\(\)\tStatus: Up/) ;# Un-named
              $flag = '** ' if ($hostline =~ /\([^)]+\)\tStatus: Down/) ;# Named, but not responding
              my $hostip = $1 if ($hostline =~ /^Host: ([0-9\.]+) .+ Up$/);
              my $prefix=$ipname . $flag;
              my $postfix=nbtstat($hostip) if ($hostip);
              $hostline =~ s/^Host: /$prefix/;
              $hostline =~ s/Status: Up$/NBT: $postfix/ if ($postfix);
              # print STDERR "Host Line $hostline | $postfix | $hostip\n";
              push @nmapout, $hostline;
          }
      }
  }
}
$header .= "and ".localtime(time())."\n";

if ($ipall)
{    # Some addresses seen
  $header .= "$ipall addressses pinged:- Up: $ipup, Down: $ipdown\n";
}
else
{
  $header .= "No addresses scanned. NMAP in path?\n";
}

# Preserve an archive Version
my ($sec, $min, $hr, $day, $mon, $yr, $wday, $yday, $isdst) = localtime(time());
$yr = $yr + 1900;
$mon = $mon + 1;
my $prefix = sprintf "%04d-%02d-%02d.", $yr, $mon, $day ;
my $afn="$opath$prefix$ofn";
my $hfn="$opath$ofn";

#Don't overwrite existing hosts file until we have finished scanning.
open (AHOSTS, ">$afn") or die "could not open $afn $!";
open (HOSTS, ">$hfn") or die "could not open $hfn $!";
print AHOSTS $header; print HOSTS $header;
foreach my $hostline (@nmapout)
{
  print HOSTS "$hostline\n";print AHOSTS "$hostline\n";
}
close HOSTS; close AHOSTS;

sub nbtstat
{
  my ($hostip) = @_;
  my $cmd = "nbtstat -a $hostip |";
  my %survey; my $ret;
  my $domain; my $server;

  # print STDERR "survey $cmd\n";
  open (NBTSTAT, $cmd) or die "can't run $cmd $!";
  while ()
  {
      # print STDERR;
      # The meat of the NBTNAME command is in this format: name  type
      if (/^\s+(\S+)\s*<([0-9A-F]{2})>\s+(GROUP|UNIQUE)/i )
      {
          my $nbtname = $1;
          my $nbtclass = $2;
          my $nbttype = $3;
          if ('00' eq $nbtclass)
          {
              $domain = $nbtname if ('GROUP' eq $nbttype);
              $server = $nbtname if (('UNIQUE' eq $nbttype) && ($nbtname !~ /(\~)|(MSBROWSE)/));
          }
          my $desc = ('GROUP' eq $nbttype)? $group{hex($nbtclass)} : $unique{hex($nbtclass)};
          $desc = "$nbtclass $nbttype Unknown" unless $desc;
          # print STDERR "$nbtname $nbtclass $nbttype $desc \n";
          push (@{$survey{$nbtname}}, $desc);
      }
  }
  close NBTSTAT;
  # We should have a good domain name and a good server if we have anything
  if (%survey)
  {
      $ret = ($domain and $server) ?  "$domain\\$server" : '??';
  }
  foreach my $nbtname (keys %survey)
  {
      $ret .= " $nbtname: ("  . join (', ', @{$survey{$nbtname}} ). ")" unless ($nbtname =~ /(\~)|(MSBROWSE)/) ;
  }
  return $ret;
}
# End of nmap.pl

No comments: