Portforward: A shell script example

Paul Dickson plug-devel@lists.PLUG.phoenix.az.us
Sat Apr 9 14:34:01 2005


This is a multi-part message in MIME format.

--Multipart=_Sat__9_Apr_2005_15_57_28_-0700_Ezsulwg/D2IZ+ayA
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 7bit

This is a shell script I use on my router to forward external ports on
the router to specific ports on internal machines.  For example, from the
external world, if you ssh to port 22222 on my router, it will forward
the connection to port 22 on an internal machine (the ssh port).

The script has a bunch of arguments to be processed, so it's a good
example of writting an application in a shell script.

The script is written for busybox's ash, but it will run under bash.

Be aware: the script uses default values for my network.  They can be
overridden by control arguments.

	-Paul


--Multipart=_Sat__9_Apr_2005_15_57_28_-0700_Ezsulwg/D2IZ+ayA
Content-Type: text/plain;
 name="portforward"
Content-Disposition: attachment;
 filename="portforward"
Content-Transfer-Encoding: 7bit

#!/bin/sh

# Author: Paul Dickson (paul -at permanentmail.com) on April 1, 2005.
# Licensed under the GPL License 

CMD="portforward"
LOOP=true
GETOPT=$(getopt -o chop:i:e: --long close,help,ip:,open,port:,internal:,external:,sleep: -n ${CMD} -- "$@")
if [ $? != 0 ] ; then LOOP=false ; USAGE=true ; fi
eval set -- "$GETOPT"
#echo "$@"

# Get values for EXT_IF and INT_IF
[ -e /etc/sys-parms ] && . /etc/sys-parms

FUNCT=error
PORT=none
USAGE=false

while true ; do
    case $1 in
        -c|--close)
	    case "$FUNCT" in
		open)  FUNCT=dupcmd   ; break ;;
		sleep) FUNCT=badsleep ; break ;;
	    esac
            FUNCT=close
            shift
            ;;
        -o|--open)
	    case "$FUNCT" in
		close) FUNCT=dupcmd   ; break ;;
		sleep) FUNCT=badsleep ; break ;;
	    esac
            FUNCT=open
            shift
            ;;
	--sleep)
	    case "$FUNCT" in
		error) FUNCT=badsleep ; break ;;
		close) FUNCT=badsleep ; break ;;
	    esac
	    FUNCT=sleep
	    SLEEP="$2"
	    shift 2
	    ;;
        -h|--help) USAGE=true ; break ;;
        -p|--port)
            case "$2" in
                bt|bittorrent|6881)
                    PORT=6881
                    LPORT=6881
                    [ -z "$LHOST" ] && LHOST=192.168.1.4
                    ;;
		bt*)  # Allow bt1-bt9 (ports 6881-6889)
		    PORT=$((6880+$(echo bt1|sed -e 's/bt//')))
		    LPORT=$PORT
                    [ -z "$LHOST" ] && LHOST=192.168.1.4
		    ;;
                ssh|22222)
                    PORT=22222
                    LPORT=22
                    [ -z "$LHOST" ] && LHOST=192.168.1.3
                    ;;
                ssh22)
                    PORT=22
                    LPORT=22
                    [ -z "$LHOST" ] && LHOST=192.168.1.3
                    ;;
                http)
                    PORT=8081
                    LPORT=80
                    [ -z "$LHOST" ] && LHOST=192.168.1.3
                    ;;
                irc)
                    PORT=6667
                    LPORT=6667
                    [ -z "$LHOST" ] && LHOST=192.168.1.4
                    ;;
                *)
                    PORT="error"
                    break
                    ;;
            esac
	    shift 2
            ;;
	-i|--internal) INT_IF="$2" ; shift 2 ;;
	-e|--external) EXT_IF="$2" ; shift 2 ;;
        --ip) LHOST="$2" ; shift 2 ;;
        --) shift ; break ;;
         *) echo "Internal error!  Verify the getopt argument lengths." ; exit 1 ;;
    esac
done

if [ -z "$INT_IF" -o -z "$EXT_IF" ]; then
        USAGE=true
        echo      "$CMD:  Both --internal and --external interfaces must specified.  The"
        echo "            interfaces correspond to the interior and exterior networks."
fi

case "$FUNCT,$PORT,$USAGE" in
    dupcmd,*,false)
        echo "$CMD:  Only ONE of --open or --close can be specified."
        USAGE=true
        ;;
    error,*,false)
        echo "$CMD:  Either --open or --close must be specified."
        USAGE=true
        ;;
    badsleep,*,false)
        echo "$CMD:  The --sleep argument can only be used after the --open."
        USAGE=true
        ;;
    *,error,false)
        echo "$CMD:  Invalid port specified.  Valid ports are:"
        echo "        bt,bt[1-9],bittorrent,6881,ssh,22222,ssh22,http(8081).irc"
        USAGE=true
        ;;
    *,none,false)
        echo "$CMD:  No port specified.  Valid ports are:"
        echo "        bt,bt[1-9],bittorrent,6881,ssh,22222,ssh22,http(8081).irc"
        USAGE=true
        ;;
    open,*,false)
        iptables --table nat --append PREROUTING \
	         -i $EXT_IF -p tcp --dport $PORT -j DNAT --to $LHOST:$LPORT
        iptables --append FORWARD \
                 -i $EXT_IF -o $INT_IF -p tcp -d $LHOST --dport $LPORT -j ACCEPT
        ;;
    close,*,false)
        iptables --table nat --delete PREROUTING \
                 -i $EXT_IF -p tcp --dport $PORT -j DNAT --to $LHOST:$LPORT
        iptables --delete FORWARD \
                 -i $EXT_IF -o $INT_IF -p tcp -d $LHOST --dport $LPORT -j ACCEPT
        ;;
    sleep,*,false)
        iptables --table nat --append PREROUTING \
	         -i $EXT_IF -p tcp --dport $PORT -j DNAT --to $LHOST:$LPORT
        iptables --append FORWARD \
                 -i $EXT_IF -o $INT_IF -p tcp -d $LHOST --dport $LPORT -j ACCEPT
	sleep $SLEEP
        iptables --table nat --delete PREROUTING \
                 -i $EXT_IF -p tcp --dport $PORT -j DNAT --to $LHOST:$LPORT
        iptables --delete FORWARD \
                 -i $EXT_IF -o $INT_IF -p tcp -d $LHOST --dport $LPORT -j ACCEPT
        ;;
esac

if [ "$USAGE" = "true" ]; then
    echo ""
    echo "  $CMD (--open|--close) --port <external-port> [--ip <local-ip>]"
    echo ""
    echo "    --open (-o)            Open command."
    echo "    --close (c)            Close command."
    echo "    --ip <local-ip>        IP address of internal host."
    echo "    --port (-p) <ext-port> External port to open."
    echo "    --internal (-i) <if>   Interior network interface (INT_IF=)."
    echo "    --external (-e) <if>   Exterior network interface (EXT_IF=)."
    echo "    --sleep N              Delay time before closing. N passed to sleep."
    echo "    --help (h)             This usage message."
    echo ""
    exit 1
fi

exit 0

--Multipart=_Sat__9_Apr_2005_15_57_28_-0700_Ezsulwg/D2IZ+ayA--