#!/usr/bin/perl 

####
#
# Read through the comments in this script for configuration guides
#
# Disclaimer:
# This software is distributed as-is. If it causes your network to catch fire, it's not my fault.
#
# ASAP v0.0.1 - asapd.pl
#
# This script runs as a daemon, listening on TCP/23. Simply configure, invoke, and test.
#
# 06.01.07 Paul Venezia (pvenezia@pvenezia.net)
#


$|=1;

use IO::Socket;
use Socket;
use Net::SNMP;
use Net::Netmask;
use Data::Dumper;
use DBI;
use strict;
no strict 'subs';

my $port = 23;
$0 = "ASAP Server (asapd.pl)";
my $serv = IO::Socket::INET->new (
				Proto => 'tcp',
				LocalPort => $port,
				Listen => 10,
				Reuse => 1) || die "Can't create server: $!";

my %info;
my %clinfo;
my ($rwcomm, $rocomm, $client, $who, $newvlan);

# Database connection params
my $database_type = 'mysql';
my $database_name = 'asap';
my $database_host = 'localhost';
my $database_port = 3306;
my $database_user = 'asap';
my $database_pass = 'asap_pa$$';
our $global_dbh;

#my @denysubnets = ( );
#my @denysubnets = ( '172.16.96.0/24' );
my @denysubnets = ( '172.16.1.0/24', '172.16.96.0/24' );

sub testIP {
	foreach my $thisblock (<@denysubnets>) {
	  	my $block = new Net::Netmask($thisblock);
	  	$block -> storeNetblock();
	}
	if (findAllNetblock($who)) {
		return 1;
	}
}

sub db_connect($) {
  my $verbose = shift;

  return $global_dbh if (defined $global_dbh);

  my $dsn = "DBI:$database_type:database=$database_name:host=$database_host:port=$database_port";
  print "DBI Connecting to $dsn\n" if $verbose;

  # Note: We do all manual error checking for db errors
  my $dbh = DBI->connect($dsn, $database_user, $database_pass,
                         { PrintError => 0, RaiseError => $verbose });

  $global_dbh = $dbh;
  return $global_dbh;
}

sub db_disconnect {
  $global_dbh->disconnect() if (defined $global_dbh);
  $global_dbh = undef;
  return 0;
}

sub updateDB {
  my $dbh = db_connect(0) or goto DB_FAILURE;
  $clinfo{name} = 'unknown' if (length($clinfo{name}) < 1);

  my $sth = $dbh->prepare("INSERT INTO reqs (timestamp, mac, ip, hostname, swport, swname, site, origvlan, vlanreq, reqip, requname, method, proc) VALUES (NOW(),?,INET_ATON(?),?,?,?,?,?,?,INET_ATON(?),'','telnet',1)") or die($DBI::errstr);
  #my $sth = $dbh->prepare("INSERT INTO reqs (timestamp, mac, ip, hostname, swport, swname, site, origvlan, vlanreq, reqip, requname, method, proc) VALUES (NOW(), '".$clinfo{mac}."','".$clinfo{ip}."','" . $clinfo{hostname} . "','".$clinfo{port}."','" . $clinfo{switch} . "','". $clinfo{site} . "','". $clinfo{vlan} ."','". $newvlan ."','". $clinfo{ip} ."','','telnet', 1)");

  $sth->execute(uc($clinfo{mac}), $clinfo{ip}, $clinfo{name}, $clinfo{port}, $clinfo{switch}, $clinfo{site}, $clinfo{vlan}, $newvlan, $clinfo{ip}) or die($DBI::errstr);
  $sth->finish;
  db_disconnect();
       return;

        DB_FAILURE:
                # Had a DB error.  Handle as configured.
                print "ERROR: Database Call Failed!\n  $DBI::errstr\n";
                return ("Failure");


}



sub getSNMP {

undef %info;

my @nets = split(/\./, $who);

if (substr($who, 0, 7) == '172.16') {

## MD Site info
	if (@nets[2] < 64) {
		$rwcomm = 'private';
		$rocomm = 'public';
		$info{sw}->{0} = '172.16.1.2';
		$info{sw}->{1} = '172.16.1.3';
		$info{router} = '172.16.1.1';
		$info{site} = 'Los Angeles';
		$info{intMap}->{1} = 341;
		$info{intMap}->{12} = 352;
		$info{intMap}->{16} = 353;
		$info{intMap}->{20} = 354;
		$info{intMap}->{22} = 355;
		$info{intMap}->{24} = 356;
		$info{intMap}->{29} = 368;
		#$info{intMap}->{24} = 11;
		$info{nameMap}->{1} = "Servers";
		$info{nameMap}->{12} = "Desktop";
		$info{nameMap}->{16} = "VoIP";
		$info{nameMap}->{20} = "Linux";
		$info{nameMap}->{22} = "Control";
		$info{nameMap}->{24} = "HD";
		$info{nameMap}->{29} = "Deadzone";
		#$info{nameMap}->{24} = "Broadcast";

## MI Site info
	} elsif (@nets[2] == 64) {
		$info{sw}->{0} = '172.16.64.3';
		$info{router} = '172.16.64.1';
		$info{site} = 'Massachusetts';
		$info{rocomm} = 'public';
		$info{intMap}->{64} = 2;

## NJ Site info
	} elsif (@nets[2] > 95) {
		$rwcomm = 'private';
		$rocomm = 'public';
		$info{sw}->{0} = '172.16.96.2';
		$info{sw}->{1} = '172.16.96.3';
		$info{router} = '172.16.96.1';
		$info{site} = 'New York';
#		$info{intMap}->{5} = 4;
		$info{intMap}->{96} = 346;
		$info{intMap}->{100} = 347;
		$info{intMap}->{104} = 348;
		$info{intMap}->{108} = 349;
		$info{intMap}->{119} = 351;
#		$info{nameMap}->{5} = "N/A";
		$info{nameMap}->{96} = "Servers";
		$info{nameMap}->{100} = "Desktop";
		$info{nameMap}->{104} = "Phones";
		$info{nameMap}->{108} = "Linux";
		$info{nameMap}->{119} = "Deadzone";
	}

} else {
	print $client "Site not found for ip $who";
}

$clinfo{site} = $info{site};

#print Dumper %info;
my $vlan;
if ($info{site} eq "Massachusetts") {
	$vlan = '@1';
} else {
	$vlan = '@' . @nets[2];
}

my $vlmap = $info{intMap}->{@nets[2]};
my $mapmacoid = ".1.3.6.1.2.1.4.22.1.2." . $vlmap . "." . $who;

my $comm = $rocomm;
#my $comm = $info{rocomm} . $vlan;

#	print "\n$info{router} - $comm - $mapmacoid - @nets[2]\n";

                my ($session, $error) = Net::SNMP->session(
                                                          -hostname  => $info{router},
                                                          -community => $comm,
                                                          -version   => 1,
               #                                           -translate => [-octetstring => 0x0],
                                                          -port      => "161"
                                                        );
                my $response = $session->get_request($mapmacoid); 
                my $err = $session->error;
                if ($err){
                        print "SNMP error getting macoid " . $err . "\n";
                } 
#		print Dumper $response;
		my $tmac = $response->{$mapmacoid};
#		print " -- $tmac\n";
		my @dmac;
		@dmac[0] = substr($tmac,2,2);
		@dmac[1] = substr($tmac,4,2);
		@dmac[2] = substr($tmac,6,2);
		@dmac[3] = substr($tmac,8,2);
		@dmac[4] = substr($tmac,10,2);
		@dmac[5] = substr($tmac,12,2);
		my $mac = join(":", @dmac);
		
	#	$mac = s/:0$/:00/;
	#	$mac = s/:(\w):/:0$1:/;
	#	$mac = s/:(\w)/:0$1/;
		$mac = lc($mac);
#		print "Mac: $mac\n";

		$clinfo{ip} = $who;
		$clinfo{mac} = $mac;
		my $iaddr = inet_aton($who);
		$clinfo{name} = gethostbyaddr($iaddr,AF_INET);

foreach my $sw ( sort values %{$info{sw}} ) {
my $swname  = ".1.3.6.1.2.1.1.5.0";
my $mactbl   = '.1.3.6.1.2.1.17.4.3.1.1';
#	print "\n$sw - $rocomm\n";
	
                my ($session, $error) = Net::SNMP->session(
                                                          -hostname  => $sw,
                                                          -community => $rocomm.$vlan,
                                                          -version   => "1",
                                                          -port      => "161"
                                                        );
                $response = $session->get_table($mactbl);
                $err = $session->error;
                if ($err){
                        print 'SNMP error getting MAC table' .$err . '\n';
                } 
		#print Dumper %{$response};
		my %mactable = %{$response};
		my $found = 0;
		foreach my $tblmacoid ( keys %mactable ) {
		#print "$mactable{$tblmacoid} - $tblmacoid\n";
		my $tblmac = $mactable{$tblmacoid};
		if ($found == 0) {
			my @dmac;
	                @dmac[0] = substr($tblmac,2,2);
        	        @dmac[1] = substr($tblmac,4,2);
              		@dmac[2] = substr($tblmac,6,2);
                	@dmac[3] = substr($tblmac,8,2);
                	@dmac[4] = substr($tblmac,10,2);
                	@dmac[5] = substr($tblmac,12,2);
                	my $testmac = join(":", @dmac);
		#print "$testmac - $clinfo{mac}\n";
		if ($testmac eq $clinfo{mac}) {
			#print "FOUND";
			my $oid = $tblmacoid;
			$oid =~ s/4\.3\.1\.1\.0/4\.3\.1\.2\.0/;
			my $resp = $session->get_request($oid);
#			my $err = $resp->error;
#			print Dumper $resp;
			my $tbp = $resp->{$oid};
#			print "$oid -- $tbp -- tbp -- $err\n";

			my $ifidxoid = ".1.3.6.1.2.1.17.1.4.1.2." . $tbp;
			my $ifidxresp = $session->get_request($ifidxoid);
			my $tifidx = $ifidxresp->{$ifidxoid};
			$info{tifidx} = $tifidx;
#			print "$tifidx";

			my $tvlan = ".1.3.6.1.4.1.9.9.68.1.2.2.1.2." . $tifidx;
			my $speed = ".1.3.6.1.2.1.31.1.1.1.15." . $tifidx;
			my $duplex = ".1.3.6.1.2.1.10.7.2.1.19." . $tifidx;
			my $realport = ".1.3.6.1.2.1.31.1.1.1.1." . $tifidx;
#			print "$tvlan = $speed = $duplex = $realport = $tifidx\n";

			#my $clresponse = $session->get_request($tvlan, $speed, $realport, $swname);
			my $clresponse = $session->get_request($tvlan, $speed, $duplex, $realport, $swname);
                $err = $session->error;
                if ($err){
                        print 'SNMP error getting host data: ' .$err;
                } 
			#print Dumper %{$clresponse};

			$clinfo{vlan} = $clresponse->{$tvlan};
			$clinfo{speed} = $clresponse->{$speed};
			$clinfo{port} = $clresponse->{$realport};
			$clinfo{switch} = $clresponse->{$swname};
			my @duplexes = ('unknown', 'unknown', 'Half', 'Full');
			$clinfo{duplex} = @duplexes[$clresponse->{$duplex}];
			$info{thissw} = $sw;
#			print $clinfo{port} . " PORT\n";
			#print Dumper %clinfo;
			if ($clinfo{port} ne "Po1") {
				$found++;
			}
			last if $found;
		}
		}
}
if ($found) {
	last;
}
	
}
$session->close;
	
}

