Skip to content

Instantly share code, notes, and snippets.

@tavinus
Created December 24, 2017 01:40
Show Gist options
  • Select an option

  • Save tavinus/3568d6cba5282ecfd8abf4820a57553d to your computer and use it in GitHub Desktop.

Select an option

Save tavinus/3568d6cba5282ecfd8abf4820a57553d to your computer and use it in GitHub Desktop.
Open ports for dynamic host names
#! /bin/bash
##################################################################################################
#
# https://ubuntuforums.org/showthread.php?t=1655443
# http://www.jrepo.org/wp/dyndns-iptables-auto-update-script/
# leave a comment for fixes
# uses: iptables, cron, perl, dig, netfilter multiport support in kernel. see command vars below.
#
#################################### variables #########################################
# commands used in script
rm=$(which rm)
cat=$(which cat)
grep=$(which grep)
perl=$(which perl)
cut=$(which cut)
tail=$(which tail)
dig=$(which dig)
mkdir=$(which mkdir)
chmod=$(which chmod)
crontab=$(which crontab)
dirname=$(which dirname)
basename=$(which basename)
hostname=$(which hostname)
date=$(which date)
sleep=$(which sleep)
iptables=$(which iptables)
iptablessave=$(which iptables-save)
if [ $? -eq 1 ]; then
inits="/etc/init.d"
[[ ! -d "$inits" ]] && read -p "Enter your init script directory: " inits
if [ -f "$inits"/iptables ]; then
iptablessave="/etc/init.d/iptables save" # make sure that "save" is a valid function in the iptables init script..
else
echo "Problem setting iptablessave command, check lines 26-36 in "$0"."
exit 1;
fi
fi
unset PATH # avoid accidental use of $PATH
# lock variables, so cron doesn't run more than one instance of this script at a time.
TMP_LOCKDIR="/tmp/dyn-iptables.lock"
checkcount="1" # start count for lockcheck()
checksleep="2" # wait this many seconds before attempting to run script again.
checkthresh="15" # give up lockcheck()ing after running this many times.
# default options
CRONMINUTES="15" # default minute interval for cron to wait before checking for HOST IP change. Set with -M switch.
CONFDIR="/root/.dyn-iptables" # where files used by script are saved. if you change, do not add trailing /
CHAIN="INPUT" # change this to whatever iptables chain you want.
TMP_CRONFILE="/tmp/crontmp" # tmp file, makes no difference as long as it doesn't already exist.
# keep these options for a log format consistent with /var/log/messages
LOGDIR="/var/log"
LOGFILE="dyn-iptables.log"
RUNDATE=$( $date +%b\ %d\ %T )
MYHOSTNAME=$( $hostname )
SCRIPTF=$( $basename "$0" ) # ('proc' field in log)
#################################### usage #########################################
usage() {
echo "
Usage: "$0" [-H hostname] [-M minutes] [-TP tcp_ports] [-UP udp_ports]
-h (help)
Show this help.
-H (host)
Dynamic host for which to create/replace firewall rule.
-M (minutes)
The interval which cron will wait before re-running the script to check for changed IP addresses. Default is 15.
-TP (tcp_ports)
Port number(s) to open for HOST.
For non-consecutive ports, use comma separated values with no spaces (-TP 22,80,443).
For consecutive ports, you can use range syntax (-TP 21:23 for ports 21-23).
-UP (udp_ports)
Same format as -TP.
Examples:
---------
# opens tcp ports 80, 443 and 3333-3337 for myhost.dyndns.org, will run cron job every 120 minutes.
dyn-iptables.sh -H myhost.dyndns.org -TP 80,443,3333:3337 -M 120
# opens udp port 177 for myhost.dyndns.org
dyn-iptables.sh -H myhost.dyndns.org -UP 177
"
$rm -rf "$TMP_LOCKDIR" && exit 0;
}
############################# cron checks #############################
addcronjob() {
# search cron file for line containing current host and protocol
GREPCRON=$( $grep "$HOST" "$TMP_CRONFILE" | $grep "$P_OPT" )
if [ "$GREPCRON" ]; then
# if found, replace ports and cron's minute interval
$perl -pi -e "s!^\*/(\d+)!\*/"$CRONMINUTES"! if m!(^.+$HOST.+$P_OPT(.+)?$)|(^.+$P_OPT.+$HOST(.+)?$)!" "$TMP_CRONFILE"
$perl -pi -e "s!(-M (\S+) )!-M "$CRONMINUTES" ! if m!(^.+$HOST.+$P_OPT(.+)?$)|(^.+$P_OPT.+$HOST(.+)?$)!" "$TMP_CRONFILE"
$perl -pi -e "s!($P_OPT (\S+) )!"$P_OPT" "$PORTS" ! if m!(^.+$HOST.+$P_OPT(.+)?$)|(^.+$P_OPT.+$HOST(.+)?$)!" "$TMP_CRONFILE"
else
# set cron string
CRONJOB="*/"$CRONMINUTES"\t*\t*\t*\t*\t/bin/bash "$SCRIPTD"/"$SCRIPTF" "$ARGS2" > /dev/null 2>&1"
# append new job to crontab.
echo -e "$CRONJOB" >> "$TMP_CRONFILE"
fi
return
}
############################# log #############################
writetolog() {
LOGLINE=""$RUNDATE" "$MYHOSTNAME" "$SCRIPTF" "$LOGMSG""
echo "$LOGLINE" >> "$LOGDIR"/"$LOGFILE"
return
}
############################# update firewall #############################
buildfirewall() {
[ ! -d "$CONFDIR" ] && $mkdir -p "$CONFDIR" && $chmod 700 "$CONFDIR"
HOSTIP_FILE="$CONFDIR/ipaddr_"$HOST""
# lookup IP of HOST from DNS servers using dig command.
echo "DIGGING $HOST"
NEWIP=$( $dig +short "$HOST" | $tail -n 1 )
echo "$NEWIP" | $grep -qi "timed out"
if [[ $? -eq 0 ]] || [[ ! "$NEWIP" ]]; then
echo "Error: couldn't lookup hostname/ip for "$HOST""
$rm -rf "$TMP_LOCKDIR"
exit 1;
fi
[ ! "$NEWIP" ] && echo "Error: couldn't lookup hostname/ip for "$HOST"" && $rm -rf "$TMP_LOCKDIR" && exit 1;
# look for existing iptables rules for NEWIP
NEWIP_RULENUM=$( $iptables -L "$CHAIN" -n --line-numbers | $perl -ane "print @F[0] if /(^.+$PROTOCOL.+$NEWIP(.+)$)/" )
if [ -f $HOSTIP_FILE ]; then
# if host ip file exists, assign OLDIP the ip address inside the file.
OLDIP=$( $cat "$HOSTIP_FILE" )
# look for existing iptables rule for OLDIP / Protocol
OLDIP_RULENUM=$( $iptables -L "$CHAIN" -n --line-numbers | $perl -ane "print @F[0] if /(^.+$PROTOCOL.+$OLDIP(.+)$)/" )
fi
# Now that OLDIP, OLDIP_RULENUM, NEWIP, and NEWIP_RULENUM are defined,
# start checking iptables rules...
if [ "$OLDIP_RULENUM" != "" -a "$NEWIP_RULENUM" != "" ]; then # if both rules exist...
if [ "$OLDIP_RULENUM" != "$NEWIP_RULENUM" ]; then # and they are not the same rule...
$iptables -D "$CHAIN" "$OLDIP_RULENUM" # then delete rule for OLDIP
# write to log
LOGMSG="[ "$HOST": "$OLDIP" Old IP deleted from "$CHAIN" chain. (New IP matches already existing rule.) ]"
writetolog "$LOGMSG"
# and update/replace existing rule for NEWIP.
if [ "$MULTIPORT" = "true" ]; then
$iptables -R "$CHAIN" "$NEWIP_RULENUM" -s "$NEWIP"/32 -p "$PROTOCOL" -m multiport --dports "$PORTS" -j ACCEPT
else
$iptables -R "$CHAIN" "$NEWIP_RULENUM" -s "$NEWIP"/32 -p "$PROTOCOL" --dport "$PORTS" -j ACCEPT
fi
# save new ip to file and save rules.
echo "$NEWIP" > "$HOSTIP_FILE"
$iptablessave
return
else
# if OLDIP_RULENUM and NEWIP_RULENUM are the same rule, replace/update it.
if [ "$MULTIPORT" = "true" ]; then
$iptables -R "$CHAIN" "$NEWIP_RULENUM" -s "$NEWIP"/32 -p "$PROTOCOL" -m multiport --dports "$PORTS" -j ACCEPT
else
$iptables -R "$CHAIN" "$NEWIP_RULENUM" -s "$NEWIP"/32 -p "$PROTOCOL" --dport "$PORTS" -j ACCEPT
fi
# save new ip to file and save rules.
echo "$NEWIP" > "$HOSTIP_FILE"
$iptablessave
return
fi
elif [ "$OLDIP_RULENUM" != "" -a "$NEWIP_RULENUM" = "" ]; then # replace OLDIP rule with NEWIP rule.
if [ "$MULTIPORT" = "true" ]; then
$iptables -R "$CHAIN" "$OLDIP_RULENUM" -s "$NEWIP"/32 -p "$PROTOCOL" -m multiport --dports "$PORTS" -j ACCEPT
LOGMSG="[ "$HOST": "$OLDIP" -> "$NEWIP" IP changed. Replaced "$CHAIN" rule #"$OLDIP_RULENUM". ]"
writetolog "$LOGMSG"
else
$iptables -R "$CHAIN" "$OLDIP_RULENUM" -s "$NEWIP"/32 -p "$PROTOCOL" --dport "$PORTS" -j ACCEPT
LOGMSG="[ "$HOST": "$OLDIP" -> "$NEWIP" IP changed. Replaced "$CHAIN" rule #"$OLDIP_RULENUM". ]"
writetolog "$LOGMSG"
fi
# save new ip to file and save rules.
echo "$NEWIP" > "$HOSTIP_FILE"
$iptablessave
return
elif [ "$OLDIP_RULENUM" = "" -a "$NEWIP_RULENUM" != "" ]; then # if rule exists for NEWIP but not OLDIP, update it.
if [ "$MULTIPORT" = "true" ]; then
$iptables -R "$CHAIN" "$NEWIP_RULENUM" -s "$NEWIP"/32 -p "$PROTOCOL" -m multiport --dports "$PORTS" -j ACCEPT
else
$iptables -R "$CHAIN" "$NEWIP_RULENUM" -s "$NEWIP"/32 -p "$PROTOCOL" --dport "$PORTS" -j ACCEPT
fi
# save new ip to file and save rules.
echo "$NEWIP" > "$HOSTIP_FILE"
$iptablessave
return
else # Neither rule exists so append new rule for NEWIP
if [ "$MULTIPORT" = "true" ]; then
$iptables -A "$CHAIN" -s "$NEWIP"/32 -p "$PROTOCOL" -m multiport --dports "$PORTS" -j ACCEPT
LOGMSG="[ "$HOST": "$NEWIP" New rule appended to "$CHAIN" chain. ]"
writetolog "$LOGMSG"
else
$iptables -A "$CHAIN" -s "$NEWIP"/32 -p "$PROTOCOL" --dport "$PORTS" -j ACCEPT
LOGMSG="[ "$HOST": "$NEWIP" New rule appended to "$CHAIN" chain. ]"
writetolog "$LOGMSG"
fi
# save new ip to file and save rules.
echo "$NEWIP" > "$HOSTIP_FILE"
$iptablessave
return
fi
}
############################# set port options #############################
portopts() {
# port is numeric check
echo "$PORTS" | $grep -E '[^0-9,:]' > /dev/null
if [ $? -eq 0 ]; then
echo -e "\nInvalid port format.\n"
$rm -rf "$TMP_LOCKDIR"
exit 1;
fi
# Do we need to use iptables multiport module?
GREPPORTS=$( echo "$PORTS" | $grep -E ',|:' )
[ "$GREPPORTS" ] && MULTIPORT=true || MULTIPORT=false
return
}
############################# start checking args #############################
main() {
# check if root
[[ "$UID" -ne 0 ]] && echo -e "\nOnly root can run this script.\n" && $rm -rf "$TMP_LOCKDIR" && exit 1;
# if no args given or arg is -h, show usage
[ "${#ARGS[@]}" -lt 1 -o "${ARGS[0]}" = "-h" -o "${ARGS[0]}" = "--help" ] && usage
# if TCP and UDP are set at same time
if echo "$ARGS2" | $grep -E "\-TP" | $grep -E "\-UP" > /dev/null
then
echo -e "\nPlease select one protocol or the other (-TP or -UP)\n" && $rm -rf "$TMP_LOCKDIR" && exit 1;
fi
#get absolute path to script
SCRIPTD="$( cd "$( "$dirname" "$0" )" && pwd )"
while [ "${ARGS[0]}" ]; do
# make sure that ARGS array has second element (the value for element 0, which is the flag)..
[ -z "${ARGS[1]}" ] && echo -e "\nYou must specifiy a value for parameter: "${ARGS[0]}"\n" && $rm -rf "$TMP_LOCKDIR" && exit 1;
case "${ARGS[0]}" in
-H )
HOST="${ARGS[1]}"
unset ARGS[0] ARGS[1]
ARGS=( "${ARGS[@]}" )
;;
-M )
CRONMINUTES="${ARGS[1]}";
unset ARGS[0] ARGS[1]
ARGS=( "${ARGS[@]}" )
;;
-TP )
PROTOCOL="tcp"
P_OPT="\-TP" # used in croncheck
PORTS="${ARGS[1]}"
portopts $PORTS $TMP_LOCKDIR
unset ARGS[0] ARGS[1]
ARGS=( "${ARGS[@]}" )
;;
-UP )
PROTOCOL="udp"
P_OPT="\-UP" # used in croncheck
PORTS="${ARGS[1]}"
portopts $PORTS $TMP_LOCKDIR
unset ARGS[0] ARGS[1]
ARGS=( "${ARGS[@]}" )
;;
* )
usage
esac
done
if [[ "$PORTS" ]] && [[ "$HOST" ]]; then
# write current root crontab to temp file
$crontab -u root -l > "$TMP_CRONFILE"
# set -M minute interval (for the croncheck) if it wasn't specified
echo "$ARGS2" | $grep -q "\-M"
[[ $? -eq 1 ]] && ARGS2=$( echo ""$ARGS2" -M 15" ) # add default -M 15
buildfirewall
addcronjob
# finish up
$perl -pi -e 's/^\n//g' "$TMP_CRONFILE" # remove any empty lines in cronfile
echo -e "\n" >> "$TMP_CRONFILE" # add trailing newline
$crontab -u root "$TMP_CRONFILE" # write to root's crontab
if [ "$iptablessave" = "/sbin/iptablessave" ]; then
$iptablessave > "$RULES_FILE" # save separate copy of rules
fi
$rm -rf "$TMP_LOCKDIR" "$TMP_CRONFILE"
exit 0
else # ports are not set
echo -e "\nPlease specify a host (-H) and allowed ports to access.\n"
$rm -rf "$TMP_LOCKDIR"
exit 1
fi
}
lockcheck() {
if [ "$checkcount" -le "$checkthresh" ]; then
if ! $mkdir "$TMP_LOCKDIR" > /dev/null 2>&1
then
$sleep "$checksleep"
((checkcount++))
lockcheck
else
main # start checking args.
fi
else
# Script won't run because $TMP_LOCKDIR still exists.
LOGLINE=""$RUNDATE" "$MYHOSTNAME" "$SCRIPTF" [ Oops, script didn't run because "$TMP_LOCKDIR" still exists from previous instance. ]"
echo "$LOGLINE" >> "$LOGDIR"/"$LOGFILE"
exit 1;
fi
}
ARGS=( $* ) # store script input arguments to an array to be chopped up in 'main'
ARGS2="${ARGS[*]}" # ARGS2 used in CRONJOB variable in 'addcronjob'
lockcheck # start script.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment