#!/bin/env perl
#
# Copyright (c) 2008 Torbjörn Svensson <azoff@se.linux.org>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
# USA
#
#
# Based on software by Jens Hjalmarsson <jens@interwave.se>
#

use strict;
use warnings;

# Define ports to map here
my %PORTS = (
	"TCP" => [22, 80, 161, 443, 8080, 32111],
	"UDP" => [123, 161, 162],
);

# Define paths
my $UPNP = "/usr/bin/upnp";
my $IFCONFIG = "/sbin/ifconfig";

# Define interface to use as internal iface
my $IFACE = "eth0";

# Increse to get more info, 0 disables.
my $verbose = 0;







############################################################################
#
# Do not modify anything below this line unless you know what you are doing!
#
#

# Headers
sub getIp($);
sub buildHashMap();
sub defineMapEntry($$$$);
sub parseArgs();
sub main();

# Map that holds known ports
my %hashMap;

# Get ip for $IFACE
my $iaddr = getIp($IFACE);


# Start our program
&main();


# Method to fetch ip
# Takes 1 argument, the iterface to check
# Returns ip of iface
sub getIp($) {
	my $iface = $_[0];
	my $ip = 0;

	# Some verbose info
	if ($verbose > 1) {
		print "getIp()\n";
	}

	# Get ifconfig information for $iface
	open ifconfigHandler, "$IFCONFIG $iface|" or die "Can't get ip of iface $iface: $!";
	while (<ifconfigHandler>) {
		# Get addr row
		if ($_ =~ /inet addr:\s*(\S+)/) {
			# Got ip, close up and return
			$ip = $1;
			last;
		}
	}
	close ifconfigHandler or die "Unable to close $IFCONFIG: $!";

	# Did iface have any ip?
	if ($ip eq 0) {
		die "Can't get ip, is iface $iface up?";
	}

	# Some verbose info
	if ($verbose > 0) {
		print "Configured with $ip as internal ip.\n";
	}

	# Return he ip
	return $ip;
}

# Build hash of known maps
sub buildHashMap() {
	# Print some verbose data
	if ($verbose > 1) {
		print "buildHashMap()\n";
	}

	open upnpHandler, "$UPNP -l|" or die "Can't get list $!";

	# Check each line
	while (<upnpHandler>) {
		# Example line:
		# 0 TCP 17370->192.168.1.112:17370 'Skype' ''
		if ($_ =~ /^\s*\d+\s+(\S+)\s+(\d+)->([^:]+):(\d+)/) {
			my $proto = $1;	# holds protocol		(TCP)
			my $eport = $2;	# holds external port	(17370)
			my $iaddr = $3;	# holds internal ip		(192.168.1.112)
			my $iport = $4;	# holds internal port	(17370)

			# Create a map with this data
			&defineMapEntry($proto, $eport, $iaddr, $iport);
		}
	}

	close upnpHandler or die "Unable to close $UPNP: $!";
}

# Defines a map in %hashMap
# Takes 4 arguments:
#  * protocol
#  * external port
#  * internal ip
#  * internal port
sub defineMapEntry($$$$) {
	my ($proto, $eport, $iaddr, $iport) = @_;

	# Print some verbose info
	if ($verbose > 1) {
		print "defineMapEntry()\n";
	}
	if ($verbose > 2) {
		printf "proto = %3s; eport = %5d; ipaddr = %16s; iport = %5d\n", $proto, $eport, $iaddr, $iport;
	}

	# Define the map
	$hashMap{$proto}{$eport} = {
		proto => $proto,
		eport => $eport,
		iaddr => $iaddr,
		iport => $iport,
	};
}

# Parse arguments...
sub parseArgs() {
	foreach my $arg (@ARGV) {
		my @argArr = split //, $arg;
		if ($argArr[0] eq '-') {
			foreach (@argArr) {
				if ($_ eq 'v') {
					$verbose++;
				} elsif ($_ eq 'q') {
					$verbose = -100;
				}
			}
		}
	}
}


# Our program starts here
sub main() {

	# Parse arguments to script
	&parseArgs;

	# Initialize map of known forwards.
	&buildHashMap;

	# Do once for each protocol (UDP, TCP)
	foreach my $proto (keys %PORTS) {

		# Some verbose info
		if ($verbose > 1) {
			printf "Doing protocol %s.\n", $proto;
		}
		
		# Do once for each port in this protocol
		foreach my $port (@{$PORTS{$proto}}) {

			# Some verbose info
			if ($verbose > 1) {
				printf "Doing port %d on protocol %s.\n", $port, $proto;
			}

			# Check if port is known
			if (defined $hashMap{$proto}{$port}) {
				
				# Port is defined, check if it points to us or someone else
				if ($hashMap{$proto}{$port}{iaddr} eq $iaddr) {
					# Ok. skip this one, it already points to us

					# Print some verbose info
					if ($verbose > 0) {
						printf "Port %d points to us on protocol %s.\n", $port, $proto;
					}

					next;
				} else {
					# ERROR! This port oints to someone else
					printf "ERROR: Port %d points to %s!\n", $port, $hashMap{$proto}{$port}{iaddr};
					next;
				}
			}

			my $commandStr = "$UPNP -a $iaddr $port $port $proto";

			# Some verbose info
			if ($verbose > 0) {
				print "Executing $commandStr\n";
			}

			# Add the rule
			my $output;
			{
				local ( $/ );
				open upnpHandler, "$commandStr|" or die "Error executing $UPNP: $!";
				$output = <upnpHandler>;
				close upnpHandler or die "Error closing $UPNP: $!";
			}

			# The result of the command
			if ($verbose > 2) {
				print "\n$output\n\n";
			}

			# Save for check of dupes
			&defineMapEntry($proto, $port, $iaddr, $port);

		} # Next port
	
	} # Next protocol


	# We are finished
	exit 0;
}



