This script creates iptables rules and cron jobs for dynamic hosts (dyndns.com, no-ip.com, etc). The cron job runs the script at regular intervals (every 15 minutes by default) checking to see if each host’s IP has changed. If so, it updates iptables to allow access from the host’s new IP, replacing the old rule, and logs changes to /var/log/dyn-iptables.log
crontab entries, log entries, and iptables outputCode:#! /bin/bash # 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.
Bookmarks