thalports.pl



#!/usr/bin/env perl
# thalports - Special-purpose port redirection tool
# License:  CC BY-NC-SA 3.0 Unported
# Revision: 121106

#---------------------------------------------------------------------
#                           important note
#---------------------------------------------------------------------

# This software is provided on an  AS IS basis with ABSOLUTELY NO WAR-
# RANTY.  The  entire risk as to the  quality and  performance of  the
# software is with you.  Should the software prove defective,  you as-
# sume the cost of all necessary  servicing, repair or correction.  In
# no event will any of the developers,  or any other party, be  liable
# to anyone for damages arising out of use of the software, or inabil-
# ity to use the software.

#---------------------------------------------------------------------
#                       help and/or usage text
#---------------------------------------------------------------------

                                # Label must be single-quoted here
my $USAGE_TEXT = << 'END_OF_USAGE_TEXT';
Usage: $PROGNAME -d OFFSET -o HOST

All of the following switch formats should work:

    --offset foo   -ofoo     --dest foo    -dfoo
    --offset=foo   -o foo    --dest=foo    -d foo

OFFSET should be a positive decimal integer or zero. HOST should be an
IP address or hostname.

WARNING:  This program will overwrite the existing  "xinetd.conf" file
if it exists.

This program redirects a predefined set of TCP and/or UDP ports to the
specified host  at the same ports plus the specified offset.  The cur-
rent version operates as follows:

xinetd is required. inetd  should be uninstalled or disabled. The pro-
gram generates  an xinetd  configuration file that  sets things  up as
necessary.  If xinetd is started or restarted subsequently  the redir-
ects should take  place. For most modern Debian systems,  this step is
handled automatically.

If  normal  xinetd entries are desired,  in addition to the redirects,
they must be added to a template in this file.

To review or modify the set of ports involved, edit this file and look
for @PORTS_TCP and @PORTS_UDP.

To modify the default settings that "xinetd" will run with,  or to add
normal "xinetd" entries for system services other than redirects, edit
this file and look for $BASE_CONF.
END_OF_USAGE_TEXT

#---------------------------------------------------------------------
#                        module initial setup
#---------------------------------------------------------------------

require 5.10.1;
use strict;
use Carp;
use warnings;
use Getopt::Long;
                                # Trap warnings
$SIG{__WARN__} = sub { die @_; };

#---------------------------------------------------------------------
#                           basic constants
#---------------------------------------------------------------------

use constant ZERO  => 0;        # Zero
use constant ONE   => 1;        # One
use constant TWO   => 2;        # Two

use constant FALSE => 0;        # Boolean FALSE
use constant TRUE  => 1;        # Boolean TRUE

#---------------------------------------------------------------------
#                         program parameters
#---------------------------------------------------------------------

                                # Short description of purpose
my $PURPOSE   = 'Special-purpose port redirection';

my $REVISION  = '121106';       # Short revision string
my $USE_LESS  = TRUE;           # Flag: Use "less" for usage text
                                # Absolute path name for output file
my $CONF_FILE = '/etc/xinetd.conf';
                                # Absolute path for Debian "service"
my $SERV_FILE = '/usr/sbin/service';

#---------------------------------------------------------------------
#                            misc. tables
#---------------------------------------------------------------------

# Specify the TCP and/or UDP ports  you'd  like to redirect in @PORTS_
# TCP and/or @PORTS_UDP as appropriate. A given port number may appear
# in either or both lists.

my @PORTS_TCP = qw (21 22 23 25 53 80 443);
my @PORTS_UDP = qw (53);

#---------------------------------------------------------------------

# To  modify the default settings that  "xinetd" will run with,  or to
# add normal  "xinetd" entries for  system services  other than redir-
# ects, change $BASE_CONF.  For more information about the format used
# here, see the official "xinetd" documentation.

my $BASE_CONF = << 'END';
defaults
{
    instances      = 60
    log_type       = SYSLOG authpriv
    log_on_success = HOST PID
    log_on_failure = HOST
    cps            = 25 30
}
END

#---------------------------------------------------------------------

# This is a template  for an "xinetd" redirection entry.  For more in-
# formation about the format used here, see the official "xinetd" doc-
# umentation.

my $CONF_ENTRY = << 'END';
service _SERVICE
{
    disable     = no
    type        = UNLISTED
    socket_type = stream
    protocol    = _PROTOCOL
    user        = nobody
    wait        = no
    redirect    = _REDIRECT
    port        = _PORT
}
END

#---------------------------------------------------------------------
#                          global variables
#---------------------------------------------------------------------

my $PROGNAME;                   # Program name (without path)
   $PROGNAME =  $0;
   $PROGNAME =~ s@.*/@@;

#---------------------------------------------------------------------
#                         usage-error routine
#---------------------------------------------------------------------

# Name:     UsageError
# Input:    None
# Returns:  Doesn't return

# "UsageError" prints  usage text for the current program,  then term-
# inates the program with exit status one.

#---------------------------------------------------------------------

