#!/usr/bin/perl -wT
#
# Author: Jefferson Ogata (JO317) <ogata@pobox.com>.
# Date: 1999/05/14
# Version: 0.1
#
# Please note that this program comes with NO WARRANTY WHATSOEVER. Your use
# of this program constitutes your complete acceptance of all liability for
# any damage or loss caused by the aforesaid use.
#
# You are free to use or redistribute this program as long as this header
# remains intact.
#
# If you have suggestions -- or better yet -- bits of new code, send them
# to me and I will deal with them when I have time. The latest version of
# this program may always be found at the URL
#
#    http://pobox.com/~ogata/webtools/stealport.pl
#
# For complete documentation on what this program does, and how to use it,
# run "perldoc stealport.pl".
#
# Change history:
#

=pod

=head2 NAME

stealport - Bind and listen on ports to prevent dynamic allocation.

=head2 SYNOPSIS

C<stealport [-vitu] [-a address] [port] [port1-port2] ...>

=head2 DESCRIPTION

Binds and listens on a range of TCP/UDP ports for no reason other than to
keep them from being used by other programs. Why? Sometimes we want to
set up a firewall packet filter to block outside access to certain ports
over an entire network. So we block all packets destined for, say, port
6000 of our class C network. Now, suppose the mail server in this network
isn't running an X server, so port 6000 is unbound. Sooner or later an
outgoing SMTP connection receives a local endpoint on port 6000, and the
firewall blocks return network traffic; thus the SMTP connection hangs
until timeout.

This situation causes unexpected failures in network programs, sometimes
with unpleasant results. To avoid this problem it is necessary to tailor
your packet filter rules to the specific services available on each
machine. This slows your firewall down because filter lists become
longer. It also leaks information about what machines are listening on
which ports. For example, in the above example, suppose we tailor our
packet filter to allow traffic to port 6000 on the mail server. Now a
portscan of your network may indicate a different signature for
connection attempts to port 6000 on the mail server.

You can use this program to bind certain ports on your machine so they
won't be used as endpoints for dynamic outgoing connections. This lets
you limit dynamic port assignments to a set that is consistent across
all hosts in your network.

You can also use this program to force RPC services onto certain ports,
which you can then block at your firewall. Many RPC services do not use
well-known ports, instead relying on the portmapper to provide a
directory so clients can locate the server. Even if you limit portmapper
access to machines you trust, you cannot easily block access to the
actual service ports, should an attacker bypass the portmapper and find
the service by trial and error.

If you use the C<-i> option, the program will ignore failed attempts to
create and bind a socket. So, you might wait until all RPC services are
started, and then run

    stealport -i -t 1024-1099 -u 1024-1099

You may then block all traffic to TCP and UDP ports 1024 - 1099 at your
firewall and know you are protecting all dynamic RPC services without
hindering other services.

=head2 OPTIONS

=over 4

=item C<port>

Listen on the specified port.

=item C<port1-port2>

Listen on all ports in the specified range, inclusive.

=item C<-a address>

Listen on the specified address. This option applies to subsequent port
ranges until another occurrence of C<-a>. The default behavior is to
listen on all local addresses, which is equivalent to C<-a 0.0.0.0>.

=item C<-u>

Create and bind UDP sockets. This option applies to subsequent port
ranges until an occurrence of C<-t>.

=item C<-t>

Create and bind TCP sockets (the default). This option applies to
subsequent port ranges until an occurrence of C<-u>.

=item C<-i>

Ignore errors when binding sockets. Use this if you expect a port within
your range already to be bound by a service.

=item C<-v>

Increase verbosity. If you run this program as part of your boot process,
redirect its output to a log file, lest it die of a broken pipe.

=back

=head2 EXAMPLES

    stealport -i -a 192.168.0.1 111 1024-1099 6000 -a 0.0.0.0 -u 111 1024-1099 

=over 4

The above example will bind and listen on TCP ports 111, 1024-1099, and
6000 on the address 192.168.0.1, and UDP ports 111 and 1024-1099 on all
IP addresses on the machine.

=back

=head2 BUGS

=over 4

Oh, I reckon so.

At the very least, we are bound by the per-process descriptor limit. You
can always use multiple invocations of the program.

To some degree, the problem address by this program is better handled
using stateful packet filtering at the firewall.

=back

=head2 SEE ALSO

=over 4

netstat(1).

=back

=head2 AUTHOR

Jefferson Ogata (JO317) <ogata@pobox.com>

=cut

use Socket;
use IO::Socket;
use IO::Select;
require 5.004;

my $me = $0;
$me =~ s/^.*\///;

my @ports = ();
my $listenQueue = 1;
my $verbosity = 0;
my $address = '0.0.0.0';
my $proto = 'tcp';
my $ignoreSocketErrors = 0;

