#!/usr/bin/perl
# $Id: notify.pl,v 1.3 2003/09/25 16:55:48 gadsden Exp $
#
# Send a Windows Messenger Service popup to one or more destinations
#
# USAGE
#        notify.pl -a <ip-address>
#    or  nofify.pl -f <file-containing-ip-addresses>
#
#  where <file-containing-ip-addresses> has one IP address per line, optionally
#  followed by a tab followed by hostname
#
# OTHER OPTIONS
#
# -d	Debug mode, show verbose status returned from smbclient command(s)
# -t	Test mode, do everything except execute smbclient command(s)
#
# OTHER FILES
#        winpopup.txt: file containing template for the popup message;
#                      any occurrences of 'XXXX' are replaced by a string
#                      representation of the destination IP address
#
#        winpopup.pid: temporary file containing the substituted text of
#                      the popup message for the current destination IP
#
# NOTES
#
# The list @SKIPNETS may contain a list of networks, in CIDR notation, which
# should be skipped, i.e. never send pop-ups to any addresses on those networks
#
# See  Microsoft Knowledge Base Article - 163409 for list of "NetBIOS suffixes",
# known as "NetBIOS name types" in Samba parlance. 
# Some common examples:
#       00      Workstation Service
#       03      Messenger Service
#       20      File Server Service

use Getopt::Std;
use Socket;
use FileHandle;
autoflush STDOUT;
autoflush STDERR;
require "ctime.pl";

@SKIPNETS = ( "192.168.1.0/24", "192.168.2.0/23" );

getopts ('a:df:t');
if ($opt_a) { open (ADDRESSES, "echo $opt_a |"); }
elsif ($opt_f) { open (ADDRESSES, "<$opt_f") or die "Cannot open $opt_f: $!"; }
else { print STDERR "Must include -a or -f address specifiers\n"; exit; }

$winpopup = "winpopup.txt";
$popup = "winpopup.$$";

unless (-r $winpopup) { die "Cannot open template $winpopup, exiting\n"; }

print STDERR "# $0 started at ", &ctime (time());

ADDRESS: while (<ADDRESSES>) {
  chop;
  if (/\t/) {
    ($ip, $hostname) = split /\t/;
  }
  else {
    $ip = $_;
    $hostname = gethostbyaddr (inet_aton ($ip), AF_INET);
  }
  foreach $net (@SKIPNETS) {
    next ADDRESS if (&within_range ($ip, $net));
  }
  $time = &ctime (time());
  chop $time;
  print STDERR "\nTrying $ip: $hostname  at $time\n";
  open (NMBLOOKUP, "nmblookup -A $ip |");
  while (<NMBLOOKUP>) {
    chop;
    if (($computerName) = /\t(.*) <03> -   /) {
      $computerName =~ s/\s*$//;
      print STDERR "\tNETBIOS Computer Name: $computerName\n";
      $cleanComputerName = &escapeName ($computerName);
      if (system "sed -e's/XXXX/$ip/' $winpopup > $popup") {
        die "Unable to create $warning file.\n";
      }
      unless ($opt_t) {
	open (SMBPOPUP, "cat $popup|smbclient -M $cleanComputerName -I $ip|");
	$cmdOut = "";
	while (chomp ($out = <SMBPOPUP>)) {
	  $cmdOut .= $out;
	}
	close (SMBPOPUP);
	if ($opt_d) { print STDERR "\t(WinPopup) results: \"$cmdOut\"\n"; }
	if (grep(/sent/,$cmdOut)) {
	  print STDERR "\tSUCCESS: WinPopup sent to $cleanComputerName.\n";
	}
	else {
	  print STDERR "\tALERT: WinPopup failed to $cleanComputerName.\n";
	}
      }
      unlink $popup;
    }
  }
}
print STDERR "# $0 finished at ", &ctime (time()), "\n\n";
exit;

sub escapeName {
  local ($_) = @_;
  s/\'/\\'/g;
  s/\{/\\{/g;
  s/\}/\\}/g;
  s/\(/\\(/g;
  s/\)/\\)/g;
  s/\&/\\&/g;
  s/\`/\\`/g;
  s/\#/\\#/g;
  s/\;/\\;/g;
  s/\>/\\>/g;
  s/\</\\</g;
  s/\ /\\ /g;
  s/\|/\\|/g;
  s/\"/\\"/g;
  return ($_);
}

sub within_range {
  my ($addr, $net) = @_;
  my $netlo = &lowest_addr ($net);
  my $nethi = &highest_addr ($net);
  my @octetA = split(/\./, $addr);
  my @octetL = split(/\./, $netlo);
  my @octetH = split(/\./, $nethi);
  for (my $j = 0; $j <= 3 ; $j++) {
    if ($octetA[$j] < $octetL[$j]) { return (0) }
    if ($octetA[$j] > $octetH[$j]) { return (0) }
  }
  return (1);
}

sub lowest_addr {
  my ($net) = @_;
  my ($addr, $netbits) = split(/\//, $net);
  return $addr;
}

sub highest_addr {
  my ($net) = @_;
  my ($addr, $netbits) = split(/\//, $net);
  my $hostbits = 32 - $netbits;
  my $netsize = 2**$hostbits;
  my @octet = split(/\./, $addr);
  $octet[3] += $netsize - 1;
  for (my $j = 3 ; $j > 0 ; $j--) {
    if ($octet[$j] >= 256) {
      $octet[$j-1] += int ($octet[$j]/256);
      $octet[$j] %= 256;
    }
  }
  return join(".", @octet);
}