sub setVLAN() {

my ($session, $error) = Net::SNMP->session(
                                           -hostname  => $info{thissw},
                                           -community => $rwcomm,
                                           -version   => "1",
                                           -port      => "161"
                                          );

my $setoid = ".1.3.6.1.4.1.9.9.68.1.2.2.1.2." . $info{tifidx};
print $client "\n\rMoving to VLAN ". $info{newvlan}. ". Please reboot or up/down the Ethernet interface...\n\r";
close $client;
#print $client "\nChanging VLAN $setoid - $rwcomm - $info{newvlan} " . $info{thissw} . "...\n";

my $result = $session->set_request(
	-varbindlist => ["$setoid", INTEGER, $info{newvlan}] 
	);	
	
print $client "Result" . $result->{$setoid};
if (!defined($result)) {
	print $client "SNMP Error setting VLAN " . $session->error;
} #else {
#	print $client "Success" . $result->{$setoid} . "\n\n";
#}

#print Dumper %{$result};
$session->close;

} 

sub daemonize {
   # open STDOUT, ">>$log" or die "Can't write to $log: $!";
    defined(my $pid = fork)   or die "Can't fork: $!";
    exit if $pid;
    setsid                    or die "Can't start a new session: $!";
    umask 0;
}

&daemonize;

