#!/usr/bin/perl
#################################################
#		SMB-SCAN
#	A command-line tool to sweep
#	the local SMB/CIFS network and
#	find all accessible workgroups,
#	servers, and shares.
#
#	Requires: nmblookup & smbclient
#################################################

use strict;
use warnings;

#================================================
# Call nmblookup to locate the IP addresses of
# all the Master Browsers on the local network.
# Output this information to .smbscanfile.dat.
#================================================

system("nmblookup -M - > .smbscanfile.dat");

#================================================
# Read the formatted data in from .smbscanfile.dat,
# erase the file, and use a pattern match substitution
# to convert the data into an array of IP addresses.
#================================================

open(FIN, ".smbscanfile.dat");
my @addrs = <FIN>;
close(FIN);
unlink(".smbscanfile.dat");

shift @addrs;
for (my $i=0; $i<@addrs; $i++) {
	$addrs[$i] =~ s/\s.*//;
	chomp $addrs[$i];
}

#================================================
# Randomly select one of the IPs from the list, 
# and call smbclient to query its data cache.
# Output this information to .smbscanfile.dat.
#================================================

my $connectionfailed;
my @lines;
do {
	my $randint = int(rand(@addrs));
	print "Querying IP $addrs[$randint]...";
	system("smbclient -NL $addrs[$randint] > .smbscanfile.dat");
	print "done\n";

	open(FIN, ".smbscanfile.dat");
	@lines = <FIN>;
	close(FIN);
	unlink(".smbscanfile.dat");

	#================================================
	# Test to make sure that smbclient's query got
	# a positive name query response.  If not, pick
	# another random IP and try again.
	#================================================

	$connectionfailed = 0;
	until ($lines[0] =~ /^\n$/) {
		if ($lines[0] =~ /Connection to .* failed/ || scalar(@lines) == 1) {
			print "Connection to $addrs[$randint] failed\n";
			$connectionfailed = 1;
			last;
		}
		shift @lines;
	}
} while ($connectionfailed);

#================================================
# Read the file back in, delete it, and parse its 
# data to get the WORKGROUP to MASTER_BROWSER
# mapping.  Then construct a hash named sambatree,
# which contains data in the form:
#
#	WORKGROUP => [SERVER1, SERVER2, ETC.]
# 
# Note: sambatree will only have one SERVER entry
# for each WORKGROUP to begin with, and that one
# will be its MASTER_BROWSER.
#================================================

until ($lines[0] =~ /Workgroup\s+Master/) { shift @lines; }
for (1..2) { shift @lines };

my @mappingtable;
foreach (@lines) {
	$_ =~ s/\t//;
	$_ =~ s/\s+\W/^^^/;
	chomp $_;
	push(@mappingtable, split(/\^\^\^/, $_));
}

my %sambatree;
for (my $i=0; $i<@mappingtable; $i+=2) {
	$sambatree{$mappingtable[$i]} = [$mappingtable[$i+1]];
}

#================================================
# Print the contents of the sambatree
#================================================

print "Determined the list of WORKGROUPS\n";
#foreach (keys %sambatree) {
#	printf("%-20s%s\n", $_, @{$sambatree{$_}});
#}

#================================================
# Create a hash to contain the mappings of:
#
#	SERVER => [SHARE1, SHARE2, etc.]
#
# This will be used in the following foreach loop.
#================================================

my %servershares;

#================================================
# Use smbclient to query each WORKGROUP's
# MASTER_BROWSER.  Redirect the data to .smbscanfile.dat
# as usual, read the file back in, and delete it.
#================================================

