#!/usr/bin/perl
# Saint module for SNMP offering devices
#
# Developed by Tom Payerle
#
# Based on Simon Leinen's (<simon@switch.ch>) SNMP_Session.pm and BER.pm modules
# and example scripts given with his package
#
# $Source: /dept/phys/software/saint/saint-1.3.6/bin/RCS/snmp.saint,v $
# $Revision: 1.3 $
# $Date: 1999/08/16 19:50:07 $
# payerle@physics.umd.edu
#
# Made bad_communities a list instead of hash so we
# can control the order and put common ones first (s.k. 10/00)

require 5;

use lib './perllib';

require 'config/paths.pl';
require 'perl/misc.pl';
require 'perllib/getopts.pl';


#Get known community names for hosts
require 'config/SNMP_communities.pl';

#Following do all the nasty SNMP work
use SNMP_Session;
use BER;

print "BEGIN\n";
$SNMP_Session::suppress_warnings = 1;

%OIDS =      qw(sysDescr	1.3.6.1.2.1.1.1
		sysLocation	1.3.6.1.2.1.1.6
		);

#Fields for saint
$service="snmp";
$status="a";
$severity="zwoi";
$trustee="ANY\@ANY";
$trusted="ANY\@ANY";
$service_output="";
$text="";

# Community names which indicate IOS vulnerabilities

@ios_communities = ( 'community',
		     'cable-docsis'
		);

#List of bad community names to try

@bad_communities = ( 'public',
		     'private',
		     'secret',
		     'write',
		     'test1',
		     'ILMI',
		     '',
		     @ios_communities
		);

sub guess_read_community ($)
#Tries to guess read community string for given target, using strings
#in keys %bad_communities
#Returns valid SNMP_Session if succeeds, plus generates fact
{	my $target=shift;
	my ($comm, $desc,$oid,$err);
	$oid="sysDescr.0";
	foreach $comm ( @bad_communities )
	{	($desc,$err) = &snmpget($target, $comm, $oid);
		if ($err != -1)
		{	if ( ! exists $ignore_bad_read_community{$target} )
			{	if (grep /^$comm$/, @ios_communities)
				{	$text="Cisco IOS SNMP access ($comm)";
					$service_output="Cisco IOS SNMP access";
					$severity="yi";
				} else {
					$text="guessable read community string";
					$service_output="Guessable Read Community";
					$severity="zwoi";
				}
				$trustee="ANY\@ANY";
				$trusted="ANY\@ANY";
				&saint_print;
				if ($opt_p)
				{ 	print STDERR 
					"guessed read comm '$comm' for $target\n";
				}
			}

			#Also log sysDescr for better host id'ing
			$text=$desc;
			$service_output="";
			$severity="";
			$trustee="";
			$trusted="";
			&saint_print;
			#print "$target is a $text\n";
			return 1;
			
		}
	}
	#Failed to guess
	return undef;
}

#
# My knowledge of SNMP is a bit weak, so this actually attempts to modify
# an oid, specifically sysLocation.  Try to be reasonable, set it back to
#what was if succeed in changing it, but this could be considered a hostile
#act by user of the other system, so only use on your systems.  Will only run
#if called with -w flag
sub guess_write_community ($)
{	my $target=shift;
	my ($session, $comm, $desc, $newdesc,$oid,$err);
	$oid="sysLocation.0";

	foreach $comm ( @bad_communities )
	{	($desc,$err) = &snmpget($target, $comm, $oid);
		if ($err != -1)
		{ 	#We read it
			#Now try setting it
			($newdesc,$err) = &snmpset($target, $comm, $oid, 
					'string', "BAD$desc");
			($newdesc,$err) = &snmpget($target, $comm, $oid);
			if ( $desc ne $newdesc )
			{	#We altered it (most likely)
				#Set it back
				($newdesc) = &snmpset($target, $comm, $oid, 
					'string', "$desc");
				#WARN
				if ( grep /^$comm$/, @ios_communities )
				{	$text="Cisco IOS SNMP access ($comm)";
					$service_output="Cisco IOS SNMP access";
					$severity="yi";
				} else {
					$text="guessable write community string";
					$service_output="Guessable Write Community";
					$severity="zcio";
				}
				$trustee="ANY\@ANY";
				$trusted="ANY\@ANY";
				#print "$text\n";
				&saint_print;
				if ($opt_p)
				{ 	print STDERR 
					"guessed write comm '$comm' for $target\n";
				}
			}
			
		}
	}
	#Failed to guess
	return undef;
}

#Start of main routine

#I think SNMP_Sesion wants this
#srand();

