#!/bin/sh
VER="v1.05"
#======================================================================================================= © 2018 Martineau, v1.05
#
# Import Port Forwarding rules and apply manually rather than create NVRAM variable as v382.xx/v384.xx limits variable size
#
# PortForwardRules [-h|help] ['csv_file_name'] ['del'] ['test']
#
# PortForwardRules
# Import Port Forward rules from CSV file 'PortForwards.csv'
# PortForwardRules /mnt/USB/This_file
# Import Port Forward rules from CSV file '/mnt/USB/This_file'
# PortForwardRules del
# Delete Port Forward rules using CSV file 'PortForwards.csv'
# PortForwardRules test
# List simulated Port Forward rules using CSV file 'PortForwards.csv'
#
# Import file is in format:
#
# Service Name, Source IP, Port Range, Local IP, Local Port, Protocol
#
# If Protocol is omitted, then the default is TCP, but TCP,UDP or BOTH may be specified
# If Port Range is omitted, then it will be set to the Local Port
# If Local Port is omitted, then it will be set to the Port Range, unless Port Range is in the form 'nnnnn:nnnnn'
SayT(){
echo -e $$ $@ | /usr/bin/logger -t "($(basename $0))"
}
# Print between line beginning with'#==' to first blank line inclusive
ShowHelp() {
awk '/^#==/{f=1} f{print; if (!NF) exit}' $0
}
ANSIColours () {
cRESET="\e[0m";cBLA="\e[30m";cRED="\e[31m";cGRE="\e[32m";cYEL="\e[33m";cBLU="\e[34m";cMAG="\e[35m";cCYA="\e[36m";cGRA="\e[37m"
cBGRA="\e[90m";cBRED="\e[91m";cBGRE="\e[92m";cBYEL="\e[93m";cBBLU="\e[94m";cBMAG="\e[95m";cBCYA="\e[96m";cBWHT="\e[97m"
aBOLD="\e[1m";aDIM="\e[2m";aUNDER="\e[4m";aBLINK="\e[5m";aREVERSE="\e[7m"
cRED_="\e[41m";cGRE_="\e[42m"
}
ANSIColours
TXT="import from"
ACTION="-I"
if [ $(echo $@ | grep -cw "del") -gt 0 ];then
ACTION="-D"
TXT="deletion using"
fi
EXEC="iptables"
TEST_MODE=
if [ "$(echo $@ | grep -cw "test")" -ge 1 ];then
EXEC="echo -e $cCYA\tSimulate:"
TEST_MODE="Simulate: "
fi
FN="PortForward.csv"
if [ ! -z "$1" ] && [ "$ACTION" != "-D" ] && [ "$1" != "test" ];then
FN=$1
fi
# Need assistance!???
if [ "$1" == "help" ] || [ "$1" == "-h" ]; then
echo -e $cBWHT
ShowHelp
echo -e $cRESET
exit 0
fi
echo -e $cBWHT
logger -st "($(basename $0))" $$ "Port Forward rule" $TXT "CSV file '"$FN"' starting....."
echo -e
READ_CNT=0
SUCCESS_CNT=0
LINE_NUMBER=0
# Service Name,Source IP,Port Range,Local IP,Local Port,Protocol
while IFS="," read DESC SRC_IP EXT_PORT DEST_IP INT_PORT PROTOCOLS
do
LINE_NUMBER=$((LINE_NUMBER+1))
if [ "$(echo $DESC | grep -oE "^#")" == "#" ] || [ -z "$DESC" ];then
continue # Ignore comments and blank lines
fi
READ_CNT=$((READ_CNT+1))
if [ -z "$PROTOCOLS" ];then
PROTOCOLS="TCP" # Default Protocol if not specified
fi
PROTOCOLS=$(echo "$PROTOCOLS" | tr "A-Z" "a-z")
case $PROTOCOLS in # Only accept Protocols TCP,UDP or BOTH
tcp|udp)
;;
both)
PROTOCOLS="tcp udp"
READ_CNT=$((READ_CNT+1)) # Allow for creation of 2 physical rules!
;;
*)
# GUI 'other' means accept a value 1-255 e.g. 8 etc., but I suspect it is rarely used but let user know that (for now) we are ignoring it!
# NOTE: see http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
# e.g. TCP=6, UDP=17, ICMP=1 EGP=8 etc.
logger -t "($(basename $0))" $$ "***ERROR: Invalid Protocol '"$PROTOCOLS"'" $DESC","$SRC_IP","$EXT_PORT","$DEST_IP","$INT_PORT","$PROTOCOLS"'"
;;
esac
SRC=
if [ ! -z "$SRC_IP" ];then
SRC="-s "$SRC_IP
fi
# If 'Local Port' is missing then Error message: "iptables v1.4.15: Port `' not valid"
# if 'Port Range' is missing then Error message: "iptables v1.4.15: invalid port/service `-j' specified"
if [ -z "$EXT_PORT" ] && [ -z "$INT_PORT" ];then
EXT_PORT="<?????>" # Create Error message "iptables v1.4.15: invalid port/service `?????' specified"
INT_PORT="<?????>" # Set 'eye-catcher' for missing 'Local Port'
fi
if [ -z "$EXT_PORT" ] && [ ! -z "$INT_PORT" ];then
EXT_PORT=$INT_PORT # Set 'Port Range' to 'Local Port'
fi
if [ ! -z "$EXT_PORT" ] && [ -z "$INT_PORT" ] && [ -z "$(echo $EXT_PORT | grep -oF ":" )" ];then
INT_PORT=$EXT_PORT # Set 'Local Port' to 'Port Range' unless Port Range is 'nnnnn:nnnnn'
fi
EXT_PORT=$(echo $EXT_PORT | tr '+' ',') # e.g. '444:555+666' -> '444:555,666'
if [ -z "$TEST_MODE" ];then
echo -en $cRED
else
echo -en $cWHT
fi
for PROTOCOL in $PROTOCOLS
do
iptables -t nat -C VSERVER -p $PROTOCOL -m $PROTOCOL $SRC --dport $EXT_PORT -j DNAT --to-destination $DEST_IP":"$INT_PORT 2> /dev/null
RC=$?
# For insert RC=1 means rule doesn't exist; RC=2 means syntax Error i.e. Invalid IP or Protocol
if [ "$ACTION" == "-D" ] || [ $RC -eq 1 ] || [ $RC -eq 2 ];then
$EXEC -t nat $ACTION VSERVER -p $PROTOCOL -m $PROTOCOL $SRC --dport $EXT_PORT -j DNAT --to-destination $DEST_IP":"$INT_PORT
RC=$?
if [ $RC -eq 0 ];then
SUCCESS_CNT=$((SUCCESS_CNT+1))
else
echo -e $cBRED"\n\t\a**ERROR: rc="$RC " for record number" $LINE_NUMBER":\t'"$DESC","$SRC_IP","$EXT_PORT","$DEST_IP","$INT_PORT","$PROTOCOL"'\n"
SayT "***ERROR: rc="$RC "for record number" $LINE_NUMBER": '"$DESC","$SRC_IP","$EXT_PORT","$DEST_IP","$INT_PORT","$PROTOCOL"'"
fi
else
if [ $RC -ne 2 ];then
echo -e $cBYEL"\t*Warning: Rule exists for record number" $LINE_NUMBER":\t'"$DESC","$SRC_IP","$EXT_PORT","$DEST_IP","$INT_PORT","$PROTOCOL"'"
SayT "*Warning: Rule exists for record number" $LINE_NUMBER": '"$DESC","$SRC_IP","$EXT_PORT","$DEST_IP","$INT_PORT","$PROTOCOL"'"
else
echo -e $cBRED"\n\t\a**ERROR: rc="$RC " for record number" $LINE_NUMBER":\t'"$DESC","$SRC_IP","$EXT_PORT","$DEST_IP","$INT_PORT","$PROTOCOL"'\n"
SayT "***ERROR: rc="$RC "for record number" $LINE_NUMBER": '"$DESC","$SRC_IP","$EXT_PORT","$DEST_IP","$INT_PORT","$PROTOCOL"'"
fi
fi
done
done < $FN
echo -e $cBMAG
iptables -t nat -nvL VSERVER --line
TXT_STATUS=$cBGRE"Success Count="${SUCCESS_CNT}$cBWHT" out of"$cBCYA
if [ ! -z "$TEST_MODE" ];then
TXT_STATUS=
fi
logger -t "($(basename $0))" $$ "Port Forward" $TXT "CSV file '"$FN"' complete, "$TXT_STATUS
echo -e $cBWHT"\n\t"$TEST_MODE"Port Forward" $TXT "CSV file '"$FN"' complete, "$TXT_STATUS "Total="$READ_CNT"\n"$cRESET
exit 0