foreach my $workgroup (sort keys %sambatree) {
	my $masterbrowser = ${$sambatree{$workgroup}}[0];
	print "Querying $masterbrowser...";
	system("smbclient -NL \"$masterbrowser\" > .smbscanfile.dat");
	print "done\n";

	open(FIN, ".smbscanfile.dat");
	my @lines = <FIN>;
	close(FIN);
	unlink(".smbscanfile.dat");

	#================================================
	# Test to make sure that smbclient's query got
	# a positive name query response.  If not, skip
	# to the next MASTER_BROWSER and continue the query
	# loop.
	#================================================

	my $connectionfailed = 0;
	until ($lines[0] =~ /^\n$/) {
		if ($lines[0] =~ /Connection to .* failed/ || scalar(@lines) == 1) {
#			print "Connection to $masterbrowser failed\n";
			$connectionfailed = 1;
			last;
		}
		shift @lines;
	}
	if ($connectionfailed) {
		$servershares{$masterbrowser} = ["_CONNECT_FAILED_"];
		next;
	}

	#================================================
	# If the query was indeed positive, parse the
	# returned data, and extract the LOCAL_SHARES on
	# this machine as well as the other LOCAL_SERVERS
	# in this WORKGROUP.
	#================================================

	my @localshares;
	for (1..3) { shift @lines; }
	until ($lines[0] =~ /^\n$/) { push(@localshares, shift @lines); }

	my @temp = ();
	foreach (@localshares) {
		$_ =~ s/\t//;
		$_ =~ s/\s\s\s.*$//;
		chomp $_;
		push(@temp, $_);
	}
	@localshares = @temp;
	
	my @localservers;
	for (1..3) { shift @lines; }
	until ($lines[0] =~ /^\n$/) { push(@localservers, shift @lines); }

	@temp = ();
	foreach (@localservers) {
		$_ =~ s/\t//;
		$_ =~ s/\s\s\s.*$//;
		chomp $_;
		push(@temp, $_);
	}
	@localservers = @temp;

	#================================================
	# Update the sambatree to contain all of the 
	# LOCAL_SERVERS in the WORKGROUP.
	#================================================

	$sambatree{$workgroup} = \@localservers if (@localservers);

	#================================================
	# Update the servershares hash to include the
	# MASTER_BROWSER's LOCAL_SHARES as well as the 
	# names of the other LOCAL_SERVERS in its WORKGROUP.
	#================================================

	$servershares{$masterbrowser} = \@localshares;
	foreach (@localservers) {
		unless ($_ eq $masterbrowser) {
			$servershares{$_} = [];
		}
	}
}

#================================================
# Use smbclient to query each SERVER that is not a
# MASTER_BROWSER.  Redirect the data to .smbscanfile.dat
# as usual, read the file back in, and delete it.
#================================================

foreach my $server (sort keys %servershares) {
	unless (@{$servershares{$server}}) {
		print "Querying $server...";
		system("smbclient -NL \"$server\" > .smbscanfile.dat");
		print "done\n";

		open(FIN, ".smbscanfile.dat");
		my @lines = <FIN>;
		close(FIN);
		unlink(".smbscanfile.dat");

		#================================================
		# Test to make sure that smbclient's query got
		# a positive name query response.  If not, skip
		# to the next SERVER and continue the query
		# loop.
		#================================================

		my $connectionfailed = 0;
		until ($lines[0] =~ /^\n$/) {
			if ($lines[0] =~ /Connection to .* failed/ || scalar(@lines) == 1) {
#				print "Connection to $server failed\n";
				$connectionfailed = 1;
				last;
			}
			shift @lines;
		}
		if ($connectionfailed) {
			$servershares{$server} = ["_CONNECT_FAILED_"];
			next;
		}

		#================================================
		# If the query was indeed positive, parse the
		# returned data, and extract the LOCAL_SHARES on
		# this machine.
		#================================================

		my @localshares;
		for (1..3) { shift @lines; }
		until ($lines[0] =~ /^\n$/) { push(@localshares, shift @lines); }

		my @temp = ();
		foreach (@localshares) {
			$_ =~ s/\t//;
			$_ =~ s/\s\s\s.*$//;
			chomp $_;
			push(@temp, $_);
		}
		@localshares = @temp;
	
		#================================================
		# Update the servershares hash to include this
		# SERVER's LOCAL_SHARES.
		#================================================

		$servershares{$server} = \@localshares;
	}
}

#================================================
# PRETTY PRINTING TIME!
#================================================

print "Creating output file smbscan.out...";
open(FOUT, ">smbscan.out");

print FOUT "SAMBA TREE:\n\n";
print FOUT "-" x 80, "\n";
foreach my $workgroup (sort keys %sambatree) {
	print FOUT "WORKGROUP: $workgroup\n";
	foreach my $server (@{$sambatree{$workgroup}}) {
		print FOUT "\t$server => ";
		foreach my $share (@{$servershares{$server}}) {
			if ($share eq ${$servershares{$server}}[-1]) {
				print FOUT "$share\n";
			} else {
				print FOUT "$share, ";
			}
		}
	}
	print FOUT "-" x 80, "\n";
}

close(FOUT);
print "done\n";

#my $server = "OEDIPUSWREK'D";
#my $share = "DYLAN-BOTT";
#system("smbclient \"//$server/$share\"");
#print "Exited out\n";