$usage="usage: $0 [-w][-p] target\nWill only check write community if -w set.\
\nWill print guessed passwords if -p set.\n";
&Getopts("wp") || die "$usage";
die "$usage" unless ($#ARGV == 0 || $#ARGV == 1); # $#ARGV is # of args -1
$target = shift @ARGV;

#Compile known_oids
foreach (keys %known_oids) {
    $ugly_oids{$_} = encode_oid (split (/\./, $known_oids{$_}));
}

$rcomm = $read_community{$target};

#Our SNMP_session, if we can open one
$session=undef;

if ( $rcomm )
{	#We were given the read community name, so open a read session

	#Warn if is a bad community string and not explicitly told to ignore
	if  (grep(/^$rcomm$/, @bad_communities) &&
		! exists $ignore_bad_read_community{$target} )
	{	#But we would have guessed it anyhow
		if (grep /^$rcomm$/, @ios_communities)
		{	$text="Cisco IOS SNMP access ($rcomm)";
			$service_output="Cisco IOS SNMP access";
			$severity="yi";
		} else {
			$text="guessable read community string";
			$service_output="Guessable Read Community";
			$severity="zwoi";
		}
		$trustee="ANY\@ANY";
		$trusted="ANY\@ANY";
		&saint_print;
		#Don't ever print guessed passwords if given
		#print "guessed read for $target\n";
	}
	$oid="sysDescr.0";
	($desc,$err) = &snmpget($target, $rcomm, $oid);
	if ($err == -1)
	{
		warn "Incorrect read community string in config file for $target\n";
		#Undef $rcomm so will try to guess
		undef $rcomm;
	} else
	{	#Also log sysDescr for better host id'ing
		$text=$desc;
		$severity="";
		$service_output="";
		$trustee="";
		$trusted="";
		&saint_print;
		#print "$target is a $text\n";
	}

} 

#Don't use else in case were given wrong community string, still guess
if ( ! $rcomm )
{	guess_read_community($target);
}

# Now see if can get write community string if -w flag set
if ( $opt_w ) {	guess_write_community($target); }


sub snmpget {
	my($host,$community,@varList) = @_;
	my(@enoid, $var,$response, $bindings, $binding, $value, $inoid,
		$outoid, $upoid,$oid,@retvals);
	grep ($_=toOID($_), @varList);
	srand();
	#print "Trying $host, $community, 161\n";
	my $session = SNMP_Session->open ($host , $community, 161);
	if ( ! ($session) ) { return (-1,-1); }
	if ($session->get_request_response(@varList)) {
		$response = $session->pdu_buffer;
		($bindings) = $session->decode_get_response ($response);
		$session->close ();
		while ($bindings) {
			($binding,$bindings) = decode_sequence ($bindings);
			($oid,$value) = decode_by_template ($binding, "%O%@");
			my $tempo = pretty_print($value);
			$tempo=~s/\t/ /g;
			$tempo=~s/\n/ /g;
			$tempo=~s/^\s+//;
			$tempo=~s/\s+$//;
			push @retvals,  $tempo;
		}
		return (@retvals);
	} else {
		return (-1,-1);
	}
}

sub snmpset {
	my($host,$community,@varList) = @_;
	my(@enoid, $response, $bindings, $binding, $inoid,$outoid,
		$upoid,$oid,@retvals);
  	my ($type,$value);
	while (@varList) {
		$oid   = toOID(shift @varList);
		$type  = shift @varList;
		$value = shift @varList;
		($type eq 'string') && do {
			$value = encode_string($value);
			push @enoid, [$oid,$value];
			next;
		};
		($type eq 'int') && do {
			$value = encode_int($value);
			push @enoid, [$oid,$value];
			next;
		};
		die "Unknown SNMP type: $type";
	}
	srand();
	my $session = SNMP_Session->open ($host , $community, 161);
	if ($session->set_request_response(@enoid)) {
		$response = $session->pdu_buffer;
		($bindings) = $session->decode_get_response ($response);
		$session->close ();
		while ($bindings) {
			($binding,$bindings) = decode_sequence ($bindings);
			($oid,$value) = decode_by_template ($binding, "%O%@");
			my $tempo = pretty_print($value);
			$tempo=~s/\t/ /g;
			$tempo=~s/\n/ /g;
			$tempo=~s/^\s+//;
			$tempo=~s/\s+$//;
			push @retvals,  $tempo;
		}
		return (@retvals);
	} else {
		return (-1,-1);
	}
}

#
#  Given an OID in either ASN.1 or mixed text/ASN.1 notation, return an
#  encoded OID.
#
sub toOID {
	my $var = shift;
	if ($var =~ /^([a-z]+[^\.]*)/i) {
		my $oid = $OIDS{$1};
		if ($oid) {
			$var =~ s/$1/$oid/;
		} else {
			die "Unknown SNMP var $var\n"
		}
	}
	encode_oid((split /\./, $var));
}