##
## Main 
##

while ($client = $serv->accept()) {
	eval {
		$client->autoflush(1); # Always remember to flush!
		$who = $client->peerhost;
		#print STDERR "Telmet2: Connection from $who\n";

		print $client "\n\rLAN Access/ASAP Service\n\r";
		print $client "Current Info:\n\r";
		&getSNMP;
#		print Dumper %clinfo;
		my $OUT;
		foreach my $tkey ( keys %clinfo ) {
			$OUT .= uc($tkey) . "\t=>\t$clinfo{$tkey}\n\r";
		}
		if (@denysubnets) {
			if (testIP) {
				$OUT .= "\n\rNOTICE: This system cannot be modified. Please contact the network admin.\n\r";
				#print $client "\n\rNOTICE: This system cannot be modified. Please contact the network admin.\n\r\n\r";
				#print $client "test\r\n\r\n";
			print $client "\n\r$OUT\n\r";
				close($client);
			} elsif (!$clinfo{port}) {
				$OUT .= "\n\rNOTICE: This system cannot be modified. Please contact the network admin.\n\r";
				print $client "\n\r$OUT\n\r";
				close($client);
			} else {
			print $client "\n\r$OUT\n\r";
			}
		} else {
			print $client "\n\r$OUT\n\r";
		}

		print $client "(C)hange VLAN assignment, or e(X)it? ";
		my $ch = <$client>;
		if ($ch =~ /(x|X)/) {
			close $client;
		} else {
	    	  print $client "Enter new VLAN number:\n\r";
#		  print Dumper %{$info{intMap}};

		  foreach my $prvlan ( sort { %{$info{intMap}}->{$a} cmp %{$info{intMap}}->{$b} } keys %{$info{intMap}} ) {
			print $client "\t[$prvlan] (". $info{nameMap}->{$prvlan} . ")\n\r";
		  }
		  print $client "Selection: ";
		  $newvlan = <$client>;
		  chomp($newvlan);
		  $newvlan =~ s/[^\d]//;
		  if ($newvlan =~ /\d+/) {
		    print $client "This change will be immediate. You may need to reboot this system.\n\r";
		    print $client "Enter \"Yes\" to change this system from VLAN " . $clinfo{vlan} . " to VLAN " . $newvlan;
		    print $client "\n\rSelection: ";
		    my $go = <$client>;
		    chomp($go);
		    if ($go =~ /(YES|Yes|yes)/) {
			$info{newvlan} = $newvlan;
	  		&setVLAN;
			&updateDB;
		    } else {
			print $client "Exiting\n\r\n\r";
			close $client;
		    }

		   } else {
		    die;
		}
		}
		
	};
close $client;
}

__END__
