#!/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