while (defined ($_ = shift))
{
    if (/^(\d+)$/)
    {
	push (@ports, [ $proto, $address, $1, ]);
	next;
    }

    if (/^(\d+)\-(\d+)$/)
    {
	push (@ports, map { [ $proto, $address, $_, ]; } ($1 .. $2));
	next;
    }

    if (s/^-//)
    {
	$verbosity += s/v//g;
	$ignoreSocketErrors += s/i//g;
	$proto = 'udp' if (s/u//g);
	$proto = 'tcp' if (s/t//g);
	while (length ($_))
	{
	    if (s/a//)
	    {
		my $a = shift;
		defined ($a)
		    || &usage (1, '-a requires an address');
		($a =~ /^([a-zA-Z0-9\-\.]+)$/)
		    || &usage (1, "invalid address: $a");
		$address = $1;
		next;
	    }
	    &usage (1, "unknown option: -$_");
	}
	next;
    }

    &usage (1);
}

scalar (@ports)
    || &usage (0);

# A select object.
my $selector;

my $sockets = 0;

my %info;

foreach (@ports)
{
    my ($proto, $addr, $port) = @{$_};
    my $sock;
    if ($proto eq 'tcp')
    {
	$sock = new IO::Socket (
			    Domain	=> AF_INET,
			    Listen	=> $listenQueue,
			    LocalAddr	=> $addr,
			    LocalPort	=> $port,
			    Reuse	=> 1,
			    Proto	=> $proto);
    }
    else
    {
	$sock = new IO::Socket (
			    Domain	=> AF_INET,
			    LocalAddr	=> $addr,
			    LocalPort	=> $port,
			    Reuse	=> 1,
			    Proto	=> $proto);
    }
    unless (defined ($sock))
    {
	next if ($ignoreSocketErrors);
	print STDERR "$me: socket $proto:$addr:$port: $!\n";
	exit (1);
    }
    ++$sockets;
    print "bound $proto:$addr:$port\n" if ($verbosity > 1);
    if (defined ($selector))
    {
	$selector->add ($sock);
    }
    else
    {
	$selector = new IO::Select ($sock);
    }
    $info{$sock} = $_;
}

# Just block and go to sleep if we didn't actually bind anything.
if ($sockets == 0)
{
    while (1) { sleep; }
}

# For Eva...
while (1)
{
    # Wait for a connection.
    my @connections = $selector->can_read ();

    # Accept all connections.
    my $sock;
    foreach $sock (@connections)
    {
	my ($proto, $addr, $port) = @{$info{$sock}};
	if ($proto eq 'tcp')
	{
	    # Accept the connection.
	    my $newsock = $sock->accept ();

	    if ($verbosity > 0)
	    {
		# You talkin' to me? I don't see anyone else here, so you
		# must be talkin' to me.
		my $peerPort = $newsock->peerport ();
		my $peerAddress = inet_ntoa ($newsock->peeraddr ());
		my $localPort = $newsock->sockport ();
		my $localAddress = inet_ntoa ($newsock->sockaddr ());
		print "$proto connect $peerAddress:$peerPort -> $localAddress:$localPort\n";
	    }

	    # Close the new socket.
	    close ($newsock);
	}
	else
	{
	    # Receive the packet.
	    my $buf = '';
	    my $whom = $sock->recv ($buf, 8192, 0);

	    if ($verbosity > 0)
	    {
		$proto = "PROTO $proto" unless ($proto eq 'udp');
		my ($peerPort, $peerAddress) = sockaddr_in ($whom);
		$peerAddress = inet_ntoa ($peerAddress);
		my $localPort = $sock->sockport ();
		my $localAddress = inet_ntoa ($sock->sockaddr ());
		print "$proto message $peerAddress:$peerPort -> $localAddress:$localPort\n";
	    }
	}
    }
}

# You can't get here from there.
exit (0);

sub usage
{
    my $ec = shift;

    if (scalar (@_))
    {
	print STDERR "$me: ", join ('', @_), ".\n\n";
    }

    print STDERR <<EOT;
usage: $me \[port\] \[port1-port2\] \[{ -a local-addr \}\] \[-v\] ...

Binds and listens on specified ports but immediately closes all connections
made to them. Used to tie up ports on a machine that might otherwise be
allocated dynamically to other programs. Options are:
port            Bind and listen on the specified port
port1-port2     Bind and listen to all ports from port1 to port2 inclusive.
-a local-addr   Bind subsequent port specifications on the specified local
                address. To specify all local addresses, use "0.0.0.0".
                If you do not provide this option, all addresses are bound.
                You may have multiple -a options. Each one affects the ports
                named until the next -a option.
-i              Ignore errors creating and binding sockets. Useful if you
                wish to bind all unused ports in a port range, regardless
                of whether they are already bound.
-u              Create and bind UDP sockets. This option applies to
		subsequent port ranges until an occurrence of -t.
-t              Create and bind TCP sockets (the default). This option
		applies to subsequent port ranges until an occurrence of -u.
-v              Verbose operation.
EOT

    exit ($ec);
}