sub UsageError
{
    $USAGE_TEXT =~ s@^\s+@@s;
    $USAGE_TEXT =~ s@\$PROGNAME@$PROGNAME@g;

    $USAGE_TEXT = << "END";     # "END" must be double-quoted here
$PROGNAME $REVISION - $PURPOSE

$USAGE_TEXT
END
    $USAGE_TEXT =~ s@\s*\z@\n@s;

    if ($USE_LESS && (-t STDOUT) && open (OFD, "|/usr/bin/less"))
    {
                                # "END" must be double-quoted here
        $USAGE_TEXT = << "END";
To exit this "help" text, press "q" or "Q".  To scroll up or down, use
PGUP, PGDN, or the arrow keys.

$USAGE_TEXT
END
        print OFD $USAGE_TEXT;
        close OFD;
    }
    else
    {
        print "\n", $USAGE_TEXT, "\n";
    }

    exit ONE;
}

#---------------------------------------------------------------------
#                            main routine
#---------------------------------------------------------------------

sub Main
{
    my $OptDest;                # Destination (hostname or IP address)
    my $OptHelp;                # "Help" flag
    my $OptOffset;              # Port offset (0+)
    my $n;                      # Scratch (integer)
    my $str;                    # Scratch (string )

#---------------------------------------------------------------------
# Initial setup.

                                # Note: Order is significant here
    select STDERR; $| = ONE;    # Force STDERR flush on write
    select STDOUT; $| = ONE;    # Force STDOUT flush on write

#---------------------------------------------------------------------
# Process the command line.

    if (scalar (@ARGV) == ZERO)
    {
        print << "END";
Usage: $PROGNAME -d OFFSET -o HOST

For more information, specify -h or --help
END
        exit ONE;
    }

    Getopt::Long::Configure ("bundling");
    $n = GetOptions
    (
        "h"        => \$OptHelp   ,
        "help"     => \$OptHelp   ,
        "o=s"      => \$OptOffset ,
        "offset=s" => \$OptOffset ,
        "d=s"      => \$OptDest   ,
        "dest=s"   => \$OptDest
    );

    exit ONE unless $n;
    &UsageError() unless
        defined ($OptDest) && defined ($OptOffset);
    die "Error: Invalid --dest setting\n"
        unless $OptDest   =~ m@^[a-z0-9\-\.]+\z@i;
    die "Error: Invalid --offset setting\n"
        unless $OptOffset =~ m@^\d+\z@;
    die "Error: --offset setting is too large\n"
        if $OptOffset > 64000;

#---------------------------------------------------------------------
# Additional checks.

    die "Error: This program must be run as root\n" if $>;

#---------------------------------------------------------------------
# Create an "xinetd" configuration file.

                                # Open  output stream
    open (OFD, ">$CONF_FILE") ||
        die "Error: Can't create file: $!: $CONF_FILE\n";

                                # Adjust trailing white space
    $BASE_CONF =~ s@\s*\z@\n\n@;
    print OFD $BASE_CONF;       # Output base text
    my $ErrorFlag = FALSE;      # Logical-error flag

    for my $port (@PORTS_TCP)   # Process all  TCP ports
    {                           # Process next TCP port
        $str =  $CONF_ENTRY;    # Template for redirection entry
                                # Destination port
        if (($n = $port + $OptOffset) > 64000)
        {
            print "Error: Port $port+$OptOffset == $n out of range\n";
            $ErrorFlag = TRUE;
        }
                                # Interpolate settings
        $str =~ s@_PORT@$port@gi;
        $str =~ s@_PROTOCOL@tcp@gi;
        $str =~ s@_REDIRECT@$OptDest $n@gi;
        $str =~ s@_SERVICE@redirtcp$port@gi;

        $str =~ s@\s*\z@\n\n@;  # Adjust trailing white space
        print OFD $str;         # Output current entry
    }

    for my $port (@PORTS_UDP)   # Process all  UDP ports
    {                           # Process next UDP port
        $str =  $CONF_ENTRY;    # Template for redirection entry
                                # Destination port
        if (($n = $port + $OptOffset) > 64000)
        {
            print "Error: Port $port+$OptOffset == $n out of range\n";
            $ErrorFlag = TRUE;
        }
                                # Interpolate settings
        $str =~ s@_PORT@$port@gi;
        $str =~ s@_PROTOCOL@udp@gi;
        $str =~ s@_REDIRECT@$OptDest $n@gi;
        $str =~ s@_SERVICE@redirudp$port@gi;

        $str =~ s@\s*\z@\n\n@;  # Adjust trailing white space
        print OFD $str;         # Output current entry
    }
                                # Close output stream
    close (OFD) ||
        die "Error: Can't flush file: $!: $CONF_FILE\n";

    if ($ErrorFlag)             # Did a logical error occur?
    {                           # Yes
        unlink $CONF_FILE;      # Delete the output file
        exit ONE;               # Error exit
    }

#---------------------------------------------------------------------
# Wrap it up.

    if (-f $SERV_FILE)          # Does "service" executable exist?
    {                           # Apparently - Start or restart daemon
        system "$SERV_FILE xinetd restart";
    }
    else
    {                           # No
        print << "END";         # Print a status message
A new copy of $CONF_FILE has been created
Restart xinetd to pick up changes
END
    }

    undef;
}

#---------------------------------------------------------------------
#                            main program
#---------------------------------------------------------------------

&Main();                        # Call the main routine
exit ZERO;                      # Normal exit



To continue, press the browser's Back button.