#!/usr/bin/perl 
#
# show all open ports and if they are protected by a filter rule
#
# Copyright 2000 SuSE GmbH
#
# Tip: Run it like this:
#   (openports 1.1.1.1 2000; openports 1.1.1.1 20; openports 1.1.1.1 53; ) \
#   | sort -nu | grep -v " den "
#
# don't make it suid root
# requires iproute2 to be installed and in the path
#


if (defined($ARGV[0])) {
    if ($ARGV[0] eq "-h") {
        print STDERR "Syntax: $0 [sourceip] [[sourceport]]\n";
        print STDERR
"Tests which services can be accessed by sourceip/port through the filter rules.\n";
        exit(0);
    }
}


$tcpdmatch = (-x "/usr/sbin/tcpdmatch")  ? "/usr/sbin/tcpdmatch" : "tcpdmatch"; 
$iproute2 = (-x "/usr/sbin/ip")  ? "/usr/sbin/ip" : 
	        (-x "/sbin/ip") ? "/sbin/ip" : 
			(-x "/usr/local/sbin/ip") ? "/usr/local/sbin/ip" : 
			(-x "/usr/local/bin/ip") ? "/usr/local/bin/ip" : "ip"; 
$rpcinfo = (-x "/usr/sbin/rpcinfo") ? "/usr/sbin/rpcinfo" : "rpcinfo"; 

$testaddr = (defined ($ARGV[0]) ? $ARGV[0] : "1.1.1.1"); 
shift(@ARGV); 
$testsrcport = (defined($ARGV[0]) ? $ARGV[0] : "2000"); 

# XXX: assumes local routing is symmetric
open(R,"$iproute2 route get $testaddr |") || die "cannot run ip: $!\n"; 
$l = <R> ;
my ($dev,$laddr) = ($l =~ /dev ([a-z0-9]+).*src ([0-9.]+)/);  
close R;
if ($l =~ /answers/) { 
	print "no route to $testaddr: $l\n"; 
	exit 1;
}

foreach $proto ("tcp", "udp") { 

open(T,"/proc/net/$proto") || die "/proc/net/$proto: $!\n"; 
while (<T>) { 
	next if /local/; 
	my ($sl,$loc,$rem,$st,$qlen,$tr,$retrans,$uid,$to,$ino) = split;
	if ($proto ne "tcp" || hex($st) == 10) {  # LISTEN
		($addr,$port) = ($loc =~ /([^:]+):(.+)/);
		$port = hex($port); 
		$addr = hex($addr); 
		$addr = (sprintf "%d.%d.%d.%d", ($addr >> 24), ($addr >> 16)&0xff, 
						($addr >>8)&0xff, $addr & 0xff);
		my $check = 
"--check input -p $proto " .
"--source-port $testsrcport --source $testaddr/32 " .
"--destination $laddr/32 --destination-port $port -i $dev";

		open(C, "/sbin/ipchains $check |") || die "cannot run ipchains: $!\n";
		$resp = <C>; 
		close C;

		$resp = sprintf("%.3s", $resp); 

		open(L,"find 2>/dev/null /proc -lname \"*\\[$ino\\]*\" |") || 
			die "no find: $!\n";
		while (<L>) { 
			($pid) = m#/(\d+)/#; 
			unless (open(CMD,"/proc/$pid/cmdline")) {
				print "cannot read cmdline of $pid: $!\n"; 
				$proc = ""; 
			} else { 
				$proc = <CMD>; 
			}
			if ($proc =~ m#^[^\s]+/#) { 
				$proc =~ s#^[^\s]+/([^/]+)#$1#; 
			}
			if ($proc =~ /^inetd/)  { 
				$proc .= ": " . check_inetd($port,$proto);
				if ($proc =~ m#inetd: ([^\s]+/)?tcpd ([^\s]+)#) {
					$proc =~ s#[^\s]+/tcpd#tcpd:#; 
					my $status = check_tcpd($2,$addr,$testaddr); 
					$status = sprintf ("%.3s", $status); 
					$resp .= "/$status"; 
				}
			}
			my $p;
			if ($p = check_rpcinfo($port,$proto)) { 
				$proc .= ": " . $p;
			} 
			write;
		}
		close L; 

	}
}
close T;
}

%inetd = (); 
#%inetduser = (); 

sub parse_inetd { 
	open(I, "/etc/inetd.conf") || do { return "" } ; 
	while (<I>) { 
		chomp;
		s/#.*//; 
		next unless /\S/; 
		my ($service,$type,$proto,$wait,$user,$cmd,$rest) = split; 
		$cmd .= " " . $rest; 
		my $port;
		if ($service =~ /^\d+$/) { 
			$port = $service;
		} else {
			my ($name,$aliases,$pproto);
			($name,$aliases,$port,$pproto) = getservbyname($service,$proto); 
		} 
		$inetd{"$port.$proto"} = $cmd;
		#$inetduser{"$port.$proto"} = $user;
	}
	close I; 
} 

$inetd_parsed = 0;

sub check_inetd($$) { 
	my ($port,$proto) = (@_); 
	parse_inetd unless $inetd_parsed; 
	$inetd_parsed = 1;
	return $inetd{"$port.$proto"};
}

sub check_tcpd($$$) { 
	my ($server,$addr,$testaddr)=(@_); 
	my $res;
	my $cmd = "$tcpdmatch 2>&1 $server\@$addr $testaddr"; 
	open(T,"$cmd |") || do { print "cannot run tcpdmatch for $server\n"; }; 
	$res = "?"; 
	while (<T>) { 
		$res = $1 if /access:\s+(.*)/; 
	}
	close T;
	return $res;
} 

%rpcinfo = ();

sub parse_rpcinfo { 
	unless (open(RPC, "$rpcinfo -p |")) { 
		print "cannot run $rpcinfo -p: $!\n"; 
		return;
	}
	while (<RPC>) { 
		my ($id, $version, $proto, $port, $prog) = split; 
		next unless $prog; 
		$rpcinfo{"$proto.$port"} = "$prog"; # add id?
	}
	close RPC; 
} 

$rpcinfo_parsed = 0;

sub check_rpcinfo($$) {
	my ($port,$proto) = (@_); 

	parse_rpcinfo unless $rpcinfo_parsed++; 
	return $rpcinfo{"$proto.$port"}; 
}


#XXX: show owner of process
format STDOUT_TOP = 
Port    Address       Listen         FW/TCPD  Pid    Process         
.
format STDOUT = 
@<<<<<  @<<<<<<<<<<<< @<<<<<<<<<<<<  @<<<<<<  @<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<
$port,  $laddr,       $addr,         $resp,   $pid,  $proc
.
